-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
SIMSBIOHUB-632: Display Markdown in Vote Dialogs (#1418)
* Add markdown database table. * Add help buttons and ability for users to vote them up or down. --------- Co-authored-by: Nick Phura <[email protected]>
- Loading branch information
1 parent
8f48b31
commit 0a9a57f
Showing
41 changed files
with
4,602 additions
and
2,186 deletions.
There are no files selected for viewing
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 |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import { z } from 'zod'; | ||
|
||
/** | ||
* Markdown Model. | ||
* | ||
* @description Data model for `markdown`. | ||
*/ | ||
export const MarkdownModel = z.object({ | ||
markdown_id: z.number(), | ||
markdown_type_id: z.number(), | ||
data: z.string().nullable(), | ||
score: z.number(), | ||
record_end_date: z.string(), | ||
create_date: z.string(), | ||
create_user: z.number(), | ||
update_date: z.string().nullable(), | ||
update_user: z.number().nullable(), | ||
revision_count: z.number() | ||
}); | ||
|
||
export type MarkdownModel = z.infer<typeof MarkdownModel>; | ||
|
||
/** | ||
* Markdown Record. | ||
* | ||
* @description Data record for `markdown`. | ||
*/ | ||
export const MarkdownRecord = MarkdownModel.omit({ | ||
create_date: true, | ||
create_user: true, | ||
update_date: true, | ||
update_user: true, | ||
revision_count: true | ||
}); | ||
|
||
export type MarkdownRecord = z.infer<typeof MarkdownRecord>; |
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,34 @@ | ||
import { z } from 'zod'; | ||
|
||
/** | ||
* Markdown Type Model. | ||
* | ||
* @description Data model for `markdown_type`. | ||
*/ | ||
export const MarkdownTypeModel = z.object({ | ||
markdown_type_id: z.number(), | ||
name: z.string(), | ||
description: z.string(), | ||
create_date: z.string(), | ||
create_user: z.number(), | ||
update_date: z.string().nullable(), | ||
update_user: z.number().nullable(), | ||
revision_count: z.number() | ||
}); | ||
|
||
export type MarkdownTypeModel = z.infer<typeof MarkdownTypeModel>; | ||
|
||
/** | ||
* Markdown Type Record. | ||
* | ||
* @description Data record for `markdown_type`. | ||
*/ | ||
export const MarkdownTypeRecord = MarkdownTypeModel.omit({ | ||
create_date: true, | ||
create_user: true, | ||
update_date: true, | ||
update_user: true, | ||
revision_count: true | ||
}); | ||
|
||
export type MarkdownTypeRecord = z.infer<typeof MarkdownTypeRecord>; |
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,34 @@ | ||
import { z } from 'zod'; | ||
|
||
/** | ||
* Markdown User Model. | ||
* | ||
* @description Data model for `markdown_user`. | ||
*/ | ||
export const MarkdownUserModel = z.object({ | ||
markdown_user_id: z.number(), | ||
system_user_id: z.number(), | ||
markdown_id: z.number(), | ||
create_date: z.string(), | ||
create_user: z.number(), | ||
update_date: z.string().nullable(), | ||
update_user: z.number().nullable(), | ||
revision_count: z.number() | ||
}); | ||
|
||
export type MarkdownUserModel = z.infer<typeof MarkdownUserModel>; | ||
|
||
/** | ||
* Markdown User Record. | ||
* | ||
* @description Data record for `markdown_user`. | ||
*/ | ||
export const MarkdownUserRecord = MarkdownUserModel.omit({ | ||
create_date: true, | ||
create_user: true, | ||
update_date: true, | ||
update_user: true, | ||
revision_count: true | ||
}); | ||
|
||
export type MarkdownUserRecord = z.infer<typeof MarkdownUserRecord>; |
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,17 @@ | ||
import { z } from 'zod'; | ||
import { MarkdownRecord } from '../database-models/markdown'; | ||
|
||
export const MarkdownObject = MarkdownRecord.pick({ | ||
markdown_id: true, | ||
markdown_type_id: true, | ||
data: true | ||
}).extend({ | ||
participated: z.boolean() | ||
}); | ||
|
||
export type MarkdownObject = z.infer<typeof MarkdownObject>; | ||
|
||
export interface MarkdownQueryObject { | ||
system_user_id: number; | ||
markdown_type_name: string; | ||
} |
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,40 @@ | ||
import { OpenAPIV3 } from 'openapi-types'; | ||
|
||
/** | ||
* Schema for markdown records used in versioned help dialogs | ||
* | ||
*/ | ||
export const markdownSchema: OpenAPIV3.SchemaObject = { | ||
type: 'object', | ||
description: 'Schema for get markdown response', | ||
additionalProperties: false, | ||
required: ['markdown'], | ||
properties: { | ||
markdown: { | ||
type: 'object', | ||
description: 'Markdown record', | ||
required: ['markdown_id', 'markdown_type_id', 'data', 'participated'], | ||
additionalProperties: false, | ||
properties: { | ||
markdown_id: { | ||
type: 'number', | ||
description: 'Primary key of the markdown record', | ||
minimum: 1 | ||
}, | ||
markdown_type_id: { | ||
type: 'number', | ||
description: 'Type of the markdown record, used to identify which records correspond to which dialogs', | ||
minimum: 1 | ||
}, | ||
data: { | ||
type: 'string', | ||
description: 'Markdown string to display' | ||
}, | ||
participated: { | ||
type: 'boolean', | ||
description: 'True if the user has already scored the markdown record, otherwise false.' | ||
} | ||
} | ||
} | ||
} | ||
}; |
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,93 @@ | ||
import chai, { expect } from 'chai'; | ||
import sinon from 'sinon'; | ||
import sinonChai from 'sinon-chai'; | ||
import { getMarkdownByTypeName } from '.'; | ||
import * as db from '../../database/db'; | ||
import { HTTPError } from '../../errors/http-error'; | ||
import { MarkdownService } from '../../services/markdown-service'; | ||
import { KeycloakUserInformation } from '../../utils/keycloak-utils'; | ||
import { getMockDBConnection, getRequestHandlerMocks } from '../../__mocks__/db'; | ||
|
||
chai.use(sinonChai); | ||
|
||
describe('getMarkdown', () => { | ||
afterEach(() => { | ||
sinon.restore(); | ||
}); | ||
|
||
it('successfully retrieves markdown', async () => { | ||
const mockMarkdownResponse = { | ||
markdown_id: 1, | ||
markdown_type_id: 1, | ||
data: 'Sample markdown content', | ||
participated: false | ||
}; | ||
|
||
const mockDBConnection = getMockDBConnection({ | ||
open: sinon.stub(), | ||
commit: sinon.stub(), | ||
release: sinon.stub(), | ||
systemUserId: () => 20 | ||
}); | ||
|
||
sinon.stub(db, 'getDBConnection').returns(mockDBConnection); | ||
|
||
const getMarkdownStub = sinon | ||
.stub(MarkdownService.prototype, 'getMarkdownByTypeName') | ||
.resolves(mockMarkdownResponse); | ||
|
||
const { mockReq, mockRes, mockNext } = getRequestHandlerMocks(); | ||
mockReq.query = { typeName: 'help' }; | ||
mockReq.keycloak_token = {} as KeycloakUserInformation; | ||
|
||
const requestHandler = getMarkdownByTypeName(); | ||
|
||
await requestHandler(mockReq, mockRes, mockNext); | ||
|
||
expect(mockDBConnection.open).to.have.been.calledOnce; | ||
expect(mockDBConnection.commit).to.have.been.calledOnce; | ||
expect(getMarkdownStub).to.have.been.calledOnceWith({ | ||
markdown_type_name: 'help', | ||
system_user_id: 20 | ||
}); | ||
expect(mockRes.jsonValue.markdown).to.eql(mockMarkdownResponse); | ||
expect(mockDBConnection.release).to.have.been.calledOnce; | ||
}); | ||
|
||
it('handles errors gracefully', async () => { | ||
const mockDBConnection = getMockDBConnection({ | ||
open: sinon.stub(), | ||
commit: sinon.stub(), | ||
rollback: sinon.stub(), | ||
release: sinon.stub(), | ||
systemUserId: () => 20 | ||
}); | ||
|
||
sinon.stub(db, 'getDBConnection').returns(mockDBConnection); | ||
|
||
const getMarkdownStub = sinon | ||
.stub(MarkdownService.prototype, 'getMarkdownByTypeName') | ||
.rejects(new Error('a test error')); | ||
|
||
const { mockReq, mockRes, mockNext } = getRequestHandlerMocks(); | ||
mockReq.query = { typeName: 'help' }; | ||
mockReq.keycloak_token = {} as KeycloakUserInformation; | ||
|
||
const requestHandler = getMarkdownByTypeName(); | ||
|
||
try { | ||
await requestHandler(mockReq, mockRes, mockNext); | ||
expect.fail('Expected error was not thrown'); | ||
} catch (actualError) { | ||
expect(mockDBConnection.open).to.have.been.calledOnce; | ||
expect(getMarkdownStub).to.have.been.calledOnceWith({ | ||
markdown_type_name: 'help', | ||
system_user_id: 20 | ||
}); | ||
expect(mockDBConnection.rollback).to.have.been.calledOnce; | ||
expect(mockDBConnection.release).to.have.been.calledOnce; | ||
|
||
expect((actualError as HTTPError).message).to.equal('a test error'); | ||
} | ||
}); | ||
}); |
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,106 @@ | ||
import { RequestHandler } from 'express'; | ||
import { Operation } from 'express-openapi'; | ||
import { getDBConnection } from '../../database/db'; | ||
import { markdownSchema } from '../../openapi/schemas/markdown'; | ||
import { authorizeRequestHandler } from '../../request-handlers/security/authorization'; | ||
import { MarkdownService } from '../../services/markdown-service'; | ||
import { getLogger } from '../../utils/logger'; | ||
|
||
const defaultLog = getLogger('paths/markdown/index'); | ||
|
||
export const GET: Operation = [ | ||
authorizeRequestHandler(() => { | ||
return { | ||
and: [ | ||
{ | ||
discriminator: 'SystemUser' | ||
} | ||
] | ||
}; | ||
}), | ||
getMarkdownByTypeName() | ||
]; | ||
|
||
GET.apiDoc = { | ||
description: 'Gets a markdown record to display in a help dialog.', | ||
tags: ['markdown'], | ||
security: [ | ||
{ | ||
Bearer: [] | ||
} | ||
], | ||
parameters: [ | ||
{ | ||
in: 'query', | ||
name: 'typeName', | ||
description: 'The name of a markdown type to retrieve the latest markdown record for', | ||
required: true, | ||
schema: { | ||
type: 'string' | ||
} | ||
} | ||
], | ||
responses: { | ||
200: { | ||
description: 'Markdown response object.', | ||
content: { | ||
'application/json': { | ||
schema: markdownSchema | ||
} | ||
} | ||
}, | ||
400: { | ||
$ref: '#/components/responses/400' | ||
}, | ||
401: { | ||
$ref: '#/components/responses/401' | ||
}, | ||
403: { | ||
$ref: '#/components/responses/403' | ||
}, | ||
500: { | ||
$ref: '#/components/responses/500' | ||
}, | ||
default: { | ||
$ref: '#/components/responses/default' | ||
} | ||
} | ||
}; | ||
|
||
/** | ||
* Get the latest markdown text for a given markdown type | ||
* | ||
* @returns {RequestHandler} | ||
*/ | ||
export function getMarkdownByTypeName(): RequestHandler { | ||
return async (req, res) => { | ||
defaultLog.debug({ label: 'getMarkdownByTypeName' }); | ||
|
||
const connection = getDBConnection(req.keycloak_token); | ||
|
||
try { | ||
await connection.open(); | ||
|
||
const systemUserId = connection.systemUserId(); | ||
|
||
const markdownTypeName = req.query.typeName as string; | ||
|
||
const markdownService = new MarkdownService(connection); | ||
|
||
const markdown = await markdownService.getMarkdownByTypeName({ | ||
markdown_type_name: markdownTypeName, | ||
system_user_id: systemUserId | ||
}); | ||
|
||
await connection.commit(); | ||
|
||
return res.status(200).json({ markdown }); | ||
} catch (error) { | ||
defaultLog.error({ label: 'getMarkdown', message: 'error', error }); | ||
await connection.rollback(); | ||
throw error; | ||
} finally { | ||
connection.release(); | ||
} | ||
}; | ||
} |
Oops, something went wrong.