Skip to content

Commit

Permalink
Merge branch 'development' into dependabot/npm_and_yarn/recharts-2.13.3
Browse files Browse the repository at this point in the history
  • Loading branch information
mfacar authored Nov 22, 2024
2 parents 8506efc + 279d952 commit b2ae163
Show file tree
Hide file tree
Showing 45 changed files with 1,753 additions and 1,084 deletions.
14 changes: 12 additions & 2 deletions app/api/log.v2/infrastructure/StandardLogger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,15 @@ class StandardLogger implements Logger {
}

const DefaultLogger = (writer = UwaziJSONWriter) => new StandardLogger(writer, getTenant());

export { StandardLogger, DefaultLogger };
const SystemLogger = (writer = UwaziJSONWriter) =>
new StandardLogger(writer, {
name: 'System Logger',
dbName: 'N/a',
activityLogs: 'N/a',
attachments: 'N/a',
customUploads: 'N/a',
indexName: 'N/a',
uploadedDocuments: 'N/a',
});

export { StandardLogger, DefaultLogger, SystemLogger };
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,10 @@ class InformationExtraction {
this.taskManager.subscribeToResults();
}

async stop() {
await this.taskManager.stop();
}

requestResults = async (message: InternalIXResultsMessage) => {
const response = await request.get(message.data_url);

Expand Down
4 changes: 4 additions & 0 deletions app/api/services/pdfsegmentation/PDFSegmentation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ class PDFSegmentation {
this.segmentationTaskManager.subscribeToResults();
}

async stop() {
await this.segmentationTaskManager.stop();
}

segmentOnePdf = async (
file: { filename: string; _id: ObjectIdSchema },
serviceUrl: string,
Expand Down
18 changes: 13 additions & 5 deletions app/api/services/tasksmanager/DistributedLoop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ export class DistributedLoop {

private host: string;

private stopDelayTimeBetweenTasks?: Function;

constructor(
lockName: string,
task: () => Promise<void>,
Expand Down Expand Up @@ -63,7 +65,11 @@ export class DistributedLoop {

async waitBetweenTasks(delay = this.delayTimeBetweenTasks) {
await new Promise(resolve => {
setTimeout(resolve, delay);
const timeout = setTimeout(resolve, delay);
this.stopDelayTimeBetweenTasks = () => {
resolve(undefined);
clearTimeout(timeout);
};
});
}

Expand All @@ -74,10 +80,13 @@ export class DistributedLoop {
handleError(error, { useContext: false });
}

await this.waitBetweenTasks();
if (!this.stopTask) {
await this.waitBetweenTasks();
}
}

async stop() {
if (this.stopDelayTimeBetweenTasks) this.stopDelayTimeBetweenTasks();
await new Promise(resolve => {
this.stopTask = resolve;
});
Expand All @@ -94,16 +103,15 @@ export class DistributedLoop {
);

if (this.stopTask) {
await lock.unlock();
this.stopTask();
return;
}

await this.runTask();
await lock.unlock();
} catch (error) {
if (error instanceof Error && error.name !== 'LockError') {
throw error;
}
if (error instanceof Error && error.name !== 'LockError') throw error;
}

// eslint-disable-next-line no-void
Expand Down
70 changes: 70 additions & 0 deletions app/api/services/tasksmanager/specs/distributedLoop.spec.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as errorHelper from 'api/utils/handleError';
import Redis from 'redis';
import waitForExpect from 'wait-for-expect';
import { DistributedLoop } from '../DistributedLoop';

Expand Down Expand Up @@ -162,4 +163,73 @@ describe('DistributedLoopLock', () => {
finishTask();
await nodeTwo.stop();
});

it('when stop method is executed after task finish, it should skip delay time between tasks', async () => {
const sut = new DistributedLoop('skip_delay_time_2', task, {
delayTimeBetweenTasks: 100_000,
});

const waitBetweenTasksSpy = jest.spyOn(sut, 'waitBetweenTasks');

sut.start();
await waitForExpect(() => expect(task).toHaveBeenCalledTimes(1));

finishTask();
await sleepTime(25);
const stopPromise = sut.stop();

expect(waitBetweenTasksSpy).toHaveBeenCalled();
await expect(stopPromise).resolves.toBeUndefined();
});

test('when stop method is executed before a task finish, it should skip delay time between tasks', async () => {
const sut = new DistributedLoop('skip_delay_time_2', task, {
delayTimeBetweenTasks: 100_000,
});

const waitBetweenTasksSpy = jest.spyOn(sut, 'waitBetweenTasks');

sut.start();

await waitForExpect(() => expect(task).toHaveBeenCalledTimes(1));

const stopPromise = sut.stop();
finishTask();
await sleepTime(25);

expect(waitBetweenTasksSpy).not.toHaveBeenCalled();
await expect(stopPromise).resolves.toBeUndefined();
});

test('when stop method is executed, it should unlock the Distributed Loop', async () => {
const connectionConfig = { port: 6379, host: 'localhost' };
const connection = Redis.createClient(
`redis://${connectionConfig.host}:${connectionConfig.port}`
);
const get = key =>
new Promise((resolve, reject) => {
connection.get(key, (error, data) => {
if (error) reject(error);
resolve(data);
});
});

const lockName = 'skip_delay_time_3';
const sut = new DistributedLoop(lockName, task, {
delayTimeBetweenTasks: 100_000,
});

sut.start();
await waitForExpect(() => expect(task).toHaveBeenCalledTimes(1));

const stopPromise = sut.stop();

finishTask();
await sleepTime(25);
await expect(stopPromise).resolves.toBeUndefined();

const result = await get(`locks:${lockName}`);
expect(result).toBeFalsy();
connection.quit();
});
});
4 changes: 4 additions & 0 deletions app/api/services/twitterintegration/TwitterIntegration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ class TwitterIntegration {
this.twitterTaskManager.subscribeToResults();
}

async stop() {
await this.twitterTaskManager.stop();
}

getTwitterIntegrationSettings = async (): Promise<TwitterIntegrationSettingsType> => {
const settingsValues = await settings.get({}, 'features');
if (!settingsValues.features || !settingsValues.features.twitterIntegration) {
Expand Down
24 changes: 18 additions & 6 deletions app/api/utils/Repeater.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,39 @@
const timeout = async interval =>
new Promise(resolve => {
setTimeout(resolve, interval);
});

export class Repeater {
stopSleep = undefined;

constructor(cb, interval) {
this.cb = cb;
this.interval = interval;
this.stopped = null;
}

async sleep() {
await new Promise(resolve => {
const timeout = setTimeout(resolve, this.interval);

this.stopSleep = () => {
resolve(undefined);
clearTimeout(timeout);
};
});
}

async start() {
while (!this.stopped) {
// eslint-disable-next-line no-await-in-loop
await this.cb();
// eslint-disable-next-line no-await-in-loop
await timeout(this.interval);
await this.sleep();
}

this.stopped();
}

async stop() {
if (this.stopSleep) {
this.stopSleep();
}

return new Promise(resolve => {
this.stopped = resolve;
});
Expand Down
17 changes: 17 additions & 0 deletions app/api/utils/specs/Repeater.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,21 @@ describe('Repeater', () => {

await expect(repeaterOne.stop()).resolves.toBeUndefined();
});

it('should skip interval between executions if stop method is executed', async () => {
let promise;
let resolvePromise;
const sut = new Repeater(() => {
promise = new Promise(resolve => {
resolvePromise = resolve;
});

return promise;
}, 10_000);

sut.start();
resolvePromise();
await expect(promise).resolves.toBeUndefined();
await expect(sut.stop()).resolves.toBeUndefined();
}, 5_000);
});
62 changes: 34 additions & 28 deletions app/react/V2/Components/Analitycs/specs/Matomo.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@
import React from 'react';
import { render } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import { Provider } from 'jotai';
import { atomsGlobalState } from 'V2/shared/testingHelpers';
import { globalMatomoAtom, settingsAtom } from 'V2/atoms';
import { TestAtomStoreProvider } from 'V2/testing';
import { Matomo } from '../Matomo';

describe('Matomo', () => {
Expand All @@ -29,21 +28,20 @@ describe('Matomo', () => {
window.location = originalLocation;
});

const renderComponent = (store: any) => {
const renderComponent = (storeState: any) => {
render(
<MemoryRouter>
<Provider store={store}>
<TestAtomStoreProvider initialValues={storeState}>
<Matomo />
</Provider>
</TestAtomStoreProvider>
</MemoryRouter>
);
};

it('should set the matomo config from the user config', () => {
const store = atomsGlobalState();
store.set(settingsAtom, { matomoConfig: '{"url":"https://url.org","id":"1"}' });
const atomStoreValue = { matomoConfig: '{"url":"https://url.org","id":"1"}' };

renderComponent(store);
renderComponent([[settingsAtom, atomStoreValue]]);

expect(window._paq).toStrictEqual([
['setTrackerUrl', 'https://url.org/matomo.php'],
Expand All @@ -56,10 +54,9 @@ describe('Matomo', () => {
});

it('should set the global matomo config', () => {
const store = atomsGlobalState();
store.set(globalMatomoAtom, { url: 'https://global.org', id: '1' });
const atomStoreValue = { url: 'https://global.org', id: '1' };

renderComponent(store);
renderComponent([[globalMatomoAtom, atomStoreValue]]);

expect(window._paq).toStrictEqual([
['setTrackerUrl', 'https://global.org/tenant.php'],
Expand All @@ -72,13 +69,15 @@ describe('Matomo', () => {
});

it('should set both trackers when present', () => {
const store = atomsGlobalState();
store.set(settingsAtom, {
const settingsValue = {
matomoConfig: '{"url":"https://url.org/","id":"1"}',
});
store.set(globalMatomoAtom, { url: 'https://global.org', id: '2' });
};
const globalMatomoValue = { url: 'https://global.org', id: '2' };

renderComponent(store);
renderComponent([
[globalMatomoAtom, globalMatomoValue],
[settingsAtom, settingsValue],
]);

expect(window._paq).toStrictEqual([
['setTrackerUrl', 'https://global.org/tenant.php'],
Expand All @@ -98,23 +97,28 @@ describe('Matomo', () => {
${undefined} | ${undefined} | ${'56'}
`('should not include script when data is not available', ({ userJSON, globalUrl, globalId }) => {
window._paq = undefined;
const store = atomsGlobalState();

store.set(settingsAtom, { matomoConfig: userJSON });
store.set(globalMatomoAtom, { url: globalUrl, id: globalId });
const settingsValue = { matomoConfig: userJSON };
const globalMatomoValue = { url: globalUrl, id: globalId };

renderComponent(store);
renderComponent([
[globalMatomoAtom, globalMatomoValue],
[settingsAtom, settingsValue],
]);

expect(window._paq).toStrictEqual(undefined);
});

it('should not pollute existing keys in the window object', () => {
window._paq = [['googleTracker', 'idForTracker']];
const store = atomsGlobalState();
store.set(settingsAtom, { matomoConfig: '{"url":"https://url.org/","id":"10"}' });
store.set(globalMatomoAtom, { url: 'https://global.org', id: '5' });

renderComponent(store);
const settingsValue = { matomoConfig: '{"url":"https://url.org/","id":"10"}' };
const globalMatomoValue = { url: 'https://global.org', id: '5' };

renderComponent([
[globalMatomoAtom, globalMatomoValue],
[settingsAtom, settingsValue],
]);

expect(window._paq).toStrictEqual([
['googleTracker', 'idForTracker'],
Expand All @@ -129,11 +133,13 @@ describe('Matomo', () => {
});

it('should not break when the users configuration is malformed', () => {
const store = atomsGlobalState();
store.set(settingsAtom, { matomoConfig: '{ malformed: "3", }' });
store.set(globalMatomoAtom, { url: 'https://global.org', id: '3' });
const settingsValue = { matomoConfig: '{ malformed: "3", }' };
const globalMatomoValue = { url: 'https://global.org', id: '3' };

renderComponent(store);
renderComponent([
[globalMatomoAtom, globalMatomoValue],
[settingsAtom, settingsValue],
]);

expect(window._paq).toStrictEqual([
['setTrackerUrl', 'https://global.org/tenant.php'],
Expand Down
2 changes: 1 addition & 1 deletion app/react/V2/Components/Forms/specs/MultiselectList.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react';
import 'cypress-axe';
import { Provider } from 'react-redux';
import { mount } from '@cypress/react18';
import { LEGACY_createStore as createStore } from 'V2/shared/testingHelpers';
import { LEGACY_createStore as createStore } from 'V2/testing';
import { MultiselectList } from '../MultiselectList';

describe('MultiselectList.cy.tsx', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import 'cypress-axe';
import { BrowserRouter } from 'react-router-dom';
import { Provider } from 'react-redux';
import { mount } from '@cypress/react18';
import { LEGACY_createStore as createStore } from 'V2/shared/testingHelpers';
import { LEGACY_createStore as createStore } from 'V2/testing';
import { SettingsContent } from '../SettingsContent';

describe('ConfirmationModal', () => {
Expand Down
Loading

0 comments on commit b2ae163

Please sign in to comment.