OP4 omniJS bug in effortDone, effortRemaining?

Do we understand the relationship between setting the .effort .effortDone and .effortRemaining properties of tasks through the omniJS API in OP4 macOS ?

There appears to be a bug:

If .effortDone is set (to any value), completed in the GUI shows 0%, and effortRemaining is shown as the same as total effort.

Things seem to go better if we set .effortRemaining to the complementary value, and the API does report a duly updated value, in the expected units (seconds)

But back in the GUI, completed still shows 0%.

Any thoughts ?

Am I missing something ?

Yes - I was probably missing something.

All seems to be working well here in tests (macOS OP4, through omniplan.evaluateJavascript) with isolated tasks:

JS Test source
(() => {
    'use strict';

    // main :: IO ()
    const main = () => {

        const inner = () => {
            const
                op = Application('Omniplan'),
                ds = op.documents;

            return either(x => x)(x => x)(
                bindLR(
                    0 < ds.length ? (
                        Right(ds.at(0))
                    ) : Left('No OP documents open')
                )(
                    d => op.evaluateJavascript(
                        `(${opContext})()`
                    )
                )
            );
        };

        const opContext = () => {
            const main = () => {
                return Right(
                    actual.rootTask.subtasks
                    .map(
                        x => {
                            x.effort = 28801;
                            x.effortDone = 7 + x.effortDone;
                            x.effortRemaining = Math.max(0, x.effortRemaining - 94);
                            x.effort = x.effort - 700;
                            return JSON.stringify([
                                'title',
                                'effort',
                                'effortDone',
                                'effortRemaining'
                            ].reduce(
                                (a, k) => Object.assign(
                                    a, {
                                        [k]: x[k]
                                    }
                                ), {}
                            ))
                        }
                    ).join('\n')
                )
            };

            // Left :: a -> Either a b
            const Left = x => ({
                type: 'Either',
                Left: x
            });

            // Right :: b -> Either a b
            const Right = x => ({
                type: 'Either',
                Right: x
            });

            return main()
        }


        return inner();
    };

    // ------------------ LIBRARY IMPORT -------------------

    // Evaluate a function f :: (() -> a)
    // in the context of the JS libraries whose source
    // filePaths are listed in fps :: [FilePath]

    // Evaluate a function f :: (() -> a)
    // in the context of the JS libraries whose source
    // filePaths are listed in fps :: [FilePath]
    // usingLibs :: [FilePath] -> (() -> a) -> a
    const usingLibs = fps => f => {
        const gaps = fps.filter(fp => !doesFileExist(fp));
        return 1 > gaps.length ? eval(
            `(() => {
                'use strict';
                ${fps.map(readFile).join('\n\n')}
                return (${f})();
             })();`
        ) : 'Library not found at: ' + gaps.join('\n');
    };

    // doesFileExist :: FilePath -> IO Bool
    const doesFileExist = strPath => {
        const ref = Ref();
        return $.NSFileManager.defaultManager
            .fileExistsAtPathIsDirectory(
                $(strPath)
                .stringByStandardizingPath, ref
            ) && ref[0] !== 1;
    };

    // readFile :: FilePath -> IO String
    const readFile = fp => {
        const
            e = $(),
            ns = $.NSString.stringWithContentsOfFileEncodingError(
                $(fp).stringByStandardizingPath,
                $.NSUTF8StringEncoding,
                e
            );
        return ObjC.unwrap(
            ns.isNil() ? (
                e.localizedDescription
            ) : ns
        );
    };

    // ----------------------- MAIN ------------------------
    return usingLibs([
        '~/prelude-jxa/jsPrelude.js',
        '~/prelude-jxa/jxaSystemIO.js',
        '~/jsParserCombinators/parserCombinators.js'
    ])(main);
})();

In the absence of documentation, and as I personally found this confusing in the first round, here is a summary of what the getting and setting relationships appear to be between the following properties of Task in the macOS OP 4.02 omniJS API:

  • effortDone
  • effort
  • effortRemaining

effortDone

  • effortDone is a relatively fixed foot, mainly unaffected by the other two, except when truncated by a new value for effort which is lower than the current value of effortDone.

  • if a value larger than effort is assigned to effortDone, then effort is expanded to fit, and gets the same value as effortDone.

  • otherwise, as effortDone expands and shrinks, effortRemaining is adjusted to preserve the equality effort == effortRemaining + effortDone.

  • effortDone differs from effort and effortRemaining in that setting a negative effortDone value fails without warning (effortDone is simply zeroed), whereas attempts to assign negative values to the other two properties trip JavaScript errors and raise warnings.

effort

  • effort is always kept larger than or equal to effortDone, truncating the latter if a smaller value is assigned to effort.
  • otherwise, as effort expands and shrinks, effortDone is assumed to be fixed, and effortRemaining is adjusted to preserve the equality effort == effortRemaining + effortDone.
  • Assigning a negative value to effort trips a JS error.

effortRemaining

  • When higher or lower values are assigned to effortRemaining, effortDone is assumed to be fixed, and effort is adjusted accordingly, to preserve the the equality effort == effortRemaining + effortDone.
  • Assigning a negative value to effortRemaining trips a JS error.

At least, this is my current impression

In the absence of documentation, caution is a virtue :-)

Your summary of the relationship and expected behaviors with these three effort properties is all spot on. I’ll file a request with the team to better document the interaction between these properties in our scripting API and user manual.

1 Like