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

Merge v0.14.0 tag into develop branch #7450

Merged
merged 47 commits into from
Aug 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
d241548
improved layout
indirectlylit Aug 1, 2020
1784ab2
remove truncator from topic title
indirectlylit Aug 1, 2020
4868f38
don't render unnecessary parts of TextTruncator
indirectlylit Aug 1, 2020
b13494d
remove tooltip from text truncator
indirectlylit Aug 1, 2020
4e2ccd5
bug fix
indirectlylit Aug 1, 2020
3f5df7f
linting
indirectlylit Aug 3, 2020
95bceb3
Network name -> Name
indirectlylit Aug 3, 2020
8e91bad
Merge pull request #7420 from indirectlylit/cards
indirectlylit Aug 4, 2020
7e0f5ad
Try to prevent runtime error if User has incomplete roles set
jonboiser Aug 4, 2020
afc3f4f
Pass filenames to only lint changed files in pre-commit.
rtibbles Aug 4, 2020
9e65b83
Update .pre-commit-config.yaml
rtibbles Aug 4, 2020
f3dbfb6
Fetch topic child progress by parent id.
rtibbles Aug 4, 2020
47b521c
Merge pull request #7432 from jonboiser/default-user-type-to-learner
rtibbles Aug 4, 2020
9232a37
Load content progress by lesson id
rtibbles Aug 4, 2020
80bc8cb
Merge pull request #7430 from indirectlylit/name
jonboiser Aug 4, 2020
65cc7db
Fetch contentnode progress in recommended components.
rtibbles Aug 4, 2020
c16dfff
Load ancestor information with content nodes.
rtibbles Aug 4, 2020
0ea795a
Update lesson editing to handle missing content.
rtibbles Aug 4, 2020
2ccfdb6
Catch missing content in quiz previews.
rtibbles Aug 4, 2020
5e7cdf7
Remove outdated test.
rtibbles Aug 4, 2020
7926ff5
Update EditDetailsResourceListTable for missing content.
rtibbles Aug 4, 2020
edab8e3
Fix resource backup setting which would have removed missing content …
rtibbles Aug 4, 2020
898eabf
buttons in tables should be flat, not raised
indirectlylit Aug 4, 2020
c27e76d
Merge pull request #7433 from rtibbles/linting_again
jonboiser Aug 4, 2020
58d41d5
Remove subpage module.
rtibbles Aug 4, 2020
b4d4d62
Remove dead code.
rtibbles Aug 4, 2020
3a091bc
Merge pull request #7435 from indirectlylit/flat
jonboiser Aug 4, 2020
6f4fd6f
Merge pull request #7434 from rtibbles/progress_fetch
rtibbles Aug 4, 2020
0b7d5ae
fixes https://github.com/learningequality/kolibri/issues/7437
indirectlylit Aug 4, 2020
0c92c44
fixes https://github.com/learningequality/kolibri/issues/7422
indirectlylit Aug 4, 2020
00a6256
Workaround for queries that can't be used as subqueries
micahscopes Aug 4, 2020
3a1090b
Merge pull request #20 from micahscopes/missing_content
rtibbles Aug 4, 2020
80ffebe
relax shutdown time requirement on zombie test
micahscopes Aug 5, 2020
dfa3753
Merge pull request #7438 from indirectlylit/more-cards
jonboiser Aug 5, 2020
8a3e835
Merge pull request #7431 from rtibbles/missing_content
jonboiser Aug 5, 2020
cdff1e9
more explicit shutdown time test
micahscopes Aug 5, 2020
6222402
ensure travis runs `timely_shutdown_no_zombies`
micahscopes Aug 5, 2020
b8f4ca6
Bump kolibri_exercise_perseus_plugin to 1.3.3
jonboiser Aug 5, 2020
75fdf3f
stop time test: fix iteration
micahscopes Aug 5, 2020
be91fd1
Merge pull request #7443 from micahscopes/give-zombies-a-chance-to-fi…
rtibbles Aug 5, 2020
f3ecf47
Bump kolibri-design-system to 0.2.0
jonboiser Aug 5, 2020
0c70de0
Update crowdin context
jonboiser Aug 6, 2020
71fdf1e
Update font subset css files
jonboiser Aug 6, 2020
c002c49
Update CHANGELOG for 0.14.0
jonboiser Aug 6, 2020
76bbfbc
Update VERSION to (0, 14, 0, “final”, 0)
jonboiser Aug 6, 2020
0beb63c
Update VERSION = (0, 14, 1, "alpha", 0)
jonboiser Aug 7, 2020
743c10a
Merge remote-tracking branch 'origin/release-v0.14.x' into 0.14.0-int…
jonboiser Aug 7, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ repos:
description: This hook handles all frontend linting for Kolibri
entry: yarn run lint-frontend:format
language: system
pass_filenames: false
files: \.(js|vue|scss|css)$
- repo: local
hooks:
- id: no-auto-migrations
Expand Down
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ matrix:
include:
- python: "3.6"
env:
- TOX_ENV=startup_provision_shutdown_without_zombies
- TOX_ENV=timely_shutdown_no_zombies
script:
- tox -e $TOX_ENV
- python: "2.7"
Expand Down
40 changes: 40 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,46 @@

List of the most important changes for each release.

## 0.14.0

### Internationalization and localization

- Added German
- Added Khmer
- CSV data files have localized headers and filenames

### Added

- In the Setup Wizard, users can import an existing facility from peer Kolibri devices on the network
- Facility admins can sync facility data with peer Kolibri devices on the network or Kolibri Data Portal
- Facility admins can import and export user accounts to and from a CSV file
- Channels can display a learner-facing "tagline" on Learn channel list
- Device and facility names can now be edited by admins
- Super admins can delete facilities from a device
- Quizzes and lessons can be assigned to individual learners in addition to whole groups or classes
- Super admins can view the Facility and Coach pages for all facilities
- Pingbacks to the telemetry server can now be disabled

### Changed

- New card layout for channels on Learn Page is more efficient and displays new taglines
- Simplified setup process when using Kolibri for personal use
- Improved sign-in flow, especially for devices with multiple facilities
- The experience for upgrading channels has been improved with resource highlighting, improved statistics, and more efficient navigation
- Improved icons for facilities, classrooms, quizzes, and other items
- More consistent wording of notifications in the application
- Quizzes and lessons with missing resources are more gracefully handled
- Shut-down times are faster and more consistent

### Fixed

- Many visual and user experience issues
- Language filter not working when viewing channels for import/export
- A variety of mobile responsiveness issues have been addressed


([0.14.0 Github milestone](https://github.com/learningequality/kolibri/issues?q=label%3Achangelog+milestone%3A0.14.0))

## 0.13.3

### Changed or fixed
Expand Down
2 changes: 1 addition & 1 deletion kolibri/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

#: This may not be the exact version as it's subject to modification with
#: get_version() - use ``kolibri.__version__`` for the exact version string.
VERSION = (0, 14, 0, "beta", 0)
VERSION = (0, 14, 1, "alpha", 0)

__author__ = "Learning Equality"
__email__ = "info@learningequality.org"
Expand Down
3 changes: 0 additions & 3 deletions kolibri/core/assets/src/api-resources/contentNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,6 @@ export default new Resource({
fetchNodeAssessments(ids) {
return this.getListEndpoint('node_assessments', { ids });
},
fetchAncestors(id) {
return this.fetchDetailCollection('ancestors', id);
},
fetchRecommendationsFor(id, getParams) {
return this.fetchDetailCollection('recommendations_for', id, getParams);
},
Expand Down
3 changes: 2 additions & 1 deletion kolibri/core/assets/src/mixins/notificationStrings.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ export default createTranslator('NotificationStrings', {
},
coachesAssignedNoCount: {
message: '{count, plural, one {Coach assigned} other {Coaches assigned}}',
context: 'Assigning an unspecified number of coaches to a class',
context:
'String appears as a short notification in a bottom left corner of the screen when more than one coach is assigned to a class, but without specifying the number.',
},
coachesRemovedNoCount: {
message: '{count, plural, one {Coach removed} other {Coaches removed}}',
Expand Down
16 changes: 15 additions & 1 deletion kolibri/core/assets/src/utils/UserType.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,19 @@ export default function UserType(userObject) {
}

// get first role associated with this facility
return userObject.roles.find(role => role.collection === userObject.facility).kind;
const firstRole = userObject.roles.find(role => role.collection === userObject.facility);

if (firstRole) {
return firstRole.kind;
} else {
// HACK check for edge case where the User has a Classroom-level COACH role in
// spite of not having a Facility-level ASSIGNABLE_COACH role
const coachRole = userObject.roles.find(role => role.kind === UserKinds.COACH);
if (coachRole) {
return UserKinds.ASSIGNABLE_COACH;
} else {
// If all else fails, display user as a LEARNER
return UserKinds.LEARNER;
}
}
}
32 changes: 7 additions & 25 deletions kolibri/core/assets/src/views/TextTruncator.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,14 @@
<div v-if="viewAllText">
{{ text }}
</div>
<template v-else>
<div ref="shaveEl">
{{ text }}
</div>
<KTooltip
reference="shaveEl"
:refs="$refs"
:disabled="!tooltipText"
>
{{ tooltipText }}
</KTooltip>
</template>
<div class="show-more">
<div v-else ref="shaveEl">
{{ text }}
</div>
<div
v-if="showViewMore && (textIsTruncated || viewAllText)"
class="show-more"
>
<KButton
v-if="showViewMore && (textIsTruncated || viewAllText)"
appearance="basic-link"
:text="viewAllText ? $tr('viewLessButtonPrompt') : coreString('viewMoreAction')"
@click.stop.prevent="viewAllText = !viewAllText"
Expand Down Expand Up @@ -51,11 +44,6 @@
return value > 0;
},
},
showTooltip: {
type: Boolean,
required: false,
default: true,
},
showViewMore: {
type: Boolean,
required: false,
Expand All @@ -69,12 +57,6 @@
};
},
computed: {
tooltipText() {
if (!this.showTooltip || this.showViewMore || !this.textIsTruncated) {
return null;
}
return this.text;
},
currentDimensions() {
return {
text: this.text,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@
},
duplicateFacilityNamesExplanation: {
message: "This facility is different from '{facilities}'. These facilities will not sync.",
context: 'Explanation that is shown if two facilities with the same name are on the device',
context: 'Explanation displayed when two facilities with the same name are on the device',
},
},
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,10 @@
errorInvalidAddress: 'Please enter a valid IP address, URL, or hostname',
header: 'New address',
nameDesc: 'Choose a name for this address so you can remember it later:',
nameLabel: 'Network name',
nameLabel: {
message: 'Name',
context: "\nThis should be just 'Name', not 'Network name'",
},
namePlaceholder: 'e.g. House network',
submitButtonLabel: 'Add',
tryingToConnect: 'Trying to connect to server…',
Expand Down
137 changes: 78 additions & 59 deletions kolibri/core/content/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,12 @@
from kolibri.core.content.utils.stopwords import stopwords_set
from kolibri.core.decorators import query_params_required
from kolibri.core.device.models import ContentCacheKey
from kolibri.core.lessons.models import Lesson
from kolibri.core.logger.models import ContentSessionLog
from kolibri.core.logger.models import ContentSummaryLog
from kolibri.core.query import SQSum


logger = logging.getLogger(__name__)


Expand Down Expand Up @@ -305,6 +307,9 @@ class ContentNodeViewset(ValuesViewset):
"parent",
"sort_order",
"title",
"lft",
"rght",
"tree_id",
)

field_map = {
Expand All @@ -314,55 +319,72 @@ class ContentNodeViewset(ValuesViewset):
read_only = True

def consolidate(self, items, queryset):
assessmentmetadata = {
a["contentnode"]: a
for a in models.AssessmentMetaData.objects.filter(
contentnode__in=queryset
).values(
"assessment_item_ids",
"number_of_assessments",
"mastery_model",
"randomize",
"is_manipulable",
"contentnode",
)
}
output = []

files = {}

for f in models.File.objects.filter(contentnode__in=queryset).values(
"id",
"contentnode",
"local_file__id",
"priority",
"local_file__available",
"local_file__file_size",
"local_file__extension",
"preset",
"lang__id",
"lang__lang_code",
"lang__lang_subcode",
"lang__lang_name",
"lang__lang_direction",
"supplementary",
"thumbnail",
):
if f["contentnode"] not in files:
files[f["contentnode"]] = []
f["checksum"] = f.pop("local_file__id")
f["available"] = f.pop("local_file__available")
f["file_size"] = f.pop("local_file__file_size")
f["extension"] = f.pop("local_file__extension")
files[f["contentnode"]].append(f)
if items:
assessmentmetadata = {
a["contentnode"]: a
for a in models.AssessmentMetaData.objects.filter(
contentnode__in=queryset
).values(
"assessment_item_ids",
"number_of_assessments",
"mastery_model",
"randomize",
"is_manipulable",
"contentnode",
)
}

output = []
files = {}

for item in items:
item["assessmentmetadata"] = assessmentmetadata.get(item["id"])
item["files"] = list(
map(lambda x: map_file(x, item), files.get(item["id"], []))
for f in models.File.objects.filter(contentnode__in=queryset).values(
"id",
"contentnode",
"local_file__id",
"priority",
"local_file__available",
"local_file__file_size",
"local_file__extension",
"preset",
"lang__id",
"lang__lang_code",
"lang__lang_subcode",
"lang__lang_name",
"lang__lang_direction",
"supplementary",
"thumbnail",
):
if f["contentnode"] not in files:
files[f["contentnode"]] = []
f["checksum"] = f.pop("local_file__id")
f["available"] = f.pop("local_file__available")
f["file_size"] = f.pop("local_file__file_size")
f["extension"] = f.pop("local_file__extension")
files[f["contentnode"]].append(f)

ancestors = queryset.get_ancestors().values(
"id", "title", "lft", "rght", "tree_id"
)
output.append(item)

for item in items:
item["assessmentmetadata"] = assessmentmetadata.get(item["id"])
item["files"] = list(
map(lambda x: map_file(x, item), files.get(item["id"], []))
)

lft = item.pop("lft")
rght = item.pop("rght")
tree_id = item.pop("tree_id")
item["ancestors"] = list(
filter(
lambda x: x["lft"] < lft
and x["rght"] > rght
and x["tree_id"] == tree_id,
ancestors,
)
)
output.append(item)
return output

def get_queryset(self):
Expand Down Expand Up @@ -511,19 +533,6 @@ def next_content(self, request, **kwargs):
{"kind": next_item.kind, "id": next_item.id, "title": next_item.title}
)

@detail_route(methods=["get"])
def ancestors(self, request, **kwargs):
cache_key = "contentnode_ancestors_{pk}".format(pk=kwargs.get("pk"))

if cache.get(cache_key) is not None:
return Response(cache.get(cache_key))

ancestors = list(self.get_object().get_ancestors().values("id", "title"))

cache.set(cache_key, ancestors, 60 * 10)

return Response(ancestors)

@detail_route(methods=["get"])
def recommendations_for(self, request, **kwargs):
"""
Expand Down Expand Up @@ -904,9 +913,19 @@ def retrieve(self, request, pk):


class ContentNodeProgressFilter(IdFilter):
lesson = UUIDFilter(method="filter_by_lesson")

def filter_by_lesson(self, queryset, name, value):
try:
lesson = Lesson.objects.get(pk=value)
node_ids = list(map(lambda x: x["contentnode_id"], lesson.resources))
return queryset.filter(pk__in=node_ids)
except Lesson.DoesNotExist:
return queryset.none()

class Meta:
model = models.ContentNode
fields = ["ids"]
fields = ["ids", "parent", "lesson"]


class ContentNodeProgressViewset(viewsets.ReadOnlyModelViewSet):
Expand Down
11 changes: 0 additions & 11 deletions kolibri/core/content/test/test_content_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -792,17 +792,6 @@ def test_contentnode_parent(self):
for i in range(len(children)):
self.assertEqual(response.data[i]["title"], children[i].title)

def test_contentnode_ancestors(self):
node = content.ContentNode.objects.get(title="c2c2")
ancestors = node.get_ancestors()
ancestors_titles = {n.title for n in ancestors}
response = self.client.get(
reverse("kolibri:core:contentnode-ancestors", kwargs={"pk": node.id})
)
response_titles = {n["title"] for n in response.data}
self.assertEqual(len(response.data), ancestors.count())
self.assertEqual(ancestors_titles, response_titles)

def test_channelmetadata_list(self):
response = self.client.get(reverse("kolibri:core:channel-list", kwargs={}))
self.assertEqual(response.data[0]["name"], "testing")
Expand Down