From 7153251058ddc80012cf7eec59057e23b8bc4d97 Mon Sep 17 00:00:00 2001 From: Parkreiner Date: Mon, 4 Mar 2024 23:09:20 +0000 Subject: [PATCH 01/32] docs: finish drafts for hooks/types README files --- .../docs/api-reference/README.md | 9 + .../docs/api-reference/hooks.md | 129 +++++++++++++ .../docs/api-reference/types.md | 169 ++++++++++++++++++ 3 files changed, 307 insertions(+) create mode 100644 plugins/backstage-plugin-coder/docs/api-reference/README.md create mode 100644 plugins/backstage-plugin-coder/docs/api-reference/hooks.md create mode 100644 plugins/backstage-plugin-coder/docs/api-reference/types.md diff --git a/plugins/backstage-plugin-coder/docs/api-reference/README.md b/plugins/backstage-plugin-coder/docs/api-reference/README.md new file mode 100644 index 00000000..c7ac8d72 --- /dev/null +++ b/plugins/backstage-plugin-coder/docs/api-reference/README.md @@ -0,0 +1,9 @@ +# Plugin API Reference – Coder for Backstage + +For users who need more information about how to extend and modify the Coder plugin. For general setup, please see our main [README](link goes here). + +## Documentation directory + +- [Components](./components.md) +- [Custom React hooks](./hooks.md) +- [General types](./types.md) diff --git a/plugins/backstage-plugin-coder/docs/api-reference/hooks.md b/plugins/backstage-plugin-coder/docs/api-reference/hooks.md new file mode 100644 index 00000000..eb2d9a6a --- /dev/null +++ b/plugins/backstage-plugin-coder/docs/api-reference/hooks.md @@ -0,0 +1,129 @@ +# Plugin API reference – React hooks + +This is the main documentation page for the Coder plugin's React hooks. + +## Hook list + +- [`useCoderEntityConfig`](#useCoderEntityConfig) +- [`useCoderWorkspaces`](#useCoderWorkspaces) + +## `useCoderEntityConfig` + +This hook gives you access to compiled [`CoderEntityConfig`](./types.md#coderentityconfig) data. + +### Type signature + +```tsx +declare function useCoderEntityConfig(): CoderEntityConfig; +``` + +[Type definition for `CoderEntityConfig`](./types.md#coderentityconfig) + +### Example usage + +```tsx +function YourComponent() { + const config = useCoderEntityConfig(); + return

Your repo URL is {config.repoUrl}

; +} + +// All other components provided via @backstage/plugin-catalog +// and should be statically initialized +const overviewContent = ( + + + + + +); + +const serviceEntityPage = ( + + + {overviewContent} + + +); + +// etc. +``` + +### Throws + +- Will throw an error if called outside a React component +- Will throw an error if called outside an `EntityLayout` (or any other Backstage component that exposes `Entity` data via React Context) + +### Notes + +- The type definition for `CoderEntityConfig` [can be found here](./types.md#coderentityconfig). That section also includes info on the heuristic used for compiling the data +- The hook tries to ensure that the returned value maintains a stable memory reference as much as possible, if you ever need to use that value in other React hooks that use dependency arrays (e.g., `useEffect`, `useCallback`) + +## `useCoderWorkspaces` + +This hook gives you access to all workspaces that match a given query string. If +[`repoConfig`](#usecoderentityconfig) is defined via `options`, the workspaces returned will be filtered down further to only those that match the the repo. + +### Type signature + +```ts +type UseCoderWorkspacesOptions = Readonly< + Partial<{ + repoConfig: CoderEntityConfig; + }> +>; + +declare function useCoderEntityConfig( + coderQuery: string, + options?: UseCoderWorkspacesOptions, +): UseQueryResult; +``` + +### Example usage + +```tsx +function YourComponent() { + const entityConfig = useCoderEntityConfig(); + const [filter, setFilter] = useState('owner:me'); + + const query = useCoderWorkspaces(filter, { + repoConfig: entityConfig, + }); + + return ( + <> + {query.isLoading && } + {query.isError && } + + {query.data?.map(workspace => ( +
    +
  1. {workspace.name}
  2. +
+ ))} + + ); +} + +const coderAppConfig: CoderAppConfig = { + // ...Properties go here +}; + + + + + +; +``` + +### Throws + +- Will throw an error if called outside a React component +- Will throw an error if the component calling the hook is not wrapped inside a [`CoderProvider`](./components.md#CoderProvider) + +### Notes + +- `UseQueryResult` is taken from [React Query v4](https://tanstack.com/query/v4/docs/framework/react/reference/useQuery) + - We recommend [TK Dodo's Practical React Query blog series](https://tkdodo.eu/blog/practical-react-query) for how to make the most of its features. (Particularly the article on [React Query status checks](https://tkdodo.eu/blog/status-checks-in-react-query)) +- The underlying query will not be enabled if: + 1. The user is not currently authenticated (We recommend wrapping your component inside [`CoderAuthWrapper`](./components.md#coderauthwrapper) to make these checks easier) + 2. If `repoConfig` is passed in via `options`: when the value of `coderQuery` is an empty string +- `CoderEntityConfig` is the return type of [`useCoderEntityConfig`](#usecoderentityconfig) diff --git a/plugins/backstage-plugin-coder/docs/api-reference/types.md b/plugins/backstage-plugin-coder/docs/api-reference/types.md new file mode 100644 index 00000000..76130226 --- /dev/null +++ b/plugins/backstage-plugin-coder/docs/api-reference/types.md @@ -0,0 +1,169 @@ +# Plugin API reference – Important types + +## Types directory + +- [`CoderAppConfig`](#coderappconfig) +- [`CoderEntityConfig`](#coderentityconfig) +- [`Workspace`](#workspace) +- [`WorkspaceResponse`](#workspaceresponse) + +## `CoderAppConfig` + +## `CoderEntityConfig` + +Represents the result of compiling Coder plugin configuration data. All data will be compiled from the following sources: + +1. The [`CoderAppConfig`](#coderappconfig) passed to [`CoderProvider`](./components.md#coderprovider) +2. The entity-specific fields for a given repo's `catalog-info.yaml` file +3. The entity's location metadata (corresponding to the repo) + +### Type definition + +```tsx +type CoderEntityConfig = Readonly<{ + mode: 'manual' | 'auto'; + params: Record; + repoUrl: string | undefined; + repoUrlParamKeys: [string, ...string[]][]; + templateName: string; +}>; +``` + +### Example + +Let's say that you have these inputs: + +```tsx +const appConfig: CoderAppConfig = { + deployment: { + accessUrl: 'https://dev.coder.com', + }, + + workspaces: { + templateName: 'devcontainers', + mode: 'manual', + repoUrlParamKeys: ['custom_repo', 'repo_url'], + params: { + repo: 'custom', + region: 'eu-helsinki', + }, + }, +}; +``` + +```yaml +# https://github.com/Parkreiner/python-project/blob/main/catalog-info.yaml +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: python-project +spec: + type: other + lifecycle: unknown + owner: pms + coder: + templateName: 'devcontainers' + mode: 'auto' + params: + repo: 'custom' + region: 'us-pittsburgh' +``` + +Your output will look like this: + +```tsx +const config: CoderEntityConfig = { + mode: 'auto', + params: { + repo: 'custom', + region: 'us-pittsburgh', + custom_repo: 'https://github.com/Parkreiner/python-project/', + repo_url: 'https://github.com/Parkreiner/python-project/', + }, + repoUrl: 'https://github.com/Parkreiner/python-project/', + repoUrlParamKeys: ['custom_repo', 'repo_url'], + templateName: 'devcontainers', +}; +``` + +### Notes + +- See the notes for [`CoderAppConfig`](#coderappconfig) for additional information on some of the fields. +- The value of the `repoUrl` property is derived from [Backstage's `getEntitySourceLocation`](https://backstage.io/docs/reference/plugin-catalog-react.getentitysourcelocation/), which does not guarantee that a URL will always be defined. +- This is the current order of operations used to reconcile param data between `CoderAppConfig`, `catalog-info.yaml`, and the entity location data: + 1. Start with an empty `Record` value + 2. Populate the record with the data from `CoderAppConfig` + 3. Go through all properties parsed from `catalog-info.yaml` and inject those. If the properties are already defined, overwrite them + 4. Grab the repo URL from the entity's location fields. + 5. For each key in `CoderAppConfig`'s `workspaces.repoUrlParamKeys` property, take that key, and inject it as a key-value pair, using the URL as the value. If the key already exists, always override it with the URL + +## `Workspace` + +Represents a single Coder workspace. + +### Type definition + +The below type definitions are likely to be split up at a later date. They are currently defined together for convenience. + +```tsx +type WorkspaceAgentStatus = + | 'connected' + | 'connecting' + | 'disconnected' + | 'timeout'; + +type WorkspaceAgent = { + id: string; + status: WorkspaceAgentStatus; +}; + +type WorkspaceResource = { + id: string; + agents: WorkspaceAgent[]; +}; + +type WorkspaceStatus = + | 'canceled' + | 'canceling' + | 'deleted' + | 'deleting' + | 'failed' + | 'pending' + | 'running' + | 'starting' + | 'stopped' + | 'stopping'; + +type Workspace = { + name: string; + id: string; + template_icon: string; + owner_name: string; + latest_build: { + id: string; + status: WorkspaceStatus; + resources: WorkspaceResource[]; + }; +}; +``` + +### Notes + +- Right now, the number of fields is limited. One planned feature is to expand the type definition to make all Coder workspace properties available + +## `WorkspaceResponse` + +Represents the JSON value that will be part of the response to any workspace API call. + +### Type definition + +```tsx +type WorkspaceResponse = { + count: number; + workspaces: Workspace[]; +}; +``` + +### Notes + +- `count` is the total number of workspaces in the response From 6d67225dd34787132825511f1cc14972d33530cd Mon Sep 17 00:00:00 2001 From: Parkreiner Date: Mon, 4 Mar 2024 23:10:09 +0000 Subject: [PATCH 02/32] wip: add stub for components README --- .../backstage-plugin-coder/docs/api-reference/components.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 plugins/backstage-plugin-coder/docs/api-reference/components.md diff --git a/plugins/backstage-plugin-coder/docs/api-reference/components.md b/plugins/backstage-plugin-coder/docs/api-reference/components.md new file mode 100644 index 00000000..df0ff529 --- /dev/null +++ b/plugins/backstage-plugin-coder/docs/api-reference/components.md @@ -0,0 +1,5 @@ +# Plugin API reference – React hooks + +## Component list + +## `CoderProvider` From be54ee63909bc1e0d87c29190e6a626cd581aeeb Mon Sep 17 00:00:00 2001 From: Parkreiner Date: Mon, 4 Mar 2024 23:21:29 +0000 Subject: [PATCH 03/32] fix: remove typo in main docs --- plugins/backstage-plugin-coder/docs/api-reference/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/backstage-plugin-coder/docs/api-reference/README.md b/plugins/backstage-plugin-coder/docs/api-reference/README.md index c7ac8d72..7bf19981 100644 --- a/plugins/backstage-plugin-coder/docs/api-reference/README.md +++ b/plugins/backstage-plugin-coder/docs/api-reference/README.md @@ -6,4 +6,4 @@ For users who need more information about how to extend and modify the Coder plu - [Components](./components.md) - [Custom React hooks](./hooks.md) -- [General types](./types.md) +- [Important types](./types.md) From cb0d81628465ab6c769766b31f075249b2683a3b Mon Sep 17 00:00:00 2001 From: Parkreiner Date: Mon, 4 Mar 2024 23:21:46 +0000 Subject: [PATCH 04/32] wip: add stub for component docs --- .../docs/api-reference/components.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/plugins/backstage-plugin-coder/docs/api-reference/components.md b/plugins/backstage-plugin-coder/docs/api-reference/components.md index df0ff529..d7eddc7f 100644 --- a/plugins/backstage-plugin-coder/docs/api-reference/components.md +++ b/plugins/backstage-plugin-coder/docs/api-reference/components.md @@ -1,5 +1,16 @@ -# Plugin API reference – React hooks +# Plugin API reference – React components ## Component list +- `CoderAuthWrapper` +- `CoderErrorBoundary` +- `CoderProvider` +- `CoderWorkspacesCard` + +## `CoderAuthWrapper` + +## `CoderErrorBoundary` + ## `CoderProvider` + +## `CoderWorkspacesCard` From d0fa96c69c45fa16f4f664a7e0a29776d12a0763 Mon Sep 17 00:00:00 2001 From: Parkreiner Date: Mon, 4 Mar 2024 23:23:40 +0000 Subject: [PATCH 05/32] wip: add outline for component documentation --- .../docs/api-reference/components.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/plugins/backstage-plugin-coder/docs/api-reference/components.md b/plugins/backstage-plugin-coder/docs/api-reference/components.md index d7eddc7f..00bd4e6a 100644 --- a/plugins/backstage-plugin-coder/docs/api-reference/components.md +++ b/plugins/backstage-plugin-coder/docs/api-reference/components.md @@ -1,11 +1,13 @@ # Plugin API reference – React components +This is the main documentation page for the Coder plugin's React components. + ## Component list -- `CoderAuthWrapper` -- `CoderErrorBoundary` -- `CoderProvider` -- `CoderWorkspacesCard` +- [`CoderAuthWrapper`](#coderauthwrapper) +- [`CoderErrorBoundary`](#codererrorboundary) +- [`CoderProvider`](#coderprovider) +- [`CoderWorkspacesCard`](#coderworkspacescard) ## `CoderAuthWrapper` From aead3007c20467776164df1b30f17b475d38ef84 Mon Sep 17 00:00:00 2001 From: Parkreiner Date: Tue, 5 Mar 2024 02:47:46 +0000 Subject: [PATCH 06/32] docs: add documentation for CoderAuthWrapper and CoderErrorBoundary --- .../docs/api-reference/components.md | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/plugins/backstage-plugin-coder/docs/api-reference/components.md b/plugins/backstage-plugin-coder/docs/api-reference/components.md index 00bd4e6a..3b5fd18c 100644 --- a/plugins/backstage-plugin-coder/docs/api-reference/components.md +++ b/plugins/backstage-plugin-coder/docs/api-reference/components.md @@ -11,8 +11,104 @@ This is the main documentation page for the Coder plugin's React components. ## `CoderAuthWrapper` +This component is designed to simplify authentication checks for other components that need to be authenticated with Coder. Place any child component inside the wrapper. If the user is authenticated, they will see the children. Otherwise, they will see a form for authenticating themselves. + +### Type signature + +```tsx +type WrapperProps = Readonly< + PropsWithChildren<{ + type: 'card'; + }> +>; + +declare function CoderAuthWrapper(props: WrapperProps): JSX.Element; +``` + +### Sample usage + +```tsx +function YourComponent() { + // This query requires authentication + const query = useCoderWorkspaces('owner:lil-brudder'); + return

{query.isLoading ? 'Loading' : 'Not loading'}

; +} + + + + + +; +``` + +### Throws + +- Throws a render error if this component mounts outside of `CoderProvider` + +### Notes + +- The wrapper will also stop displaying the child component(s) if the auth token expires, or if the token cannot be safely verified. If that happens, the component will also display some form controls for troubleshooting. +- `CoderAuthWrapper` only supports the `card` type for now, but more types will be added as we add more UI components to the library + ## `CoderErrorBoundary` +Provides an error boundary for catching render errors thrown by Coder's custom hooks (e.g., parsing logic). + +### Type signature + +```tsx +type CoderErrorBoundaryProps = { + children?: ReactNode; + fallbackUi?: ReactNode; +}; + +declare function CoderErrorBoundary( + props: CoderErrorBoundaryProps, +): JSX.Element; +``` + +### Sample usage + +```tsx +function YourComponent() { + // Pretend that there is an issue with this hook, and that it will always + // throw an error + const config = useCoderEntityConfig(); + return

Will never reach this code

; +} + + + +; +``` + +### Throws + +- Does not throw + - (Need to verify this - our own code for this component doesn't throw any errors, but it does rely on Backstage's `useApi` hook. Unfortunately, TypeScript type signatures can't communicate whether they throw errors, and the documentation has no info. Will need to go through the source code) + +### Notes + +- All other Coder components are exported with this component wrapped around them. Unless you are making extension use of the plugin's custom hooks, it is not expected that you will need this component. +- If `fallbackUi` is not specified, `CoderErrorBoundary` will default to a simple error message +- Although Backstage automatically places error boundaries around each exported component, `CoderErrorBoundary` is designed to handle and process specific kinds of errors from the Coder plugins. + ## `CoderProvider` +### Type signature + +### Sample usage + +### Throws + +### Notes + ## `CoderWorkspacesCard` + +### Type signature + +### Sample usage + +### Throws + +### Notes From d37ae307fe3a15890d85338dfd00588ff51f883b Mon Sep 17 00:00:00 2001 From: Parkreiner Date: Tue, 5 Mar 2024 03:26:01 +0000 Subject: [PATCH 07/32] docs: finish up section for CoderProvider --- .../docs/api-reference/components.md | 67 +++++++++++++++++-- 1 file changed, 61 insertions(+), 6 deletions(-) diff --git a/plugins/backstage-plugin-coder/docs/api-reference/components.md b/plugins/backstage-plugin-coder/docs/api-reference/components.md index 3b5fd18c..4dd5a279 100644 --- a/plugins/backstage-plugin-coder/docs/api-reference/components.md +++ b/plugins/backstage-plugin-coder/docs/api-reference/components.md @@ -16,13 +16,13 @@ This component is designed to simplify authentication checks for other component ### Type signature ```tsx -type WrapperProps = Readonly< +type Props = Readonly< PropsWithChildren<{ type: 'card'; }> >; -declare function CoderAuthWrapper(props: WrapperProps): JSX.Element; +declare function CoderAuthWrapper(props: Props): JSX.Element; ``` ### Sample usage @@ -57,14 +57,12 @@ Provides an error boundary for catching render errors thrown by Coder's custom h ### Type signature ```tsx -type CoderErrorBoundaryProps = { +type Props = { children?: ReactNode; fallbackUi?: ReactNode; }; -declare function CoderErrorBoundary( - props: CoderErrorBoundaryProps, -): JSX.Element; +declare function CoderErrorBoundary(props: Props): JSX.Element; ``` ### Sample usage @@ -95,14 +93,71 @@ function YourComponent() { ## `CoderProvider` +Provides + ### Type signature +```tsx +type Props = PropsWithChildren<{ + children?: React.ReactNode; + appConfig: CoderAppConfig; + queryClient?: QueryClient; +}>; + +declare function CoderProvider(props: Props): JSX.Element; +``` + +The type of `QueryClient` comes from [Tanstack Router v4](https://tanstack.com/query/v4/docs/reference/QueryClient). + ### Sample usage +```tsx +function YourComponent() { + const query = useCoderWorkspaces('owner:brennan-lee-mulligan'); + return ( +
    + {query.data?.map(workspace => ( +
  • {workspace.owner_name}
  • + ))} +
+ ); +} + +const appConfig: CoderAppConfig = { + deployment: { + accessUrl: 'https://dev.coder.com', + }, + + workspaces: { + templateName: 'devcontainers', + mode: 'manual', + repoUrlParamKeys: ['custom_repo', 'repo_url'], + params: { + repo: 'custom', + region: 'eu-helsinki', + }, + }, +}; + + + +; +``` + ### Throws +- Does not throw + ### Notes +- This component was deliberately designed to be agnostic of as many Backstage APIs as possible - it can be placed as high as the top of the app, or treated as a wrapper around a specific plugin component. + - That said, it is recommended that only have one instance of `CoderProvider` per Backstage deployment. Multiple `CoderProvider` component instances could interfere with each other and accidentally fragment caching state +- If you are already using TanStack Query in your deployment, you can provide your own `QueryClient` value via the `queryClient` prop. + - If not specified, `CoderProvider` will use its own client + - Even if you aren't using TanStack Query anywhere else, you could consider adding your own client to configure it with more specific settings + - All Coder-specific queries use a query key prefixed with `coder-backstage-plugin` to prevent any accidental key collisions. +- Regardless of whether you pass in a custom `queryClient` value, `CoderProvider` will spy on the active client to detect any queries that likely failed because of Coder auth tokens expiring + ## `CoderWorkspacesCard` ### Type signature From 5fed31e08d69cf32473c0d0f4e8bc0b3ce868991 Mon Sep 17 00:00:00 2001 From: Parkreiner Date: Tue, 5 Mar 2024 03:27:28 +0000 Subject: [PATCH 08/32] fix: update typo in library name --- plugins/backstage-plugin-coder/docs/api-reference/hooks.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/backstage-plugin-coder/docs/api-reference/hooks.md b/plugins/backstage-plugin-coder/docs/api-reference/hooks.md index eb2d9a6a..cc38cc06 100644 --- a/plugins/backstage-plugin-coder/docs/api-reference/hooks.md +++ b/plugins/backstage-plugin-coder/docs/api-reference/hooks.md @@ -121,7 +121,7 @@ const coderAppConfig: CoderAppConfig = { ### Notes -- `UseQueryResult` is taken from [React Query v4](https://tanstack.com/query/v4/docs/framework/react/reference/useQuery) +- `UseQueryResult` is taken from [TanStack Query v4](https://tanstack.com/query/v4/docs/framework/react/reference/useQuery) - We recommend [TK Dodo's Practical React Query blog series](https://tkdodo.eu/blog/practical-react-query) for how to make the most of its features. (Particularly the article on [React Query status checks](https://tkdodo.eu/blog/status-checks-in-react-query)) - The underlying query will not be enabled if: 1. The user is not currently authenticated (We recommend wrapping your component inside [`CoderAuthWrapper`](./components.md#coderauthwrapper) to make these checks easier) From 89fde983dceba40f65f415e769ebcb099da9e531 Mon Sep 17 00:00:00 2001 From: Parkreiner Date: Tue, 5 Mar 2024 03:27:51 +0000 Subject: [PATCH 09/32] docs: finish section for CoderAppConfig --- .../docs/api-reference/types.md | 52 ++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/plugins/backstage-plugin-coder/docs/api-reference/types.md b/plugins/backstage-plugin-coder/docs/api-reference/types.md index 76130226..4a0fa72a 100644 --- a/plugins/backstage-plugin-coder/docs/api-reference/types.md +++ b/plugins/backstage-plugin-coder/docs/api-reference/types.md @@ -1,5 +1,21 @@ # Plugin API reference – Important types +## General notes + +- All type definitions for the Coder plugin are defined as type aliases and not interfaces, to prevent the risk of accidental interface merging. If you need to extend from one of our types, you can do it in one of two ways: + + ```tsx + // Type intersection + type CustomType = CoderEntityConfig & { + customProperty: boolean; + }; + + // Interface extension - new interface must have a different name + interface CustomInterface extends CoderEntityConfig { + customProperty: string; + } + ``` + ## Types directory - [`CoderAppConfig`](#coderappconfig) @@ -9,6 +25,40 @@ ## `CoderAppConfig` +Defines a set of configuration options for integrating Backstage with Coder. Primarily has two main uses: + +1. Defining a centralized source of truth for certain Coder configuration options (such as which workspace parameters should be used for injecting repo URL values) +2. Defining "fallback" workspace parameters when a repository entity either doesn't have a `catalog-info.yaml` file at all, or only specifies a handful of properties. + +### Type definition + +```tsx +type CoderAppConfig = Readonly<{ + workspaces: Readonly<{ + templateName: string; + mode?: 'auto' | 'manual' | undefined; + params?: Record; + repoUrlParamKeys: readonly [string, ...string[]]; + }>; + + deployment: Readonly<{ + accessUrl: string; + }>; +}>; +``` + +### Example usage + +See example for [`CoderProvider`](./components.md#coderprovider) + +### Notes + +- `accessUrl` is the URL pointing at your specific Coder deployment +- `templateName` refers to the name of the Coder template that you wish to use as default for creating workspaces +- If `mode` is not specified, the plugin will default to a value of `manual` +- `repoUrlParamKeys` is defined as a non-empty array – there must be at least one element inside it. +- For more info on how this type is used within the plugin, see [`CoderEntityConfig`](./types.md#coderentityconfig) and [`useCoderEntityConfig`](./hooks.md#usecoderentityconfig) + ## `CoderEntityConfig` Represents the result of compiling Coder plugin configuration data. All data will be compiled from the following sources: @@ -29,7 +79,7 @@ type CoderEntityConfig = Readonly<{ }>; ``` -### Example +### Example usage Let's say that you have these inputs: From 06963f7c355e3c199b689f9ad22b74ad151b2cb5 Mon Sep 17 00:00:00 2001 From: Parkreiner Date: Tue, 5 Mar 2024 03:50:08 +0000 Subject: [PATCH 10/32] fix: export useWorkspacesCardContext --- plugins/backstage-plugin-coder/src/plugin.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/backstage-plugin-coder/src/plugin.ts b/plugins/backstage-plugin-coder/src/plugin.ts index a8472cda..6421bde9 100644 --- a/plugins/backstage-plugin-coder/src/plugin.ts +++ b/plugins/backstage-plugin-coder/src/plugin.ts @@ -63,6 +63,7 @@ export const CoderWorkspacesCard = coderPlugin.provide( */ export { useCoderEntityConfig } from './hooks/useCoderEntityConfig'; export { useCoderWorkspaces } from './hooks/useCoderWorkspaces'; +export { useWorkspacesCardContext } from './components/CoderWorkspacesCard/Root'; /** * All custom types From a2e2e4bf698ed3dab27e4e25789f0a052a4e8cf0 Mon Sep 17 00:00:00 2001 From: Parkreiner Date: Tue, 5 Mar 2024 03:57:22 +0000 Subject: [PATCH 11/32] docs: finish section for useWorkspacesCardContext --- .../docs/api-reference/hooks.md | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/plugins/backstage-plugin-coder/docs/api-reference/hooks.md b/plugins/backstage-plugin-coder/docs/api-reference/hooks.md index cc38cc06..0b9865a9 100644 --- a/plugins/backstage-plugin-coder/docs/api-reference/hooks.md +++ b/plugins/backstage-plugin-coder/docs/api-reference/hooks.md @@ -6,6 +6,7 @@ This is the main documentation page for the Coder plugin's React hooks. - [`useCoderEntityConfig`](#useCoderEntityConfig) - [`useCoderWorkspaces`](#useCoderWorkspaces) +- [`useWorkspacesCardContext`](#useWorkspacesCardContext) ## `useCoderEntityConfig` @@ -127,3 +128,63 @@ const coderAppConfig: CoderAppConfig = { 1. The user is not currently authenticated (We recommend wrapping your component inside [`CoderAuthWrapper`](./components.md#coderauthwrapper) to make these checks easier) 2. If `repoConfig` is passed in via `options`: when the value of `coderQuery` is an empty string - `CoderEntityConfig` is the return type of [`useCoderEntityConfig`](#usecoderentityconfig) + +## `useWorkspacesCardContext` + +A helper hook for making it easy to share state between a `CoderWorkspacesCardRoot` and the various sub-components for `CoderWorkspacesCard`, without requiring that they all be direct children. + +### Type signature + +```tsx +type WorkspacesCardContext = Readonly<{ + queryFilter: string; + onFilterChange: (newFilter: string) => void; + workspacesQuery: UseQueryResult; + headerId: string; + entityConfig: CoderEntityConfig | undefined; +}>; + +declare function useWorkspacesCardContext(): WorkspacesCardContext; +``` + +### Example usage + +```tsx +function YourComponent1() { + return ( + + + + ); +} + +function YourComponent2() { + return ( + + + + ); +} + +function YourComponent3() { + const { queryFilter, onFilterChange } = useWorkspacesCardContext(); + + return ( + + ); +} + +; +``` + +### Throws + +- If called outside of `CoderProvider` or `CoderWorkspacesCardRoot` + +### Notes + +- See [`CoderWorkspacesCard`](./components.md#coderworkspacescard) for more information. +- `headerId` is for ensuring that the landmark region for `CoderWorkspacesCard` is linked to a header, so that the landmark is available to screen readers. It should be used exclusively for accessibility purposes. From c89d7ad332a78d3f888d4de6153ce44c7c0b4431 Mon Sep 17 00:00:00 2001 From: Parkreiner Date: Tue, 5 Mar 2024 03:57:39 +0000 Subject: [PATCH 12/32] wip: add stubs for CoderWorkspacesCard --- .../docs/api-reference/components.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/plugins/backstage-plugin-coder/docs/api-reference/components.md b/plugins/backstage-plugin-coder/docs/api-reference/components.md index 4dd5a279..e69b5b6e 100644 --- a/plugins/backstage-plugin-coder/docs/api-reference/components.md +++ b/plugins/backstage-plugin-coder/docs/api-reference/components.md @@ -160,6 +160,10 @@ const appConfig: CoderAppConfig = { ## `CoderWorkspacesCard` +A set of sub-components that together make up a form for searching for Coder workspaces that you own. + +\[Need to figure out how to document all the sub-components\] + ### Type signature ### Sample usage @@ -167,3 +171,8 @@ const appConfig: CoderAppConfig = { ### Throws ### Notes + +- All sub-components have been designed with accessibility in mind: + - All content is accessible via screen reader - all icon buttons have accessible text + - There are no color contrast violations in the components' default color schemes (with either the dark or light themes) + - When used together (like with `CoderWorkspacesCard`, the entire search area is exposed as an accessible search landmark) From e4d5e0af70358da446ee1997f48e40b00ff4b6ce Mon Sep 17 00:00:00 2001 From: Parkreiner Date: Tue, 5 Mar 2024 04:02:52 +0000 Subject: [PATCH 13/32] fix: update link in main directory README --- plugins/backstage-plugin-coder/docs/api-reference/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/backstage-plugin-coder/docs/api-reference/README.md b/plugins/backstage-plugin-coder/docs/api-reference/README.md index 7bf19981..52648c4c 100644 --- a/plugins/backstage-plugin-coder/docs/api-reference/README.md +++ b/plugins/backstage-plugin-coder/docs/api-reference/README.md @@ -1,6 +1,6 @@ # Plugin API Reference – Coder for Backstage -For users who need more information about how to extend and modify the Coder plugin. For general setup, please see our main [README](link goes here). +For users who need more information about how to extend and modify the Coder plugin. For general setup, please see our main [README](../../README.md). ## Documentation directory From 3f8c852eb04bee7457042d60394d7b814bd8123e Mon Sep 17 00:00:00 2001 From: Parkreiner Date: Wed, 6 Mar 2024 22:38:14 +0000 Subject: [PATCH 14/32] docs: add more info to component docs --- .../docs/api-reference/components.md | 170 +++++++++++++++++- 1 file changed, 166 insertions(+), 4 deletions(-) diff --git a/plugins/backstage-plugin-coder/docs/api-reference/components.md b/plugins/backstage-plugin-coder/docs/api-reference/components.md index e69b5b6e..741af4d8 100644 --- a/plugins/backstage-plugin-coder/docs/api-reference/components.md +++ b/plugins/backstage-plugin-coder/docs/api-reference/components.md @@ -8,6 +8,14 @@ This is the main documentation page for the Coder plugin's React components. - [`CoderErrorBoundary`](#codererrorboundary) - [`CoderProvider`](#coderprovider) - [`CoderWorkspacesCard`](#coderworkspacescard) + - [`CoderWorkspacesCard.CreateWorkspacesLink`](#coderworkspacescard.createworkspaceslink) + - [`CoderWorkspacesCard.ExtraActionsButton`](#coderworkspacescard.extraactionsbutton) + - [`CoderWorkspacesCard.HeaderRow`](#coderworkspacescard.headerrow) + - [`CoderWorkspacesCard.Root`](#coderworkspacescard.root) + - [`CoderWorkspacesCard.SearchBox`](#coderworkspacescard.searchbox) + - [`CoderWorkspacesCard.WorkspacesList`](#coderworkspacescard.workspaceslist) + - [`CoderWorkspacesCard.WorkspacesListIcon`](#coderworkspacescard.workspaceslisticon) + - [`CoderWorkspacesCard.WorkspacesListItem`](#coderworkspacescard.workspaceslistitem) ## `CoderAuthWrapper` @@ -160,19 +168,173 @@ const appConfig: CoderAppConfig = { ## `CoderWorkspacesCard` -A set of sub-components that together make up a form for searching for Coder workspaces that you own. +Allows you to search for and display Coder workspaces that the currently-authenticated user has access to. The component handles all data-fetching, caching -\[Need to figure out how to document all the sub-components\] +Has two "modes" – one where the component has access to all Coder workspaces for the user, and one where the component is aware of entity data and filters workspaces to those that match the currently-open repo page. See sample usage for examples. + +All "pieces" of the component are also available as modular sub-components that can be imported and composed together individually. ### Type signature +```tsx +type Props = { + queryFilter?: string; + defaultQueryFilter?: string; + onFilterChange?: (newFilter: string) => void; + readEntityData?: boolean; + + // Plus all props from the native HTMLDivElement type, except + // "role", "aria-labelledby", and "children" +}; + +declare function CoderWorkspacesCard(props: Props): JSX.Element; +``` + ### Sample usage +In "general mode" – the component displays ALL user workspaces: + +```tsx +const appConfig: CoderAppConfig = { + /* Content goes here */ +}; + +// If readEntityData is false or not specified, the component +// can effectively be placed anywhere, as long as it's wrapped +// in a provider + + +; +``` + +In "aware mode" – the component only displays workspaces that +match the repo data for the currently-open entity page: + +```tsx +const appConfig: CoderAppConfig = { + /* Content goes here */ +}; + +// While readEntityData is true, it must be placed somewhere +// that exposes entity data via React Context + + + + + + +; +``` + +Using the component as a controlled component: + +```tsx +function YourComponent() { + const [searchText, setSearchText] = useState('owner:me'); + + return ( + setSearchText(newSearchText)} + /> + ); +} + + + +; +``` + ### Throws +- Will throw a render error if called outside `CoderProvider`. +- Will throw a render error if the value of `readEntityData` changes across re-renders – it must remain a static value for the entire lifecycle of the component. +- If `readEntityData` is `true`: will throw if the component is called outside of an `EntityLayout` (or any other component that exposes entity data via React Context) + ### Notes -- All sub-components have been designed with accessibility in mind: +- All `CoderWorkspacesCard` (and its sub-components) have been designed with accessibility in mind: - All content is accessible via screen reader - all icon buttons have accessible text - There are no color contrast violations in the components' default color schemes (with either the dark or light themes) - - When used together (like with `CoderWorkspacesCard`, the entire search area is exposed as an accessible search landmark) + - When wired together properly (`CoderWorkspacesCard` does this automatically), the entire search component is exposed as an accessible search landmark for screen readers +- `queryFilter` and `onFilterChange` allow you to change the component from [being uncontrolled to controlled](https://react.dev/reference/react-dom/components/input#controlling-an-input-with-a-state-variable). If `queryFilter` is not specified, the component will manage all its search state internally. + +## `CoderWorkspacesCard.CreateWorkspacesLink` + +### Type definition + +### Example usage + +### Throws + +### Notes + +## `CoderWorkspacesCard.ExtraActionsButton` + +### Type definition + +### Example usage + +### Throws + +### Notes + +## `CoderWorkspacesCard.HeaderRow` + +### Type definition + +### Example usage + +### Throws + +### Notes + +## `CoderWorkspacesCard.Root` + +### Type definition + +### Example usage + +### Throws + +### Notes + +## `CoderWorkspacesCard.SearchBox` + +### Type definition + +### Example usage + +### Throws + +### Notes + +## `CoderWorkspacesCard.WorkspacesList` + +### Type definition + +### Example usage + +### Throws + +### Notes + +## `CoderWorkspacesCard.WorkspacesListIcon` + +### Type definition + +### Example usage + +### Throws + +### Notes + +## `CoderWorkspacesCard.WorkspacesListItem` + +### Type definition + +### Example usage + +### Throws + +### Notes From 4a31ac6cd836255fbb269109c2f1a31d2df7237d Mon Sep 17 00:00:00 2001 From: Parkreiner Date: Wed, 6 Mar 2024 22:56:39 +0000 Subject: [PATCH 15/32] fix: ensure link configs are combined properly --- .../components/CoderWorkspacesCard/CreateWorkspaceLink.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/backstage-plugin-coder/src/components/CoderWorkspacesCard/CreateWorkspaceLink.tsx b/plugins/backstage-plugin-coder/src/components/CoderWorkspacesCard/CreateWorkspaceLink.tsx index fa88f925..963161c9 100644 --- a/plugins/backstage-plugin-coder/src/components/CoderWorkspacesCard/CreateWorkspaceLink.tsx +++ b/plugins/backstage-plugin-coder/src/components/CoderWorkspacesCard/CreateWorkspaceLink.tsx @@ -58,7 +58,11 @@ export const CreateWorkspaceLink = forwardRef( const styles = useStyles(); const appConfig = useCoderAppConfig(); const { entityConfig } = useWorkspacesCardContext(); - const activeConfig = entityConfig ?? appConfig.workspaces; + + const activeConfig = { + ...appConfig.workspaces, + ...(entityConfig ?? {}), + }; return ( From 93bb583e0e2fae919d82ca8e5da7d86124c6bd4d Mon Sep 17 00:00:00 2001 From: Parkreiner Date: Wed, 6 Mar 2024 23:01:04 +0000 Subject: [PATCH 16/32] docs: finish docs for CreateWorkspacesLink --- .../docs/api-reference/components.md | 39 ++++++++++++------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/plugins/backstage-plugin-coder/docs/api-reference/components.md b/plugins/backstage-plugin-coder/docs/api-reference/components.md index 741af4d8..c9f17b87 100644 --- a/plugins/backstage-plugin-coder/docs/api-reference/components.md +++ b/plugins/backstage-plugin-coder/docs/api-reference/components.md @@ -258,23 +258,44 @@ function YourComponent() { - There are no color contrast violations in the components' default color schemes (with either the dark or light themes) - When wired together properly (`CoderWorkspacesCard` does this automatically), the entire search component is exposed as an accessible search landmark for screen readers - `queryFilter` and `onFilterChange` allow you to change the component from [being uncontrolled to controlled](https://react.dev/reference/react-dom/components/input#controlling-an-input-with-a-state-variable). If `queryFilter` is not specified, the component will manage all its search state internally. +- See notes for the individual sub-components for additional information. ## `CoderWorkspacesCard.CreateWorkspacesLink` +A link-button for creating new workspaces. Clicking this link will take you to "create workspace page" in your Coder deployment, with as many fields filled out as possible. + ### Type definition -### Example usage +```tsx +type Props = { + tooltipText?: string; + tooltipProps?: Omit; + tooltipRef?: ForwardedRef; + + // Also supports all props from the native HTMLAnchorElement + // component type +}; + +declare function CreateWorkspacesLink( + props: Props, + ref?: ForwardedRef, +): JSX.Element; +``` + +All Tooltip-based props come from the type definitions for the MUI `Tooltip` component. ### Throws +- Will throw a render error if called outside of either a `CoderProvider` or `CoderWorkspacesCard.Root` + ### Notes +- If `readEntityData` is `true` in `CoderWorkspacesCard.Root`: this component will include YAML properties parsed from the current page's entity data. + ## `CoderWorkspacesCard.ExtraActionsButton` ### Type definition -### Example usage - ### Throws ### Notes @@ -283,8 +304,6 @@ function YourComponent() { ### Type definition -### Example usage - ### Throws ### Notes @@ -293,8 +312,6 @@ function YourComponent() { ### Type definition -### Example usage - ### Throws ### Notes @@ -303,8 +320,6 @@ function YourComponent() { ### Type definition -### Example usage - ### Throws ### Notes @@ -313,8 +328,6 @@ function YourComponent() { ### Type definition -### Example usage - ### Throws ### Notes @@ -323,8 +336,6 @@ function YourComponent() { ### Type definition -### Example usage - ### Throws ### Notes @@ -333,8 +344,6 @@ function YourComponent() { ### Type definition -### Example usage - ### Throws ### Notes From 833ad2d4c40f9b60af35e052c2954eabcee74603 Mon Sep 17 00:00:00 2001 From: Parkreiner Date: Wed, 6 Mar 2024 23:15:51 +0000 Subject: [PATCH 17/32] docs: finish section for ExtraActionsButton --- .../docs/api-reference/components.md | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/plugins/backstage-plugin-coder/docs/api-reference/components.md b/plugins/backstage-plugin-coder/docs/api-reference/components.md index c9f17b87..8fa6aba6 100644 --- a/plugins/backstage-plugin-coder/docs/api-reference/components.md +++ b/plugins/backstage-plugin-coder/docs/api-reference/components.md @@ -294,12 +294,57 @@ All Tooltip-based props come from the type definitions for the MUI `Tooltip` com ## `CoderWorkspacesCard.ExtraActionsButton` +A contextual menu of additional tertiary actions that can be performed for workspaces. Current actions: + +- Refresh workspaces list +- Eject token + ### Type definition +```tsx +type ExtraActionsButtonProps = Omit< + ButtonHTMLAttributes, + 'id' | 'aria-controls' +> & { + onClose?: MenuProps['onClose']; + toolTipProps?: Omit; + tooltipText?: string; + tooltipRef?: ForwardedRef; + + menuProps?: Omit< + MenuProps, + | 'id' + | 'open' + | 'anchorEl' + | 'MenuListProps' + | 'children' + | 'onClose' + | 'getContentAnchorEl' + > & { + MenuListProps: Omit; + }; + + // Also supports all props from the native HTMLButtonElement + // component, except "id" and "aria-controls" +}; + +declare function ExtraActionsButton( + props: Props, + ref?: ForwardedRef, +): JSX.Element; +``` + +All Tooltip- and Menu-based props come from the type definitions for the MUI `Tooltip` and `Menu` components. + ### Throws +- Will throw a render error if called outside of either a `CoderProvider` or `CoderWorkspacesCard.Root` + ### Notes +- When the menu opens, the first item of the list will auto-focus +- While the menu is open, you can navigate through items with the Up and Down arrow keys on the keyboard. These instructions are available for screen readers to announce + ## `CoderWorkspacesCard.HeaderRow` ### Type definition From cbca5fb4dda7ca3c8a53d7b61468df7025a73546 Mon Sep 17 00:00:00 2001 From: Parkreiner Date: Wed, 6 Mar 2024 23:38:20 +0000 Subject: [PATCH 18/32] docs: finish section for HeaderRow --- .../docs/api-reference/components.md | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/plugins/backstage-plugin-coder/docs/api-reference/components.md b/plugins/backstage-plugin-coder/docs/api-reference/components.md index 8fa6aba6..979fdda7 100644 --- a/plugins/backstage-plugin-coder/docs/api-reference/components.md +++ b/plugins/backstage-plugin-coder/docs/api-reference/components.md @@ -347,12 +347,42 @@ All Tooltip- and Menu-based props come from the type definitions for the MUI `To ## `CoderWorkspacesCard.HeaderRow` +Provides a wrapper around various heading information, as well as a section for additional buttons/actions to go. Provides critical landmark information for screen readers. + ### Type definition +```tsx +type HeaderProps = { + headerText?: string; + headerLevel?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'; + actions?: ReactNode; + fullBleedLayout?: boolean; + activeRepoFilteringText?: string | ReactNode; + + headerClassName?: string; + hgroupClassName?: string; + subheaderClassName?: string; + + // Also supports all props from the native HTMLDivElement + // component except "children" +}; + +declare function HeaderGroup( + props: Props, + ref?: ForwardedRef, +): JSX.Element; +``` + ### Throws +- Will throw a render error if called outside of either a `CoderProvider` or `CoderWorkspacesCard.Root` + ### Notes +- If `headerLevel` is not specified, the component will default to `h2` +- If `fullBleedLayout` is `true`, the component will exert negative horizontal margins to fill out its parent +- If `activeRepoFilteringText` will only display if the value of `readEntityData` in `CoderWorkspacesCard.Root` is `true` + ## `CoderWorkspacesCard.Root` ### Type definition From 21f06eb20f79f86af08d50911a8fc1222b72a9dc Mon Sep 17 00:00:00 2001 From: Parkreiner Date: Wed, 6 Mar 2024 23:43:29 +0000 Subject: [PATCH 19/32] docs: finish Root --- .../docs/api-reference/components.md | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/plugins/backstage-plugin-coder/docs/api-reference/components.md b/plugins/backstage-plugin-coder/docs/api-reference/components.md index 979fdda7..bd982f01 100644 --- a/plugins/backstage-plugin-coder/docs/api-reference/components.md +++ b/plugins/backstage-plugin-coder/docs/api-reference/components.md @@ -385,12 +385,33 @@ declare function HeaderGroup( ## `CoderWorkspacesCard.Root` +Wrapper that acts as a context provider for all other sub-components in `CoderWorkspacesCard` – does not define any components that will render to HTML. + ### Type definition +```tsx +type WorkspacesCardContext = { + queryFilter: string; + onFilterChange: (newFilter: string) => void; + workspacesQuery: UseQueryResult; + headerId: string; + entityConfig: CoderEntityConfig | undefined; +}; + +declare function Root(props: Props): JSX.Element; +``` + +All props mirror those returned by [`useWorkspacesCardContext`](./hooks.md#useworkspacescardcontext) + ### Throws +- Will throw a render error if called outside of a `CoderProvider` + ### Notes +- If `entityConfig` is defined, the Root will auto-filter all workspaces down to those that match the repo for the currently-opened entity page +- The key for `entityConfig` is not optional – even if it isn't defined, it must be explicitly passed an `undefined` value + ## `CoderWorkspacesCard.SearchBox` ### Type definition From f3a8daee97402301355fbc7ebbb9112d9a5e4546 Mon Sep 17 00:00:00 2001 From: Parkreiner Date: Wed, 6 Mar 2024 23:55:01 +0000 Subject: [PATCH 20/32] docs: finish section for SearchBox --- .../docs/api-reference/components.md | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/plugins/backstage-plugin-coder/docs/api-reference/components.md b/plugins/backstage-plugin-coder/docs/api-reference/components.md index bd982f01..3fca414f 100644 --- a/plugins/backstage-plugin-coder/docs/api-reference/components.md +++ b/plugins/backstage-plugin-coder/docs/api-reference/components.md @@ -414,12 +414,34 @@ All props mirror those returned by [`useWorkspacesCardContext`](./hooks.md#usewo ## `CoderWorkspacesCard.SearchBox` +Provides the core search functionality for Coder workspaces. + ### Type definition +```tsx +type Props = { + searchInputRef?: ForwardedRef; + clearButtonRef?: ForwardedRef; + + labelWrapperClassName?: string; + clearButtonClassName?: string; + searchInputClassName?: string; + + // Also supports all props from the native HTMLFieldSetElement + // component, except "children" and "aria-labelledby" +}; + +declare function SearchBox(props: Props): JSX.Element; +``` + ### Throws +- Will throw a render error if called outside of either a `CoderProvider` or `CoderWorkspacesCard.Root` + ### Notes +- The logic for processing user input into a new workspaces query is automatically debounced to wait 400ms. + ## `CoderWorkspacesCard.WorkspacesList` ### Type definition From 88bd95d6d67042f0ff41f579548f511b8b8410b8 Mon Sep 17 00:00:00 2001 From: Parkreiner Date: Thu, 7 Mar 2024 00:11:33 +0000 Subject: [PATCH 21/32] docs: finish rough draft of API docs --- .../docs/api-reference/components.md | 87 +++++++++++++++++-- 1 file changed, 79 insertions(+), 8 deletions(-) diff --git a/plugins/backstage-plugin-coder/docs/api-reference/components.md b/plugins/backstage-plugin-coder/docs/api-reference/components.md index 3fca414f..ade05999 100644 --- a/plugins/backstage-plugin-coder/docs/api-reference/components.md +++ b/plugins/backstage-plugin-coder/docs/api-reference/components.md @@ -8,14 +8,14 @@ This is the main documentation page for the Coder plugin's React components. - [`CoderErrorBoundary`](#codererrorboundary) - [`CoderProvider`](#coderprovider) - [`CoderWorkspacesCard`](#coderworkspacescard) - - [`CoderWorkspacesCard.CreateWorkspacesLink`](#coderworkspacescard.createworkspaceslink) - - [`CoderWorkspacesCard.ExtraActionsButton`](#coderworkspacescard.extraactionsbutton) - - [`CoderWorkspacesCard.HeaderRow`](#coderworkspacescard.headerrow) - - [`CoderWorkspacesCard.Root`](#coderworkspacescard.root) - - [`CoderWorkspacesCard.SearchBox`](#coderworkspacescard.searchbox) - - [`CoderWorkspacesCard.WorkspacesList`](#coderworkspacescard.workspaceslist) - - [`CoderWorkspacesCard.WorkspacesListIcon`](#coderworkspacescard.workspaceslisticon) - - [`CoderWorkspacesCard.WorkspacesListItem`](#coderworkspacescard.workspaceslistitem) + - [`CoderWorkspacesCard.CreateWorkspacesLink`](#coderworkspacescardcreateworkspaceslink) + - [`CoderWorkspacesCard.ExtraActionsButton`](#coderworkspacescardextraactionsbutton) + - [`CoderWorkspacesCard.HeaderRow`](#coderworkspacescardheaderrow) + - [`CoderWorkspacesCard.Root`](#coderworkspacescardroot) + - [`CoderWorkspacesCard.SearchBox`](#coderworkspacescardsearchbox) + - [`CoderWorkspacesCard.WorkspacesList`](#coderworkspacescardworkspaceslist) + - [`CoderWorkspacesCard.WorkspacesListIcon`](#coderworkspacescardworkspaceslisticon) + - [`CoderWorkspacesCard.WorkspacesListItem`](#coderworkspacescardworkspaceslistitem) ## `CoderAuthWrapper` @@ -444,24 +444,95 @@ declare function SearchBox(props: Props): JSX.Element; ## `CoderWorkspacesCard.WorkspacesList` +Main container for displaying all workspaces returned from a query. + ### Type definition +```tsx +type RenderListItemInput = Readonly<{ + workspace: Workspace; + index: number; + workspaces: readonly Workspace[]; +}>; + +type Props = { + emptyState?: ReactNode; + ordered?: boolean; + listClassName?: string; + fullBleedLayout?: boolean; + renderListItem?: (input: RenderListItemInput) => ReactNode; + + // Also supports all props from the native HTMLDivElement + // component, except for "children" +}; +``` + ### Throws +- Will throw a render error if called outside of either a `CoderProvider` or `CoderWorkspacesCard.Root` + ### Notes +- If `ordered` is `true`, the component will render as an `
    `. Otherwise, the output will be a `
      `. `ordered` defaults to `true`. +- If `fullBleedLayout` is `true`, the component will exert negative horizontal margins to fill out its parent +- If `renderListItem` is not specified, this component will default to rendering each list item with [`CoderWorkspacesCard.ListItem`](./components.md#coderworkspacescardworkspaceslistitem) + ## `CoderWorkspacesCard.WorkspacesListIcon` +The image to use to represent each workspace. + ### Type definition +```tsx +type WorkspaceListIconProps = { + src: string; + workspaceName: string; + imageClassName?: string; + imageRef?: ForwardedRef; + + // Also accepts all props from the native HTMLDivElement component, + // except "children" and "aria-hidden" +}; + +declare function WorkspaceListIcon(prop: Props): JSX.Element; +``` + ### Throws +- Does not throw (even if outside `CoderWorkspacesList.Root`) + ### Notes +- If there is no `src` available to pass to this component, use an empty string. +- When there is no `src` value, the component will display a fallback graphic + ## `CoderWorkspacesCard.WorkspacesListItem` +The default render component to use when the `renderListItem` prop for [`CoderWorkspacesCard.WorkspacesList`] is not defined. + ### Type definition +```tsx +type Props = { + workspace: Workspace; + + buttonClassName?: string; + linkClassName?: string; + listFlexRowClassName?: string; + onlineStatusContainerClassName?: string; + onlineStatusLightClassName?: string; + + // Also supports all props from the native HTMLLIElement + // component, except for "children" +}; + +declare function WorkspaceListItem(props: Props): JSX.Element; +``` + ### Throws +- Will throw a render error if called outside of either a `CoderProvider` (can be called outside of a `CoderWorkspacesCard.Root`) + ### Notes + +- Supports full link-like functionality (right-clicking and middle-clicking to open in a new tab, etc.) From 605b2b9e51b53410c49097d5bbb5c103e51a9922 Mon Sep 17 00:00:00 2001 From: Parkreiner Date: Thu, 7 Mar 2024 00:20:32 +0000 Subject: [PATCH 22/32] fix: fill in missing section for CoderProvider --- .../backstage-plugin-coder/docs/api-reference/components.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/backstage-plugin-coder/docs/api-reference/components.md b/plugins/backstage-plugin-coder/docs/api-reference/components.md index ade05999..fb955b11 100644 --- a/plugins/backstage-plugin-coder/docs/api-reference/components.md +++ b/plugins/backstage-plugin-coder/docs/api-reference/components.md @@ -101,7 +101,10 @@ function YourComponent() { ## `CoderProvider` -Provides +Provides top-level Coder-specific data to the rest of the frontend Coder plugin components. Data such as: + +- The Coder access URL +- Fallback workspace parameters ### Type signature From d5a46afa0b0e108e2e6a4b3bf311966b94465c30 Mon Sep 17 00:00:00 2001 From: Parkreiner Date: Thu, 7 Mar 2024 00:21:27 +0000 Subject: [PATCH 23/32] chore: move API docs up one level --- plugins/backstage-plugin-coder/docs/{api-reference => }/README.md | 0 .../backstage-plugin-coder/docs/{api-reference => }/components.md | 0 plugins/backstage-plugin-coder/docs/{api-reference => }/hooks.md | 0 plugins/backstage-plugin-coder/docs/{api-reference => }/types.md | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename plugins/backstage-plugin-coder/docs/{api-reference => }/README.md (100%) rename plugins/backstage-plugin-coder/docs/{api-reference => }/components.md (100%) rename plugins/backstage-plugin-coder/docs/{api-reference => }/hooks.md (100%) rename plugins/backstage-plugin-coder/docs/{api-reference => }/types.md (100%) diff --git a/plugins/backstage-plugin-coder/docs/api-reference/README.md b/plugins/backstage-plugin-coder/docs/README.md similarity index 100% rename from plugins/backstage-plugin-coder/docs/api-reference/README.md rename to plugins/backstage-plugin-coder/docs/README.md diff --git a/plugins/backstage-plugin-coder/docs/api-reference/components.md b/plugins/backstage-plugin-coder/docs/components.md similarity index 100% rename from plugins/backstage-plugin-coder/docs/api-reference/components.md rename to plugins/backstage-plugin-coder/docs/components.md diff --git a/plugins/backstage-plugin-coder/docs/api-reference/hooks.md b/plugins/backstage-plugin-coder/docs/hooks.md similarity index 100% rename from plugins/backstage-plugin-coder/docs/api-reference/hooks.md rename to plugins/backstage-plugin-coder/docs/hooks.md diff --git a/plugins/backstage-plugin-coder/docs/api-reference/types.md b/plugins/backstage-plugin-coder/docs/types.md similarity index 100% rename from plugins/backstage-plugin-coder/docs/api-reference/types.md rename to plugins/backstage-plugin-coder/docs/types.md From abfcf0fc66b1c47b15045c5cf7c31b1ab1022664 Mon Sep 17 00:00:00 2001 From: Parkreiner Date: Thu, 7 Mar 2024 00:22:02 +0000 Subject: [PATCH 24/32] docs: fill in link for TODO comment --- plugins/backstage-plugin-coder/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/backstage-plugin-coder/README.md b/plugins/backstage-plugin-coder/README.md index eea90e07..e28fdf8b 100644 --- a/plugins/backstage-plugin-coder/README.md +++ b/plugins/backstage-plugin-coder/README.md @@ -89,7 +89,7 @@ the devcontainer. ); ``` - + **Note:** You can also wrap a single page or component with `CoderProvider` if you only need Coder in a specific part of your app. See our [API reference](./docs/components.md#coderprovider) for more details. 1. Add the `CoderWorkspacesCard` card to the entity page in your app: From 799132cf5798b34e871ba0b4b906fc02b7ecd28c Mon Sep 17 00:00:00 2001 From: Parkreiner Date: Thu, 7 Mar 2024 00:24:45 +0000 Subject: [PATCH 25/32] fix: reword docs message for clarity --- plugins/backstage-plugin-coder/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/backstage-plugin-coder/README.md b/plugins/backstage-plugin-coder/README.md index e28fdf8b..5ddc8519 100644 --- a/plugins/backstage-plugin-coder/README.md +++ b/plugins/backstage-plugin-coder/README.md @@ -89,7 +89,7 @@ the devcontainer. ); ``` - **Note:** You can also wrap a single page or component with `CoderProvider` if you only need Coder in a specific part of your app. See our [API reference](./docs/components.md#coderprovider) for more details. + **Note:** You can also wrap a single page or component with `CoderProvider` if you only need Coder in a specific part of your app. See our [API reference](./docs/README.md) (particularly the section on [the `CoderProvider` component](./docs/components.md#coderprovider)) for more details. 1. Add the `CoderWorkspacesCard` card to the entity page in your app: From 8085717efc2fb82a02773c5b284c2f09549d213f Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Thu, 7 Mar 2024 13:55:20 -0500 Subject: [PATCH 26/32] Update plugins/backstage-plugin-coder/docs/README.md Co-authored-by: Ben Potter --- plugins/backstage-plugin-coder/docs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/backstage-plugin-coder/docs/README.md b/plugins/backstage-plugin-coder/docs/README.md index 52648c4c..7ca73a4e 100644 --- a/plugins/backstage-plugin-coder/docs/README.md +++ b/plugins/backstage-plugin-coder/docs/README.md @@ -1,6 +1,6 @@ # Plugin API Reference – Coder for Backstage -For users who need more information about how to extend and modify the Coder plugin. For general setup, please see our main [README](../../README.md). +For users who need more information about how to extend and modify the Coder plugin. For general setup, please see our main [README](../README.md). ## Documentation directory From 54c65c2365d88626cc10fba6ce4e7b5e1002923e Mon Sep 17 00:00:00 2001 From: Parkreiner Date: Thu, 7 Mar 2024 19:08:06 +0000 Subject: [PATCH 27/32] fix: update version for Coder plugin --- packages/app/package.json | 2 +- yarn.lock | 30 ++++++++++++++++++++++++++++-- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/packages/app/package.json b/packages/app/package.json index 4a3f716e..378df67e 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -39,7 +39,7 @@ "@backstage/plugin-techdocs-react": "^1.1.15", "@backstage/plugin-user-settings": "^0.8.0", "@backstage/theme": "^0.5.0", - "@coder/backstage-plugin-coder": "^0.0.0", + "@coder/backstage-plugin-coder": "0.0.1", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "history": "^5.0.0", diff --git a/yarn.lock b/yarn.lock index 2b77509d..c5533230 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4217,6 +4217,23 @@ style-mod "^4.1.0" w3c-keyname "^2.2.4" +"@coder/backstage-plugin-coder@0.0.1": + version "0.0.1" + resolved "https://registry.yarnpkg.com/@coder/backstage-plugin-coder/-/backstage-plugin-coder-0.0.1.tgz#2c2fca13c2959c93ccb14ab94772b87e0eec52f2" + integrity sha512-iTAUg4J9AIdSRr47i83TRRaO6bIU7OF1dfRw098JbacJXn6ip0P4VxerD1oFcNoVi2uDrFnvF2YPffvzNwqyDw== + dependencies: + "@backstage/core-components" "^0.13.10" + "@backstage/core-plugin-api" "^1.8.2" + "@backstage/integration-react" "^1.1.24" + "@backstage/plugin-catalog-react" "^1.10.0" + "@backstage/theme" "^0.5.0" + "@material-ui/core" "^4.12.2" + "@material-ui/icons" "^4.9.1" + "@material-ui/lab" "4.0.0-alpha.61" + "@tanstack/react-query" "4.36.1" + react-use "^17.2.4" + valibot "^0.28.1" + "@colors/colors@1.6.0", "@colors/colors@^1.6.0": version "1.6.0" resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.6.0.tgz#ec6cd237440700bc23ca23087f513c75508958b0" @@ -8967,7 +8984,7 @@ resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb" integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== -"@types/react-dom@*", "@types/react-dom@^18", "@types/react-dom@^18.0.0": +"@types/react-dom@*", "@types/react-dom@^18.0.0": version "18.2.19" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.19.tgz#b84b7c30c635a6c26c6a6dfbb599b2da9788be58" integrity sha512-aZvQL6uUbIJpjZk4U8JZGbau9KDeAwMfmhyWorxgBkqDIEf6ROjRozcmPIicqsUwPUjbkDfHKgGee1Lq65APcA== @@ -9005,7 +9022,7 @@ dependencies: "@types/react" "*" -"@types/react@*", "@types/react@^16.13.1 || ^17.0.0", "@types/react@^16.13.1 || ^17.0.0 || ^18.0.0", "@types/react@^18": +"@types/react@*", "@types/react@^16.13.1 || ^17.0.0 || ^18.0.0": version "18.2.60" resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.60.tgz#df026eaef1100b6dafe420f36fecb1d209a8cee1" integrity sha512-dfiPj9+k20jJrLGOu9Nf6eqxm2EyJRrq2NvwOFsfbb7sFExZ9WELPs67UImHj3Ayxg8ruTtKtNnbjaF8olPq0A== @@ -9014,6 +9031,15 @@ "@types/scheduler" "*" csstype "^3.0.2" +"@types/react@^16.13.1 || ^17.0.0": + version "17.0.76" + resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.76.tgz#bfdf762046699e265655cd8f67a51beab6cd1e80" + integrity sha512-w9Aq+qeszGYoQM0hgFcdsAODGJdogadBDiitPm+zjBFJ0mLymvn2qSXsDaLJUndFRqqXk1FQfa9avHUBk1JhJQ== + dependencies: + "@types/prop-types" "*" + "@types/scheduler" "*" + csstype "^3.0.2" + "@types/request@^2.47.1", "@types/request@^2.48.8": version "2.48.12" resolved "https://registry.yarnpkg.com/@types/request/-/request-2.48.12.tgz#0f590f615a10f87da18e9790ac94c29ec4c5ef30" From dd56f005af5cef240a4b8395518444a093fb1f1f Mon Sep 17 00:00:00 2001 From: Parkreiner Date: Thu, 7 Mar 2024 19:15:57 +0000 Subject: [PATCH 28/32] docs: add info about useCoderEntityConfig --- plugins/backstage-plugin-coder/docs/components.md | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/backstage-plugin-coder/docs/components.md b/plugins/backstage-plugin-coder/docs/components.md index fb955b11..e37aff20 100644 --- a/plugins/backstage-plugin-coder/docs/components.md +++ b/plugins/backstage-plugin-coder/docs/components.md @@ -91,7 +91,6 @@ function YourComponent() { ### Throws - Does not throw - - (Need to verify this - our own code for this component doesn't throw any errors, but it does rely on Backstage's `useApi` hook. Unfortunately, TypeScript type signatures can't communicate whether they throw errors, and the documentation has no info. Will need to go through the source code) ### Notes From a899bad74859dd89f3c4c96b4fc1991901409515 Mon Sep 17 00:00:00 2001 From: Parkreiner Date: Fri, 8 Mar 2024 14:56:54 +0000 Subject: [PATCH 29/32] fix: make sure all exported components have unique name values --- plugins/backstage-plugin-coder/src/plugin.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/plugins/backstage-plugin-coder/src/plugin.ts b/plugins/backstage-plugin-coder/src/plugin.ts index 6421bde9..6002d43f 100644 --- a/plugins/backstage-plugin-coder/src/plugin.ts +++ b/plugins/backstage-plugin-coder/src/plugin.ts @@ -13,10 +13,14 @@ export const coderPlugin = createPlugin({ /** * All public component exports exposed by the plugin. + * + * Make sure that all name properties for each exported component are unique. If + * there are conflicts, you could run into Backstage compilation issues with no + * good error messages to help you track down the source. */ export const CoderProvider = coderPlugin.provide( createComponentExtension({ - name: 'CoderAuthWrapper', + name: 'CoderProvider', component: { lazy: () => import('./components/CoderProvider').then(m => m.CoderProvider), @@ -36,7 +40,7 @@ export const CoderAuthWrapper = coderPlugin.provide( export const CoderErrorBoundary = coderPlugin.provide( createComponentExtension({ - name: 'CoderAuthWrapper', + name: 'CoderErrorBoundary', component: { lazy: () => import('./components/CoderErrorBoundary').then( @@ -48,7 +52,7 @@ export const CoderErrorBoundary = coderPlugin.provide( export const CoderWorkspacesCard = coderPlugin.provide( createComponentExtension({ - name: 'CoderAuthWrapper', + name: 'CoderWorkspacesCard', component: { lazy: () => import('./components/CoderWorkspacesCard').then( From af5e67aa7eb99c1a219ab2da2b6c40740be16cc7 Mon Sep 17 00:00:00 2001 From: Parkreiner Date: Fri, 8 Mar 2024 15:13:59 +0000 Subject: [PATCH 30/32] fix: make sure all values are properly exported at top-level plugin root --- plugins/backstage-plugin-coder/src/plugin.ts | 87 ++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/plugins/backstage-plugin-coder/src/plugin.ts b/plugins/backstage-plugin-coder/src/plugin.ts index 6002d43f..790327aa 100644 --- a/plugins/backstage-plugin-coder/src/plugin.ts +++ b/plugins/backstage-plugin-coder/src/plugin.ts @@ -62,6 +62,93 @@ export const CoderWorkspacesCard = coderPlugin.provide( }), ); +export const CoderWorkspacesCardCreateWorkspacesLink = coderPlugin.provide( + createComponentExtension({ + name: 'CoderWorkspacesCard.CreateWorkspacesLink', + component: { + lazy: () => + import('./components/CoderWorkspacesCard').then( + m => m.CreateWorkspaceLink, + ), + }, + }), +); + +export const CoderWorkspacesCardExtraActionsButton = coderPlugin.provide( + createComponentExtension({ + name: 'CoderWorkspacesCard.ExtraActionsButton', + component: { + lazy: () => + import('./components/CoderWorkspacesCard').then( + m => m.ExtraActionsButton, + ), + }, + }), +); + +export const CoderWorkspacesCardHeaderRow = coderPlugin.provide( + createComponentExtension({ + name: 'CoderWorkspacesCard.HeaderRow', + component: { + lazy: () => + import('./components/CoderWorkspacesCard').then(m => m.HeaderRow), + }, + }), +); + +export const CoderWorkspacesCardRoot = coderPlugin.provide( + createComponentExtension({ + name: 'CoderWorkspacesCard.Root', + component: { + lazy: () => import('./components/CoderWorkspacesCard').then(m => m.Root), + }, + }), +); + +export const CoderWorkspacesCardSearchBox = coderPlugin.provide( + createComponentExtension({ + name: 'CoderWorkspacesCard.SearchBox', + component: { + lazy: () => + import('./components/CoderWorkspacesCard').then(m => m.SearchBox), + }, + }), +); + +export const CoderWorkspacesCardWorkspacesList = coderPlugin.provide( + createComponentExtension({ + name: 'CoderWorkspacesCard.WorkspacesList', + component: { + lazy: () => + import('./components/CoderWorkspacesCard').then(m => m.WorkspacesList), + }, + }), +); + +export const CoderWorkspacesCardWorkspacesListIcon = coderPlugin.provide( + createComponentExtension({ + name: 'CoderWorkspacesCard.WorkspacesListIcon', + component: { + lazy: () => + import('./components/CoderWorkspacesCard').then( + m => m.WorkspacesListIcon, + ), + }, + }), +); + +export const CoderWorkspacesCardWorkspacesListItem = coderPlugin.provide( + createComponentExtension({ + name: 'CoderWorkspacesCard.WorkspacesListItem', + component: { + lazy: () => + import('./components/CoderWorkspacesCard').then( + m => m.WorkspacesListItem, + ), + }, + }), +); + /** * All custom hooks exposed by the plugin. */ From 3a09f9cceef2340150d9ab2a83530a53e3a9edb8 Mon Sep 17 00:00:00 2001 From: Parkreiner Date: Fri, 8 Mar 2024 15:26:58 +0000 Subject: [PATCH 31/32] fix: remove forwardRef from exported sub-components --- .../CreateWorkspaceLink.tsx | 83 +++++++++---------- .../ExtraActionsButton.tsx | 34 ++++---- .../WorkspacesListIcon.tsx | 31 +++---- 3 files changed, 62 insertions(+), 86 deletions(-) diff --git a/plugins/backstage-plugin-coder/src/components/CoderWorkspacesCard/CreateWorkspaceLink.tsx b/plugins/backstage-plugin-coder/src/components/CoderWorkspacesCard/CreateWorkspaceLink.tsx index 963161c9..eea5132c 100644 --- a/plugins/backstage-plugin-coder/src/components/CoderWorkspacesCard/CreateWorkspaceLink.tsx +++ b/plugins/backstage-plugin-coder/src/components/CoderWorkspacesCard/CreateWorkspaceLink.tsx @@ -1,8 +1,4 @@ -import React, { - type AnchorHTMLAttributes, - type ForwardedRef, - forwardRef, -} from 'react'; +import React, { type AnchorHTMLAttributes, type ForwardedRef } from 'react'; import { makeStyles } from '@material-ui/core'; import { useCoderAppConfig } from '../CoderProvider'; @@ -43,47 +39,42 @@ type CreateButtonLinkProps = Readonly< } >; -export const CreateWorkspaceLink = forwardRef( - (props: CreateButtonLinkProps, ref?: ForwardedRef) => { - const { - children, - className, - tooltipRef, - target = '_blank', - tooltipText = 'Add a new workspace', - tooltipProps = {}, - ...delegatedProps - } = props; +export const CreateWorkspaceLink = ({ + children, + className, + tooltipRef, + target = '_blank', + tooltipText = 'Add a new workspace', + tooltipProps = {}, + ...delegatedProps +}: CreateButtonLinkProps) => { + const styles = useStyles(); + const appConfig = useCoderAppConfig(); + const { entityConfig } = useWorkspacesCardContext(); - const styles = useStyles(); - const appConfig = useCoderAppConfig(); - const { entityConfig } = useWorkspacesCardContext(); - - const activeConfig = { - ...appConfig.workspaces, - ...(entityConfig ?? {}), - }; + const activeConfig = { + ...appConfig.workspaces, + ...(entityConfig ?? {}), + }; - return ( - - - {children ?? } + return ( + + + {children ?? } - - {tooltipText} - {target === '_blank' && <> (Link opens in new tab)} - - - - ); - }, -); + + {tooltipText} + {target === '_blank' && <> (Link opens in new tab)} + + + + ); +}; diff --git a/plugins/backstage-plugin-coder/src/components/CoderWorkspacesCard/ExtraActionsButton.tsx b/plugins/backstage-plugin-coder/src/components/CoderWorkspacesCard/ExtraActionsButton.tsx index 07f6d9db..d9c693b0 100644 --- a/plugins/backstage-plugin-coder/src/components/CoderWorkspacesCard/ExtraActionsButton.tsx +++ b/plugins/backstage-plugin-coder/src/components/CoderWorkspacesCard/ExtraActionsButton.tsx @@ -1,7 +1,6 @@ import React, { type ButtonHTMLAttributes, type ForwardedRef, - forwardRef, useEffect, useRef, useState, @@ -73,6 +72,7 @@ type ExtraActionsMenuProps = Readonly< type ExtraActionsButtonProps = Readonly< Omit, 'id' | 'aria-controls'> & { onClose?: MenuProps['onClose']; + buttonRef?: ForwardedRef; menuProps?: ExtraActionsMenuProps; toolTipProps?: Omit; tooltipText?: string; @@ -80,22 +80,18 @@ type ExtraActionsButtonProps = Readonly< } >; -export const ExtraActionsButton = forwardRef< - HTMLButtonElement, - ExtraActionsButtonProps ->((props, ref) => { - const { - menuProps, - toolTipProps, - tooltipRef, - children, - className, - onClick: outerOnClick, - onClose: outerOnClose, - tooltipText = 'See additional workspace actions', - ...delegatedButtonProps - } = props; - +export const ExtraActionsButton = ({ + menuProps, + buttonRef, + toolTipProps, + tooltipRef, + children, + className, + onClick: outerOnClick, + onClose: outerOnClose, + tooltipText = 'See additional workspace actions', + ...delegatedButtonProps +}: ExtraActionsButtonProps) => { const { className: menuListClassName, ref: menuListRef, @@ -119,7 +115,7 @@ export const ExtraActionsButton = forwardRef< <>