Convert properties to an action (#2751)

* update actions response types and partially implement in UI

* further remove diagnostic ui

* convert action response nested to array

* prepare action res modal for Alex

* ad dproperties action for Bitcoin

* feat: add action success dialog (#2753)

* feat: add action success dialog

* mocks for string action res and hide properties from actions page

---------

Co-authored-by: Matt Hill <mattnine@protonmail.com>

* return null

* remove properties from backend

* misc fixes

* make severity separate argument

* rename ActionRequest to ActionRequestOptions

* add clearRequests

* fix s9pk build

* remove config and properties, introduce action requests

* better ux, better moocks, include icons

* fix dependency types

* add variant for versionCompat

* fix dep icon display and patch operation display

* misc fixes

* misc fixes

* alpha 12

* honor provided input to set values in action

* fix: show full descriptions of action success items (#2758)

* fix type

* fix: fix build:deps command on Windows (#2752)

* fix: fix build:deps command on Windows

* fix: add escaped quotes

---------

Co-authored-by: Aiden McClelland <me@drbonez.dev>

* misc db compatibility fixes

---------

Co-authored-by: Alex Inkin <alexander@inkin.ru>
Co-authored-by: Aiden McClelland <me@drbonez.dev>
Co-authored-by: Aiden McClelland <3732071+dr-bonez@users.noreply.github.com>
This commit is contained in:
Matt Hill
2024-10-17 13:31:56 -06:00
committed by GitHub
parent fb074c8c32
commit 2ba56b8c59
105 changed files with 1385 additions and 1578 deletions

View File

@@ -52,7 +52,7 @@ export type Effects = {
options: RequestActionParams,
): Promise<null>
clearRequests(
options: { only: ActionId[] } | { except: ActionId[] },
options: { only: string[] } | { except: string[] },
): Promise<null>
}

View File

@@ -1,5 +1,6 @@
import * as T from "../types"
import * as IST from "../actions/input/inputSpecTypes"
import { Action } from "./setupActions"
export type RunActionInput<Input> =
| Input
@@ -43,23 +44,62 @@ export const runAction = async <
})
}
}
type GetActionInputType<
A extends Action<T.ActionId, any, any, Record<string, unknown>>,
> = A extends Action<T.ActionId, any, any, infer I> ? I : never
// prettier-ignore
export type ActionRequest<T extends Omit<T.ActionRequest, "packageId">> =
T extends { when: { condition: "input-not-matches" } }
? (T extends { input: T.ActionRequestInput } ? T : "input is required for condition 'input-not-matches'")
: T
type ActionRequestBase = {
reason?: string
replayId?: string
}
type ActionRequestInput<
T extends Action<T.ActionId, any, any, Record<string, unknown>>,
> = {
kind: "partial"
value: Partial<GetActionInputType<T>>
}
export type ActionRequestOptions<
T extends Action<T.ActionId, any, any, Record<string, unknown>>,
> = ActionRequestBase &
(
| {
when?: Exclude<
T.ActionRequestTrigger,
{ condition: "input-not-matches" }
>
input?: ActionRequestInput<T>
}
| {
when: T.ActionRequestTrigger & { condition: "input-not-matches" }
input: ActionRequestInput<T>
}
)
const _validate: T.ActionRequest = {} as ActionRequestOptions<any> & {
actionId: string
packageId: string
severity: T.ActionSeverity
}
export const requestAction = <
T extends Omit<T.ActionRequest, "packageId">,
T extends Action<T.ActionId, any, any, Record<string, unknown>>,
>(options: {
effects: T.Effects
request: ActionRequest<T> & { replayId?: string; packageId: T.PackageId }
packageId: T.PackageId
action: T
severity: T.ActionSeverity
options?: ActionRequestOptions<T>
}) => {
const request = options.request
const request = options.options || {}
const actionId = options.action.id
const req = {
...request,
replayId: request.replayId || `${request.packageId}:${request.actionId}`,
actionId,
packageId: options.packageId,
action: undefined,
severity: options.severity,
replayId: request.replayId || `${options.packageId}:${actionId}`,
}
delete req.action
return options.effects.action.request(req)
}

View File

@@ -11,7 +11,7 @@ export type Run<
> = (options: {
effects: T.Effects
input: ExtractInputSpecType<A> & Record<string, any>
}) => Promise<T.ActionResult | null>
}) => Promise<T.ActionResult | null | void | undefined>
export type GetInput<
A extends
| Record<string, any>
@@ -19,7 +19,9 @@ export type GetInput<
| InputSpec<Record<string, any>, never>,
> = (options: {
effects: T.Effects
}) => Promise<null | (ExtractInputSpecType<A> & Record<string, any>)>
}) => Promise<
null | void | undefined | (ExtractInputSpecType<A> & Record<string, any>)
>
export type MaybeFn<T> = T | ((options: { effects: T.Effects }) => Promise<T>)
function callMaybeFn<T>(
@@ -91,7 +93,7 @@ export class Action<
): Action<Id, Store, {}, {}> {
return new Action(
id,
mapMaybeFn(metadata, (m) => ({ ...m, hasInput: true })),
mapMaybeFn(metadata, (m) => ({ ...m, hasInput: false })),
{},
async () => null,
run,
@@ -114,7 +116,7 @@ export class Action<
effects: T.Effects
input: Type
}): Promise<T.ActionResult | null> {
return this.runFn(options)
return (await this.runFn(options)) || null
}
}

View File

@@ -1,21 +0,0 @@
import { VersionRange } from "../exver"
export class Dependency {
constructor(
readonly data:
| {
/** Either "running" or "exists". Does the dependency need to be running, or does it only need to exist? */
type: "running"
/** The acceptable version range of the dependency. */
versionRange: VersionRange
/** A list of the dependency's health check IDs that must be passing for the service to be satisfied. */
healthChecks: string[]
}
| {
/** Either "running" or "exists". Does the dependency need to be running, or does it only need to exist? */
type: "exists"
/** The acceptable version range of the dependency. */
versionRange: VersionRange
},
) {}
}

View File

@@ -1,22 +1,11 @@
import * as T from "../types"
import { once } from "../util"
import { Dependency } from "./Dependency"
type DependencyType<Manifest extends T.Manifest> = {
[K in keyof {
[K in keyof Manifest["dependencies"]]: Manifest["dependencies"][K]["optional"] extends false
? K
: never
}]: Dependency
} & {
[K in keyof {
[K in keyof Manifest["dependencies"]]: Manifest["dependencies"][K]["optional"] extends true
? K
: never
}]?: Dependency
type DependencyType<Manifest extends T.SDKManifest> = {
[K in keyof Manifest["dependencies"]]: Omit<T.DependencyRequirement, "id">
}
export function setupDependencies<Manifest extends T.Manifest>(
export function setupDependencies<Manifest extends T.SDKManifest>(
fn: (options: { effects: T.Effects }) => Promise<DependencyType<Manifest>>,
): (options: { effects: T.Effects }) => Promise<null> {
const cell = { updater: async (_: { effects: T.Effects }) => null }
@@ -30,24 +19,12 @@ export function setupDependencies<Manifest extends T.Manifest>(
const dependencyType = await fn(options)
return await options.effects.setDependencies({
dependencies: Object.entries(dependencyType).map(
([
id,
{
data: { versionRange, ...x },
},
]) => ({
id,
...x,
...(x.type === "running"
? {
kind: "running",
healthChecks: x.healthChecks,
}
: {
kind: "exists",
}),
versionRange: versionRange.toString(),
}),
([id, { versionRange, ...x }, ,]) =>
({
id,
...x,
versionRange: versionRange.toString(),
}) as T.DependencyRequirement,
),
})
}

View File

@@ -2,12 +2,14 @@
import type { ActionId } from "./ActionId"
import type { ActionRequestInput } from "./ActionRequestInput"
import type { ActionRequestTrigger } from "./ActionRequestTrigger"
import type { ActionSeverity } from "./ActionSeverity"
import type { PackageId } from "./PackageId"
export type ActionRequest = {
packageId: PackageId
actionId: ActionId
description?: string
severity: ActionSeverity
reason?: string
when?: ActionRequestTrigger
input?: ActionRequestInput
}

View File

@@ -0,0 +1,7 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ActionResultV0 } from "./ActionResultV0"
import type { ActionResultV1 } from "./ActionResultV1"
export type ActionResult =
| ({ version: "0" } & ActionResultV0)
| ({ version: "1" } & ActionResultV1)

View File

@@ -0,0 +1,8 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type ActionResultV0 = {
message: string
value: string | null
copyable: boolean
qr: boolean
}

View File

@@ -0,0 +1,18 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type ActionResultV1 =
| {
type: "string"
name: string
value: string
description: string | null
copyable: boolean
qr: boolean
masked: boolean
}
| {
type: "object"
name: string
value: Array<ActionResultV1>
description?: string
}

View File

@@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type ActionSeverity = "critical" | "important"

View File

@@ -1,6 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type ActionVisibility =
| "hidden"
| { disabled: { reason: string } }
| "enabled"
export type ActionVisibility = "hidden" | { disabled: string } | "enabled"

View File

@@ -2,6 +2,7 @@
import type { ActionId } from "./ActionId"
import type { ActionRequestInput } from "./ActionRequestInput"
import type { ActionRequestTrigger } from "./ActionRequestTrigger"
import type { ActionSeverity } from "./ActionSeverity"
import type { PackageId } from "./PackageId"
import type { ReplayId } from "./ReplayId"
@@ -9,7 +10,8 @@ export type RequestActionParams = {
replayId: ReplayId
packageId: PackageId
actionId: ActionId
description?: string
severity: ActionSeverity
reason?: string
when?: ActionRequestTrigger
input?: ActionRequestInput
}

View File

@@ -7,6 +7,10 @@ export { ActionRequestEntry } from "./ActionRequestEntry"
export { ActionRequestInput } from "./ActionRequestInput"
export { ActionRequestTrigger } from "./ActionRequestTrigger"
export { ActionRequest } from "./ActionRequest"
export { ActionResult } from "./ActionResult"
export { ActionResultV0 } from "./ActionResultV0"
export { ActionResultV1 } from "./ActionResultV1"
export { ActionSeverity } from "./ActionSeverity"
export { ActionVisibility } from "./ActionVisibility"
export { AddAdminParams } from "./AddAdminParams"
export { AddAssetParams } from "./AddAssetParams"

View File

@@ -33,10 +33,6 @@ export const SIGKILL: Signals = "SIGKILL"
export const NO_TIMEOUT = -1
export type PathMaker = (options: { volume: string; path: string }) => string
export type ExportedAction = (options: {
effects: Effects
input?: Record<string, unknown>
}) => Promise<ActionResult>
export type MaybePromise<A> = Promise<A> | A
export namespace ExpectedExports {
version: 1
@@ -86,10 +82,6 @@ export namespace ExpectedExports {
nextVersion: null | string
}) => Promise<unknown>
export type properties = (options: {
effects: Effects
}) => Promise<PropertiesReturn>
export type manifest = Manifest
export type actions = Actions<
@@ -105,7 +97,6 @@ export type ABI = {
containerInit: ExpectedExports.containerInit
packageInit: ExpectedExports.packageInit
packageUninit: ExpectedExports.packageUninit
properties: ExpectedExports.properties
manifest: ExpectedExports.manifest
actions: ExpectedExports.actions
}
@@ -177,58 +168,6 @@ export type ExposeServicePaths<Store = never> = {
paths: ExposedStorePaths
}
export type SdkPropertiesValue =
| {
type: "object"
value: { [k: string]: SdkPropertiesValue }
description?: string
}
| {
type: "string"
/** The value to display to the user */
value: string
/** A human readable description or explanation of the value */
description?: string
/** Whether or not to mask the value, for example, when displaying a password */
masked?: boolean
/** Whether or not to include a button for copying the value to clipboard */
copyable?: boolean
/** Whether or not to include a button for displaying the value as a QR code */
qr?: boolean
}
export type SdkPropertiesReturn = {
[key: string]: SdkPropertiesValue
}
export type PropertiesValue =
| {
/** The type of this value, either "string" or "object" */
type: "object"
/** A nested mapping of values. The user will experience this as a nested page with back button */
value: { [k: string]: PropertiesValue }
/** (optional) A human readable description of the new set of values */
description: string | null
}
| {
/** The type of this value, either "string" or "object" */
type: "string"
/** The value to display to the user */
value: string
/** A human readable description of the value */
description: string | null
/** Whether or not to mask the value, for example, when displaying a password */
masked: boolean | null
/** Whether or not to include a button for copying the value to clipboard */
copyable: boolean | null
/** Whether or not to include a button for displaying the value as a QR code */
qr: boolean | null
}
export type PropertiesReturn = {
[key: string]: PropertiesValue
}
export type EffectMethod<T extends StringObject = Effects> = {
[K in keyof T]-?: K extends string
? T[K] extends Function
@@ -264,13 +203,6 @@ export type Metadata = {
mode: number
}
export type ActionResult = {
version: "0"
message: string
value: string | null
copyable: boolean
qr: boolean
}
export type SetResult = {
dependsOn: DependsOn
signal: Signals

View File

@@ -4,9 +4,9 @@
"types": "./index.d.ts",
"sideEffects": true,
"scripts": {
"peggy": "peggy --allowed-start-rules '*' --plugin ./node_modules/ts-pegjs/dist/tspegjs -o lib/exver/exver.ts lib/exver/exver.pegjs",
"peggy": "peggy --allowed-start-rules \"*\" --plugin ./node_modules/ts-pegjs/dist/tspegjs -o lib/exver/exver.ts lib/exver/exver.pegjs",
"test": "jest -c ./jest.config.js --coverage",
"buildOutput": "npx prettier --write '**/*.ts'",
"buildOutput": "npx prettier --write \"**/*.ts\"",
"check": "tsc --noEmit",
"tsc": "tsc"
},