Skip to content

Commit

Permalink
Merge pull request #7027 from jonboiser/peer-sync-ui-integration
Browse files Browse the repository at this point in the history
  • Loading branch information
jonboiser authored Jun 11, 2020
2 parents 14d1b8d + 15d5e2d commit b428c0c
Show file tree
Hide file tree
Showing 32 changed files with 614 additions and 229 deletions.
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 @@ -62,5 +63,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/"
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

0 comments on commit b428c0c

Please sign in to comment.