-
Notifications
You must be signed in to change notification settings - Fork 32
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
feat: auto setting active linked enterprise #1153
Conversation
66066a3
to
395ae17
Compare
395ae17
to
a06d361
Compare
Codecov ReportAttention:
Additional details and impacted files@@ Coverage Diff @@
## master #1153 +/- ##
==========================================
+ Coverage 85.33% 85.34% +0.01%
==========================================
Files 495 496 +1
Lines 10652 10694 +42
Branches 2232 2239 +7
==========================================
+ Hits 9090 9127 +37
- Misses 1520 1525 +5
Partials 42 42 ☔ View full report in Codecov by Sentry. |
ec355cf
to
70423df
Compare
}) => { | ||
const [isLoading, setIsLoading] = useState(false); | ||
|
||
const updateActiveEnterpriseAndRefreshJWT = useCallback(async (username, currentEnterpriseId) => { |
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.
nit: likely don't necessarily need to pass currentEnterpriseId
. This hook function itself gets enterpriseId
as an argument, and it'd be available within updateActiveEnterpriseAndRefreshJWT
without it as an explicit function argument.
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 didn't realize useCallback's get access to arguments passed to the hook!
enterpriseId, | ||
user, | ||
}) => { | ||
const [isLoading, setIsLoading] = useState(false); |
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.
[curious, non-blocking] Might consider what scope is involved in refactoring this hook a bit to utilize @tanstack/react-query
? What benefits might we get from React Query in relation to the scope? If the benefits are not significant in this particular use case, OK to not utilize React Query here.
E.g., removes boilerplate around needing to manage our own loading states, etc.
await LmsApiService.getActiveLinkedEnterprise(username).then(async (linkedEnterpriseId) => { | ||
if (linkedEnterpriseId !== currentEnterpriseId) { | ||
await LmsApiService.updateUserActiveEnterprise(currentEnterpriseId); | ||
await LmsApiService.loginRefresh(); |
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.
[curious] I'm wondering if we need the loginRefresh
call here anymore now that we're no longer making assumptions of JWT role ordering. That is, I believe we were calling loginRefresh
because our prior assumption that the JWT roles would be re-ordered.
Given that this hook no longer cares about the JWT roles, could we nix the call to login_refresh
?
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.
oh yeah, that's a great point! It's all done properly now 😄
src/data/services/LmsApiService.js
Outdated
} | ||
|
||
static getActiveLinkedEnterprise(username) { | ||
return this.fetchEnterpriseLearnerData({ username }).then((response) => { |
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.
nit: might recommend using async/await
over .then
chaining.
src/data/services/LmsApiService.js
Outdated
return this.fetchEnterpriseLearnerData({ username }).then((response) => { | ||
const { data } = response; | ||
const results = data?.results; | ||
for (let i = 0; i < results.length; i++) { |
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.
[suggestion] might be able to trim/simplify this logic down a bit by relying on .find(...)
instead of explicitly looping through each record to find the active=true
?
static async getActiveLinkedEnterprise(username) {
const response = await this.fetchEnterpriseLearnerData({ username });
const transformedResponse = camelCaseObject(response.data);
const enterprisesForLearner = transformedResponse.results;
const activeLinkedEnterprise = enterprisesForLearner.find(enterprise => enterprise.active);
if (!activeLinkedEnterprise) {
logError(`${username} does not have any active linked enterprise customers`);
}
}
src/data/services/LmsApiService.js
Outdated
static getActiveLinkedEnterprise(username) { | ||
return this.fetchEnterpriseLearnerData({ username }).then((response) => { | ||
const { data } = response; | ||
const results = data?.results; |
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.
[curious] When would data
be undefined? Do we need to use the optional chaining you have here? If we do, when data
is undefined, results
would also be undefined and would subsequently throw a JS error on line 431 when trying to access results.length
.
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 think I was mostly just following the pattern in the file- but I agree I can't think of a reason why it would be undefined.
src/data/services/LmsApiService.js
Outdated
return results[i].uuid; | ||
} | ||
} | ||
return ''; |
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.
If there is no active enterprise customer (super edge case, one should always exist), likely should return undefined
or null
instead of empty string.
4f11d11
to
6e1fe54
Compare
return { | ||
isLoading, | ||
}; |
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.
nit: generally a good idea to return everything from useQuery
's output in useUpdateActiveEnterpriseForUser
(as opposed to cherry-picking just isLoading
). That said, if you also adopt useMutation
here per the above suggestion (non-blocking), then this comment isn't as relevant (since there'd be 2 loading states, 2 error states, etc.).
}, | ||
}); | ||
|
||
if (error) { logError(`Could not set active enterprise for learner, failed with error: ${logError}`); } |
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.
Would recommend using the onError
callback function passed to useQuery
for any error logging as opposed to handling your own conditional here.
const useUpdateActiveEnterpriseForUser = ({ enterpriseId, user }) => { | ||
const { username } = user; | ||
const { isLoading, error } = useQuery({ | ||
queryKey: ['updateUsersActiveEnterprise'], |
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.
Treat queryKey
like dependency arrays. Any data that is utilized by queryFn
should likely be part of the query key.
For example, if this query is specific to the given username
, that username
should be part of the query key. Rationale: the API call for a specific username is stored in the query cache based on the queryKey
; without the username
in the query key, useQuery
will think it has the API data still cached even if that username has since changed.
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.
makes sense!
const useUpdateActiveEnterpriseForUser = ({ enterpriseId, user }) => { | ||
const { username } = user; | ||
const { isLoading, error } = useQuery({ | ||
queryKey: ['updateUsersActiveEnterprise'], |
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.
nit: consider creating a query key factory similar to learnerCreditManagementQueryKeys
. See https://tkdodo.eu/blog/effective-react-query-keys#use-query-key-factories.
That said, totally fine to defer on this feedback for if/when we take on the work to migrate existing API calls to useQuery
where query keys will become more important across the application.
const { isLoading, error } = useQuery({ | ||
queryKey: ['updateUsersActiveEnterprise'], | ||
queryFn: async () => { | ||
await LmsApiService.getActiveLinkedEnterprise(username).then(async (linkedEnterprise) => { |
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.
Typically, you wouldn't use .then
chaining when using async
and await
, e.g.:
const activeLinkedEnterprise = await LmsApiService.getActiveLinkedEnterprise(username);
if (activeLinkedEnterprise.uuid !== enterpriseId) { ... }
queryFn: async () => { | ||
await LmsApiService.getActiveLinkedEnterprise(username).then(async (linkedEnterprise) => { | ||
if (linkedEnterprise.uuid !== enterpriseId) { | ||
await LmsApiService.updateUserActiveEnterprise(enterpriseId); |
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.
[suggestion] Generally, POST
API calls are done with useMutation
; useQuery
is intended for reading data, not updating. While what you have should work, you might consider making use of useQuery
for the GET request and useMutation
for the POST request.
Main rationale is to avoid side effects that change server data within a useQuery
(kind of similar to avoiding side effects in a GET API endpoint's implementation).
Something along the lines of the following (consider it pseudocode):
const useUpdateActiveEnterpriseForUser = ({ enterpriseId, user }) => {
// Sets up POST call to update active enterprise.
const { mutateAsync, isLoading: isUpdatingActiveEnterprise } = useMutation({
mutationFn: (enterpriseId) => await LmsApiService.updateUserActiveEnterprise(enterpriseId);
onError: (error) => {
logError(`Could not set active enterprise for username ${username}, failed with error: ${error}`);
},
});
// Determines active enterprise customer for authenticated user
const { username } = user;
const {
data: activeEnterpriseCustomer,
isLoading: isLoadingActiveEnterprise,
...restActiveLinkedCustomer
} = useQuery({
queryKey: ['enterprise', 'activeLinkedEnterpriseCustomer', username],
queryFn: async () => await LmsApiService.getActiveLinkedEnterprise(username),
onSuccess: (activeLinkedEnterprise) => {
// Successfully retrieved learners active linked enterprise; if it differs from the viewed enterprise, make the viewed enterprise active.
if (activeLinkedEnterprise.uuid !== enterpriseId) {
await mutateAsync(enterpriseId);
}
},
onError: (error) => {
logError(`Could not fetch active enterprises for username ${username}, failed with error: ${error}`);
},
});
return {
isLoading: isLoadingActiveEnterprise || isUpdatingActiveEnterprise,
};
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.
ahh I see, on successfully querying the active linked org, we use mutateAsync
.. ok ok
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.
we might have to tweak with the deprecation of the callbacks
a2faa79
to
ff2820a
Compare
ff2820a
to
80ebfb1
Compare
https://2u-internal.atlassian.net/browse/ENT-8038
For all changes
Only if submitting a visual change