Skip to content

Commit

Permalink
PDE-4989 feat(schema): Add support for bulk writes
Browse files Browse the repository at this point in the history
  • Loading branch information
kola-er committed May 8, 2024
1 parent 0ba70f9 commit b34f341
Show file tree
Hide file tree
Showing 7 changed files with 269 additions and 1 deletion.
31 changes: 31 additions & 0 deletions packages/schema/docs/build/schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ This is automatically generated by the `npm run docs` command in `zapier-platfor
* [/BasicHookOperationSchema](#basichookoperationschema)
* [/BasicOperationSchema](#basicoperationschema)
* [/BasicPollingOperationSchema](#basicpollingoperationschema)
* [/BulkObjectSchema](#bulkobjectschema)
* [/BulkReadSchema](#bulkreadschema)
* [/BulkReadsSchema](#bulkreadsschema)
* [/CreateSchema](#createschema)
Expand Down Expand Up @@ -428,6 +429,8 @@ Key | Required | Type | Description
`lock` | no | [/LockObjectSchema](#lockobjectschema) | **INTERNAL USE ONLY**. Zapier uses this configuration for internal operation locking.
`throttle` | no | [/ThrottleObjectSchema](#throttleobjectschema) | Zapier uses this configuration to apply throttling when the limit for the window is exceeded.
`shouldLock` | no | `boolean` | Should this action be performed one at a time (avoid concurrency)?
`bulk` | no | [/BulkObjectSchema](#bulkobjectschema) | Zapier uses this configuration for writing in bulk.
`performBulk` | no | [/FunctionSchema](#functionschema) | A function to write in bulk with.

#### Examples

Expand Down Expand Up @@ -593,6 +596,34 @@ Key | Required | Type | Description

-----

## /BulkObjectSchema

Zapier uses this configuration for writing in bulk.

#### Details

* **Type** - `object`
* [**Source Code**](https://github.com/zapier/zapier-platform/blob/zapier-platform-schema@15.7.2/packages/schema/lib/schemas/BulkObjectSchema.js)

#### Properties

Key | Required | Type | Description
--- | -------- | ---- | -----------
`groupedBy` | **yes** | `array` | The list of keys of input fields to group bulk-write with. The actual user data provided for the fields will be used during execution. Note that a required input field should be referenced to get user data always.
`limit` | **yes** | `integer` | The maximum number of items to call performBulk with.

#### Examples

* `{ groupedBy: [ 'workspace', 'sheet' ], limit: 100 }`

#### Anti-Examples

* `{ groupedBy: [], limit: 100 }` - _Empty groupedBy list provided: `[]`._
* `{ groupedBy: [ 'workspace' ] }` - _Missing required key: `limit`._
* `{ limit: 1 }` - _Missing required key: `groupedBy`._

-----

## /BulkReadSchema

How will Zapier fetch resources from your application?
Expand Down
26 changes: 26 additions & 0 deletions packages/schema/exported-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1351,6 +1351,24 @@
},
"additionalProperties": false
},
"BulkObjectSchema": {
"id": "/BulkObjectSchema",
"description": "Zapier uses this configuration for writing in bulk.",
"type": "object",
"required": ["groupedBy", "limit"],
"properties": {
"groupedBy": {
"description": "The list of keys of input fields to group bulk-write with. The actual user data provided for the fields will be used during execution. Note that a required input field should be referenced to get user data always.",
"type": "array",
"minItems": 1
},
"limit": {
"description": "The maximum number of items to call performBulk with.",
"type": "integer"
}
},
"additionalProperties": false
},
"BasicCreateActionOperationSchema": {
"id": "/BasicCreateActionOperationSchema",
"description": "Represents the fundamental mechanics of a create.",
Expand Down Expand Up @@ -1417,6 +1435,14 @@
"shouldLock": {
"description": "Should this action be performed one at a time (avoid concurrency)?",
"type": "boolean"
},
"bulk": {
"description": "Zapier uses this configuration for writing in bulk.",
"$ref": "/BulkObjectSchema"
},
"performBulk": {
"description": "A function to write in bulk with.",
"$ref": "/FunctionSchema"
}
},
"additionalProperties": false
Expand Down
45 changes: 45 additions & 0 deletions packages/schema/lib/functional-constraints/bulkWriteConstraints.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
'use strict';

const _ = require('lodash');
const jsonschema = require('jsonschema');

const bulkWriteConstraints = (definition) => {
const errors = [];
const actionType = 'creates';

if (definition[actionType]) {
_.each(definition[actionType], (actionDef) => {
if (actionDef.operation && actionDef.operation.bulk) {
if (!actionDef.operation.performBulk) {
errors.push(
new jsonschema.ValidationError(
'must contain performBulk because bulk is configured.',
actionDef.operation,
'/BasicCreateActionOperationSchema',
`instance.${actionType}.${actionDef.key}.operation`,
'invalid',
'performBulk'
)
);
}

if (actionDef.operation.perform) {
errors.push(
new jsonschema.ValidationError(
'must not contain perform because it is mutually exclusive with bulk.',
actionDef.operation,
'/BasicCreateActionOperationSchema',
`instance.${actionType}.${actionDef.key}.operation`,
'invalid',
'perform'
)
);
}
}
});
}

return errors;
};

module.exports = bulkWriteConstraints;
1 change: 1 addition & 0 deletions packages/schema/lib/functional-constraints/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const checks = [
require('./matchingKeys'),
require('./labelWhenVisible'),
require('./uniqueInputFieldKeys'),
require('./bulkWriteConstraints'),
];

const runFunctionalConstraints = (definition, mainSchema) => {
Expand Down
14 changes: 13 additions & 1 deletion packages/schema/lib/schemas/BasicCreateActionOperationSchema.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
const makeSchema = require('../utils/makeSchema');

const BasicActionOperationSchema = require('./BasicActionOperationSchema');
const BulkObjectSchema = require('./BulkObjectSchema');
const FunctionSchema = require('./FunctionSchema');

// TODO: would be nice to deep merge these instead
// or maybe use allOf which is built into json-schema
Expand All @@ -20,7 +22,17 @@ BasicCreateActionOperationSchema.properties.shouldLock = {
type: 'boolean',
};

BasicCreateActionOperationSchema.properties.bulk = {
description: 'Zapier uses this configuration for writing in bulk.',
$ref: BulkObjectSchema.id,
};

BasicCreateActionOperationSchema.properties.performBulk = {
description: 'A function to write in bulk with.',
$ref: FunctionSchema.id,
};

module.exports = makeSchema(
BasicCreateActionOperationSchema,
BasicActionOperationSchema.dependencies
BasicActionOperationSchema.dependencies.concat(BulkObjectSchema)
);
48 changes: 48 additions & 0 deletions packages/schema/lib/schemas/BulkObjectSchema.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
'use strict';

const makeSchema = require('../utils/makeSchema');

module.exports = makeSchema({
id: '/BulkObjectSchema',
description:
'Zapier uses this configuration for writing in bulk.',
type: 'object',
required: ['groupedBy', 'limit'],
properties: {
groupedBy: {
description:
'The list of keys of input fields to group bulk-write with. The actual user data provided for the fields will be used during execution. Note that a required input field should be referenced to get user data always.',
type: 'array',
minItems: 1,
},
limit: {
description:
'The maximum number of items to call performBulk with.',
type: 'integer',
},
},
examples: [
{
groupedBy: ['workspace', 'sheet'],
limit: 100,
},
],
antiExamples: [
{
example: {
groupedBy: [],
limit: 100,
},
reason: 'Empty groupedBy list provided: `[]`.',
},
{
example: {groupedBy: ['workspace']},
reason: 'Missing required key: `limit`.',
},
{
example: {limit: 1},
reason: 'Missing required key: `groupedBy`.',
},
],
additionalProperties: false,
});
105 changes: 105 additions & 0 deletions packages/schema/test/functional-constraints/bulkWriteConstraints.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
'use strict';

require('should');
const schema = require('../../schema');

describe('bulkWriteConstraints', () => {
it('should error on creates with both bulk and perform defined', () => {
const definition = {
version: '1.0.0',
platformVersion: '1.0.0',
creates: {
foo: {
key: 'foo',
noun: 'Foo',
display: {
label: 'Create Foo',
description: 'Creates a...',
},
operation: {
perform: '$func$2$f$',
performBulk: '$func$2$f$',
bulk: {
groupedBy: ['orderId'],
limit: 10,
},
sample: { id: 1 },
inputFields: [
{ key: 'orderId', type: 'number' },
],
},
},
},
};

const results = schema.validateAppDefinition(definition);
results.errors.should.have.length(1);
results.errors[0].stack.should.eql(
"instance.creates.foo.operation must not contain perform because it is mutually exclusive with bulk."
);
});

it('should error on creates with bulk but no performBulk defined', () => {
const definition = {
version: '1.0.0',
platformVersion: '1.0.0',
creates: {
foo: {
key: 'foo',
noun: 'Foo',
display: {
label: 'Create Foo',
description: 'Creates a...',
},
operation: {
bulk: {
groupedBy: ['orderId'],
limit: 10,
},
sample: { id: 1 },
inputFields: [
{ key: 'orderId', type: 'number' },
],
},
},
},
};

const results = schema.validateAppDefinition(definition);
results.errors.should.have.length(1);
results.errors[0].stack.should.eql(
"instance.creates.foo.operation must contain performBulk because bulk is configured."
);
});

it('should not error on creates with both bulk and performBulk but no perform defined', () => {
const definition = {
version: '1.0.0',
platformVersion: '1.0.0',
creates: {
foo: {
key: 'foo',
noun: 'Foo',
display: {
label: 'Create Foo',
description: 'Creates a...',
},
operation: {
performBulk: '$func$2$f$',
bulk: {
groupedBy: ['orderId'],
limit: 10,
},
sample: { id: 1 },
inputFields: [
{ key: 'orderId', type: 'number' },
],
},
},
},
};

const results = schema.validateAppDefinition(definition);
results.errors.should.have.length(0);
});
});

0 comments on commit b34f341

Please sign in to comment.