-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature: Add External Source (and External Event) Attribute Validation #116
base: develop
Are you sure you want to change the base?
Changes from 15 commits
69f2ec2
b9839d3
f77f9e0
78c94a8
ca07a17
6995e66
578fbac
05efd84
897e88c
eb1ac18
3fbecf9
48cb21c
accf345
6b68977
0821432
de0f009
30967c3
3ccd817
c283394
c62023e
cf1dfe9
06b0415
e32f95a
27422a4
574a0cd
6edace6
092671e
c8cb137
1a0ee20
9f90170
d25e67a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -60,6 +60,6 @@ | |
"nodemon": "^3.1.4", | ||
"prettier": "^3.0.0", | ||
"typescript": "^5.1.6", | ||
"vitest": "^1.2.2" | ||
"vitest": "^1.4.0" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,136 @@ | ||||||
import type { Express, Request, Response } from 'express'; | ||||||
import type { ExternalEventTypeInsertInput } from '../../types/external-event.js'; | ||||||
import Ajv from 'ajv'; | ||||||
import { getEnv } from '../../env.js'; | ||||||
import getLogger from '../../logger.js'; | ||||||
import gql from './gql.js'; | ||||||
import { HasuraError } from '../../types/hasura.js'; | ||||||
|
||||||
const logger = getLogger('packages/external-event/external-event'); | ||||||
const { HASURA_API_URL } = getEnv(); | ||||||
const GQL_API_URL = `${HASURA_API_URL}/v1/graphql`; | ||||||
|
||||||
const ajv = new Ajv(); | ||||||
|
||||||
async function uploadExternalEventType(req: Request, res: Response) { | ||||||
const authorizationHeader = req.get('authorization'); | ||||||
|
||||||
const { | ||||||
headers: { 'x-hasura-role': roleHeader, 'x-hasura-user-id': userHeader }, | ||||||
} = req; | ||||||
|
||||||
const { body } = req; | ||||||
const { external_event_type_name, attribute_schema } = body; | ||||||
logger.info(`POST /uploadExternalEventType: Uploading External Event Type: ${external_event_type_name}`); | ||||||
|
||||||
const headers: HeadersInit = { | ||||||
Authorization: authorizationHeader ?? '', | ||||||
'Content-Type': 'application/json', | ||||||
'x-hasura-role': roleHeader ? `${roleHeader}` : '', | ||||||
'x-hasura-user-id': userHeader ? `${userHeader}` : '', | ||||||
}; | ||||||
|
||||||
// Validate schema is valid JSON Schema | ||||||
const schemaIsValid: boolean = ajv.validateSchema(attribute_schema); | ||||||
if (!schemaIsValid) { | ||||||
logger.error( | ||||||
`POST /uploadExternalEventType: Schema validation failed for External Event Type ${external_event_type_name}`, | ||||||
); | ||||||
ajv.errors?.forEach(ajvError => logger.error(ajvError)); | ||||||
res.status(500).send({ message: ajv.errors }); | ||||||
return; | ||||||
} | ||||||
|
||||||
// Make sure name in schema (title) and provided name match | ||||||
try { | ||||||
if (attribute_schema['title'] === undefined || attribute_schema.title !== external_event_type_name) { | ||||||
throw new Error('Schema title does not match provided external event type name.'); | ||||||
} | ||||||
} catch (error) { | ||||||
logger.error( | ||||||
`POST /uploadExternalEventType: Error occurred during External Event Type ${external_event_type_name} upload`, | ||||||
); | ||||||
logger.error((error as Error).message); | ||||||
res.status(500).send({ message: (error as Error).message }); | ||||||
return; | ||||||
} | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this a try-catch instead of an if-return? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Cleaned up, no longer using |
||||||
|
||||||
logger.info(`POST /uploadExternalEventType: Attribute schema was VALID`); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please use present tense in logs, unless the log is explicitly referring to a "prior state":
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All |
||||||
|
||||||
// Run the Hasura migration for creating an external event | ||||||
const externalEventTypeInsertInput: ExternalEventTypeInsertInput = { | ||||||
attribute_schema: attribute_schema, | ||||||
name: external_event_type_name, | ||||||
}; | ||||||
|
||||||
const response = await fetch(GQL_API_URL, { | ||||||
body: JSON.stringify({ | ||||||
query: gql.CREATE_EXTERNAL_EVENT_TYPE, | ||||||
variables: { eventType: externalEventTypeInsertInput }, | ||||||
}), | ||||||
headers, | ||||||
method: 'POST', | ||||||
}); | ||||||
|
||||||
type CreateExternalEventTypeResponse = { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Types should be declared in a file in the Please use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Moved all type definitions out to |
||||||
data: { createExternalEventType: { attribute_schema: object; name: string } | null }; | ||||||
}; | ||||||
const jsonResponse = await response.json(); | ||||||
const createExternalEventTypeResponse = jsonResponse as CreateExternalEventTypeResponse | HasuraError; | ||||||
|
||||||
res.json(createExternalEventTypeResponse); | ||||||
} | ||||||
|
||||||
export default (app: Express) => { | ||||||
/** | ||||||
* @swagger | ||||||
* /uploadExternalEventType: | ||||||
* post: | ||||||
* security: | ||||||
* - bearerAuth: [] | ||||||
* consumes: | ||||||
* - application/json | ||||||
* produces: | ||||||
* - application/json | ||||||
* parameters: | ||||||
* - in: header | ||||||
* name: x-hasura-role | ||||||
* schema: | ||||||
* type: string | ||||||
* required: false | ||||||
* requestBody: | ||||||
* content: | ||||||
* application/json: | ||||||
* schema: | ||||||
* type: object | ||||||
* properties: | ||||||
* attribute_schema: | ||||||
* type: object | ||||||
* external_event_type_name: | ||||||
* type: string | ||||||
* required: | ||||||
* - external_event_type_name | ||||||
* attribute_schema | ||||||
* responses: | ||||||
* 200: | ||||||
* description: Created External Event Type | ||||||
* content: | ||||||
* application/json: | ||||||
* schema: | ||||||
* properties: | ||||||
* attribute_schema: | ||||||
* description: JSON Schema for the created External Event Type's attributes | ||||||
* type: object | ||||||
* name: | ||||||
* description: Name of the created External Event Type | ||||||
* type: string | ||||||
* 403: | ||||||
* description: Unauthorized error | ||||||
* 401: | ||||||
* description: Unauthenticated error | ||||||
* summary: Uploads an External Event Type definition (containing name & attributes schema) to Hasura. | ||||||
* tags: | ||||||
* - Hasura | ||||||
*/ | ||||||
app.post('/uploadExternalEventType', uploadExternalEventType); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This endpoint is currently unauthenticated. Example of an authenticated endpoint from L716 of app.post('/uploadDataset', upload.single('external_dataset'), refreshLimiter, auth, uploadDataset); There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Re-worked the routes so they properly use |
||||||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
export default { | ||
CREATE_EXTERNAL_EVENT_TYPE: `#graphql | ||
mutation CreateExternalEventType($eventType: external_event_type_insert_input!) | ||
{ | ||
createExternalEventType: insert_external_event_type_one(object: $eventType) { | ||
attribute_schema | ||
name | ||
} | ||
} | ||
`, | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Organization question: why are these separate
external-event
andexternal-source
packages instead of one united package?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I started with them separate since they'd been separated at times when doing the UI work in the original pull-request, but I do think it makes sense to condense to one package - I can't think of a name though, maybe just naming it
external-source
orexternal-source-events
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't the feature bundled up under the name
external-events
in the UI? If so, that works. Otherwiseexternal-source
works.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ended up moving all new routes to a singular package as
external-source
!