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

Manage users in LOD #12228

Open
wants to merge 28 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
644cf53
Consolidate list remote users api
AlexVelezLl Jun 19, 2024
13f4be0
Remove comment
AlexVelezLl Jun 20, 2024
c4d0ab4
Add needed values for BaseValuesViewset
AlexVelezLl Jun 26, 2024
9619363
Add initial structure
AlexVelezLl May 30, 2024
289d486
Add remove user
AlexVelezLl May 30, 2024
edd2025
Separate importLodUsersMachine
AlexVelezLl Jun 13, 2024
478aeca
Clean importLodUsersMachine
AlexVelezLl Jun 13, 2024
0aebe12
Initialize service
AlexVelezLl Jun 13, 2024
e8c6d48
Setup users routes
AlexVelezLl Jun 14, 2024
72011fc
Add select facility flow
AlexVelezLl Jun 14, 2024
d92d6c9
Fix immersivePage not closing
AlexVelezLl Jun 14, 2024
3264ad9
Add import user With credentials view
AlexVelezLl Jun 16, 2024
66b950e
Completing flow of importing users with credentials
AlexVelezLl Jun 17, 2024
0da59b4
Remove user
AlexVelezLl Jun 18, 2024
1ac99ea
Show loading status
AlexVelezLl Jun 18, 2024
5d41e1a
Add import user as admin flow
AlexVelezLl Jun 18, 2024
523135d
Remove user certificates and morango metadata
AlexVelezLl Jun 21, 2024
2724db4
Track specific user and disable import button on load
AlexVelezLl Jun 21, 2024
cff8e5c
Fix add new device flow
AlexVelezLl Jun 24, 2024
3ea25dd
Store machine state
AlexVelezLl Jun 24, 2024
6734ded
Show imported and loading user in import as admin
AlexVelezLl Jun 24, 2024
1d00a7c
Add error handing and success messages
AlexVelezLl Jun 25, 2024
7ce5576
Manage user persmissions to view users management in LOD
AlexVelezLl Jun 25, 2024
4c45c09
Optimize fetching users data
AlexVelezLl Jun 25, 2024
fc513ac
Fixes
AlexVelezLl Jun 26, 2024
ba36709
Add documentation and tests for importLodUsersMachine
AlexVelezLl Jun 26, 2024
b181f0b
misc fixes
AlexVelezLl Jun 26, 2024
d854a76
Add edit admin permissions and refactor api resources
AlexVelezLl Jun 26, 2024
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
26 changes: 26 additions & 0 deletions kolibri/core/assets/src/api-resources/facilityUser.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,31 @@
import urls from 'kolibri.urls';
import client from 'kolibri.client';
import { Resource } from 'kolibri.lib.apiResource';

export default new Resource({
name: 'facilityuser',
removeImportedUser(user_id) {
return client({
url: urls['kolibri:core:deleteimporteduser'](),
method: 'POST',
data: {
user_id,
},
});
},
async listRemoteFacilityLearners(params) {
const { data } = await client({
url: urls['kolibri:core:remotefacilityauthenticateduserinfo'](),
method: 'POST',
data: params,
});

const admin = data.find(user => user.username === params.username);
const students = data.filter(user => !user.roles || !user.roles.length);

return {
admin,
students,
};
},
});
6 changes: 6 additions & 0 deletions kolibri/core/assets/src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -198,3 +198,9 @@ 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 = 50;

// enum identifying the types of setup for Lod devices
export const LodTypePresets = Object.freeze({
JOIN: 'JOIN',
IMPORT: 'IMPORT',
});
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 @@ -113,6 +113,7 @@ import CatchErrors from '../utils/CatchErrors';
import UiToolbar from '../views/KeenUiToolbar.vue';
import shuffled from '../utils/shuffled';
import * as appCapabilities from '../utils/appCapabilities';
import * as importLodUsersMachine from '../machines/importLodUsersMachine';
import * as client from './client';
import clientFactory from './baseClient';

Expand Down Expand Up @@ -210,6 +211,9 @@ export default {
},
resources,
themeConfig,
machines: {
importLodUsersMachine,
Copy link
Member

Choose a reason for hiding this comment

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

I think I'd rather put this in kolibri-common rather than add it to the core API spec.

},
urls,
utils: {
appCapabilities,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,287 @@
import { interpret } from 'xstate';
import { LodTypePresets } from 'kolibri.coreVue.vuex.constants';
import { getImportLodUsersMachine } from '../importLodUsersMachine';

describe('importLodUsersMachine', () => {
it('should return a machine', () => {
const machine = getImportLodUsersMachine();
expect(machine).toBeDefined();
});

describe('import single user by auth flow', () => {
const service = interpret(getImportLodUsersMachine());
service.start();

it('Being at the initial state, should be able to set the lod type as import, and the deviceId to import from', () => {
// at selectLodSetupType state
const deviceId = 'test-device-id';

service.send({
type: 'CONTINUE',
value: {
importOrJoin: LodTypePresets.IMPORT,
importDeviceId: deviceId,
},
});

const state = service.getSnapshot();
const { context } = state;
expect(context.lodImportOrJoin).toBe(LodTypePresets.IMPORT);
expect(context.importDeviceId).toBe(deviceId);
expect(state.value).toBe('selectLodFacility');
});

it('Should be able to select a facility to import users from, and set the importDevice and how many facilities there are', () => {
// at selectLodFacility state
const selectedFacility = { id: 'test-facility-id' };
const importDevice = { id: 'test-device-id' };
const facilitiesCount = 2;

service.send({
type: 'CONTINUE',
value: {
selectedFacility,
importDevice,
facilitiesCount,
},
});

const state = service.getSnapshot();
const { context } = state;
expect(context.selectedFacility).toBe(selectedFacility);
expect(context.importDevice).toBe(importDevice);
expect(context.facilitiesOnDeviceCount).toBe(facilitiesCount);
expect(context.importDeviceId).toBe(importDevice.id);
expect(state.value).toBe('lodImportUserAuth');
});

it('should be able to add user being imported', () => {
// at lodImportUserAuth state
const user = { id: 'test-user-id' };

service.send({
type: 'ADD_USER_BEING_IMPORTED',
value: user,
});

const { context } = service.getSnapshot();
expect(context.usersBeingImported).toEqual([user]);
});

it('should continue to the loading state', () => {
// at lodImportUserAuth state
service.send('CONTINUE');

const state = service.getSnapshot();
expect(state.value).toBe('lodLoading');
});

it('should be able to import another user', () => {
// at lodLoading state
service.send('IMPORT_ANOTHER');

const state = service.getSnapshot();
expect(state.value).toBe('lodImportUserAuth');
});

it('should be able to finish the flow', () => {
// at lodLoading state
service.send('FINISH');

const state = service.getSnapshot();
expect(state.value).toBe('finish');
});
});

describe('import users as admin flow', () => {
const service = interpret(getImportLodUsersMachine());
service.start();

it('Being at the initial state, should be able to set the lod type as import, and the deviceId to import from', () => {
// at selectLodSetupType state
const deviceId = 'test-device-id';

service.send({
type: 'CONTINUE',
value: {
importOrJoin: LodTypePresets.IMPORT,
importDeviceId: deviceId,
},
});

const state = service.getSnapshot();
const { context } = state;
expect(context.lodImportOrJoin).toBe(LodTypePresets.IMPORT);
expect(context.importDeviceId).toBe(deviceId);
expect(state.value).toBe('selectLodFacility');
});

it('Should be able to select a facility to import users from, and set the importDevice and how many facilities there are', () => {
// at selectLodFacility state
const selectedFacility = { id: 'test-facility-id' };
const importDevice = { id: 'test-device-id' };
const facilitiesCount = 2;

service.send({
type: 'CONTINUE',
value: {
selectedFacility,
importDevice,
facilitiesCount,
},
});

const state = service.getSnapshot();
const { context } = state;
expect(context.selectedFacility).toBe(selectedFacility);
expect(context.importDevice).toBe(importDevice);
expect(context.facilitiesOnDeviceCount).toBe(facilitiesCount);
expect(context.importDeviceId).toBe(importDevice.id);
expect(state.value).toBe('lodImportUserAuth');
});

it('should be able to continue as admin and set remote users', () => {
// at lodImportUserAuth state
const remoteUsers = [{ id: 'test-user-id1' }, { id: 'test-user-id2' }];
const remoteAdmin = { id: 'test-admin-id', username: 'username', password: 'password' };

service.send({
type: 'CONTINUEADMIN',
value: {
users: remoteUsers,
adminUsername: remoteAdmin.username,
adminPassword: remoteAdmin.password,
id: remoteAdmin.id,
},
});

const state = service.getSnapshot();
const { context } = state;
expect(context.remoteUsers).toEqual(remoteUsers);
expect(context.remoteAdmin).toEqual(remoteAdmin);
expect(state.value).toBe('lodImportAsAdmin');
});

it('should be able to add user being imported', () => {
// at lodImportAsAdmin state
const user = { id: 'test-user-id' };

service.send({
type: 'ADD_USER_BEING_IMPORTED',
value: user,
});

const { context } = service.getSnapshot();
expect(context.usersBeingImported).toEqual([user]);
});

it('should be able to go to the loading page', () => {
// at lodImportAsAdmin state
service.send('LOADING');

const state = service.getSnapshot();
expect(state.value).toBe('lodLoading');
});

it('should be able to import another user', () => {
// at lodLoading state
service.send('IMPORT_ANOTHER');

const state = service.getSnapshot();
expect(state.value).toBe('lodImportUserAuth');
});

it('should be able to finish the flow', () => {
// at lodLoading state
service.send('FINISH');

const state = service.getSnapshot();
expect(state.value).toBe('finish');
});
});

describe('Join facility creating a new user flow', () => {
const service = interpret(getImportLodUsersMachine());
service.start();

it('Being at the initial state, should be able to set the lod type as join, and the deviceId to import from', () => {
// at selectLodSetupType state
const deviceId = 'test-device-id';

service.send({
type: 'CONTINUE',
value: {
importOrJoin: LodTypePresets.JOIN,
importDeviceId: deviceId,
},
});

const state = service.getSnapshot();
const { context } = state;
expect(context.lodImportOrJoin).toBe(LodTypePresets.JOIN);
expect(context.importDeviceId).toBe(deviceId);
expect(state.value).toBe('selectLodFacility');
});

it('Should be able to select a facility to import users from, and set the importDevice and how many facilities there are', () => {
// at selectLodFacility state
const selectedFacility = { id: 'test-facility-id' };
const importDevice = { id: 'test-device-id' };
const facilitiesCount = 2;

service.send({
type: 'CONTINUE',
value: {
selectedFacility,
importDevice,
facilitiesCount,
},
});

const state = service.getSnapshot();
const { context } = state;
expect(context.selectedFacility).toBe(selectedFacility);
expect(context.importDevice).toBe(importDevice);
expect(context.facilitiesOnDeviceCount).toBe(facilitiesCount);
expect(context.importDeviceId).toBe(importDevice.id);
expect(state.value).toBe('lodJoinFacility');
});

it('should be able to add user being imported', () => {
// at lodJoinFacility state
const user = { id: 'test-user-id' };

service.send({
type: 'ADD_USER_BEING_IMPORTED',
value: user,
});

const { context } = service.getSnapshot();
expect(context.usersBeingImported).toEqual([user]);
});

it('should continue to the loading state', () => {
// at lodJoinFacility state
service.send('CONTINUE');

const state = service.getSnapshot();
expect(state.value).toBe('lodJoinLoading');
});

it('should be able to import another user', () => {
// at lodJoinLoading state
service.send('IMPORT_ANOTHER');

const state = service.getSnapshot();
expect(state.value).toBe('lodImportUserAuth');
});

it('should be able to finish the flow', () => {
// at lodJoinLoading state
service.send('FINISH');

const state = service.getSnapshot();
expect(state.value).toBe('finish');
});
});
});
Loading