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

More work on Device > Facility page + Setup Wizard + misc fixes #7027

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
9658d7a
Update SelectAddressForm
jonboiser May 11, 2020
998ef20
On AvailableChannelsPage, select all channels if `?setup=true`
jonboiser May 11, 2020
2d018f9
Add FacilityUser is_admin filter
jonboiser May 12, 2020
00cdf0d
Add really basic /facilityadmins endpoint
jonboiser May 12, 2020
0c3afc5
Integrate SelectSuperAdminAccountForm with /facilityadmins
jonboiser May 12, 2020
5243a4e
Stub GrantSuperUserPermissionsView and StartFacilityImportView
jonboiser May 12, 2020
bbca21b
Integrate SelectSuperAdminAccountForm with backend
jonboiser May 13, 2020
0f12d39
Add check for admin role
jonboiser May 19, 2020
d3dce16
Add startOverAction to commonCoreStrings
jonboiser May 20, 2020
a692c40
Add tests for device setup APIs
jonboiser May 26, 2020
9973162
Fix problems with Sync Modals on Data Page
jonboiser Jun 8, 2020
b56f267
Fix missing birthyear
jonboiser Jun 8, 2020
ba98d78
Update requirements/dev.txt
jonboiser Jun 8, 2020
9a22962
Stub peer facilities client-server endpoint
jonboiser Jun 8, 2020
d30acab
Merge remote-tracking branch 'origin/develop' into peer-sync-ui-integ…
jonboiser Jun 9, 2020
5f0af18
Remove manageSync vuex module
jonboiser Jun 9, 2020
1cfdbf1
Simplify SyncInterface to show only a single facility
jonboiser Jun 9, 2020
1b0224e
Remove fake facilities from FacilitiesPage
jonboiser Jun 9, 2020
649c5d5
Integrate FacilitiesPage with KDP syncing API
jonboiser Jun 9, 2020
a19f827
Fix Coach AppBar
jonboiser Jun 9, 2020
c0cdcc6
Integrate FacilityPage with KDP Sync API
jonboiser Jun 9, 2020
4360203
Merge remote-tracking branch 'origin/develop' into peer-sync-ui-integ…
jonboiser Jun 10, 2020
e99a1bb
Merge remote-tracking branch 'origin/develop' into peer-sync-ui-integ…
jonboiser Jun 10, 2020
d371cc5
Update for axios
jonboiser Jun 10, 2020
515887a
Show “Syncing” indicator
jonboiser Jun 10, 2020
51f05c1
Fix AvailableChannels URL when picking a local peer
jonboiser Jun 10, 2020
4f7cd78
Pre commit fixes
jonboiser Jun 10, 2020
17c8a3b
Remove unused is_admin filter
jonboiser Jun 10, 2020
1936c88
Fix ipdb requirement
jonboiser Jun 10, 2020
fd06e69
Merge remote-tracking branch 'origin/release-v0.14.x' into peer-sync-…
jonboiser Jun 10, 2020
bfe1977
Update for new facilitysync backend
jonboiser Jun 10, 2020
c6dcb74
Add “new or updated” label to ChannelContentsSummary.vue
jonboiser Jun 11, 2020
15d5e2d
Add messages for resources that are affected by channel upgrade
jonboiser Jun 11, 2020
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
4 changes: 4 additions & 0 deletions kolibri/core/assets/src/api-resources/facilityTask.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,8 @@ export default new Resource({
dataportalbulksync() {
return this.postListEndpoint('startdataportalbulksync');
},

deleteFinishedTasks() {
return this.postListEndpoint('deletefinishedtasks');
},
});
22 changes: 21 additions & 1 deletion kolibri/core/assets/src/mixins/commonSyncElements.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { StaticNetworkLocationResource } from 'kolibri.resources';
import some from 'lodash/some';
import { StaticNetworkLocationResource, FacilityTaskResource } from 'kolibri.resources';
import { createTranslator } from 'kolibri.utils.i18n';

// Strings that might be shared among syncing-related UIs across plugins.
Expand Down Expand Up @@ -61,5 +62,24 @@ export default {
nickname: device_name,
}).save();
},
startKdpSyncTask(facilityId) {
return FacilityTaskResource.dataportalsync(facilityId).then(response => {
return response.data;
});
},
fetchKdpSyncTasks() {
return FacilityTaskResource.fetchCollection({ force: true }).then(tasks => {
return tasks.filter(task => (task.type = 'SYNCDATAPORTAL'));
});
},
cleanupKdpSyncTasks(tasks, cb) {
if (some(tasks, { type: 'SYNCDATAPORTAL', status: 'COMPLETED' })) {
return FacilityTaskResource.deleteFinishedTasks().then(() => {
if (cb) {
cb();
}
});
}
},
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<KModal
:title="$tr('registerFacility')"
:submitText="$tr('register')"
:submitText="registerText"
:cancelText="cancelText"
@submit="registerFacility"
@cancel="$emit('cancel')"
Expand Down Expand Up @@ -56,6 +56,9 @@
? this.coreString('closeAction')
: this.coreString('cancelAction');
},
registerText() {
return this.alreadyRegistered ? null : this.$tr('register');
},
},
methods: {
registerFacility() {
Expand All @@ -71,6 +74,7 @@
data: { registered: true },
exists: true,
}).then(() => {
this.$emit('success', this.targetFacility);
this.submitting = false;
});
})
Expand Down
19 changes: 5 additions & 14 deletions kolibri/core/assets/src/views/sync/FacilityNameAndSyncStatus.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
</div>
<div>
<span>
<template v-if="facility.syncing">
<template v-if="facility.syncing || isSyncing">
<KCircularLoader class="loader" :size="16" :delay="false" />
{{ $tr('syncing') }}
</template>
Expand All @@ -46,18 +46,8 @@
<script>

import UiIcon from 'kolibri-design-system/lib/keen/UiIcon';
import has from 'lodash/has';
import every from 'lodash/every';
import { now } from 'kolibri.utils.serverClock';

const facilityFields = [
'name',
'dataset.registered',
'syncing',
'last_sync_failed',
'last_synced',
];

export default {
name: 'FacilityNameAndSyncStatus',
components: {
Expand All @@ -68,9 +58,10 @@
facility: {
type: Object,
required: true,
validator(value) {
return every(facilityFields, field => has(value, field));
},
},
isSyncing: {
type: Boolean,
default: false,
},
},
data() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
:disabled="!a.available || !a.hasContent"
/>
<KButton
v-if="!readOnlyStaticAddrersses"
v-if="!hideSavedAddresses"
:key="`forget-${idx}`"
:text="$tr('forgetAddressButtonLabel')"
appearance="basic-link"
Expand All @@ -57,7 +57,7 @@
</div>
</template>

<hr v-if="discoveredAddresses.length > 0">
<hr v-if="!hideSavedAddresses && discoveredAddresses.length > 0">

<template v-for="d in discoveredAddresses">
<div :key="`div-${d.id}`">
Expand Down Expand Up @@ -144,9 +144,8 @@
required: false,
default: '',
},
// Customizes the component specifically for the PostSetupModalGroup
// Turns off: discovery + add new + forget
readOnlyStaticAddrersses: {
// Hides "New address" button and other saved locations
hideSavedAddresses: {
type: Boolean,
default: false,
},
Expand Down Expand Up @@ -180,7 +179,7 @@
);
},
newAddressButtonDisabled() {
return this.readOnlyStaticAddrersses || this.stage === this.Stages.FETCHING_ADDRESSES;
return this.hideSavedAddresses || this.stage === this.Stages.FETCHING_ADDRESSES;
},
requestsFailed() {
return (
Expand Down Expand Up @@ -216,9 +215,7 @@
},
},
beforeMount() {
if (!this.readOnlyStaticAddrersses) {
this.startDiscoveryPolling();
}
this.startDiscoveryPolling();
return this.refreshSavedAddressList();
},
mounted() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<template>

<span v-if="isSpecified && birthYear">
{{ displayText }}
{{ birthYear }}
</span>
<KEmptyPlaceholder v-else />

Expand Down
45 changes: 45 additions & 0 deletions kolibri/core/auth/test/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -630,6 +630,51 @@ def test_user_list(self):
)


class FacilityUserFilterTestCase(APITestCase):
def setUp(self):
provision_device()
# Fixtures: 2 facilities with 1 learner + 1 admin each
self.facility_1 = FacilityFactory.create()
self.facility_2 = FacilityFactory.create()

self.user_1 = FacilityUserFactory.create(
facility=self.facility_1, username="learner_1"
)
self.admin_1 = FacilityUserFactory.create(
facility=self.facility_1, username="admin_1"
)
self.facility_1.add_admin(self.admin_1)

self.user_2 = FacilityUserFactory.create(
facility=self.facility_2, username="learner_2"
)
self.admin_2 = FacilityUserFactory.create(
facility=self.facility_2, username="admin_2"
)
self.facility_2.add_admin(self.admin_2)

# Superuser is in facility 1
self.superuser = create_superuser(self.facility_1, username="a_superuser")
self.client.login(
username=self.superuser.username,
password=DUMMY_PASSWORD,
facility=self.facility_1,
)

def _sort_by_username(self, data):
return sorted(data, key=lambda x: x["username"])

def test_user_member_of_filter(self):
response = self.client.get(
reverse("kolibri:core:facilityuser-list"), {"member_of": self.facility_1.id}
)
data = self._sort_by_username(response.data)
self.assertEqual(len(data), 3)
self.assertEqual(data[0]["id"], self.superuser.id)
self.assertEqual(data[1]["id"], self.admin_1.id)
self.assertEqual(data[2]["id"], self.user_1.id)


class LoginLogoutTestCase(APITestCase):
def setUp(self):
provision_device()
Expand Down
17 changes: 17 additions & 0 deletions kolibri/core/discovery/api.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import requests
from rest_framework import viewsets
from rest_framework.response import Response

from .models import DynamicNetworkLocation
from .models import NetworkLocation
Expand All @@ -20,3 +22,18 @@ class DynamicNetworkLocationViewSet(NetworkLocationViewSet):

class StaticNetworkLocationViewSet(NetworkLocationViewSet):
queryset = StaticNetworkLocation.objects.all()


class NetworkLocationFacilitiesView(viewsets.GenericViewSet):
"""
Given a NetworkLocation ID, returns a list of Facilities that are on
that NetworkLocation, for the purposes of syncing
"""

def retrieve(self, request, pk=None):
base_url = "http://192.168.1.8:8000/"
Copy link
Member

Choose a reason for hiding this comment

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

Using a hardcoded ip is correct?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, this endpoint is still being worked on. It will use the DynamicNetworkLocation model's baseurl field to construct the /api/public/v1/facility endpoint for a specific peer.

This hardcoded URL is just my second computer's IP on the LAN, which I'm using for testing.

facility_url = "{}api/public/v1/facility".format(base_url)
response = requests.get(facility_url)
response.raise_for_status()
facilities_data = response.json()
return Response(facilities_data)
7 changes: 7 additions & 0 deletions kolibri/core/discovery/api_urls.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from rest_framework import routers

from .api import DynamicNetworkLocationViewSet
from .api import NetworkLocationFacilitiesView
from .api import NetworkLocationViewSet
from .api import StaticNetworkLocationViewSet

Expand All @@ -18,4 +19,10 @@
base_name="dynamicnetworklocation",
)

router.register(
r"networklocation_facilities",
NetworkLocationFacilitiesView,
basename="networklocation_facilities",
)

urlpatterns = router.urls
2 changes: 1 addition & 1 deletion kolibri/plugins/coach/assets/src/views/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export const CoachCoreBase = {
const { facility_id, name } = this.$store.state.classSummary;
if (
facility_id &&
this.$store.state.core.facilities.length > 0 &&
this.$store.state.core.facilities.length > 1 &&
this.$store.getters.isSuperuser
) {
const match = find(this.$store.state.core.facilities, { id: facility_id }) || {};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,10 @@
},
multipleMode() {
const { multiple } = this.$route.query;
return multiple === true || multiple === 'true';
return this.setupMode || multiple === true || multiple === 'true';
},
setupMode() {
return Boolean(this.$route.query.setup);
},
documentTitle() {
switch (this.transferType) {
Expand Down Expand Up @@ -252,6 +255,13 @@
this.setAppBarTitle(this.toolbarTitle(this.transferType));
}
},
mounted() {
// If arriving here from the PostSetupModalGroup/WelcomeModal,
// then select all the channels automatically
if (this.setupMode) {
this.selectedChannels = [...this.allChannels];
}
},
methods: {
...mapMutations('coreBase', {
setAppBarTitle: 'SET_APP_BAR_TITLE',
Expand All @@ -276,14 +286,16 @@
toggleMultipleMode() {
let newQuery;
if (this.multipleMode) {
newQuery = omit(this.$route.query, ['multiple']);
// Remove the 'setup' query param if switching to single-channel mode.
// When the user returns, none of the channels will be selected
newQuery = omit(this.$route.query, ['multiple', 'setup']);
} else {
newQuery = {
...this.$route.query,
multiple: true,
};
}
this.$router.push({ query: newQuery });
return this.$router.push({ query: newQuery });
},
handleSubmitToken(channel) {
if (this.multipleMode) {
Expand Down
Loading