Skip to content

Commit

Permalink
Feat: build precomputed data screen in admin
Browse files Browse the repository at this point in the history
  • Loading branch information
JulienMattiussi committed Oct 18, 2023
1 parent 5fc9c76 commit 10c810d
Show file tree
Hide file tree
Showing 44 changed files with 3,291 additions and 535 deletions.
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ clear-database: ## Clear the whole database
db.publishedFacet.remove({}); \
db.subresource.remove({}); \
db.enrichment.remove({}); \
db.precomputed.remove({}); \
"
clear-publication: ## Clear the published data, keep uploaded dataset and model
docker compose exec mongo mongo lodex --eval " \
Expand Down
1,006 changes: 503 additions & 503 deletions package-lock.json

Large diffs are not rendered by default.

17 changes: 8 additions & 9 deletions packages/transformers/.babelrc
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
{
"presets": [
"@babel/preset-env"
],
"plugins": [
"@babel/plugin-syntax-dynamic-import",
"@babel/plugin-syntax-import-meta",
"@babel/plugin-proposal-class-properties",
"@babel/plugin-proposal-json-strings"
]
"presets": ["@babel/preset-env"],
"plugins": [
"@babel/plugin-syntax-dynamic-import",
"@babel/plugin-syntax-import-meta",
"@babel/plugin-proposal-class-properties",
"@babel/plugin-transform-nullish-coalescing-operator",
"@babel/plugin-proposal-json-strings"
]
}
2 changes: 2 additions & 0 deletions src/api/controller/api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import loader from './loader';
import translate from './translate';
import subresource from './subresource';
import enrichment from './enrichment';
import precomputed from './precomputed';
import job from './job';
import dump from './dump';
import displayConfig from './displayConfig';
Expand Down Expand Up @@ -108,6 +109,7 @@ app.use(mount('/characteristic', characteristic));
app.use(mount('/field', fieldRoutes));
app.use(mount('/subresource', subresource));
app.use(mount('/enrichment', enrichment));
app.use(mount('/precomputed', precomputed));
app.use(mount('/job', job));
app.use(mount('/parsing', parsing));
app.use(mount('/publish', publish));
Expand Down
160 changes: 160 additions & 0 deletions src/api/controller/api/precomputed.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import Koa from 'koa';
import route from 'koa-route';
import koaBodyParser from 'koa-bodyparser';
import { v1 as uuid } from 'uuid';

import { PRECOMPUTER } from '../../workers/precomputer';
import { workerQueues } from '../../workers';
import {
getPrecomputedDataPreview,
setPrecomputedJobId,
} from '../../services/precomputed/precomputed';
import { cancelJob, getActiveJob } from '../../workers/tools';

export const setup = async (ctx, next) => {
try {
await next();
} catch (error) {
ctx.status = 500;
ctx.body = { error: error.message };
}
};

export const postPrecomputed = async ctx => {
try {
const precomputed = ctx.request.body;
const result = await ctx.precomputed.create(precomputed);

if (result) {
ctx.body = result;
return;
}

ctx.status = 403;
} catch (error) {
ctx.status = 403;
ctx.body = { error: error.message };
return;
}
};

export const putPrecomputed = async (ctx, id) => {
const newResource = ctx.request.body;

try {
// Delete existing data from dataset
// If we change the name or the rule, existing data is obsolete
const precomputed = await ctx.precomputed.findOneById(id);
await ctx.dataset.removeAttribute(precomputed.name);

ctx.body = await ctx.precomputed.update(id, newResource);
} catch (error) {
ctx.status = 403;
ctx.body = { error: error.message };
return;
}
};

export const deletePrecomputed = async (ctx, id) => {
try {
const precomputed = await ctx.precomputed.findOneById(id);
const activeJob = await getActiveJob(ctx.tenant);
if (
activeJob?.data?.jobType === PRECOMPUTER &&
activeJob?.data?.id === id
) {
cancelJob(ctx, PRECOMPUTER);
}
await ctx.precomputed.delete(id);
await ctx.dataset.removeAttribute(precomputed.name);
ctx.status = 200;
ctx.body = { message: 'ok' };
} catch (error) {
ctx.status = 403;
ctx.body = { error: error.message };
return;
}
};

export const getPrecomputed = async (ctx, id) => {
ctx.body = await ctx.precomputed.findOneById(id);
};

export const getAllPrecomputed = async ctx => {
ctx.body = await ctx.precomputed.findAll();
};

export const precomputedAction = async (ctx, action, id) => {
if (!['launch', 'pause', 'relaunch'].includes(action)) {
throw new Error(`Invalid action "${action}"`);
}

if (action === 'launch') {
await workerQueues[ctx.tenant]
.add(
PRECOMPUTER, // Name of the job
{
id,
jobType: PRECOMPUTER,
tenant: ctx.tenant,
},
{ jobId: uuid() },
)
.then(job => {
setPrecomputedJobId(ctx, id, job);
});
ctx.body = {
status: 'pending',
};
}

if (action === 'relaunch') {
const precomputed = await ctx.precomputed.findOneById(id);
await ctx.dataset.removeAttribute(precomputed.name);
await workerQueues[ctx.tenant]
.add(
PRECOMPUTER, // Name of the job
{
id,
jobType: PRECOMPUTER,
tenant: ctx.tenant,
},
{ jobId: uuid() },
)
.then(job => {
setPrecomputedJobId(ctx, id, job);
});
ctx.body = {
status: 'pending',
};
}

ctx.status = 200;
};

export const precomputedDataPreview = async ctx => {
try {
const result = await getPrecomputedDataPreview(ctx);
ctx.status = 200;
ctx.body = result;
} catch (error) {
ctx.status = 403;
ctx.body = { error: error.message };
return;
}
};

const app = new Koa();

app.use(setup);

app.use(route.get('/', getAllPrecomputed));
app.use(route.get('/:id', getPrecomputed));
app.use(koaBodyParser());
app.use(route.post('/', postPrecomputed));
app.use(route.put('/:id', putPrecomputed));
app.use(route.delete('/:id', deletePrecomputed));
app.use(route.post('/:action/:id', precomputedAction));
app.use(route.post('/preview', precomputedDataPreview));

export default app;
142 changes: 142 additions & 0 deletions src/api/controller/api/precomputed.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import {
postPrecomputed,
putPrecomputed,
deletePrecomputed,
} from './precomputed';
import { getActiveJob, cancelJob } from '../../workers/tools';

jest.mock('../../workers/tools', () => ({
getActiveJob: jest.fn(),
cancelJob: jest.fn(),
}));

describe('Precomputed controller', () => {
describe('postPrecomputed', () => {
it('should call precomputed create repository method', async () => {
const ctx = {
request: { body: { name: 'test' } },
precomputed: { create: jest.fn() },
};

await postPrecomputed(ctx);

expect(ctx.precomputed.create).toHaveBeenCalledWith({
name: 'test',
});
});

it('should return result as body result', async () => {
const ctx = {
request: { body: { name: 'test' } },
precomputed: {
create: jest.fn(() => {
return { name: 'test' };
}),
},
};

await postPrecomputed(ctx);

expect(ctx.body).toStrictEqual({
name: 'test',
});
});

it("should set status 403 if there's not result", async () => {
const ctx = {
request: { body: 'my precomputed' },
precomputed: {
create: jest.fn(() => null),
},
};

await postPrecomputed(ctx);

expect(ctx.status).toBe(403);
});
});

describe('putPrecomputed', () => {
it('should delete existing dataset data based on the precomputed name and update it', async () => {
const ctx = {
request: { body: 'my updated precomputed' },
precomputed: {
findOneById: jest.fn(() =>
Promise.resolve({ name: 'NAME' }),
),
update: jest.fn(() =>
Promise.resolve('updated precomputed'),
),
},
dataset: { removeAttribute: jest.fn() },
};

await putPrecomputed(ctx, 42);

expect(ctx.precomputed.findOneById).toHaveBeenCalledWith(42);
expect(ctx.dataset.removeAttribute).toHaveBeenCalledWith('NAME');
expect(ctx.precomputed.update).toHaveBeenCalledWith(
42,
'my updated precomputed',
);
expect(ctx.body).toEqual('updated precomputed');
return;
});

it('should return a 403 on error if an error occured', async () => {
const ctx = {
request: { body: 'my updated precomputed' },
precomputed: {
findOneById: async () => {
throw new Error('ERROR!');
},
},
};

await putPrecomputed(ctx, 42);

expect(ctx.status).toBe(403);
expect(ctx.body).toEqual({ error: 'ERROR!' });
});
});

describe('deletePrecomputed', () => {
it('should delete existing dataset data based on the precomputed name and then delete it', async () => {
const ctx = {
precomputed: {
findOneById: jest.fn(() => ({ name: 'NAME' })),
delete: jest.fn(),
},
dataset: { removeAttribute: jest.fn() },
};
getActiveJob.mockResolvedValue({
data: { id: 42, jobType: 'precomputer' },
});

await deletePrecomputed(ctx, 42);

expect(ctx.precomputed.findOneById).toHaveBeenCalledWith(42);
expect(ctx.dataset.removeAttribute).toHaveBeenCalledWith('NAME');
expect(ctx.precomputed.delete).toHaveBeenCalledWith(42);
expect(cancelJob).toHaveBeenCalled();
expect(ctx.status).toBe(200);
});

it('should return a 403 on error if an error occured', async () => {
const ctx = {
precomputed: {
findOneById: async () => {
throw new Error('ERROR!');
},
delete: jest.fn(),
},
dataset: { removeAttribute: jest.fn() },
};

await deletePrecomputed(ctx, 42);

expect(ctx.status).toBe(403);
expect(ctx.body).toEqual({ error: 'ERROR!' });
});
});
});
Loading

0 comments on commit 10c810d

Please sign in to comment.