diff --git a/apis/enigma-mocker/src/mocks/get-object-mock.js b/apis/enigma-mocker/src/mocks/get-object-mock.js
index a93e4e1b6..1ad7b81b4 100644
--- a/apis/enigma-mocker/src/mocks/get-object-mock.js
+++ b/apis/enigma-mocker/src/mocks/get-object-mock.js
@@ -1,5 +1,4 @@
import { getPropValue, getPropFn } from '../prop';
-import { findAsync } from '../util';
/**
* Properties on `getObject()` operating synchronously.
@@ -13,6 +12,7 @@ const PROPS_SYNC = [
'removeAllListeners',
'removeListener',
'setMaxListerners',
+ 'qId',
];
/**
@@ -30,7 +30,7 @@ function isPropAsync(name) {
* @returns The mocked object
*/
function createMock(genericObject) {
- const { id, session, ...props } = genericObject;
+ const { id, session, delay = 0, ...props } = genericObject;
return {
id: getPropValue(id, { defaultValue: `object - ${+Date.now()}` }),
session: getPropValue(session, { defaultValue: true }),
@@ -39,7 +39,7 @@ function createMock(genericObject) {
...Object.entries(props).reduce(
(fns, [name, value]) => ({
...fns,
- [name]: getPropFn(value, { async: isPropAsync(name) }),
+ [name]: getPropFn(value, { async: isPropAsync(name), delay }),
}),
{}
),
@@ -63,6 +63,7 @@ function validate(genericObject) {
if (!(layout.qInfo && layout.qInfo.qId)) {
throw new Error('Generic object is missing "qId" for path "getLayout().qInfo.qId"');
}
+ genericObject.qId = layout.qInfo.qId; // eslint-disable-line no-param-reassign
}
/**
@@ -75,7 +76,7 @@ function GetObjectMock(genericObjects = []) {
const genericObjectMocks = genericObjects.map(createMock);
return async (id) => {
- const mock = findAsync(genericObjectMocks, async (m) => (await m.getLayout()).qInfo.qId === id);
+ const mock = genericObjectMocks.find((m) => m.qId() === id);
return Promise.resolve(mock);
};
}
diff --git a/apis/enigma-mocker/src/prop.js b/apis/enigma-mocker/src/prop.js
index b4b95ff7a..0458c4bc9 100644
--- a/apis/enigma-mocker/src/prop.js
+++ b/apis/enigma-mocker/src/prop.js
@@ -55,8 +55,12 @@ export const getPropValue = (prop, { args = [], defaultValue } = {}) => {
* @returns A fixture property function
*/
export const getPropFn =
- (prop, { defaultValue, async = true } = {}) =>
+ (prop, { defaultValue, async = true, delay = 0 } = {}) =>
(...args) => {
const value = getPropValue(prop, { defaultValue, args });
- return async ? Promise.resolve(value) : value;
+ return async
+ ? new Promise((resolve) => {
+ setTimeout(() => resolve(value), delay);
+ })
+ : value;
};
diff --git a/apis/nucleus/src/components/LongRunningQuery.jsx b/apis/nucleus/src/components/LongRunningQuery.jsx
index 2ae5404d3..b509e4a91 100644
--- a/apis/nucleus/src/components/LongRunningQuery.jsx
+++ b/apis/nucleus/src/components/LongRunningQuery.jsx
@@ -31,7 +31,7 @@ export function Cancel({ cancel, translator, ...props }) {
-
+
{translator.get('Object.Update.Active')}
@@ -52,7 +52,7 @@ export function Retry({ retry, translator, ...props }) {
-
+
{translator.get('Object.Update.Cancelled')}
diff --git a/test/mashup/visualize/life.int.js b/test/mashup/visualize/life.int.js
index 2d30864fd..56912b980 100644
--- a/test/mashup/visualize/life.int.js
+++ b/test/mashup/visualize/life.int.js
@@ -32,6 +32,21 @@ describe('object lifecycle', () => {
await waitForTextStatus('[data-tid="error-title"]', 'Incomplete visualization');
});
+ it('should render long running query', async () => {
+ const url = getScenarioUrl('long-running');
+ await page.goto(url);
+ await waitForTextStatus('[data-tid="update-active"]', 'Updating data');
+
+ // the cancel button should appear after 2000ms
+ await page.click('.njs-cell button');
+ await waitForTextStatus('[data-tid="update-cancelled"]', 'Data update was cancelled');
+ // Retry
+ await waitForTextStatus('.njs-cell button', 'Retry');
+ await page.click('.njs-cell button');
+
+ await waitForTextStatus('.rendered', 'Success!', { timeout: 7000 });
+ });
+
// need to fix calc condition view first
it('should show calculation unfulfilled', async () => {
const url = getScenarioUrl('calc-unfulfilled');
diff --git a/test/mashup/visualize/scenarios.js b/test/mashup/visualize/scenarios.js
index aaa85623d..16ac43ca9 100644
--- a/test/mashup/visualize/scenarios.js
+++ b/test/mashup/visualize/scenarios.js
@@ -36,6 +36,120 @@ scenarios['valid-type'] = {
},
};
+scenarios['long-running'] = {
+ name: 'Long running query',
+ genericObject: {
+ delay: 5000,
+ session: {
+ getObjectApi() {
+ return {
+ cancelRequest() {
+ return {};
+ },
+ };
+ },
+ },
+ getLayout() {
+ return {
+ qInfo: {
+ qId: 'bb8',
+ qType: 'doesnt matter',
+ },
+ qMeta: {
+ privileges: ['read', 'update', 'delete', 'exportdata'],
+ },
+ qSelectionInfo: {},
+ visualization: 'my-chart',
+ qHyperCube: {
+ qSize: {
+ qcx: 2,
+ qcy: 1,
+ },
+ qDimensionInfo: [
+ {
+ qFallbackTitle: '=a',
+ qApprMaxGlyphCount: 1,
+ qCardinal: 0,
+ qSortIndicator: 'N',
+ qGroupFallbackTitles: ['=a'],
+ qGroupPos: 0,
+ qStateCounts: {
+ qLocked: 0,
+ qSelected: 0,
+ qOption: 0,
+ qDeselected: 0,
+ qAlternative: 0,
+ qExcluded: 0,
+ qSelectedExcluded: 0,
+ qLockedExcluded: 0,
+ },
+ qTags: [],
+ qDimensionType: 'D',
+ qGrouping: 'N',
+ qNumFormat: {
+ qType: 'U',
+ qnDec: 0,
+ qUseThou: 0,
+ },
+ qIsAutoFormat: true,
+ qGroupFieldDefs: ['=a'],
+ qMin: 'NaN',
+ qMax: 'NaN',
+ qAttrExprInfo: [],
+ qAttrDimInfo: [],
+ qIsCalculated: true,
+ qCardinalities: {
+ qCardinal: 0,
+ qHypercubeCardinal: 1,
+ qAllValuesCardinal: -1,
+ },
+ },
+ ],
+ qMeasureInfo: [
+ {
+ qFallbackTitle: '=1',
+ qApprMaxGlyphCount: 1,
+ qCardinal: 0,
+ qSortIndicator: 'N',
+ qNumFormat: {
+ qType: 'U',
+ qnDec: 0,
+ qUseThou: 0,
+ },
+ qMin: 1,
+ qMax: 1,
+ qIsAutoFormat: true,
+ qAttrExprInfo: [],
+ qAttrDimInfo: [],
+ qTrendLines: [],
+ },
+ ],
+ qEffectiveInterColumnSortOrder: [0, 1],
+ qGrandTotalRow: [
+ {
+ qText: '1',
+ qNum: 1,
+ qElemNumber: -1,
+ qState: 'X',
+ qIsTotalCell: true,
+ },
+ ],
+ qDataPages: [],
+ qPivotDataPages: [],
+ qStackedDataPages: [],
+ qMode: 'S',
+ qNoOfLeftDims: -1,
+ qTreeNodesOnDim: [],
+ qColumnOrder: [],
+ },
+ };
+ },
+ getProperties() {
+ return {};
+ },
+ },
+};
+
scenarios['calc-unfulfilled'] = {
name: 'Calculations unfulfilled',
genericObject: {