Next.XP Upgrading Issues

Enonic version: 7.13.5

Good MORNING,

after Upgrading Next.XP to 2.0 following the documentation for upgrading Guillotine and Upgrading XP to 7.13.5 along with ContentStudio to 5.1.3 , I have 2 issues:

  1. Images are not rendered in Content Studio. In Preview I get an 401-Unauthorized
  2. I get an error for a scheduled tasks (BUG ?)
com.enonic.xp.task.TaskNotFoundException: Task [de.osde.app.be:fetch-market-news] not found. Missing task script

The task script is unchanged !

Hello @luctho !

I can try to look into the first one, and maybe @rymsha knows something about the second ?

Did you remember to update next.xp app for Enonic XP as well to version 2 ?
If yes, I’m going to need logs from both XP and next.js servers.

But before doing that, enable debug logging for next.xp app by doing the following:

  • go into <xp-home>/config/ folder
  • edit logback.xml file
  • add following lines:
  <logger name="com.enonic.app.nextxp" level="debug" additivity="false">
    <appender-ref ref="STDOUT"/>
  </logger>
  • if you don’t have one, create it with following contents:
<configuration scan="true" scanPeriod="60 seconds"> 
  <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> 
    <file>${xp.home}/logs/server.log</file>
    <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
      <fileNamePattern>${xp.home}/logs/server.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
      <maxFileSize>100MB</maxFileSize>
      <maxHistory>7</maxHistory>
      <totalSizeCap>3GB</totalSizeCap>
    </rollingPolicy>
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n</pattern>
    </encoder>
  </appender>

  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <withJansi>true</withJansi>
    <encoder>
      <pattern>%magenta(%date{HH:mm:ss.SSS}) %highlight(%-5level) %cyan(%logger{36}) - %msg%n</pattern>
    </encoder>
  </appender>

  <root level="info"> 
    <appender-ref ref="STDOUT"/>
    <appender-ref ref="FILE"/>
  </root>

  <logger name="com.enonic.app.nextxp" level="debug" additivity="false">
    <appender-ref ref="STDOUT"/>
  </logger>

</configuration>

Hi Pavel (@pmi) ,

i have Version 2 of the app installed!

you can download a fresh logfile here:
https://lienas-my.sharepoint.com/:u:/g/personal/t_lucas_lienas_de/EaJLzyVKQ9hCooiFKOMWv7oBz-ExJYaG8fe5wQmdSQyHUA?e=em6yJb

Thomas

Means that you don’t have a js file for the task.

Okay,

There is nothing wrong about access in xp logs, but try checking the following:

If you have following ENONIC_API in your .env file:

ENONIC_API=http://127.0.0.1:8080/site

Then try accessing the same domain in your browser as well

http://127.0.0.1:8080

INSTEAD OF

http://localhost:8080

Because browser considers 127.0.0.1 and localhost as different domains and might not include authorization cookies with requests for images

@pmi : that solved my issue ! Thx

@rymsha : The file fetch-market-news.js is part of my build folder !?
image

I converted this APP to webpack - the non webpack version works ! Maybe a webpack-config issue ???

Make sure fetch-market-news.js file is insude app jar.
Make sure it exports run finction exports.run = function ...

Hi Sergey,

both criterias are met :frowning:
Before I converted the app to a webpack-solution - the tasks was running without any issues.

The only other thing I changed is the project- as the default project is disabled by default.
But as far as I know, that should not make a difference - does it?

In addition to that , the task is displayed in Applications-view:

How do the contents of the JS file look like? Make sure that Webpack builds it in CJS (CommonJS) format, not ESM (EcmaScript Modules).

@ase
Here is the code (maybe this helps in analyse)

const taskLib = require('/lib/xp/task');
const feedUtils = require('/lib/feedUtils');
const httpClient = require("/lib/http-client");
const contentLib = require('/lib/xp/content');
const contextLib = require('/lib/xp/context');

exports.run = function (params) {

    taskLib.progress({info: 'Task started'});
    log.info("Start task with the following params %s", params);

    const NEWS_API_URL = params.api_url;
    const NEWS_FOLDER = params.news_folder;
    const ROOT_PATH = params.root_path;
    const REPOSITORY = params.repository;

    if (!NEWS_API_URL) {
        log.warning("You must specify an url in %S", app.name + '.cfg');
        return;
    }

    // fetch the news from the given url
    let newsResponse;
    try {
        newsResponse = httpClient.request({
            url: NEWS_API_URL
        })
    } catch (e) {
        log.error("Failed to fetch news from %s with the following message: %s", NEWS_API_URL, e.info);
        taskLib.progress({info: 'Task aborted'});
        return;
    }

    // exit function, when status not 200
    if (newsResponse.status !== 200) {
        log.warning('url %s responded with status %s', NEWS_API_URL, newsResponse.status);
        taskLib.progress({info: 'Task aborted'});
        return;
    }

    const newsItems = JSON.parse(newsResponse.body).items;

    let newsCount;
    newsItems ? newsCount = newsItems.length : newsCount = 0;
    log.info(`Es wurden ${newsCount} Nachrichten gelesen`);


    const newsFolderExists = runInContext(
        () => checkFolderExists(ROOT_PATH + '/' + NEWS_FOLDER));
    if (!newsFolderExists) {
        log.info("Create Market News Folder")
        //create folder
        runInContext(
            () => createDirectoryInRepo(
                ROOT_PATH,
                NEWS_FOLDER,
                NEWS_FOLDER
            ),
            REPOSITORY
        )
    }

    let folderCreated = 0;
    let newsCreated = 0;

    newsItems.map((item, i) => {
        const content = newsItems[i].content;

        // extract image from content
        // mostly higher resolution that what is in image
        const imgSrc = feedUtils.extractImg(content);

        // check image src and override image
        if (imgSrc) {
            newsItems[i].image = imgSrc;
        }

        //ensure that image url is encrypted
        //to prevent browser warnings
        newsItems[i].image = newsItems[i].image.replace("http:", "https:");

        //remove image, anchors and line-breaks
        newsItems[i].content = feedUtils.cleanContent(content);

        //convert date
        const date = new Date(newsItems[i].pubDate);
        newsItems[i].pubDate = date.toISOString();

        //get year and month in 2-digit format from date
        const year = date.getFullYear().toString();
        const month = ("0" + (date.getMonth() + 1)).slice(-2);

        //prepare folders for news
        //follows site/news/year/month
        folderCreated += runInContext(
            () => createDirectoryInRepo(ROOT_PATH + '/' + NEWS_FOLDER, year, year),
            REPOSITORY
        );
        folderCreated += runInContext(
            () => createDirectoryInRepo(`${ROOT_PATH}/${NEWS_FOLDER}/${year}`, month, `${month}/${year}`),
            REPOSITORY
        );

        //finally, save the news-article in repo
        newsCreated += runInContext(
            () => saveToRepo(item, NEWS_FOLDER, ROOT_PATH),
            REPOSITORY
        );
    });

    log.info("NEWS-FETCHER: Created %s folders and %s news", folderCreated, newsCreated);

    taskLib.progress({info: 'Task completed'});
}

/***
 * creates sub-folders in repository
 * @param baseDir path to create the folder in
 * @param dirName name of the folder to create
 * @param displayName name to show in UI
 * @param templateId id of the page-template
 */
function createDirectoryInRepo(baseDir, dirName, displayName) {

    try {
        const folder = contentLib.create({
            name: dirName,
            displayName: displayName,
            contentType: `${app.name}:marketNewsFolder`,
            parentPath: baseDir,
            data: "",
        })

        log.info("+++ created a new folder: %s +++", baseDir + '/' + dirName);
        return 1;

    } catch
        (e) {
        if (e.code === 'contentAlreadyExists') {
            return 0;
        } else {
            log.error('Unexpected error: (%s) %s ', e.code, e.message);
            return 0;
        }
    }
}

function addImageNode(node) {
    const imgSrc = node.data.image;
    if (!imgSrc) {
        log.warning('News-Article %s does not contain an image', node._displayName);
        return;
    }
    const imageName = getFileName(imgSrc);

    //log.info("Fetching image...")
    const resp = httpClient.request({
        url: imgSrc
    });

    const cTypes = resp.contentType.split(';');

    //let imageNode;
    let imageAttachment
    if (resp.status === 200) {
        imageAttachment = contentLib.addAttachment({
            key: node._path,
            name: imageName ? imageName : "Image",
            mimeType: cTypes[0],
            //label: 'news-image',
            data: resp.bodyStream
        })
    }
}

function saveToRepo(item, root, site_path) {
    try {
        const name = 'market_news_' + item.guid.contents;
        const date = new Date(item.pubDate);
        const year = date.getFullYear().toString();
        const month = ("0" + (date.getMonth() + 1)).slice(-2);

        const newMarketNews = contentLib.create({
            name: name,
            displayName: item.title,
            contentType: `${app.name}:marketNews`,
            requireValid: false,
            parentPath: `${site_path}/${root}/${year}/${month}`,
            data: item
        })

        addImageNode(newMarketNews);

        // publish direct
        if (newMarketNews) {
            contentLib.publish(
                {
                    keys: [newMarketNews._id],
                    sourceBranch: 'draft',
                    targetBranch: 'master'

                }
            )
        }

        return 1;

    } catch (e) {
        if (e.code === 'contentAlreadyExists') {
            return 0;
        } else {
            log.error('Unexpected error: (%s) %s ', e.code, e.message);
            return 0;
        }
    }

}


/*** HELPER FUNCTIONS ***/
function getFileName(url) {
    return url.split('?')[0].split('/').pop();
}

/*** check if a folder exits ***/
function checkFolderExists(path) {
    return contentLib.exists({key: path})
}

/***
 * execute a function in admin context
 * @param callback callback function to execute in the context
 * @returns {*} whatever the function returns
 */
const runInContext = function (callback, repository) {
    let result;
    try {
        result = contextLib.run({
            principals: ["role:system.admin"],
            user: {login: 'su'},
            repository: repository,

        }, callback);
    } catch (e) {
        log.info('Error: ' + e.message);
    }

    return result;
}

I guess this is the “original” code, before transpilation by Tsup? I meant how does it look after transpilation (in the /build/ folder).

Unfortunately this is the code, extracted from the jar-file in the build-folder :man_shrugging:

This is from a webpack-project - but I started migrating to tsup-project and there it is working :slight_smile:

Ok, I think I know what the issue is. The “Missing task script” error you get means that there’s a code which tries to submit a task which doesn’t have corresponding definition under /resources/tasks.

In the case of Tsup starter, you probably deleted the task folder under /resources/tasks but forgot to remove the code which submits the task in main.js

Unfortunately that is not the case. I have the script in the right folder and it is the same as before.

I started with a simple Vanilla-Starter and converted the project to a webpack-project to use ECMAScript/TypeScript).

In Vaniila my task worked ind webpack not.

Then I decided to migrate to tsup-starter-project. There it works again. As I have refactored the code massively, I can not say what the reason was.

I would say, we shoul not investigate more time in the old code. In any case, thank you very much for your efforts.

1 Like

Missing task script is also reported then js file cannot be loaded properly. For instance, if it requires (imports) missing library.