-
Notifications
You must be signed in to change notification settings - Fork 5
refactor(Coder plugin): update workspace queries to use updated API endpoint definitions #126
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
Changes from all commits
67cb4b3
e53d127
f26184f
b54324b
7daf597
86b5acc
5d14d5a
afd0203
1e16c82
d786e34
063ecf2
1d1ab3c
58566c8
cd5fef6
abfd949
26ae96d
5aabe86
425d50f
04f0f3e
111df43
1575f8e
dc6e75c
fd8b3cb
5af006c
644e632
a667b48
cca343d
aac5a0c
32fb344
170d451
9f548a3
340399e
5e6d812
b8affbd
0a306f5
24f1c29
e2e2034
09b0c48
7b534cc
7dc8b24
72edd92
8d3ad60
af0cae8
fb10624
239661d
cc4e4e7
56bcbbd
907f78e
94cd97a
648c4a4
08d38c6
c8cbba7
9f1c63e
c7fb74e
549e12e
4a7fa14
9c1dc25
c91250d
47a5a68
19242ee
ec58a24
07984be
85a0ca8
653221e
052dfef
3ad5186
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -200,37 +200,39 @@ export class CoderClient implements CoderClientApi { | |
request: WorkspacesRequest, | ||
config: CoderWorkspacesConfig, | ||
): Promise<WorkspacesResponse> => { | ||
const { workspaces } = await baseSdk.getWorkspaces(request); | ||
const paramResults = await Promise.allSettled( | ||
workspaces.map(ws => | ||
this.sdk.getWorkspaceBuildParameters(ws.latest_build.id), | ||
), | ||
if (config.repoUrl === undefined) { | ||
return { workspaces: [], count: 0 }; | ||
} | ||
|
||
// Have to store value here so that type information doesn't degrade | ||
// back to (string | undefined) inside the .map callback | ||
const stringUrl = config.repoUrl; | ||
const responses = await Promise.allSettled( | ||
config.repoUrlParamKeys.map(key => { | ||
const patchedRequest = { | ||
...request, | ||
q: appendParamToQuery(request.q, key, stringUrl), | ||
}; | ||
|
||
return baseSdk.getWorkspaces(patchedRequest); | ||
}), | ||
Comment on lines
+210
to
+218
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was one of the limitations I ran into with the new API endpoint: you can't make a single request for every repo key that the user adds to their workspaces config. If your config has the repo URL keys owner:me param:"repo_url=url.com" param:"alt_repo_url=url.com" Won't work, because it will search for workspaces that have both keys, even though realistically, a workspace will only ever have one of them There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's not a huge deal – the previous logic was using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yea, this is just an unfortunate consequence of our search filtering. We have to make opinions, and cannot support both union and intersection type searches easily at the moment. Our search filter is very limited at present, and there is no effort to improve it in it's current state. It's just too much of a headache is the answer atm. Another idea I had awhile back worth peaking at: coder/coder#12706 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ^^ What steven said
Anyway, all this to say I think what you have is great! |
||
); | ||
|
||
const matchedWorkspaces: Workspace[] = []; | ||
for (const [index, res] of paramResults.entries()) { | ||
const uniqueWorkspaces = new Map<string, Workspace>(); | ||
for (const res of responses) { | ||
if (res.status === 'rejected') { | ||
continue; | ||
} | ||
|
||
for (const param of res.value) { | ||
const include = | ||
config.repoUrlParamKeys.includes(param.name) && | ||
param.value === config.repoUrl; | ||
|
||
if (include) { | ||
// Doing type assertion just in case noUncheckedIndexedAccess | ||
// compiler setting ever gets turned on; this shouldn't ever break, | ||
// but it's technically not type-safe | ||
matchedWorkspaces.push(workspaces[index] as Workspace); | ||
break; | ||
} | ||
for (const workspace of res.value.workspaces) { | ||
uniqueWorkspaces.set(workspace.id, workspace); | ||
} | ||
} | ||
|
||
const serialized = [...uniqueWorkspaces.values()]; | ||
return { | ||
workspaces: matchedWorkspaces, | ||
count: matchedWorkspaces.length, | ||
workspaces: serialized, | ||
count: serialized.length, | ||
}; | ||
}; | ||
|
||
|
@@ -352,6 +354,27 @@ export class CoderClient implements CoderClientApi { | |
}; | ||
} | ||
|
||
function appendParamToQuery( | ||
query: string | undefined, | ||
key: string, | ||
value: string, | ||
): string { | ||
if (!key || !value) { | ||
return ''; | ||
} | ||
|
||
const keyValuePair = `param:"${key}=${value}"`; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it worth check that these don't contain empty strings? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, good call. Let me fix that really quick |
||
if (!query) { | ||
return keyValuePair; | ||
} | ||
|
||
if (query.includes(keyValuePair)) { | ||
return query; | ||
} | ||
|
||
return `${query} ${keyValuePair}`; | ||
} | ||
|
||
function assertValidUser(value: unknown): asserts value is User { | ||
if (value === null || typeof value !== 'object') { | ||
throw new Error('Returned JSON value is not an object'); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -73,7 +73,7 @@ describe(`${CoderWorkspacesCard.name}`, () => { | |
|
||
await user.tripleClick(searchbox); | ||
await user.keyboard('[Backspace]'); | ||
await user.keyboard('I can do it - I can do it nine times'); | ||
await user.keyboard('I-can-do-it-I-can-do-it-nine-times'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Removed the spaces (1) to account for the mock server logic better, and (2) to account for the fact that the actual Coder API doesn't expect spaces like this, either |
||
|
||
await waitFor(() => { | ||
// getAllByRole will throw if there isn't at least one node matched | ||
|
@@ -153,12 +153,12 @@ describe(`${CoderWorkspacesCard.name}`, () => { | |
}); | ||
|
||
/** | ||
* 2024-03-28 - MES - This is a test case to account for a previous | ||
* limitation around querying workspaces by repo URL. | ||
* For performance reasons, the queries for getting workspaces by repo are | ||
* disabled when the query string is empty. | ||
* | ||
* This limitation no longer exists, so this test should be removed once the | ||
* rest of the codebase is updated to support the new API endpoint for | ||
* searching by build parameter | ||
* Even with the API endpoint for searching workspaces by build parameter, | ||
* you still have to shoot off a bunch of requests just to find everything | ||
* that could possibly match your Backstage deployment's config options. | ||
*/ | ||
it('Will not show any workspaces at all when the query text is empty', async () => { | ||
await renderWorkspacesCard({ readEntityData: true }); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
are
key
andstringUrl
guaranteed to be strings as opposed to null or undefined?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Luckily, they're both guaranteed to be defined
With
key
, we're getting the values through.map
. That doesn't guarantee that we'll have a non-empty array, but it does mean that the mapper callback will only be called if we have a defined element to feed into it. That's one of the benefits of the array methods andfor/of
over traditionali
loops – they can guarantee no null-ish values, because they're going through JS's iterator protocolAnd as for
stringUrl
, it's wonky, but that's also guaranteed to be of typestring
. If we had this:config.repoUrl
would be typestring | undefined
. But TS type narrowing helps get around that:There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see your comment above now. Thank you for the explanation!