-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feat: build precomputed data screen in admin
- Loading branch information
1 parent
5fc9c76
commit 10c810d
Showing
44 changed files
with
3,291 additions
and
535 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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!' }); | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.