Thanks to visit codestin.com
Credit goes to github.com

Skip to content

chore(Coder plugin): Create guide for working with the Coder SDK #133

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 112 commits into from
Jun 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
112 commits
Select commit Hold shift + click to select a range
a27e95e
chore: add vendored version of experimental Coder SDK
Parkreiner May 24, 2024
9c958d3
chore: update CoderClient class to use new SDK
Parkreiner May 24, 2024
4979067
chore: delete mock SDK
Parkreiner May 24, 2024
5e7e01f
fix: improve data hiding for CoderSdk
Parkreiner May 24, 2024
937f6f5
docs: update typo
Parkreiner May 24, 2024
7e84d00
Merge branch 'mes/vendored-sdk' into mes/vendored-sdk-integration
Parkreiner May 24, 2024
294572d
wip: commit progress on updating Coder client
Parkreiner May 24, 2024
d9626a0
wip: commit more progress on updating types
Parkreiner May 24, 2024
1dcc13b
chore: remove valibot type definitions from global constants file
Parkreiner May 24, 2024
692a763
chore: rename mocks file
Parkreiner May 24, 2024
28accc8
fix: update type mismatches
Parkreiner May 24, 2024
d032768
wip: commit more update progress
Parkreiner May 24, 2024
a76db16
wip: commit progress on updating client/SDK integration
Parkreiner May 24, 2024
d22bc20
fix: get all tests passing for CoderClient
Parkreiner May 24, 2024
08cd049
fix: update UrlSync updates
Parkreiner May 24, 2024
2eb4987
fix: get all tests passing
Parkreiner May 24, 2024
37645f4
chore: update all mock data to use Coder core entity mocks
Parkreiner May 24, 2024
c6cc7b7
refactor: improve co-location for useCoderWorkspacesQuery
Parkreiner May 28, 2024
07f9cde
wip: commit progress on React Query wrappers
Parkreiner May 28, 2024
864357d
fix: add extra helpers to useCoderSdk
Parkreiner May 28, 2024
fbe1411
Merge branch 'mes/vendored-sdk-integration' into mes/vendored-sdk-hel…
Parkreiner May 28, 2024
67ac529
wip: add test stubs for useCoderQuery
Parkreiner May 28, 2024
4d66cca
fix: add queryKey patching to useCoderQuery
Parkreiner May 28, 2024
333aaa3
fix: only add queryKey prefix if it is missing
Parkreiner May 28, 2024
e0bd1a2
fix: make Coder query key prefix an opaque string
Parkreiner May 28, 2024
3c835b4
refactor: improve ergonomics of useCoderQuery
Parkreiner May 28, 2024
0d20670
refactor: clean up query key patching logic
Parkreiner May 28, 2024
6bff452
chore: let users disable fallback auth UI
Parkreiner May 28, 2024
83ee830
wip: commit progress on tests
Parkreiner May 28, 2024
df6339d
chore: update wording for clarity
Parkreiner May 28, 2024
5df5070
fix: update import for workspaces card root
Parkreiner May 28, 2024
5aa5759
chore: get first test passing
Parkreiner May 28, 2024
0b49e2f
chore: add inverted promise helper
Parkreiner May 28, 2024
d50a3af
fix: make non-authenticated queries fail faster
Parkreiner May 28, 2024
ea46f3c
fix: update tests to make setup easier
Parkreiner May 28, 2024
f4ca6e8
wip: get another test passing
Parkreiner May 28, 2024
a12d4d8
chore: finish all initial tests for useCoderQuery
Parkreiner May 28, 2024
1c11f5c
fix: tighten up types for inverted promises
Parkreiner May 28, 2024
071d8c8
fix: more tightening
Parkreiner May 28, 2024
5120b5b
fix: make sure queries aren't tried indefinitely by default
Parkreiner May 28, 2024
d172941
wip: commit docs progress
Parkreiner May 29, 2024
5830624
fix: increase granularity for auth fallback behavior
Parkreiner May 29, 2024
751ed1c
Merge branch 'mes/vendored-sdk-helpers' into mes/vendored-sdk-readme
Parkreiner May 29, 2024
883f1ab
wip: commit more docs progress
Parkreiner May 29, 2024
67713a1
fix: establish better boundaries between hooks
Parkreiner May 29, 2024
07e054d
Merge branch 'mes/vendored-sdk-helpers' into mes/vendored-sdk-readme
Parkreiner May 29, 2024
570cecc
wip: commit more progress
Parkreiner May 29, 2024
4b5265c
wip: more docs progress
Parkreiner May 29, 2024
ee6129d
fix: split up auth fallback logic into three providers
Parkreiner May 30, 2024
9d52273
Merge branch 'mes/vendored-sdk-helpers' into mes/vendored-sdk-readme
Parkreiner May 30, 2024
b95865e
fix: update example code
Parkreiner May 30, 2024
85b7942
wip: commit more progress
Parkreiner May 30, 2024
6814af8
fix: update names for auth fallback modes
Parkreiner May 30, 2024
aa4f684
Merge branch 'mes/vendored-sdk-helpers' into mes/vendored-sdk-readme
Parkreiner May 30, 2024
032e5a9
wip: more progress
Parkreiner May 30, 2024
5494443
fix: remove repetitive wording
Parkreiner May 30, 2024
cac828d
wip: more progress
Parkreiner May 31, 2024
256e2bd
fix: add table of contents header
Parkreiner May 31, 2024
4f6adb3
fix: improve granularity of expired token spy logic
Parkreiner May 31, 2024
7327d61
Merge branch 'mes/vendored-sdk-helpers' into mes/vendored-sdk-readme
Parkreiner May 31, 2024
0a0b647
fix: prevent infinite revalidation loop
Parkreiner May 31, 2024
c2decf8
fix: clean up the cleanup logic
Parkreiner May 31, 2024
2b97304
Merge branch 'mes/vendored-sdk-helpers' into mes/vendored-sdk-readme
Parkreiner May 31, 2024
0b60ebd
fix: update example code
Parkreiner May 31, 2024
e4411f5
fix: update header levels
Parkreiner May 31, 2024
2b924e9
fix: make prop optional
Parkreiner May 31, 2024
f0abd9f
Merge branch 'mes/vendored-sdk-helpers' into mes/vendored-sdk-readme
Parkreiner May 31, 2024
9cf133c
chore: add warning about query client mistakes
Parkreiner May 31, 2024
dae3e61
wip: finish last code example
Parkreiner May 31, 2024
cdec143
fix: update union/intersection mismatch
Parkreiner May 31, 2024
0fd345a
Merge branch 'mes/vendored-sdk-helpers' into mes/vendored-sdk-readme
Parkreiner May 31, 2024
c07aff0
chore: finish initial version of SDK readme
Parkreiner May 31, 2024
f3e90ca
wip: make placeholders more obvious
Parkreiner May 31, 2024
977b2eb
fix: add additional properties to hide from SDK
Parkreiner May 31, 2024
a9b24aa
Merge branch 'mes/vendored-sdk' into mes/vendored-sdk-integration
Parkreiner May 31, 2024
9038850
Merge branch 'mes/vendored-sdk-integration' into mes/vendored-sdk-hel…
Parkreiner May 31, 2024
69a1c7a
Merge branch 'mes/vendored-sdk-helpers' into mes/vendored-sdk-readme
Parkreiner May 31, 2024
259702e
Merge branch 'main' into mes/vendored-sdk-integration
Parkreiner May 31, 2024
09240cc
fix: shrink down the API of useCoderSdk
Parkreiner May 31, 2024
0cbdea7
Merge branch 'mes/vendored-sdk-integration' into mes/vendored-sdk-hel…
Parkreiner May 31, 2024
3a8accb
update method name for clarity
Parkreiner Jun 3, 2024
a67fbcf
chore: removal vestigal endpoint properties
Parkreiner Jun 3, 2024
26baf17
Merge branch 'mes/vendored-sdk-integration' into mes/vendored-sdk-hel…
Parkreiner Jun 3, 2024
cd734e3
Merge branch 'mes/vendored-sdk-helpers' into mes/vendored-sdk-readme
Parkreiner Jun 3, 2024
a0f4340
fix: swap public 'SDK' usage with 'API'
Parkreiner Jun 10, 2024
3329f3d
fix: remove temp import
Parkreiner Jun 10, 2024
a56f332
fix: update exports for end-types
Parkreiner Jun 10, 2024
3a5582a
fix: update query wrapper tests
Parkreiner Jun 10, 2024
91185aa
wip: commit current rewrite progress
Parkreiner Jun 11, 2024
5dc9035
fix: update structure of directory readme
Parkreiner Jun 11, 2024
4b52bf7
wip: commit more docs progress
Parkreiner Jun 11, 2024
753a5e5
chore: finish second draft of main README
Parkreiner Jun 11, 2024
3402ea5
refactor: rename ejectToken to unlinkToken
Parkreiner Jun 11, 2024
45eac85
refactor: reorganize readme file structure
Parkreiner Jun 11, 2024
2bc6838
update details for new versions of README
Parkreiner Jun 11, 2024
852226d
chore: delete first draft of the README
Parkreiner Jun 11, 2024
cad3e16
Merge branch 'main' into mes/vendored-sdk-readme
Parkreiner Jun 11, 2024
21b4b7f
fix: remove duplicate destructuring
Parkreiner Jun 11, 2024
7c03d27
fix: update duplicate exports
Parkreiner Jun 11, 2024
6b58a89
fix: update semver message
Parkreiner Jun 12, 2024
3c647e9
fix: remove useEffect comparison column
Parkreiner Jun 12, 2024
a37d9c7
fix: move custom query client into advanced section
Parkreiner Jun 12, 2024
8fef874
fix: remove redundant examples
Parkreiner Jun 12, 2024
2ab5836
fix: update hook overview
Parkreiner Jun 12, 2024
51de0c9
fix: update formatting for advanced file
Parkreiner Jun 12, 2024
0bb2b92
fix: regorganize prefix section
Parkreiner Jun 12, 2024
f4d05aa
chore: finish v3 of reorganization
Parkreiner Jun 13, 2024
d010cb6
chore: reorganize text content one last time
Parkreiner Jun 13, 2024
adad952
chore: group prefix examples
Parkreiner Jun 13, 2024
5012b01
chore: reorganize directory readme
Parkreiner Jun 13, 2024
b1b05d8
chore: add image of auth fallback
Parkreiner Jun 17, 2024
1ce21c9
chore: add video of auth functionality
Parkreiner Jun 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 18 additions & 7 deletions plugins/backstage-plugin-coder/docs/README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,22 @@
# Plugin API Reference – Coder for Backstage
# Documentation Directory – `backstage-plugin-coder` v0.3.0

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).
This document lists core information for the Backstage Coder plugin. It is intended for users who have already set up the plugin and are looking to take it further.

All documentation reflects version `v0.2.0` of the plugin. Note that breaking API changes may continue to happen for minor versions until the plugin reaches version `v1.0.0`.
For general setup, please see our [main README](../README.md).

## Documentation directory
## Documentation listing

- [Components](./components.md)
- [Custom React hooks](./hooks.md)
- [Important types](./types.md)
### Guides

- [Using the Coder API from Backstage](./guides/coder-api.md)
- [Advanced use cases for the Coder API](./guides//coder-api-advanced.md)

### API reference

- [Components](./api-reference/components.md)
- [Custom React hooks](./api-reference/hooks.md)
- [Important types](./api-reference/types.md)

## Notes about semantic versioning

We fully intend to follow semantic versioning with the Coder plugin for Backstage. Expect some pain points as we figure out the right abstractions needed to hit version 1, but we will try to minimize breaking changes as much as possible as the library gets ironed out.
72 changes: 72 additions & 0 deletions plugins/backstage-plugin-coder/docs/guides/coder-api-advanced.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Working with the Coder API - advanced use cases

This guide covers some more use cases that you can leverage for more advanced configuration of the Coder API from within Backstage.

## Changing fallback auth component behavior

By default, `CoderProvider` is configured to display a fallback auth UI component when two cases are true:

1. The user is not authenticated
2. There are no official Coder components are being rendered to the screen.

<img src="../../screenshots/auth-fallback.png" alt="The Coder auth fallback UI" />

All official Coder plugin components are configured to let the user add auth information if the user isn't already authenticated, so the fallback component only displays when there would be no other way to add the information.

However, depending on your use cases, `CoderProvider` can be configured to change how it displays the fallback, based on the value of the `fallbackAuthUiMode` prop.

```tsx
<CoderProvider fallbackAuthUiMode="assertive">
<OtherComponents />
</CoderProvider>
```

There are three values that can be set for the mode:

- `restrained` (default) - The auth fallback will only display if the user is not authenticated, and there would be no other way for the user to add their auth info.
- `assertive` - The auth fallback will always display when the user is not authenticated, regardless of what Coder component are on-screen. But the fallback will **not** appear if the user is authenticated.
- `hidden` - The auth fallback will never appear under any circumstances. Useful if you want to create entirely custom components and don't mind wiring your auth logic manually via `useCoderAuth`.

## Connecting a custom query client to the Coder plugin

By default, the Coder plugin uses and manages its own query client. This works perfectly well if you aren't using React Query for any other purposes, but if you are using it throughout your Backstage deployment, it can cause issues around redundant state (e.g., not all cached data being vacated when the user logs out).

To prevent this, you will need to do two things:

1. Pass in your custom React Query query client into the `CoderProvider` component
2. "Group" your queries with the Coder query key prefix

```tsx
const yourCustomQueryClient = new QueryClient();

<CoderProvider queryClient={yourCustomQueryClient}>
<YourCustomComponents />
</CoderProvider>;

// Ensure that all queries have the correct query key prefix
import { useQuery } from '@tanstack/react-react-query';
import {
CODER_QUERY_KEY_PREFIX,
useCoderQuery,
} from '@coder/backstage-plugin-coder';

function CustomComponent() {
const query1 = useQuery({
queryKey: [CODER_QUERY_KEY_PREFIX, 'workspaces'],
queryFn: () => {
// Get workspaces here
},
});

// useCoderQuery automatically prefixes all query keys with
// CODER_QUERY_KEY_PREFIX if it's not already the first value of the array
const query2 = useCoderQuery({
queryKey: ['workspaces'],
queryFn: () => {
// Get workspaces here
},
});

return <div>Main component content</div>;
}
```
262 changes: 262 additions & 0 deletions plugins/backstage-plugin-coder/docs/guides/coder-api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
# Coder API - Quick-start guide

## Overview

The Coder plugin makes it easy to bring the entire Coder API into your Backstage deployment. This guide covers how to get it set up so that you can start accessing Coder from Backstage.

Note: this covers the main expected use cases with the plugin. For more information and options on customizing your Backstage deployment further, see our [Advanced API guide](./coder-api-advanced.md).

### Before you begin

Please ensure that you have the Coder plugin fully installed before proceeding. You can find instructions for getting up and running in [our main README](../../README.md).

### Important hooks for using the Coder API

The Coder plugin exposes three (soon to be four) main hooks for accessing Coder plugin state and making queries/mutations

- `useCoderAuth` - Provides methods and state values for interacting with your current Coder auth session from within Backstage.

```tsx
function SessionTokenInputForm() {
const [sessionTokenDraft, setSessionTokenDraft] = useState('');
const coderAuth = useCoderAuth();

const onSubmit = (event: FormEvent<HTMLFormElement>) => {
coderAuth.registerNewToken(sessionToken);
setSessionTokenDraft('');
};

return (
<form onSubmit={onSubmit}>
<MainFormContent />
</form>
);
}
```

- `useCoderQuery` - Makes it simple to query data from the Coder API and share it throughout your application.

```tsx
function WorkspacesList() {
// Return type matches the return type of React Query's useQuerys
const workspacesQuery = useCoderQuery({
queryKey: ['workspaces'],
queryFn: ({ coderApi }) => coderApi.getWorkspaces({ limit: 5 }),
});
}
```

- `useCoderMutation` (coming soon) - Makes it simple to mutate data via the Coder API.
- `useCoderApi` - Exposes an object with all available Coder API methods. None of the state in this object is tied to React render logic - it can be treated as a "function bucket". Once `useCoderMutation` is available, the main value of this hook will be as an escape hatch in the rare situations where `useCoderQuery` and `useCoderMutation` don't meet your needs. Under the hood, both `useCoderQuery` and `useCoderMutation` receive their `coderApi` context value from this hook.

```tsx
function HealthCheckComponent() {
const coderApi = useCoderApi();

const processWorkspaces = async () => {
const workspacesResponse = await coderApi.getWorkspaces({
limit: 10,
});

processHealthChecks(workspacesResponse.workspaces);
};
}
```

Internally, the Coder plugin uses [React Query/TanStack Query v4](https://tanstack.com/query/v4/docs/framework/react/overview). In fact, `useCoderQuery` and `useCoderMutation` are simply wrappers over `useQuery` and `useMutation`. Both simplify the process of wiring up the hooks' various properties to the Coder auth, while exposing a more convenient way of accessing the Coder API object.

If you ever need to coordinate queries and mutations, you can use `useQueryClient` from React Query - no custom plugin-specific hook needed.

The bottom of this document has examples of both queries and mutations.

### Grouping queries with the Coder query key prefix
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we move this up so its closer to the hook section


The plugin exposes a `CODER_QUERY_KEY_PREFIX` constant that you can use to group all Coder queries. `useCoderQuery` automatically injects this value into all its `queryKey` arrays. However, if you need to escape out with `useQuery`, you can import the constant and manually include it as the first value of your query key.

In addition, all official Coder plugin components use this prefix internally.

```tsx
// All grouped queries can be invalidated at once from the query client
const queryClient = useQueryClient();
const invalidateAllCoderQueries = () => {
queryClient.invalidateQuery({
queryKey: [CODER_QUERY_KEY_PREFIX],
});
};

// The prefix is only needed when NOT using useCoderQuery
const customQuery = useQuery({
queryKey: [CODER_QUERY_KEY_PREFIX, 'workspaces'],
queryFn: () => {
// Your custom API logic
},
});

// When the user unlinks their session token, all queries grouped under
// CODER_QUERY_KEY_PREFIX are vacated from the active query cache
function LogOutButton() {
const { unlinkToken } = useCoderAuth();

return (
<button type="button" onClick={unlinkToken}>
Unlink Coder account
</button>
);
}
```

## Recommendations for accessing the API

1. If querying data, prefer `useCoderQuery`. It automatically wires up all auth logic to React Query (which includes pausing queries if the user is not authenticated). It also lets you access the Coder API via its query function. `useQuery` works as an escape hatch if `useCoderQuery` doesn't meet your needs, but it requires more work to wire up correctly.
2. If mutating data, you will need to call `useMutation`, `useQueryClient`, and `useCoderApi` in tandem\*.

We highly recommend **not** fetching with `useState` + `useEffect`, or with `useAsync`. Both face performance issues when trying to share state. See [ui.dev](https://www.ui.dev/)'s wonderful [_The Story of React Query_ video](https://www.youtube.com/watch?v=OrliU0e09io) for more info on some of the problems they face.

\* `useCoderMutation` can be used instead of all three once that hook is available.

### Comparing query caching strategies

| | `useAsync` | `useQuery` | `useCoderQuery` |
| ------------------------------------------------------------------ | ---------- | ---------- | --------------- |
| Automatically handles race conditions | ✅ | ✅ | ✅ |
| Can retain state after component unmounts | 🚫 | ✅ | ✅ |
| Easy, on-command query invalidation | 🚫 | ✅ | ✅ |
| Automatic retry logic when a query fails | 🚫 | ✅ | ✅ |
| Less need to fight dependency arrays | 🚫 | ✅ | ✅ |
| Easy to share state for sibling components | 🚫 | ✅ | ✅ |
| Pre-wired to Coder auth logic | 🚫 | 🚫 | ✅ |
| Can consume Coder API directly from query function | 🚫 | 🚫 | ✅ |
| Automatically groups Coder-related queries by prefixing query keys | 🚫 | 🚫 | ✅ |

## Authentication

All API calls to **any** of the Coder API functions will fail if you have not authenticated yet. Authentication can be handled via any of the official Coder components that can be imported via the plugin. However, if there are no Coder components on the screen, the `CoderProvider` component will automatically\* inject a fallback auth button for letting the user add their auth info.

https://github.com/coder/backstage-plugins/assets/28937484/0ece4410-36fc-4b32-9223-66f35953eeab

Once the user has been authenticated, all Coder API functions will become available. When the user unlinks their auth token (effectively logging out), all cached queries that start with `CODER_QUERY_KEY_PREFIX` will automatically be vacated.

\* This behavior can be disabled. Please see our [advanced API guide](./coder-api-advanced.md) for more information.

## Component examples

Here are some full code examples showcasing patterns you can bring into your own codebase.

Note: To keep the examples simple, none of them contain any CSS styling or MUI components.

### Displaying recent audit logs

```tsx
import React from 'react';
import { useCoderQuery } from '@coder/backstage-plugin-coder';

function RecentAuditLogsList() {
const auditLogsQuery = useCoderQuery({
queryKey: ['audits', 'logs'],
queryFn: ({ coderApi }) => coderApi.getAuditLogs({ limit: 10 }),
});

return (
<>
{auditLogsQuery.isLoading && <p>Loading&hellip;</p>}
{auditLogsQuery.error instanceof Error && (
<p>Encountered the following error: {auditLogsQuery.error.message}</p>
)}

{auditLogsQuery.data !== undefined && (
<ul>
{auditLogsQuery.data.audit_logs.map(log => (
<li key={log.id}>{log.description}</li>
))}
</ul>
)}
</>
);
}
```

## Creating a new workspace

Note: this example showcases how to perform mutations with `useMutation`. The example will be updated once `useCoderMutation` is available.

```tsx
import React, { type FormEvent, useState } from 'react';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import {
type CreateWorkspaceRequest,
CODER_QUERY_KEY_PREFIX,
useCoderQuery,
useCoderApi,
} from '@coder/backstage-plugin-coder';

export function WorkspaceCreationForm() {
const [newWorkspaceName, setNewWorkspaceName] = useState('');
const coderApi = useCoderSdk();
const queryClient = useQueryClient();

const currentUserQuery = useCoderQuery({
queryKey: ['currentUser'],
queryFn: coderApi.getAuthenticatedUser,
});

const workspacesQuery = useCoderQuery({
queryKey: ['workspaces'],
queryFn: coderApi.getWorkspaces,
});

const createWorkspaceMutation = useMutation({
mutationFn: (payload: CreateWorkspaceRequest) => {
if (currentUserQuery.data === undefined) {
throw new Error(
'Cannot create workspace without data for current user',
);
}

const { organization_ids, id: userId } = currentUserQuery.data;
return coderApi.createWorkspace(organization_ids[0], userId, payload);
},
});

const onSubmit = async (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();

// If the mutation fails, useMutation will expose the error in the UI via
// its own exposed properties
await createWorkspaceMutation.mutateAsync({
name: newWorkspaceName,
});

setNewWorkspaceName('');
queryClient.invalidateQueries({
queryKey: [CODER_QUERY_KEY_PREFIX, 'workspaces'],
});
};

return (
<>
{createWorkspaceMutation.isSuccess && (
<p>
Workspace {createWorkspaceMutation.data.name} created successfully!
</p>
)}

<form onSubmit={onSubmit}>
<fieldset>
<legend>Required fields</legend>

<label>
Workspace name
<input
type="text"
value={newWorkspaceName}
onChange={event => setNewWorkspaceName(event.target.value)}
/>
</label>
</fieldset>

<button type="submit">Create workspace</button>
</form>
</>
);
}
```
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading