Skip to content

Commit

Permalink
Merge pull request #2 from BabyElias/develop
Browse files Browse the repository at this point in the history
pr
  • Loading branch information
BabyElias authored Sep 7, 2024
2 parents 1505978 + 7470df5 commit cf689c1
Show file tree
Hide file tree
Showing 107 changed files with 1,658 additions and 1,061 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/notify_team_new_comment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
uses: slackapi/slack-github-action@v1.26.0
uses: slackapi/slack-github-action@v1.27.0
with:
payload: |
{
Expand Down
1 change: 1 addition & 0 deletions AUTHORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,4 @@ If you have contributed to Kolibri, feel free to add your name and Github accoun
| Mazen Oweiss | moweiss |
| Eshaan Aggarwal | EshaanAgg |
| Nikhil Sharma | ThEditor |
| - | BabyElias |
85 changes: 85 additions & 0 deletions kolibri/core/assets/src/composables/__mocks__/useSnackbar.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/**
* `useSnackbar` composable function mock.
*
* If default values are sufficient for tests,
* you only need call `jest.mock('<useSnackbar file path>')`
* at the top of a test file.
*
* If you need to override some default values for some tests,
* or if you need to inspect the state of the refs during tests,
* you can import a helper function `useSnackbarMock` that accepts
* an object with values to be overriden and use it together
* with `mockImplementation` as follows:
*
* ```
* // eslint-disable-next-line import/named
* import useSnackbar, { useSnackbarMock } from '<useSnackbar file path>';
*
* jest.mock('<useSnackbar file path>')
* describe('describe test', function () {
* let snackbar = { snackbarIsVisible: ref(false) }
*
* beforeAll(() => {
* useSnackbar.mockImplementation(() => useSnackbarMock(snackbar)
* })
*
* it('the test', () => {
* expect(get(snackbar.snackbarIsVisible)).toEqual(false);
* )
* })
* ```
*/
import { ref } from 'kolibri.lib.vueCompositionApi';
import { get, set } from '@vueuse/core';

const MOCK_DEFAULTS = {
snackbarIsVisible: ref(false),
snackbarOptions: ref({
text: '',
autoDismiss: true,
}),
};

export function useSnackbarMock(overrides = {}) {
const mocks = {
...MOCK_DEFAULTS,
...overrides,
};

const createSnackbar = (options = {}) => {
// reset
set(mocks.snackbarIsVisible, false);
set(mocks.snackbarOptions, {});

// set new options
set(mocks.snackbarIsVisible, true);

// options include text, autoDismiss, duration, actionText, actionCallback,
// hideCallback, bottomPosition
// if the options are a string, set it as the snackbar text
// and default autoDismiss to true
if (typeof options === 'string') {
set(mocks.snackbarOptions, { text: options, autoDismiss: true });
} else {
set(mocks.snackbarOptions, options);
}
};

const clearSnackbar = () => {
set(mocks.snackbarIsVisible, false);
set(mocks.snackbarOptions, {});
};

const setSnackbarText = text => {
set(mocks.snackbarOptions, { ...get(mocks.snackbarOptions), text });
};

return {
createSnackbar,
clearSnackbar,
setSnackbarText,
...mocks,
};
}

export default jest.fn(() => useSnackbarMock());
62 changes: 62 additions & 0 deletions kolibri/core/assets/src/composables/__mocks__/useTotalProgress.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/**
* `useTotalProgress` composable function mock.
*
* If default values are sufficient for tests,
* you only need call `jest.mock('<useTotalProgress file path>')`
* at the top of a test file.
*
* If you need to override some default values for some tests,
* or if you need to inspect the state of the refs during tests,
* you can import a helper function `useTotalProgressMock` that accepts
* an object with values to be overriden and use it together
* with `mockImplementation` as follows:
*
* ```
* // eslint-disable-next-line import/named
* import useTotalProgress, { useTotalProgressMock } from '<useTotalProgress file path>';
*
* jest.mock('<useTotalProgress file path>')
* describe('describe test', function () {
* let totalProgressMock = { totalProgress: ref(null) }
*
* beforeAll(() => {
* useTotalProgress.mockImplementation(() => useTotalProgressMock(totalProgressMock)
* })
*
* it('the test', () => {
* expect(get(totalProgressMock.totalProgress)).toEqual(null);
* )
* })
* ```
*/
import { ref, computed } from 'kolibri.lib.vueCompositionApi';
import { get, set } from '@vueuse/core';
import { MaxPointsPerContent } from '../../constants';

const MOCK_DEFAULTS = {
totalProgress: ref(null),
};

export function useTotalProgressMock(overrides = {}) {
const mocks = {
...MOCK_DEFAULTS,
...overrides,
};

const totalPoints = computed(() => mocks.totalProgress.value * MaxPointsPerContent);

const fetchPoints = jest.fn();

const incrementTotalProgress = progress => {
set(mocks.totalProgress, get(mocks.totalProgress) + progress);
};

return {
totalPoints,
fetchPoints,
incrementTotalProgress,
...mocks,
};
}

export default jest.fn(() => useTotalProgressMock());
49 changes: 49 additions & 0 deletions kolibri/core/assets/src/composables/useSnackbar.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { ref } from 'kolibri.lib.vueCompositionApi';
import { get, set } from '@vueuse/core';

const snackbarIsVisible = ref(false);
const snackbarOptions = ref({
text: '',
autoDismiss: true,
});

export default function useSnackbar() {
const createSnackbar = (options = {}) => {
// reset
set(snackbarIsVisible, false);
set(snackbarOptions, {});

// set new options
set(snackbarIsVisible, true);

// options include text, autoDismiss, duration, actionText, actionCallback,
// hideCallback, bottomPosition
// if the options are a string, set it as the snackbar text
// and default autoDismiss to true
if (typeof options === 'string') {
set(snackbarOptions, { text: options, autoDismiss: true });
} else {
set(snackbarOptions, options);
}
};

const clearSnackbar = () => {
set(snackbarIsVisible, false);
set(snackbarOptions, {});
};

const setSnackbarText = text => {
set(snackbarOptions, { ...get(snackbarOptions), text });
};

return {
// state
snackbarIsVisible,
snackbarOptions,

// mutators
createSnackbar,
clearSnackbar,
setSnackbarText,
};
}
31 changes: 31 additions & 0 deletions kolibri/core/assets/src/composables/useTotalProgress.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { ref, computed } from 'kolibri.lib.vueCompositionApi';
import { get, set } from '@vueuse/core';
import useUser from 'kolibri.coreVue.composables.useUser';
import { UserProgressResource } from 'kolibri.resources';
import { MaxPointsPerContent } from '../constants';

const totalProgress = ref(null);

export default function useTotalProgress() {
const totalPoints = computed(() => totalProgress.value * MaxPointsPerContent);

const fetchPoints = () => {
const { isUserLoggedIn, currentUserId } = useUser();
if (get(isUserLoggedIn) && get(totalProgress) === null) {
UserProgressResource.fetchModel({ id: get(currentUserId) }).then(progress => {
set(totalProgress, progress.progress);
});
}
};

const incrementTotalProgress = progress => {
set(totalProgress, get(totalProgress) + progress);
};

return {
totalProgress,
totalPoints,
fetchPoints,
incrementTotalProgress,
};
}
4 changes: 2 additions & 2 deletions kolibri/core/assets/src/composables/useUserSyncStatus.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { SyncStatus } from 'kolibri.coreVue.vuex.constants';
import { get, useTimeoutPoll } from '@vueuse/core';
import useUser from './useUser';

const { isLearnerOnlyImport, isUserLoggedIn } = useUser();
const { isLearnerOnlyImport, isUserLoggedIn, currentUserId } = useUser();

const status = ref(SyncStatus.NOT_CONNECTED);
const queued = ref(false);
Expand Down Expand Up @@ -41,7 +41,7 @@ export function pollUserSyncStatusTask() {
if (!get(isUserLoggedIn) || !get(isLearnerOnlyImport)) {
return Promise.resolve();
}
return fetchUserSyncStatus({ user: store.state.core.session.user_id }).then(syncData => {
return fetchUserSyncStatus({ user: get(currentUserId) }).then(syncData => {
if (syncData && syncData[0]) {
queued.value = syncData[0].queued;
lastSynced.value = syncData[0].last_synced ? new Date(syncData[0].last_synced) : null;
Expand Down
2 changes: 2 additions & 0 deletions kolibri/core/assets/src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -198,3 +198,5 @@ export const Presets = Object.freeze({
// This should be kept in sync with the value in
// kolibri/core/exams/constants.py
export const MAX_QUESTIONS_PER_QUIZ_SECTION = 25;

export const DisconnectionErrorCodes = [0, 502, 504, 511];
4 changes: 4 additions & 0 deletions kolibri/core/assets/src/core-app/apiSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ import NotificationsRoot from '../views/NotificationsRoot';
import useMinimumKolibriVersion from '../composables/useMinimumKolibriVersion';
import useUserSyncStatus from '../composables/useUserSyncStatus';
import useUser from '../composables/useUser';
import useSnackbar from '../composables/useSnackbar';
import useTotalProgress from '../composables/useTotalProgress';
import { registerNavItem } from '../composables/useNav';
import useNow from '../composables/useNow';

Expand Down Expand Up @@ -202,6 +204,8 @@ export default {
useNow,
useUser,
useUserSyncStatus,
useSnackbar,
useTotalProgress,
},
},
resources,
Expand Down
11 changes: 6 additions & 5 deletions kolibri/core/assets/src/core-app/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import { CancelToken } from 'axios';
import qs from 'qs';
import heartbeat from 'kolibri.heartbeat';
import logger from 'kolibri.lib.logging';
import store from 'kolibri.coreVue.vuex.store';
import { get } from '@vueuse/core';
import errorCodes from '../disconnectionErrorCodes';
import useUser from 'kolibri.coreVue.composables.useUser';
import { DisconnectionErrorCodes } from 'kolibri.coreVue.vuex.constants';
import useConnection from '../composables/useConnection';
import clientFactory from './baseClient';

Expand Down Expand Up @@ -41,23 +41,24 @@ baseClient.interceptors.response.use(
// if they were logged in.
if (error.response) {
if (error.response.status === 403) {
if (store.state.core.session.id && !store.state.core.session.user_id) {
const { id, user_id } = useUser();
if (get(id) && !get(user_id)) {
// We have session information but no user_id, which means we are not logged in
// This is a sign that the user has been logged out due to inactivity
heartbeat.signOutDueToInactivity();
} else {
// In this case, we should check right now if they are still logged in
heartbeat.pollSessionEndPoint().then(() => {
// If they are not, we should handle sign out
if (!store.state.core.session.user_id) {
if (!get(user_id)) {
heartbeat.signOutDueToInactivity();
}
});
}
}
// On every error, check to see if the status code is one of our designated
// disconnection status codes.
if (errorCodes.includes(error.response.status)) {
if (DisconnectionErrorCodes.includes(error.response.status)) {
// If so, set our heartbeat module to start monitoring the disconnection state
heartbeat.monitorDisconnect(error.response.status);
}
Expand Down
16 changes: 10 additions & 6 deletions kolibri/core/assets/src/disconnection.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { createTranslator } from 'kolibri.utils.i18n';
import { get } from '@vueuse/core';
import useSnackbar from 'kolibri.coreVue.composables.useSnackbar';
import useConnection from './composables/useConnection';

export const trs = createTranslator('DisconnectionSnackbars', {
Expand All @@ -24,8 +25,9 @@ export const trs = createTranslator('DisconnectionSnackbars', {
},
});

export function createTryingToReconnectSnackbar(store) {
store.commit('CORE_CREATE_SNACKBAR', {
export function createTryingToReconnectSnackbar() {
const { createSnackbar } = useSnackbar();
createSnackbar({
text: trs.$tr('tryingToReconnect'),
backdrop: true,
autoDismiss: false,
Expand All @@ -42,7 +44,8 @@ export function createDisconnectedSnackbar(store, beatCallback) {
const { reconnectTime } = useConnection();
setDynamicReconnectTime(get(reconnectTime));
// create snackbar
store.commit('CORE_CREATE_SNACKBAR', {
const { createSnackbar, setSnackbarText } = useSnackbar();
createSnackbar({
text: generateDisconnectedSnackbarText(),
actionText: trs.$tr('tryNow'),
actionCallback: beatCallback,
Expand All @@ -53,7 +56,7 @@ export function createDisconnectedSnackbar(store, beatCallback) {
// start timeout
timer = setInterval(() => {
setDynamicReconnectTime(dynamicReconnectTime - 1);
store.commit('CORE_SET_SNACKBAR_TEXT', generateDisconnectedSnackbarText());
setSnackbarText(generateDisconnectedSnackbarText());
}, 1000);
}

Expand All @@ -73,7 +76,8 @@ function clearTimer() {
}
}

export function createReconnectedSnackbar(store) {
export function createReconnectedSnackbar() {
clearTimer();
store.dispatch('createSnackbar', trs.$tr('successfullyReconnected'));
const { createSnackbar } = useSnackbar();
createSnackbar(trs.$tr('successfullyReconnected'));
}
1 change: 0 additions & 1 deletion kolibri/core/assets/src/disconnectionErrorCodes.js

This file was deleted.

Loading

0 comments on commit cf689c1

Please sign in to comment.