Skip to content
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

feat(schema) support hook to poll triggers INT-15776 #432

Merged
merged 9 commits into from
Nov 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions packages/schema/exported-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1063,6 +1063,83 @@
},
"additionalProperties": false
},
"BasicHookToPollOperationSchema": {
"id": "/BasicHookToPollOperationSchema",
"description": "Represents the inbound mechanics of hook to poll style triggers. Defers to list for fields.",
"type": "object",
"required": ["performList", "performSubscribe", "performUnsubscribe"],
"properties": {
"type": {
"description": "Must be explicitly set to `\"hook_to_poll\"`.",
"type": "string",
"enum": ["hook_to_poll"],
"required": {
"type": "replace",
"value": "**yes** (with exceptions, see description)"
}
},
"performList": {
"description": "Similar a polling trigger, but checks for new data when a webhook is received, instead of every few minutes",
"oneOf": [
{
"$ref": "/RequestSchema"
},
{
"$ref": "/FunctionSchema"
}
]
},
"canPaginate": {
"description": "Does this endpoint support pagination via temporary cursor storage?",
"type": "boolean"
},
"performSubscribe": {
"description": "Takes a URL and any necessary data from the user and subscribes. ",
"oneOf": [
{
"$ref": "/RequestSchema"
},
{
"$ref": "/FunctionSchema"
}
]
},
"performUnsubscribe": {
"description": "Takes a URL and data from a previous subscribe call and unsubscribes. ",
"oneOf": [
{
"$ref": "/RequestSchema"
},
{
"$ref": "/FunctionSchema"
}
]
},
"inputFields": {
"description": "What should the form a user sees and configures look like?",
"$ref": "/DynamicFieldsSchema"
},
"outputFields": {
"description": "What fields of data will this return? Will use resource outputFields if missing, will also use sample if available.",
"$ref": "/DynamicFieldsSchema"
},
"sample": {
"description": "What does a sample of data look like? Will use resource sample if missing. Requirement waived if `display.hidden` is true or if this belongs to a resource that has a top-level sample",
"type": "object",
"minProperties": 1,
"docAnnotation": {
"required": {
"type": "replace",
"value": "**yes** (with exceptions, see description)"
}
}
}
},
"additionalProperties": false,
"docAnnotation": {
"hide": true
}
},
"TriggerSchema": {
"id": "/TriggerSchema",
"description": "How will Zapier get notified of new objects?",
Expand Down Expand Up @@ -1091,6 +1168,9 @@
},
{
"$ref": "/BasicHookOperationSchema"
},
{
"$ref": "/BasicHookToPollOperationSchema"
}
]
}
Expand Down
93 changes: 93 additions & 0 deletions packages/schema/lib/schemas/BasicHookToPollOperationSchema.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
'use strict';

const makeSchema = require('../utils/makeSchema');
const { SKIP_KEY } = require('../constants');

const BasicOperationSchema = require('./BasicOperationSchema');
const FunctionSchema = require('./FunctionSchema');
const RequestSchema = require('./RequestSchema');

// TODO: would be nice to deep merge these instead
// or maybe use allOf which is built into json-schema
const BasicHookToPollOperationSchema = JSON.parse(
JSON.stringify(BasicOperationSchema.schema)
);

BasicHookToPollOperationSchema.id = '/BasicHookToPollOperationSchema';

BasicHookToPollOperationSchema.description =
'Represents the inbound mechanics of hook to poll style triggers. Defers to list for fields.';

BasicHookToPollOperationSchema.docAnnotation = {
hide: true,
};

BasicHookToPollOperationSchema.required = [
'performList',
'performSubscribe',
'performUnsubscribe',
];

BasicHookToPollOperationSchema.properties = {
type: {
description: 'Must be explicitly set to `"hook_to_poll"`.',
type: 'string',
enum: ['hook_to_poll'],
required: {
type: 'replace',
value: '**yes** (with exceptions, see description)',
},
},
performList: {
description:
'Similar a polling trigger, but checks for new data when a webhook is received, instead of every few minutes',
oneOf: [{ $ref: RequestSchema.id }, { $ref: FunctionSchema.id }],
},
canPaginate: {
description:
'Does this endpoint support pagination via temporary cursor storage?',
type: 'boolean',
},
performSubscribe: {
description:
'Takes a URL and any necessary data from the user and subscribes. ',
oneOf: [{ $ref: RequestSchema.id }, { $ref: FunctionSchema.id }],
},
performUnsubscribe: {
description:
'Takes a URL and data from a previous subscribe call and unsubscribes. ',
oneOf: [{ $ref: RequestSchema.id }, { $ref: FunctionSchema.id }],
},
inputFields: BasicHookToPollOperationSchema.properties.inputFields,
outputFields: BasicHookToPollOperationSchema.properties.outputFields,
sample: BasicHookToPollOperationSchema.properties.sample,
};

BasicHookToPollOperationSchema.examples = [
{
type: 'hook_to_poll',
performList: { require: 'some/path/to/file2.js' },
performSubscribe: { require: 'some/path/to/file3.js' },
performUnsubscribe: { require: 'some/path/to/file4.js' },
sample: { id: 42, name: 'Hooli' },
},
];

BasicHookToPollOperationSchema.antiExamples = [
{
[SKIP_KEY]: true, // Cannot validate that sample is only required if display isn't true
example: {
type: 'hook_to_poll',
performList: { require: 'some/path/to/file2.js' },
performSubscribe: { require: 'some/path/to/file3.js' },
performUnsubscribe: { require: 'some/path/to/file4.js' },
},
reason:
'Missing required key: sample. Note - This is only invalid if `display` is not explicitly set to true',
},
];

module.exports = makeSchema(
BasicHookToPollOperationSchema,
BasicOperationSchema.dependencies
);
3 changes: 3 additions & 0 deletions packages/schema/lib/schemas/TriggerSchema.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const makeSchema = require('../utils/makeSchema');

const BasicDisplaySchema = require('./BasicDisplaySchema');
const BasicHookOperationSchema = require('./BasicHookOperationSchema');
const BasicHookToPollOperationSchema = require('./BasicHookToPollOperationSchema');
const BasicPollingOperationSchema = require('./BasicPollingOperationSchema');
const KeySchema = require('./KeySchema');

Expand Down Expand Up @@ -34,6 +35,7 @@ module.exports = makeSchema(
anyOf: [
{ $ref: BasicPollingOperationSchema.id },
{ $ref: BasicHookOperationSchema.id },
{ $ref: BasicHookToPollOperationSchema.id },
casshill marked this conversation as resolved.
Show resolved Hide resolved
],
},
},
Expand Down Expand Up @@ -89,5 +91,6 @@ module.exports = makeSchema(
BasicDisplaySchema,
BasicPollingOperationSchema,
BasicHookOperationSchema,
BasicHookToPollOperationSchema,
]
);
17 changes: 14 additions & 3 deletions packages/schema/lib/utils/buildDocs.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const links = require('./links');
const NO_DESCRIPTION = '_No description given._';
const COMBOS = ['anyOf', 'allOf', 'oneOf'];
const { SKIP_KEY } = require('../constants');
const hiddenRefs = [];

const walkSchemas = (InitSchema, callback) => {
const recurse = (Schema, parents) => {
Expand All @@ -27,7 +28,11 @@ const walkSchemas = (InitSchema, callback) => {
const collectSchemas = (InitSchema) => {
const schemas = {};
walkSchemas(InitSchema, (Schema) => {
schemas[Schema.id] = Schema;
if (!_.get(Schema, 'schema.docAnnotation.hide')) {
casshill marked this conversation as resolved.
Show resolved Hide resolved
schemas[Schema.id] = Schema;
} else {
hiddenRefs.push(Schema.id);
}
});
return schemas;
};
Expand Down Expand Up @@ -64,12 +69,18 @@ const typeOrLink = (schema) => {
return `${quoteOrNa(schema.type)}[${typeOrLink(schema.items)}]`;
}
if (schema.$ref) {
return `[${schema.$ref}](${links.anchor(schema.$ref)})`;
if (!hiddenRefs.includes(schema.$ref)) {
return `[${schema.$ref}](${links.anchor(schema.$ref)})`;
}
return;
casshill marked this conversation as resolved.
Show resolved Hide resolved
}
for (let i = 0; i < COMBOS.length; i++) {
const key = COMBOS[i];
if (schema[key] && schema[key].length) {
return `${key}(${schema[key].map(typeOrLink).join(', ')})`;
return `${key}(${schema[key]
.map(typeOrLink)
.filter(Boolean)
.join(', ')})`;
}
}
if (schema.enum && schema.enum.length) {
Expand Down
2 changes: 1 addition & 1 deletion packages/schema/test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const appDefinition = require('../examples/definition.json');

const copy = (o) => JSON.parse(JSON.stringify(o));

const NUM_SCHEMAS = 50; // changes regularly as we expand
const NUM_SCHEMAS = 51; // changes regularly as we expand

describe('app', () => {
describe('validation', () => {
Expand Down