From d898ad781f0b54c72dfb2543d5d0250cfd85efec Mon Sep 17 00:00:00 2001 From: Jacob Mischka Date: Tue, 25 Oct 2022 14:15:28 -0500 Subject: [PATCH 1/2] Rename action slug prop to route Using the `action` prop is deprecated, and is logged for `ctx.redirect` and `io.display.link`. Like with the `href` prop in tables, we aren't iterating through table data in order to log it for table cells or menuItems. We may want to consider some way to do that. I cannot get TypeScript to complain about extraneous keys in the `renderCell` return type, so usages of `action` instead of `route` in versions of the SDK moving forward will simply do nothing, but won't trigger a TypeScript error alerting developers about it. Obviously not ideal. We haven't yet fully migrated from `actions` to `routes` in the non-experimental API, and merging this will update the live docs for all of the effected methods. Maybe we shouldn't do that yet? Or should not make the documentation changes yet, and allow both properties for now? Close T-338 --- src/components/displayLink.ts | 2 +- src/examples/app/index.ts | 16 ++++----- src/examples/basic/index.ts | 23 +++++++----- src/examples/basic/selectFromTable.ts | 2 +- src/examples/basic/table.ts | 12 +++---- src/examples/structure/index.ts | 12 +++---- src/internalRpcSchema.ts | 9 ++++- src/ioSchema.ts | 51 +++++++++++++++++++++------ src/types.ts | 38 ++++++++++++-------- 9 files changed, 107 insertions(+), 58 deletions(-) diff --git a/src/components/displayLink.ts b/src/components/displayLink.ts index c342905..cfa729d 100644 --- a/src/components/displayLink.ts +++ b/src/components/displayLink.ts @@ -8,7 +8,7 @@ export default function displayLink( url: string } | { - action: string + route: string params?: SerializableRecord } ) diff --git a/src/examples/app/index.ts b/src/examples/app/index.ts index 0c975dc..e7903d4 100644 --- a/src/examples/app/index.ts +++ b/src/examples/app/index.ts @@ -35,7 +35,7 @@ const hello_app = new Page({ }, { label: 'Action link', - action: 'hello_app/hello_world', + route: 'hello_app/hello_world', }, // { // label: 'Inline action', @@ -59,7 +59,7 @@ const hello_app = new Page({ rowMenuItems: () => [ { label: 'Hello', - action: 'hello_app/hello_world', + route: 'hello_app/hello_world', }, ], }), @@ -82,7 +82,7 @@ const hello_app = new Page({ rowMenuItems: () => [ { label: 'Hello', - action: 'hello_app/hello_world', + route: 'hello_app/hello_world', }, ], }), @@ -122,11 +122,11 @@ const users = new Page({ menuItems: [ { label: 'View funnel', - action: 'users/view_funnel', + route: 'users/view_funnel', }, { label: 'Create user', - action: 'users/create', + route: 'users/create', }, ], children: [ @@ -135,7 +135,7 @@ const users = new Page({ rowMenuItems: row => [ { label: 'Edit', - action: 'users/edit', + route: 'users/edit', params: { id: row.id }, }, ], @@ -210,11 +210,11 @@ const interval = new Interval({ menuItems: [ { label: 'Reload', - action: 'info', + route: 'info', }, { label: 'Add timestamp param', - action: 'info', + route: 'info', params: { timestamp: new Date().valueOf() }, }, ], diff --git a/src/examples/basic/index.ts b/src/examples/basic/index.ts index 3b8ad00..a5b2aeb 100644 --- a/src/examples/basic/index.ts +++ b/src/examples/basic/index.ts @@ -45,13 +45,13 @@ const actionLinks: IntervalActionHandler = async () => { url: 'https://example.com', }), io.display.link('Action link', { - action: 'helloCurrentUser', + route: 'helloCurrentUser', params: { message: 'From a button!', }, }), io.display.link('This same action', { - action: 'actionLinks', + route: 'actionLinks', params: { prevActionAt: new Date().toISOString(), }, @@ -124,7 +124,7 @@ const prod = new Interval({ const { workDone = false } = ctx.params if (!workDone) { await ctx.redirect({ - action: 'perform_common_work', + route: 'perform_common_work', }) startedWork = true } @@ -142,7 +142,7 @@ const prod = new Interval({ ) await sleep(2000) await ctx.redirect({ - action: 'perform_redirect_flow', + route: 'perform_redirect_flow', params: { workDone: true, }, @@ -405,6 +405,11 @@ const interval = new Interval({ action: 'helloCurrentUser', params: { message: 'Hello from metadata!' }, }, + { + label: 'External link', + value: 'Click me', + url: 'https://interval.com', + }, { label: 'Image', value: 'Optional caption', @@ -1046,7 +1051,7 @@ const interval = new Interval({ return { url: url.href } }, redirect: async () => { - const [url, , action, paramsStr] = await io.group([ + const [url, , route, paramsStr] = await io.group([ io.input.url('Enter a URL').optional(), io.display.markdown('--- or ---'), io.input.text('Enter an action slug').optional(), @@ -1060,7 +1065,7 @@ const interval = new Interval({ let params = undefined if (url) { await ctx.redirect({ url: url.toString() }) - } else if (action) { + } else if (route) { if (paramsStr) { try { params = JSON.parse(paramsStr) @@ -1069,20 +1074,20 @@ const interval = new Interval({ } } - await ctx.redirect({ action, params }) + await ctx.redirect({ route, params }) } else { throw new Error('Must enter either a URL or an action slug') } console.log({ url, - action, + route, params, }) return { url: url?.toString(), - action, + route, paramsStr, } }, diff --git a/src/examples/basic/selectFromTable.ts b/src/examples/basic/selectFromTable.ts index 3787a4f..9b1f2e4 100644 --- a/src/examples/basic/selectFromTable.ts +++ b/src/examples/basic/selectFromTable.ts @@ -82,7 +82,7 @@ export const table_actions: IntervalActionHandler = async io => { rowMenuItems: row => [ { label: 'Edit', - action: 'edit_user', + route: 'edit_user', params: { email: row.email }, }, ], diff --git a/src/examples/basic/table.ts b/src/examples/basic/table.ts index 46df738..ab987ef 100644 --- a/src/examples/basic/table.ts +++ b/src/examples/basic/table.ts @@ -98,24 +98,24 @@ export const display_table: IntervalActionHandler = async io => { rowMenuItems: row => [ { label: 'Edit', - action: 'edit_user', + route: 'edit_user', params: { email: row.email }, }, { label: 'Edit', - action: 'edit_user', + route: 'edit_user', params: { email: row.email }, disabled: true, }, { label: 'Delete', - action: 'delete_user', + route: 'delete_user', params: { email: row.email }, theme: 'danger', }, { label: 'Delete', - action: 'delete_user', + route: 'delete_user', params: { email: row.email }, theme: 'danger', disabled: true, @@ -223,7 +223,7 @@ export const async_table: IntervalActionHandler = async io => { rowMenuItems: row => [ { label: 'Edit', - action: 'edit_user', + route: 'edit_user', params: { email: row.email }, }, ], @@ -261,7 +261,7 @@ export const select_table: IntervalActionHandler = async io => { rowMenuItems: row => [ { label: 'Edit', - action: 'edit_user', + route: 'edit_user', params: { email: row.email }, }, ], diff --git a/src/examples/structure/index.ts b/src/examples/structure/index.ts index 4a4842b..d9c30a7 100644 --- a/src/examples/structure/index.ts +++ b/src/examples/structure/index.ts @@ -47,7 +47,7 @@ const routes: IntervalRouteDefinitions = { menuItems: [ { label: 'Create user', - action: 'users/create', + route: 'users/create', }, ], children: [ @@ -56,7 +56,7 @@ const routes: IntervalRouteDefinitions = { rowMenuItems: row => [ { label: 'Edit', - action: 'users/edit', + route: 'users/edit', params: { id: row.id }, }, ], @@ -99,12 +99,12 @@ const routes: IntervalRouteDefinitions = { rowMenuItems: row => [ { label: 'Edit', - action: 'users/subscriptions/edit', + route: 'users/subscriptions/edit', params: { id: row.id }, }, { label: 'Cancel', - action: 'users/subscriptions/cancel', + route: 'users/subscriptions/cancel', theme: 'danger', params: { id: row.id }, }, @@ -140,7 +140,7 @@ const routes: IntervalRouteDefinitions = { menuItems: [ { label: 'Create comment', - action: 'users/comments/create', + route: 'users/comments/create', }, ], children: [ @@ -149,7 +149,7 @@ const routes: IntervalRouteDefinitions = { rowMenuItems: row => [ { label: 'Edit', - action: 'users/comments/edit', + route: 'users/comments/edit', params: { id: row.id }, }, ], diff --git a/src/internalRpcSchema.ts b/src/internalRpcSchema.ts index 864938b..9421543 100644 --- a/src/internalRpcSchema.ts +++ b/src/internalRpcSchema.ts @@ -257,7 +257,14 @@ export const wsServerSchema = { z.object({ transactionId: z.string(), }), - linkSchema + z.union([ + linkSchema, + // deprecated in favor of `route` from linkSchema + z.object({ + action: z.string(), + params: serializableRecord.optional(), + }), + ]) ), returns: z.boolean(), }, diff --git a/src/ioSchema.ts b/src/ioSchema.ts index 8d55d7a..bc87779 100644 --- a/src/ioSchema.ts +++ b/src/ioSchema.ts @@ -191,7 +191,9 @@ export const tableRowValue = z.union([ url: z.string(), }) .optional(), + // Deprecated in favor of route action: z.string().optional(), + route: z.string().optional(), params: serializableRecord.optional(), }), ]) @@ -209,11 +211,21 @@ export const menuItem = z.intersection( theme: z.enum(['default', 'danger']).optional(), }), z.union([ - z.object({ - action: z.string(), - params: serializableRecord.optional(), - disabled: z.boolean().optional(), - }), + z.intersection( + z.object({ + params: serializableRecord.optional(), + disabled: z.boolean().optional(), + }), + z.union([ + z.object({ + route: z.string(), + }), + z.object({ + // deprecated in favor of `route` + action: z.string(), + }), + ]) + ), z.object({ url: z.string(), disabled: z.boolean().optional(), @@ -230,11 +242,21 @@ export const buttonItem = z.intersection( theme: buttonTheme.optional(), }), z.union([ - z.object({ - action: z.string(), - params: serializableRecord.optional(), - disabled: z.boolean().optional(), - }), + z.intersection( + z.object({ + params: serializableRecord.optional(), + disabled: z.boolean().optional(), + }), + z.union([ + z.object({ + route: z.string(), + }), + z.object({ + // deprecated in favor of `route` + action: z.string(), + }), + ]) + ), z.object({ url: z.string(), disabled: z.boolean().optional(), @@ -250,7 +272,7 @@ export const linkSchema = z.union([ url: z.string(), }), z.object({ - action: z.string(), + route: z.string(), params: serializableRecord.optional(), }), ]) @@ -318,6 +340,8 @@ export const metaItemSchema = z.object({ url: z.string(), }) .optional(), + route: z.string().optional(), + // Deprecated in favor of `route` above action: z.string().optional(), params: serializableRecord.optional(), error: z.string().nullish(), @@ -373,6 +397,11 @@ const DISPLAY_SCHEMA = { z.object({ href: z.string(), }), + // deprecated in favor of `route` in linkSchema + z.object({ + action: z.string(), + params: serializableRecord.optional(), + }), linkSchema, ]) ), diff --git a/src/types.ts b/src/types.ts index bce8a2a..41b8a2a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -16,6 +16,7 @@ import type { ButtonTheme, serializableRecord, ImageSize, + SerializableRecord, } from './ioSchema' import type { HostSchema } from './internalRpcSchema' import type { IOClient, IOClientRenderValidator } from './classes/IOClient' @@ -293,25 +294,32 @@ export type IOComponentDefinition< ) => Promise> } -type DistributiveOmit = T extends any - ? Omit - : never - export type InternalMenuItem = z.input -export type MenuItem = DistributiveOmit & { +export type MenuItem = { + label: string theme?: 'danger' -} +} & ( + | { + route: string + params?: SerializableRecord + disabled?: boolean + } + | { + url: string + disabled?: boolean + } + | { disabled: true } +) export type InternalButtonItem = z.input -export type ButtonItem = DistributiveOmit & { +export type ButtonItem = { + label: string theme?: 'primary' | 'secondary' | 'danger' -} -// | { -// label: InternalMenuItem['label'] -// theme?: InternalMenuItem['theme'] -// action: IntervalActionHandler -// disabled?: boolean -// } +} & ( + | { route: string; params?: SerializableRecord; disabled?: boolean } + | { url: string; disabled?: boolean } + | { disabled: true } +) export type ButtonConfig = { label?: string @@ -335,7 +343,7 @@ export type TableColumnResult = height?: ImageSize } & ({ url: string } | { buffer: Buffer }) url?: string - action?: string + route?: string params?: z.infer } | TableCellValue From 587951899cfa2d24b463318076e5cebc847b2cdf Mon Sep 17 00:00:00 2001 From: Jacob Mischka Date: Wed, 26 Oct 2022 15:49:12 -0500 Subject: [PATCH 2/2] Once again allow action prop in SDK, plan to deprecate soon --- src/classes/IntervalClient.ts | 6 +++--- src/components/displayLink.ts | 6 ++++++ src/examples/app/index.ts | 2 +- src/internalRpcSchema.ts | 10 ++-------- src/ioSchema.ts | 18 ++++++++++++------ src/types.ts | 16 +++++++++++++++- 6 files changed, 39 insertions(+), 19 deletions(-) diff --git a/src/classes/IntervalClient.ts b/src/classes/IntervalClient.ts index f79a7b1..502f686 100644 --- a/src/classes/IntervalClient.ts +++ b/src/classes/IntervalClient.ts @@ -24,7 +24,7 @@ import { ActionResultSchema, IOFunctionReturnType, IO_RESPONSE, - LinkProps, + LegacyLinkProps, T_IO_RENDER_INPUT, T_IO_RESPONSE, } from '../ioSchema' @@ -1041,7 +1041,7 @@ export default class IntervalClient { }) }, }), - redirect: (props: LinkProps) => + redirect: (props: LegacyLinkProps) => this.#sendRedirect(transactionId, props), } @@ -1301,7 +1301,7 @@ export default class IntervalClient { }) } - async #sendRedirect(transactionId: string, props: LinkProps) { + async #sendRedirect(transactionId: string, props: LegacyLinkProps) { const response = await this.#send('SEND_REDIRECT', { transactionId, ...props, diff --git a/src/components/displayLink.ts b/src/components/displayLink.ts index cfa729d..70313eb 100644 --- a/src/components/displayLink.ts +++ b/src/components/displayLink.ts @@ -11,6 +11,12 @@ export default function displayLink( route: string params?: SerializableRecord } + // deprecated in favor of `route` + // TODO: Add TS deprecated flag soon + | { + action: string + params?: SerializableRecord + } ) ) { return { diff --git a/src/examples/app/index.ts b/src/examples/app/index.ts index e7903d4..1eb4b84 100644 --- a/src/examples/app/index.ts +++ b/src/examples/app/index.ts @@ -82,7 +82,7 @@ const hello_app = new Page({ rowMenuItems: () => [ { label: 'Hello', - route: 'hello_app/hello_world', + action: 'hello_app/hello_world', }, ], }), diff --git a/src/internalRpcSchema.ts b/src/internalRpcSchema.ts index 9421543..3356d1b 100644 --- a/src/internalRpcSchema.ts +++ b/src/internalRpcSchema.ts @@ -1,6 +1,7 @@ import { z } from 'zod' import { deserializableRecord, + legacyLinkSchema, linkSchema, serializableRecord, } from './ioSchema' @@ -257,14 +258,7 @@ export const wsServerSchema = { z.object({ transactionId: z.string(), }), - z.union([ - linkSchema, - // deprecated in favor of `route` from linkSchema - z.object({ - action: z.string(), - params: serializableRecord.optional(), - }), - ]) + legacyLinkSchema ), returns: z.boolean(), }, diff --git a/src/ioSchema.ts b/src/ioSchema.ts index bc87779..19a680b 100644 --- a/src/ioSchema.ts +++ b/src/ioSchema.ts @@ -279,6 +279,17 @@ export const linkSchema = z.union([ export type LinkProps = z.infer +// TODO: Remove soon +export const legacyLinkSchema = z.union([ + linkSchema, + z.object({ + action: z.string(), + params: serializableRecord.optional(), + }), +]) + +export type LegacyLinkProps = z.infer + export const internalTableRow = z.object({ key: z.string(), data: tableRow, @@ -397,12 +408,7 @@ const DISPLAY_SCHEMA = { z.object({ href: z.string(), }), - // deprecated in favor of `route` in linkSchema - z.object({ - action: z.string(), - params: serializableRecord.optional(), - }), - linkSchema, + legacyLinkSchema, ]) ), state: z.null(), diff --git a/src/types.ts b/src/types.ts index 41b8a2a..971b078 100644 --- a/src/types.ts +++ b/src/types.ts @@ -17,6 +17,7 @@ import type { serializableRecord, ImageSize, SerializableRecord, + LegacyLinkProps, } from './ioSchema' import type { HostSchema } from './internalRpcSchema' import type { IOClient, IOClientRenderValidator } from './classes/IOClient' @@ -218,7 +219,7 @@ export type ActionLogFn = (...args: any[]) => Promise export type NotifyFn = (config: NotifyConfig) => Promise -export type RedirectFn = (props: LinkProps) => Promise +export type RedirectFn = (props: LegacyLinkProps) => Promise export type ResponseHandlerFn = (fn: T_IO_RESPONSE) => void @@ -304,6 +305,13 @@ export type MenuItem = { params?: SerializableRecord disabled?: boolean } + // Deprecated in favor of `route` + // TODO: Add TS deprecation soon + | { + action: string + params?: SerializableRecord + disabled?: boolean + } | { url: string disabled?: boolean @@ -317,6 +325,9 @@ export type ButtonItem = { theme?: 'primary' | 'secondary' | 'danger' } & ( | { route: string; params?: SerializableRecord; disabled?: boolean } + // Deprecated in favor of `route` + // TODO: Add TS deprecation soon + | { action: string; params?: SerializableRecord; disabled?: boolean } | { url: string; disabled?: boolean } | { disabled: true } ) @@ -344,6 +355,9 @@ export type TableColumnResult = } & ({ url: string } | { buffer: Buffer }) url?: string route?: string + // Deprecated in favor of `route` + // TODO: Add TS deprecation soon + action?: string params?: z.infer } | TableCellValue