Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

refactor: improve test isolation for Axios API logic#13125

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
Parkreiner merged 9 commits intomainfrommes/axios-instance-isolation
May 12, 2024

Conversation

Parkreiner
Copy link
Member

@ParkreinerParkreiner commentedMay 1, 2024
edited
Loading

Closes#13013

Summary

This PR moves all the Axios-based API functionality behind aClient class. This lets you can instantiate a brand new client with all Axios functionality scoped to that specific instance vianew Client(). For convenience, the file also exports a global-ish client calledclient (lowercase)

I knew this PR was going to be huge ahead of time, so I deliberately restricted myself to moving things around only. There should be zero changes in overall functionality.

Changes made

  • Created newApi andClient classes inside ofsrc/api/api.ts, and turned all Axios methods into methods forApi

Why these changes?

  • We are currently trying to bring "SDK-ish" functionality to the Backstage plugin, letting users make any API calls they want with full type-safety.
    • One of the limitations, though, is that even though the Coder codebase is not using the global Axios instance, it's still basically a global singleton value
    • One of the core pieces of the Backstage plugin is aCoderClient class that is in charge of caroling the Backstage APIs, and will eventually need to expose the Coder "SDK". As part of this functionality, it also needs to patch Backstage-specific information via the Axios interceptors, with some of this info being dynamic per request
    • Some of the Backstage plugin test cases instantiate a new class per case. This works fine without bringing in the Coder functionality, but if we were to bring in the Coder code unchanged, every single test would need to go through a set of manual cleanup steps to ensure no interceptor collisions. Otherwise, you get a bunch of test flakes, because all theCoderClients would be going through a singleton
    • These are not challenges that can simply be solved with thelet keyword; the only real solution is to have something "factory-ish". While you could make a single function that sets things up via closure, there's so much functionality that a class felt like a better organization tool
    • Edit (2024-05-07): We decided to vendor things for Backstage (basically copy-paste the files as a stopgap), but all these changes are still very helpful for the Backstage tests
  • While core isn't making use of the benefits of this new isolation just yet, this PR will make it significantly easier to spin up arbitrary clients per test case, and get more sophisticated with interceptor logic

Cliff notes of what changes

TheClient class implements this API:

interfaceClientApi{api:Api;getCsrfToken:()=>string;setSessionToken:(token:string)=>void;setHost:(host:string|undefined)=>void;getAxiosInstance:()=>AxiosInstance;}

For convenience, theapi file exports a defaultclient instance:

exportconstclient=newClient();

If you were doing imports like this for the Axios functionality:

import*asapifrom"api/api";constworkspaces=api.getWorkspaces(/* args */);

Not much changes – the import now looks like this:

import{client}from"api/api".;constworkspaces=client.api.getWorkspaces(/* args */);

This also means that if you were relying on* as api imports to handle Jest mocking, you now have a more straightforward option:

jest.spyOn(client.api,"postWorkspaceBuild").mockResolvedValueOnce(MockWorkspaceBuild);

And while this is all class-based, steps have been taken to make sure that the API functions can freely be passed around any number of React components without losing theirthis context:

exportfunctionworkspaces(config:WorkspacesRequest={}){const{ q, limit}=config;return{queryKey:workspacesKey(config),queryFn:()=>client.api.getWorkspaces({ q, limit}),}asconstsatisfiesQueryOptions<WorkspacesResponse>;}

@ParkreinerParkreiner self-assigned thisMay 1, 2024
@ParkreinerParkreiner changed the titlerefactor: update codebase to use Client class for better test isolationrefactor: consolidate Axios API logic into Client class for better test isolationMay 2, 2024
@Parkreiner
Copy link
MemberAuthor

Putting off prepping this PR for review until I have#13130 done

Copy link
MemberAuthor

@ParkreinerParkreinerMay 7, 2024
edited
Loading

Choose a reason for hiding this comment

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

To give a high-level overview of the file now, there's basically four categories of things in here now:

  • Api class – Defines all the Axios-based API calls in one spot
  • Client class – Top-level wrapper for client-ish functionality; contains anApi instance
  • Ad-hoc helper functions – these weren't moved into either class because, while they help with the API calls, they don't actually have any reliance on Axios. These are all stateless functions
  • Additional types – I wish I could move these closer to the methods where they're used, but that's one of the tradeoffs with classes – you can't put type definitions between methods

I was very deliberate about not changing any of the functionality for the methods/functions, aside from changing variable references. You can assume all the overall functionality should be unchanged (especially if the unit/E2E tests are still passing)

Honestly, though, with how many changes this file has, it might be worthlooking at it without the diffs

Comment on lines 1861 to 1867
interface ClientApi {
api: Api;
getCsrfToken: () => string;
setSessionToken: (token: string) => void;
setHost: (host: string | undefined) => void;
getAxiosInstance: () => AxiosInstance;
}
Copy link
MemberAuthor

@ParkreinerParkreinerMay 7, 2024
edited
Loading

Choose a reason for hiding this comment

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

Just have these defined outsideClient so that I can ensure it explicitly implements the properties (and has type errors if it doesn't)

@ParkreinerParkreiner requested a review fromaslilacMay 7, 2024 20:20
@ParkreinerParkreiner marked this pull request as ready for reviewMay 7, 2024 20:26
? document.head.querySelector('meta[property="csrf-token"]')
: null;

interface ClientApi {
Copy link
Member

Choose a reason for hiding this comment

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

What if instead of having anapi property, this wasinterface ClientApi extends Api? Then we don't need the.api everywhere

Copy link
MemberAuthor

Choose a reason for hiding this comment

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

I considered that – my main concern was organization/discovery, especially with all the functions the file has

At least with the current approach, you know that everything inapi is an API function, while everything else is something directly relevant to manipulating the client itself

Copy link
MemberAuthor

Choose a reason for hiding this comment

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

This feels like a lot of stuff to have in a completely flat hierarchy
Screenshot 2024-05-08 at 3 27 44 PM

Copy link
Member

Choose a reason for hiding this comment

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

yes, but the difference between the "flattened" list and the.api list as it is is only 4 functions, and they're 4 functions you hardly ever want to call. it feels odd to me to hide all of stuff youactually want the deepest in the hierarchy, and there are plenty of place's in the go bits of the codebase where we flatten things for exactly this reason.

Copy link
MemberAuthor

@ParkreinerParkreinerMay 8, 2024
edited
Loading

Choose a reason for hiding this comment

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

Yeah, I can see that. If we're trying to optimize for the most common use case, then (1) the switch reduces the amount of typing the user needs to do, and (2) it reduces the emphasis placed on methods that are effectively escape hatches, andshould probably be a little more hidden/hard to find

You changed my mind – I'll get this updated tomorrow morning

aslilac reacted with heart emoji
};
}

export const client = new Client();
Copy link
Member

Choose a reason for hiding this comment

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

Just a thought: if we named thisAPI we could reduce the diff in a bunch of places. Change theimport, but the code stays the same.

@ParkreinerParkreiner changed the titlerefactor: consolidate Axios API logic into Client class for better test isolationrefactor: consolidate Axios API logic into client class for better test isolationMay 9, 2024
@ParkreinerParkreiner changed the titlerefactor: consolidate Axios API logic into client class for better test isolationrefactor: improve test isolation for Axios API logicMay 9, 2024
@ParkreinerParkreiner requested a review fromaslilacMay 9, 2024 18:15
@Parkreiner
Copy link
MemberAuthor

@aslilac Updated the code to flatten things out. Nothing else should've changed, aside from me updatinggetAppearance to accommodate your banner PR from the other day

Also put some comments in theapi.ts file about why arrow functions are needed

Copy link
Member

@aslilacaslilac left a comment

Choose a reason for hiding this comment

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

I quite dislike the one file that has global constants named bothapi andAPI, but I don't really have a great suggestion for fixing that given the need to usejest.mock in there. Otherwise, I like it!

@@ -26,6 +26,8 @@ import type { MonacoEditorProps } from "./MonacoEditor";
import { Language } from "./PublishTemplateVersionDialog";
import TemplateVersionEditorPage from "./TemplateVersionEditorPage";

const { API } = api;
Copy link
Member

Choose a reason for hiding this comment

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

oh no

Copy link
MemberAuthor

@ParkreinerParkreinerMay 9, 2024
edited
Loading

Choose a reason for hiding this comment

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

9DCE9091-D68D-4372-A932-963175A5FA70_1200x1200

Yeah, let me see if I can't rename some thing really quick to make things less confusing. I didn't think that part through after I consolidated the classes

aslilac reacted with heart emoji
@ParkreinerParkreinerenabled auto-merge (squash)May 12, 2024 18:57
@ParkreinerParkreiner merged commitf13b1c9 intomainMay 12, 2024
@ParkreinerParkreiner deleted the mes/axios-instance-isolation branchMay 12, 2024 19:05
@github-actionsgithub-actionsbot locked and limited conversation to collaboratorsMay 12, 2024
Sign up for freeto subscribe to this conversation on GitHub. Already have an account?Sign in.
Reviewers

@aslilacaslilacaslilac approved these changes

Assignees

@ParkreinerParkreiner

Labels
None yet
Projects
None yet
Milestone
No milestone
Development

Successfully merging this pull request may close these issues.

Stop using global Axios instance for API calls
2 participants
@Parkreiner@aslilac

[8]ページ先頭

©2009-2025 Movatter.jp