Skip to content

Commit

Permalink
OpenAPI: addon operations (#3421)
Browse files Browse the repository at this point in the history
This PR updates the OpenAPI schemas for all the operations tagged with
"addons". In doing so, I also uncovered a few bugs and inconsistencies.
These have also been fixed.

## Changes

I've added inline comments to the changed files to call out anything
that I think is worth clarifying specifically. As an overall
description, this PR does the following:

Splits `addon-schema` into `addon-schema` and
`addon-create-update-schema`. The former is used when describing addons
that exist within Unleash and contain IDs and `created_at` timestamps.
The latter is used when creating or updating addons.

Adds examples and descriptions to all relevant schemas (and their
dependencies).

Updates addons operations descriptions and response codes (including the
recently introduced 413 and 415).

Fixes a bug where the server would crash if it didn't recognize the
addon provider (test added).

Fixes a bug where updating an addon wouldn't return anything, even if
the API said that it would. (test added)

Resolves some inconsistencies in handling of addon description. (tests
added)

### Addon descriptions

when creating addons, descriptions are optional. The original
`addonSchema` said they could be `null | string | undefined`. This
caused some inconsistencies in return values. Sometimes they were
returned, other times not. I've made it so that `descriptions` are now
always returned from the API. If it's not defined or if it's set to
`null`, the API will return `description: null`.

### `IAddonDto`

`IAddonDto`, the type we used internally to model the incoming addons
(for create and update) says that `description` is required. This hasn't
been true at least since we introduced OpenAPI schemas. As such, the
update and insert methods that the service uses were incompatible with
the **actual** data that we require.

I've changed the type to reflect reality for now. Assuming the tests
pass, this **should** all be good, but I'd like the reviewer(s) to give
this a think too.

---------

Co-authored-by: Christopher Kolstad <chriswk@getunleash.ai>
  • Loading branch information
thomasheartman and Christopher Kolstad committed Apr 18, 2023
1 parent d3b8056 commit e11fae5
Show file tree
Hide file tree
Showing 19 changed files with 1,127 additions and 46 deletions.
10 changes: 6 additions & 4 deletions src/lib/db/addon-store.ts
Expand Up @@ -16,6 +16,7 @@ const COLUMNS = [
'events',
'projects',
'environments',
'created_at',
];
const TABLE = 'addons';

Expand Down Expand Up @@ -71,18 +72,19 @@ export default class AddonStore implements IAddonStore {
stopTimer();
// eslint-disable-next-line @typescript-eslint/naming-convention
const { id, created_at } = rows[0];
return { id, createdAt: created_at, ...addon };
return this.rowToAddon({ id, createdAt: created_at, ...addon });
}

async update(id: number, addon: IAddonDto): Promise<IAddon> {
const rows = await this.db(TABLE)
.where({ id })
.update(this.addonToRow(addon));
.update(this.addonToRow(addon))
.returning(COLUMNS);

if (!rows) {
throw new NotFoundError('Could not find addon');
}
return rows[0];
return this.rowToAddon(rows[0]);
}

async delete(id: number): Promise<void> {
Expand Down Expand Up @@ -114,7 +116,7 @@ export default class AddonStore implements IAddonStore {
id: row.id,
provider: row.provider,
enabled: row.enabled,
description: row.description,
description: row.description ?? null,
parameters: row.parameters,
events: row.events,
projects: row.projects || [],
Expand Down
2 changes: 2 additions & 0 deletions src/lib/openapi/index.ts
Expand Up @@ -3,6 +3,7 @@ import {
adminFeaturesQuerySchema,
addonParameterSchema,
addonSchema,
addonCreateUpdateSchema,
addonsSchema,
addonTypeSchema,
apiTokenSchema,
Expand Down Expand Up @@ -181,6 +182,7 @@ export const schemas: UnleashSchemas = {
adminFeaturesQuerySchema,
addonParameterSchema,
addonSchema,
addonCreateUpdateSchema,
addonsSchema,
addonTypeSchema,
apiTokenSchema,
Expand Down
8 changes: 0 additions & 8 deletions src/lib/openapi/meta-schema-rules.test.ts
Expand Up @@ -79,10 +79,6 @@ const metaRules: Rule[] = [
},
},
knownExceptions: [
'addonParameterSchema',
'addonSchema',
'addonsSchema',
'addonTypeSchema',
'apiTokenSchema',
'apiTokensSchema',
'applicationSchema',
Expand Down Expand Up @@ -202,10 +198,6 @@ const metaRules: Rule[] = [
},
knownExceptions: [
'adminFeaturesQuerySchema',
'addonParameterSchema',
'addonSchema',
'addonsSchema',
'addonTypeSchema',
'apiTokenSchema',
'apiTokensSchema',
'applicationSchema',
Expand Down
79 changes: 79 additions & 0 deletions src/lib/openapi/spec/addon-create-update-schema.ts
@@ -0,0 +1,79 @@
import { FromSchema } from 'json-schema-to-ts';

export const addonCreateUpdateSchema = {
$id: '#/components/schemas/addonCreateUpdateSchema',
type: 'object',
required: ['provider', 'enabled', 'parameters', 'events'],
description:
'Data required to create or update an [Unleash addon](https://docs.getunleash.io/reference/addons) instance.',
properties: {
provider: {
type: 'string',

description: `The addon provider, such as "webhook" or "slack". This string is **case sensitive** and maps to the provider's \`name\` property.
The list of all supported providers and their parameters for a specific Unleash instance can be found by making a GET request to the \`api/admin/addons\` endpoint: the \`providers\` property of that response will contain all available providers.
The default set of providers can be found in the [addons reference documentation](https://docs.getunleash.io/reference/addons). The default supported options are:
- \`datadog\` for [Datadog](https://docs.getunleash.io/reference/addons/datadog)
- \`slack\` for [Slack](https://docs.getunleash.io/reference/addons/slack)
- \`teams\` for [Microsoft Teams](https://docs.getunleash.io/reference/addons/teams)
- \`webhook\` for [webhooks](https://docs.getunleash.io/reference/addons/webhook)
The provider you choose for your addon dictates what properties the \`parameters\` object needs. Refer to the documentation for each provider for more information.
`,
example: 'webhook',
},
description: {
type: 'string',
description: 'A description of the addon.',
example:
'This addon posts updates to our internal feature tracking system whenever a feature is created or updated.',
},
enabled: {
type: 'boolean',
description: 'Whether the addon should be enabled or not.',
},
parameters: {
type: 'object',
additionalProperties: {},
example: {
url: 'http://localhost:4242/webhook',
},
description:
'Parameters for the addon provider. This object has different required and optional properties depending on the provider you choose. Consult the documentation for details.',
},
events: {
type: 'array',
description:
'The event types that will trigger this specific addon.',
items: {
type: 'string',
},
example: ['feature-created', 'feature-updated'],
},
projects: {
type: 'array',
description:
'The projects that this addon will listen to events from. An empty list means it will listen to events from **all** projects.',
example: ['new-landing-project', 'signups-v2'],
items: {
type: 'string',
},
},
environments: {
type: 'array',
description:
'The list of environments that this addon will listen to events from. An empty list means it will listen to events from **all** environments.',
example: ['development', 'production'],
items: {
type: 'string',
},
},
},
components: {},
} as const;

export type AddonCreateUpdateSchema = FromSchema<
typeof addonCreateUpdateSchema
>;
23 changes: 23 additions & 0 deletions src/lib/openapi/spec/addon-parameter-schema.ts
Expand Up @@ -3,28 +3,51 @@ import { FromSchema } from 'json-schema-to-ts';
export const addonParameterSchema = {
$id: '#/components/schemas/addonParameterSchema',
type: 'object',
additionalProperties: false,
required: ['name', 'displayName', 'type', 'required', 'sensitive'],
description: 'An addon parameter definition.',
properties: {
name: {
type: 'string',
example: 'emojiIcon',
description:
'The name of the parameter as it is used in code. References to this parameter should use this value.',
},
displayName: {
type: 'string',
example: 'Emoji Icon',
description:
'The name of the parameter as it is shown to the end user in the Admin UI.',
},
type: {
type: 'string',
description:
'The type of the parameter. Corresponds roughly to [HTML `input` field types](https://developer.mozilla.org/docs/Web/HTML/Element/Input#input_types). Multi-line inut fields are indicated as `textfield` (equivalent to the HTML `textarea` tag).',
example: 'text',
},
description: {
type: 'string',
example:
'The emoji_icon to use when posting messages to slack. Defaults to ":unleash:".',
description:
'A description of the parameter. This should explain to the end user what the parameter is used for.',
},
placeholder: {
type: 'string',
example: ':unleash:',
description:
'The default value for this parameter. This value is used if no other value is provided.',
},
required: {
type: 'boolean',
example: false,
description:
'Whether this parameter is required or not. If a parameter is required, you must give it a value when you create the addon. If it is not required it can be left out. It may receive a default value in those cases.',
},
sensitive: {
type: 'boolean',
example: false,
description: `Indicates whether this parameter is **sensitive** or not. Unleash will not return sensitive parameters to API requests. It will instead use a number of asterisks to indicate that a value is set, e.g. "******". The number of asterisks does not correlate to the parameter\'s value.`,
},
},
components: {},
Expand Down
2 changes: 2 additions & 0 deletions src/lib/openapi/spec/addon-schema.test.ts
Expand Up @@ -5,6 +5,8 @@ test('addonSchema', () => {
const data: AddonSchema = {
provider: 'some-provider',
enabled: true,
description: null,
id: 5,
parameters: {
someKey: 'some-value',
},
Expand Down
43 changes: 35 additions & 8 deletions src/lib/openapi/spec/addon-schema.ts
Expand Up @@ -3,43 +3,70 @@ import { FromSchema } from 'json-schema-to-ts';
export const addonSchema = {
$id: '#/components/schemas/addonSchema',
type: 'object',
required: ['provider', 'enabled', 'parameters', 'events'],
description: `An [addon](https://docs.getunleash.io/reference/addons) instance description. Contains data about what kind of provider it uses, whether it's enabled or not, what events it listens for, and more.`,
required: [
'id',
'description',
'provider',
'enabled',
'parameters',
'events',
],
properties: {
id: {
type: 'number',
},
createdAt: {
type: 'string',
format: 'date-time',
nullable: true,
type: 'integer',
minimum: 1,
example: 27,
description: "The addon's unique identifier.",
},
provider: {
type: 'string',
description: `The addon provider, such as "webhook" or "slack".`,
example: 'webhook',
},
description: {
type: 'string',
description:
'A description of the addon. `null` if no description exists.',
example:
'This addon posts updates to our internal feature tracking system whenever a feature is created or updated.',
nullable: true,
},
enabled: {
type: 'boolean',
description: 'Whether the addon is enabled or not.',
},
parameters: {
type: 'object',
additionalProperties: true,
additionalProperties: {},
example: {
url: 'http://localhost:4242/webhook',
},
description:
'Parameters for the addon provider. This object has different required and optional properties depending on the provider you choose.',
},
events: {
type: 'array',
description: 'The event types that trigger this specific addon.',
items: {
type: 'string',
},
example: ['feature-created', 'feature-updated'],
},
projects: {
type: 'array',
description:
'The projects that this addon listens to events from. An empty list means it listens to events from **all** projects.',
example: ['new-landing-project', 'signups-v2'],
items: {
type: 'string',
},
},
environments: {
type: 'array',
description:
'The list of environments that this addon listens to events from. An empty list means it listens to events from **all** environments.',
example: ['development', 'production'],
items: {
type: 'string',
},
Expand Down

0 comments on commit e11fae5

Please sign in to comment.