Skip to content
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

Redirect user when loading class summary results in 403 #12755

Merged
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
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
13 changes: 10 additions & 3 deletions kolibri/plugins/coach/assets/src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,9 +139,16 @@ class CoachToolsModule extends KolibriApp {
}

if (promises.length > 0) {
Promise.all(promises).then(next, error => {
this.store.dispatch('handleApiError', { error });
});
Promise.all(promises)
.catch(error => {
this.store.dispatch('handleApiError', { error });
})
.catch(() => {
// We catch here because `handleApiError` throws the error back again, in this case,
// we just want things to keep moving so that the AuthMessage shows as expected
next();
Copy link
Member

Choose a reason for hiding this comment

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

This may end up calling next twice, because we catch and then then - but I don't think this matters.

Copy link
Member Author

Choose a reason for hiding this comment

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

I think calling next() basically stops any further code execution so if one promise rejects at any point, we'll have next called and will be redirected altogether.

That said, I did look back at Promise.all's docs and see that in this case, the then would be called if next wasn't called because of the chaining order.

If the then comes first in the chain series, then it would not be called if the catch blocks ran because Promise.all handles any rejection immediately. But, if you catch errors and chain a then to the catch though, the then is run.

Might be misinterpreting a bit there but seemed to be the case when I played around a bit w/ Promises in the terminal

In any case, I think you're right that it won't have any effect as-is.

})
.then(next);
} else {
next();
}
Expand Down
8 changes: 5 additions & 3 deletions kolibri/plugins/coach/assets/src/modules/pluginModule.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,9 +131,11 @@ export default {
return Promise.all([
// Make sure we load any class list data, so that we know
// whether this user has access to multiple classes or not.
store
.dispatch('classSummary/loadClassSummary', classId)
.then(summary => store.dispatch('setClassList', summary.facility_id)),
store.dispatch('classSummary/loadClassSummary', classId).then(summary => {
if (summary?.facility_id) {
store.dispatch('setClassList', summary?.facility_id);
}
}),
store.dispatch('coachNotifications/fetchNotificationsForClass', classId),
]).catch(error => {
store.dispatch('handleApiError', { error, reloadOnReconnect: true });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -268,9 +268,14 @@
},
// @public
setError(error) {
this.$store.dispatch('handleApiError', { error });
this.loading = false;
this.$store.dispatch('notLoading');
try {
this.$store.dispatch('handleApiError', { error });
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 only put the handleApiError call in here, so that the other two lines get executed?

Copy link
Member Author

Choose a reason for hiding this comment

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

Updated here, thanks Richard

this.loading = false;
this.$store.dispatch('notLoading');
} catch (e) {
// nothing to do here, just catching the error to avoid
// unhandled errors in the dispatch to handleApiError
}
},
setCurrentAction(action) {
if (action === 'EDIT_DETAILS') {
Expand Down
9 changes: 7 additions & 2 deletions packages/kolibri/components/AuthMessage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@
:text="linkText"
:href="signInLink"
appearance="basic-link"
data-test="signinlink"
/>
</p>
<p v-else>
<KRouterLink
<KExternalLink
:text="$tr('goBackToHomeAction')"
:to="{ path: '/' }"
:href="rootUrl"
appearance="basic-link"
data-test="gohomelink"
/>
</p>
</div>
Expand Down Expand Up @@ -68,6 +70,9 @@
},
},
computed: {
rootUrl() {
return urls['kolibri:core:redirect_user']();
},
detailsText() {
return this.details || this.$tr(this.authorizedRole);
},
Expand Down
21 changes: 15 additions & 6 deletions packages/kolibri/components/__tests__/auth-message.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { stubWindowLocation } from 'testUtils'; // eslint-disable-line
import useUser, { useUserMock } from 'kolibri/composables/useUser'; // eslint-disable-line
import AuthMessage from '../AuthMessage';

jest.mock('kolibri/urls', () => ({}));
jest.mock('kolibri/urls');
jest.mock('kolibri/composables/useUser');

const localVue = createLocalVue();
Expand Down Expand Up @@ -94,7 +94,7 @@ describe('auth message component', () => {

it('shows correct link text if there is a user plugin', () => {
const wrapper = makeWrapper();
const link = wrapper.find('kexternallink-stub');
const link = wrapper.find('[data-test=signinlink]');
expect(link.attributes()).toMatchObject({
href: 'http://localhost:8000/en/auth/#/signin?next=http%3A%2F%2Flocalhost%3A8000%2F%23%2Ftest_url',
text: 'Sign in to Kolibri',
Expand All @@ -104,7 +104,7 @@ describe('auth message component', () => {
it('if the next param is in URL, it is what is used in the sign-in page link', () => {
window.location.href = 'http://localhost:8000/#/some_other_url';
const wrapper = makeWrapper();
const link = wrapper.find('kexternallink-stub');
const link = wrapper.find('[data-test=signinlink]');
expect(link.attributes()).toMatchObject({
href: 'http://localhost:8000/en/auth/#/signin?next=http%3A%2F%2Flocalhost%3A8000%2F%23%2Fsome_other_url',
text: 'Sign in to Kolibri',
Expand All @@ -113,8 +113,17 @@ describe('auth message component', () => {
});

it('shows correct link text if there is not a user plugin', () => {
const wrapper = makeWrapper();
const link = wrapper.find('kexternallink-stub');
// linkText checks to see if `userAuthPluginUrl` is truthy and it's either a
// function or undefined and if there is no user plugin, then it needs to be
// falsy for this test case
const wrapper = makeWrapper({
computed: {
userAuthPluginUrl() {
return false;
},
},
});
const link = wrapper.find('[data-test=signinlink]');
expect(link.attributes()).toMatchObject({
href: '/',
text: 'Go to home page',
Expand All @@ -124,6 +133,6 @@ describe('auth message component', () => {
it('does not show a link if the user is logged in', () => {
useUser.mockImplementation(() => useUserMock({ isUserLoggedIn: true }));
const wrapper = makeWrapper();
expect(wrapper.find('kexternallink-stub').exists()).toBe(false);
expect(wrapper.find('[data-test=signinlink]').exists()).toBe(false);
});
});