placementt-core 1.400.1103 → 1.400.1104
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/surveys/selection.d.ts +33 -0
- package/lib/surveys/selection.js +40 -1
- package/lib/surveys/selection.js.map +1 -1
- package/lib/surveys/selection.test.js +53 -0
- package/lib/surveys/selection.test.js.map +1 -1
- package/package.json +1 -1
- package/src/surveys/selection.test.ts +61 -1
- package/src/surveys/selection.ts +50 -0
|
@@ -18,6 +18,13 @@
|
|
|
18
18
|
* timeline it does not belong on. Being wrong here is worse than being silent: a
|
|
19
19
|
* cycle stamped with the wrong year group shows up in another cohort's numbers.
|
|
20
20
|
*/
|
|
21
|
+
import type { WhereFilterOp } from "firebase/firestore";
|
|
22
|
+
/** One clause of a DataViewer selection, as it is stored. */
|
|
23
|
+
export type SelectionFilter = {
|
|
24
|
+
k: string;
|
|
25
|
+
e?: WhereFilterOp;
|
|
26
|
+
v: unknown;
|
|
27
|
+
};
|
|
21
28
|
/** The field a year group is filtered on. `resolveSelectionStudents` builds its own
|
|
22
29
|
* fallback selection with this key, so a year group is always a `details.year`
|
|
23
30
|
* clause however the selection was made. */
|
|
@@ -30,3 +37,29 @@ export declare const YEAR_GROUP_SELECTION_FIELD = "details.year";
|
|
|
30
37
|
* selection is not one year group.
|
|
31
38
|
*/
|
|
32
39
|
export declare const yearGroupFromSelection: (selectionJson?: string) => string | undefined;
|
|
40
|
+
/** A selection that names one year group, and the clauses that cut it down. */
|
|
41
|
+
export type YearGroupSubset = {
|
|
42
|
+
/** The year group it sits under, the same tag yearGroupFromSelection gives. */
|
|
43
|
+
yearGroup: string;
|
|
44
|
+
/** Every clause other than the year itself, in the order staff applied them. */
|
|
45
|
+
narrowing: SelectionFilter[];
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* The part of a year group a selection describes, when it is only part of one.
|
|
49
|
+
*
|
|
50
|
+
* A task sent to "Year 10, pupil premium" is stamped Year 10, because the year is
|
|
51
|
+
* the one thing the filter says for certain, and the cycle belongs on Year 10's
|
|
52
|
+
* timeline. It is NOT Year 10 though, and a row that draws it beside a survey sent
|
|
53
|
+
* to the whole year invites somebody to read two response rates as comparable when
|
|
54
|
+
* they have different denominators.
|
|
55
|
+
*
|
|
56
|
+
* So this answers the second question the first one leaves open: is this all of the
|
|
57
|
+
* year group, or some of it? `undefined` covers both the selections that are no
|
|
58
|
+
* year group at all and the ones that are the whole of it; a result means some of
|
|
59
|
+
* it, and carries what narrowed it so a page can say so.
|
|
60
|
+
*
|
|
61
|
+
* @param {string} [selectionJson] JSON-serialised DataViewerSelectionBackend.
|
|
62
|
+
* @return {YearGroupSubset|undefined} the year group and what narrows it, or
|
|
63
|
+
* nothing when the selection is a whole year group or not a year group at all.
|
|
64
|
+
*/
|
|
65
|
+
export declare const yearGroupSubsetFromSelection: (selectionJson?: string) => YearGroupSubset | undefined;
|
package/lib/surveys/selection.js
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* cycle stamped with the wrong year group shows up in another cohort's numbers.
|
|
21
21
|
*/
|
|
22
22
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
-
exports.yearGroupFromSelection = exports.YEAR_GROUP_SELECTION_FIELD = void 0;
|
|
23
|
+
exports.yearGroupSubsetFromSelection = exports.yearGroupFromSelection = exports.YEAR_GROUP_SELECTION_FIELD = void 0;
|
|
24
24
|
/** The field a year group is filtered on. `resolveSelectionStudents` builds its own
|
|
25
25
|
* fallback selection with this key, so a year group is always a `details.year`
|
|
26
26
|
* clause however the selection was made. */
|
|
@@ -72,4 +72,43 @@ const yearGroupFromSelection = (selectionJson) => {
|
|
|
72
72
|
return tag || undefined;
|
|
73
73
|
};
|
|
74
74
|
exports.yearGroupFromSelection = yearGroupFromSelection;
|
|
75
|
+
/**
|
|
76
|
+
* The part of a year group a selection describes, when it is only part of one.
|
|
77
|
+
*
|
|
78
|
+
* A task sent to "Year 10, pupil premium" is stamped Year 10, because the year is
|
|
79
|
+
* the one thing the filter says for certain, and the cycle belongs on Year 10's
|
|
80
|
+
* timeline. It is NOT Year 10 though, and a row that draws it beside a survey sent
|
|
81
|
+
* to the whole year invites somebody to read two response rates as comparable when
|
|
82
|
+
* they have different denominators.
|
|
83
|
+
*
|
|
84
|
+
* So this answers the second question the first one leaves open: is this all of the
|
|
85
|
+
* year group, or some of it? `undefined` covers both the selections that are no
|
|
86
|
+
* year group at all and the ones that are the whole of it; a result means some of
|
|
87
|
+
* it, and carries what narrowed it so a page can say so.
|
|
88
|
+
*
|
|
89
|
+
* @param {string} [selectionJson] JSON-serialised DataViewerSelectionBackend.
|
|
90
|
+
* @return {YearGroupSubset|undefined} the year group and what narrows it, or
|
|
91
|
+
* nothing when the selection is a whole year group or not a year group at all.
|
|
92
|
+
*/
|
|
93
|
+
const yearGroupSubsetFromSelection = (selectionJson) => {
|
|
94
|
+
const yearGroup = (0, exports.yearGroupFromSelection)(selectionJson);
|
|
95
|
+
// Not one year group, so there is no year group for it to be a subset OF. A
|
|
96
|
+
// hand-picked list of pupils is its own thing, not "some of Year 10".
|
|
97
|
+
if (!yearGroup)
|
|
98
|
+
return undefined;
|
|
99
|
+
let selection;
|
|
100
|
+
try {
|
|
101
|
+
// Safe to assume: yearGroupFromSelection only returns a tag for a string it
|
|
102
|
+
// parsed into an object with filters.
|
|
103
|
+
selection = JSON.parse(selectionJson);
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
if (!selection || Array.isArray(selection))
|
|
109
|
+
return undefined;
|
|
110
|
+
const narrowing = (selection.filters ?? []).filter((f) => f?.k !== exports.YEAR_GROUP_SELECTION_FIELD);
|
|
111
|
+
return narrowing.length ? { yearGroup, narrowing } : undefined;
|
|
112
|
+
};
|
|
113
|
+
exports.yearGroupSubsetFromSelection = yearGroupSubsetFromSelection;
|
|
75
114
|
//# sourceMappingURL=selection.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"selection.js","sourceRoot":"","sources":["../../src/surveys/selection.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;GAmBG;;;
|
|
1
|
+
{"version":3,"file":"selection.js","sourceRoot":"","sources":["../../src/surveys/selection.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;GAmBG;;;AAQH;;4CAE4C;AAC/B,QAAA,0BAA0B,GAAG,cAAc,CAAC;AAEzD,oFAAoF;AACpF,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC;AAE5C;;;;;;GAMG;AACI,MAAM,sBAAsB,GAAG,CAAC,aAAsB,EAAoB,EAAE;IAC/E,IAAI,CAAC,aAAa;QAAE,OAAO,SAAS,CAAC;IAErC,IAAI,SAAqC,CAAC;IAC1C,IAAI,CAAC;QACD,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa,CAA+B,CAAC;IACxE,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,SAAS,CAAC;IACrB,CAAC;IAED,8EAA8E;IAC9E,yEAAyE;IACzE,IAAI,CAAC,SAAS,IAAI,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,IAAI,OAAO,SAAS,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IAC9F,8EAA8E;IAC9E,2EAA2E;IAC3E,IAAI,SAAS,CAAC,WAAW,KAAK,IAAI;QAAE,OAAO,SAAS,CAAC;IACrD,gFAAgF;IAChF,kDAAkD;IAClD,IAAI,OAAO,SAAS,CAAC,MAAM,KAAK,QAAQ,IAAI,SAAS,CAAC,MAAM,CAAC,IAAI,EAAE;QAAE,OAAO,SAAS,CAAC;IAEtF,MAAM,WAAW,GAAG,CAAC,SAAS,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,kCAA0B,CAAC,CAAC;IACjG,iFAAiF;IACjF,cAAc;IACd,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAE/C,MAAM,CAAC,MAAM,CAAC,GAAG,WAAW,CAAC;IAC7B,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;QAAE,OAAO,SAAS,CAAC;IAC9C,gFAAgF;IAChF,8EAA8E;IAC9E,IAAI,OAAO,MAAM,CAAC,CAAC,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IAEnD,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAC5B,OAAO,GAAG,IAAI,SAAS,CAAC;AAC5B,CAAC,CAAC;AAjCW,QAAA,sBAAsB,0BAiCjC;AAUF;;;;;;;;;;;;;;;;;GAiBG;AACI,MAAM,4BAA4B,GAAG,CAAC,aAAsB,EAA6B,EAAE;IAC9F,MAAM,SAAS,GAAG,IAAA,8BAAsB,EAAC,aAAa,CAAC,CAAC;IACxD,4EAA4E;IAC5E,sEAAsE;IACtE,IAAI,CAAC,SAAS;QAAE,OAAO,SAAS,CAAC;IAEjC,IAAI,SAAqC,CAAC;IAC1C,IAAI,CAAC;QACD,4EAA4E;QAC5E,sCAAsC;QACtC,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,aAAuB,CAA+B,CAAC;IAClF,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,SAAS,CAAC;IACrB,CAAC;IACD,IAAI,CAAC,SAAS,IAAI,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC;QAAE,OAAO,SAAS,CAAC;IAE7D,MAAM,SAAS,GAAG,CAAC,SAAS,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,kCAA0B,CAAC,CAAC;IAC/F,OAAO,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAC,SAAS,EAAE,SAAS,EAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AACjE,CAAC,CAAC;AAlBW,QAAA,4BAA4B,gCAkBvC"}
|
|
@@ -71,4 +71,57 @@ describe("yearGroupFromSelection", () => {
|
|
|
71
71
|
expect((0, selection_1.yearGroupFromSelection)("\"9\"")).toBeUndefined();
|
|
72
72
|
});
|
|
73
73
|
});
|
|
74
|
+
describe("yearGroupSubsetFromSelection", () => {
|
|
75
|
+
const withFilters = (filters) => json({ allSelected: true, search: undefined, filters });
|
|
76
|
+
it("reports the year group and what narrows it", () => {
|
|
77
|
+
expect((0, selection_1.yearGroupSubsetFromSelection)(withFilters([
|
|
78
|
+
{ k: "details.year", v: "9" },
|
|
79
|
+
{ k: "details.pupilPremium", v: "Yes" },
|
|
80
|
+
]))).toEqual({
|
|
81
|
+
yearGroup: "9",
|
|
82
|
+
narrowing: [{ k: "details.pupilPremium", v: "Yes" }],
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
it("keeps every narrowing clause, in the order staff applied them", () => {
|
|
86
|
+
expect((0, selection_1.yearGroupSubsetFromSelection)(withFilters([
|
|
87
|
+
{ k: "details.pupilPremium", v: "Yes" },
|
|
88
|
+
{ k: "details.year", v: "9" },
|
|
89
|
+
{ k: "details.form", e: "in", v: ["9A", "9B"] },
|
|
90
|
+
]))?.narrowing).toEqual([
|
|
91
|
+
{ k: "details.pupilPremium", v: "Yes" },
|
|
92
|
+
{ k: "details.form", e: "in", v: ["9A", "9B"] },
|
|
93
|
+
]);
|
|
94
|
+
});
|
|
95
|
+
it("gives nothing for the whole year group", () => {
|
|
96
|
+
// The point of the type: a result means SOME of a year group, so a selection
|
|
97
|
+
// that is all of one has to be indistinguishable from one that is no year
|
|
98
|
+
// group at all.
|
|
99
|
+
expect((0, selection_1.yearGroupSubsetFromSelection)(yearSelection("9"))).toBeUndefined();
|
|
100
|
+
expect((0, selection_1.yearGroupSubsetFromSelection)(withFilters([{ k: "details.year", v: "9" }]))).toBeUndefined();
|
|
101
|
+
});
|
|
102
|
+
it("gives nothing when there is no single year group to be a subset of", () => {
|
|
103
|
+
// A hand-picked list of pupils is its own thing, not some of Year 9, even
|
|
104
|
+
// when every one of them is in Year 9.
|
|
105
|
+
expect((0, selection_1.yearGroupSubsetFromSelection)(json(["uid1", "uid2"]))).toBeUndefined();
|
|
106
|
+
expect((0, selection_1.yearGroupSubsetFromSelection)(withFilters([
|
|
107
|
+
{ k: "details.year", v: "9" },
|
|
108
|
+
{ k: "details.year", v: "10" },
|
|
109
|
+
]))).toBeUndefined();
|
|
110
|
+
expect((0, selection_1.yearGroupSubsetFromSelection)(withFilters([{ k: "details.pupilPremium", v: "Yes" }])))
|
|
111
|
+
.toBeUndefined();
|
|
112
|
+
});
|
|
113
|
+
it("gives nothing for a search box, however else it is filtered", () => {
|
|
114
|
+
// yearGroupFromSelection already refuses these, and a subset of a selection
|
|
115
|
+
// we could not read in the first place is not something to draw a badge from.
|
|
116
|
+
expect((0, selection_1.yearGroupSubsetFromSelection)(json({
|
|
117
|
+
allSelected: true,
|
|
118
|
+
search: "smith",
|
|
119
|
+
filters: [{ k: "details.year", v: "9" }, { k: "details.pupilPremium", v: "Yes" }],
|
|
120
|
+
}))).toBeUndefined();
|
|
121
|
+
});
|
|
122
|
+
it("gives nothing for missing or unreadable JSON", () => {
|
|
123
|
+
expect((0, selection_1.yearGroupSubsetFromSelection)(undefined)).toBeUndefined();
|
|
124
|
+
expect((0, selection_1.yearGroupSubsetFromSelection)("{not json")).toBeUndefined();
|
|
125
|
+
});
|
|
126
|
+
});
|
|
74
127
|
//# sourceMappingURL=selection.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"selection.test.js","sourceRoot":"","sources":["../../src/surveys/selection.test.ts"],"names":[],"mappings":";;AAAA,
|
|
1
|
+
{"version":3,"file":"selection.test.js","sourceRoot":"","sources":["../../src/surveys/selection.test.ts"],"names":[],"mappings":";;AAAA,2CAAiF;AAEjF,MAAM,IAAI,GAAG,CAAC,KAAc,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;AAEvD,MAAM,aAAa,GAAG,CAAC,CAAU,EAAE,CAAU,EAAE,QAAiC,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC;IACxF,WAAW,EAAE,IAAI;IACjB,MAAM,EAAE,SAAS;IACjB,OAAO,EAAE,CAAC,EAAC,CAAC,EAAE,cAAc,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAC,CAAC,EAAC,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAC,CAAC;IACpD,GAAG,KAAK;CACX,CAAC,CAAC;AAEH,QAAQ,CAAC,wBAAwB,EAAE,GAAG,EAAE;IACpC,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QACxD,MAAM,CAAC,IAAA,kCAAsB,EAAC,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC7D,MAAM,CAAC,IAAA,kCAAsB,EAAC,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACvE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qCAAqC,EAAE,GAAG,EAAE;QAC3C,oEAAoE;QACpE,MAAM,CAAC,IAAA,kCAAsB,EAAC,aAAa,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IACnF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+CAA+C,EAAE,GAAG,EAAE;QACrD,MAAM,CAAC,IAAA,kCAAsB,EAAC,aAAa,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAChF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0DAA0D,EAAE,GAAG,EAAE;QAChE,8EAA8E;QAC9E,wCAAwC;QACxC,MAAM,CAAC,IAAA,kCAAsB,EAAC,IAAI,CAAC;YAC/B,WAAW,EAAE,IAAI;YACjB,OAAO,EAAE,CAAC,EAAC,CAAC,EAAE,cAAc,EAAE,CAAC,EAAE,GAAG,EAAC,EAAE,EAAC,CAAC,EAAE,sBAAsB,EAAE,CAAC,EAAE,IAAI,EAAC,CAAC;SAC/E,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACnB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gDAAgD,EAAE,GAAG,EAAE;QACtD,MAAM,CAAC,IAAA,kCAAsB,EAAC,IAAI,CAAC,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;IAC7E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gDAAgD,EAAE,GAAG,EAAE;QACtD,MAAM,CAAC,IAAA,kCAAsB,EAAC,aAAa,CAAC,GAAG,EAAE,SAAS,EAAE,EAAC,MAAM,EAAE,OAAO,EAAC,CAAC,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;QACjG,mCAAmC;QACnC,MAAM,CAAC,IAAA,kCAAsB,EAAC,aAAa,CAAC,GAAG,EAAE,SAAS,EAAE,EAAC,MAAM,EAAE,IAAI,EAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC5F,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8DAA8D,EAAE,GAAG,EAAE;QACpE,MAAM,CAAC,IAAA,kCAAsB,EAAC,aAAa,CAAC,CAAC,GAAG,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;QACjF,MAAM,CAAC,IAAA,kCAAsB,EAAC,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;QACzE,MAAM,CAAC,IAAA,kCAAsB,EAAC,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;IAC7E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oCAAoC,EAAE,GAAG,EAAE;QAC1C,MAAM,CAAC,IAAA,kCAAsB,EAAC,IAAI,CAAC;YAC/B,WAAW,EAAE,IAAI;YACjB,OAAO,EAAE,CAAC,EAAC,CAAC,EAAE,cAAc,EAAE,CAAC,EAAE,GAAG,EAAC,EAAE,EAAC,CAAC,EAAE,cAAc,EAAE,CAAC,EAAE,IAAI,EAAC,CAAC;SACvE,CAAC,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;IACzB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wDAAwD,EAAE,GAAG,EAAE;QAC9D,MAAM,CAAC,IAAA,kCAAsB,EAAC,IAAI,CAAC,EAAC,WAAW,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAC,CAAC,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;QACvF,MAAM,CAAC,IAAA,kCAAsB,EAAC,IAAI,CAAC,EAAC,WAAW,EAAE,IAAI,EAAC,CAAC,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;IAC9E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qDAAqD,EAAE,GAAG,EAAE;QAC3D,4EAA4E;QAC5E,6CAA6C;QAC7C,MAAM,CAAC,IAAA,kCAAsB,EAAC,IAAI,CAAC,EAAC,WAAW,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,EAAC,CAAC,EAAE,cAAc,EAAE,CAAC,EAAE,GAAG,EAAC,CAAC,EAAC,CAAC,CAAC,CAAC;aAC7F,aAAa,EAAE,CAAC;IACzB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mDAAmD,EAAE,GAAG,EAAE;QACzD,MAAM,CAAC,IAAA,kCAAsB,EAAC,aAAa,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;QACrE,MAAM,CAAC,IAAA,kCAAsB,EAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;QACjE,MAAM,CAAC,IAAA,kCAAsB,EAAC,aAAa,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;QAClE,MAAM,CAAC,IAAA,kCAAsB,EAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;IACzE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8CAA8C,EAAE,GAAG,EAAE;QACpD,MAAM,CAAC,IAAA,kCAAsB,EAAC,SAAS,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;QAC1D,MAAM,CAAC,IAAA,kCAAsB,EAAC,EAAE,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;QACnD,MAAM,CAAC,IAAA,kCAAsB,EAAC,WAAW,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;QAC5D,MAAM,CAAC,IAAA,kCAAsB,EAAC,MAAM,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;QACvD,MAAM,CAAC,IAAA,kCAAsB,EAAC,OAAO,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;IAC5D,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,8BAA8B,EAAE,GAAG,EAAE;IAC1C,MAAM,WAAW,GAAG,CAAC,OAAkB,EAAE,EAAE,CAAC,IAAI,CAAC,EAAC,WAAW,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAC,CAAC,CAAC;IAElG,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;QAClD,MAAM,CAAC,IAAA,wCAA4B,EAAC,WAAW,CAAC;YAC5C,EAAC,CAAC,EAAE,cAAc,EAAE,CAAC,EAAE,GAAG,EAAC;YAC3B,EAAC,CAAC,EAAE,sBAAsB,EAAE,CAAC,EAAE,KAAK,EAAC;SACxC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;YACT,SAAS,EAAE,GAAG;YACd,SAAS,EAAE,CAAC,EAAC,CAAC,EAAE,sBAAsB,EAAE,CAAC,EAAE,KAAK,EAAC,CAAC;SACrD,CAAC,CAAC;IACP,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+DAA+D,EAAE,GAAG,EAAE;QACrE,MAAM,CAAC,IAAA,wCAA4B,EAAC,WAAW,CAAC;YAC5C,EAAC,CAAC,EAAE,sBAAsB,EAAE,CAAC,EAAE,KAAK,EAAC;YACrC,EAAC,CAAC,EAAE,cAAc,EAAE,CAAC,EAAE,GAAG,EAAC;YAC3B,EAAC,CAAC,EAAE,cAAc,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC,EAAC;SAChD,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,OAAO,CAAC;YACpB,EAAC,CAAC,EAAE,sBAAsB,EAAE,CAAC,EAAE,KAAK,EAAC;YACrC,EAAC,CAAC,EAAE,cAAc,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC,EAAC;SAChD,CAAC,CAAC;IACP,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;QAC9C,6EAA6E;QAC7E,0EAA0E;QAC1E,gBAAgB;QAChB,MAAM,CAAC,IAAA,wCAA4B,EAAC,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;QACzE,MAAM,CAAC,IAAA,wCAA4B,EAAC,WAAW,CAAC,CAAC,EAAC,CAAC,EAAE,cAAc,EAAE,CAAC,EAAE,GAAG,EAAC,CAAC,CAAC,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;IACrG,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oEAAoE,EAAE,GAAG,EAAE;QAC1E,0EAA0E;QAC1E,uCAAuC;QACvC,MAAM,CAAC,IAAA,wCAA4B,EAAC,IAAI,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;QAC7E,MAAM,CAAC,IAAA,wCAA4B,EAAC,WAAW,CAAC;YAC5C,EAAC,CAAC,EAAE,cAAc,EAAE,CAAC,EAAE,GAAG,EAAC;YAC3B,EAAC,CAAC,EAAE,cAAc,EAAE,CAAC,EAAE,IAAI,EAAC;SAC/B,CAAC,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;QACrB,MAAM,CAAC,IAAA,wCAA4B,EAAC,WAAW,CAAC,CAAC,EAAC,CAAC,EAAE,sBAAsB,EAAE,CAAC,EAAE,KAAK,EAAC,CAAC,CAAC,CAAC,CAAC;aACrF,aAAa,EAAE,CAAC;IACzB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6DAA6D,EAAE,GAAG,EAAE;QACnE,4EAA4E;QAC5E,8EAA8E;QAC9E,MAAM,CAAC,IAAA,wCAA4B,EAAC,IAAI,CAAC;YACrC,WAAW,EAAE,IAAI;YACjB,MAAM,EAAE,OAAO;YACf,OAAO,EAAE,CAAC,EAAC,CAAC,EAAE,cAAc,EAAE,CAAC,EAAE,GAAG,EAAC,EAAE,EAAC,CAAC,EAAE,sBAAsB,EAAE,CAAC,EAAE,KAAK,EAAC,CAAC;SAChF,CAAC,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;IACzB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8CAA8C,EAAE,GAAG,EAAE;QACpD,MAAM,CAAC,IAAA,wCAA4B,EAAC,SAAS,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;QAChE,MAAM,CAAC,IAAA,wCAA4B,EAAC,WAAW,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;IACtE,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {yearGroupFromSelection} from "./selection";
|
|
1
|
+
import {yearGroupFromSelection, yearGroupSubsetFromSelection} from "./selection";
|
|
2
2
|
|
|
3
3
|
const json = (value: unknown) => JSON.stringify(value);
|
|
4
4
|
|
|
@@ -83,3 +83,63 @@ describe("yearGroupFromSelection", () => {
|
|
|
83
83
|
expect(yearGroupFromSelection("\"9\"")).toBeUndefined();
|
|
84
84
|
});
|
|
85
85
|
});
|
|
86
|
+
|
|
87
|
+
describe("yearGroupSubsetFromSelection", () => {
|
|
88
|
+
const withFilters = (filters: unknown[]) => json({allSelected: true, search: undefined, filters});
|
|
89
|
+
|
|
90
|
+
it("reports the year group and what narrows it", () => {
|
|
91
|
+
expect(yearGroupSubsetFromSelection(withFilters([
|
|
92
|
+
{k: "details.year", v: "9"},
|
|
93
|
+
{k: "details.pupilPremium", v: "Yes"},
|
|
94
|
+
]))).toEqual({
|
|
95
|
+
yearGroup: "9",
|
|
96
|
+
narrowing: [{k: "details.pupilPremium", v: "Yes"}],
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("keeps every narrowing clause, in the order staff applied them", () => {
|
|
101
|
+
expect(yearGroupSubsetFromSelection(withFilters([
|
|
102
|
+
{k: "details.pupilPremium", v: "Yes"},
|
|
103
|
+
{k: "details.year", v: "9"},
|
|
104
|
+
{k: "details.form", e: "in", v: ["9A", "9B"]},
|
|
105
|
+
]))?.narrowing).toEqual([
|
|
106
|
+
{k: "details.pupilPremium", v: "Yes"},
|
|
107
|
+
{k: "details.form", e: "in", v: ["9A", "9B"]},
|
|
108
|
+
]);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("gives nothing for the whole year group", () => {
|
|
112
|
+
// The point of the type: a result means SOME of a year group, so a selection
|
|
113
|
+
// that is all of one has to be indistinguishable from one that is no year
|
|
114
|
+
// group at all.
|
|
115
|
+
expect(yearGroupSubsetFromSelection(yearSelection("9"))).toBeUndefined();
|
|
116
|
+
expect(yearGroupSubsetFromSelection(withFilters([{k: "details.year", v: "9"}]))).toBeUndefined();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("gives nothing when there is no single year group to be a subset of", () => {
|
|
120
|
+
// A hand-picked list of pupils is its own thing, not some of Year 9, even
|
|
121
|
+
// when every one of them is in Year 9.
|
|
122
|
+
expect(yearGroupSubsetFromSelection(json(["uid1", "uid2"]))).toBeUndefined();
|
|
123
|
+
expect(yearGroupSubsetFromSelection(withFilters([
|
|
124
|
+
{k: "details.year", v: "9"},
|
|
125
|
+
{k: "details.year", v: "10"},
|
|
126
|
+
]))).toBeUndefined();
|
|
127
|
+
expect(yearGroupSubsetFromSelection(withFilters([{k: "details.pupilPremium", v: "Yes"}])))
|
|
128
|
+
.toBeUndefined();
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("gives nothing for a search box, however else it is filtered", () => {
|
|
132
|
+
// yearGroupFromSelection already refuses these, and a subset of a selection
|
|
133
|
+
// we could not read in the first place is not something to draw a badge from.
|
|
134
|
+
expect(yearGroupSubsetFromSelection(json({
|
|
135
|
+
allSelected: true,
|
|
136
|
+
search: "smith",
|
|
137
|
+
filters: [{k: "details.year", v: "9"}, {k: "details.pupilPremium", v: "Yes"}],
|
|
138
|
+
}))).toBeUndefined();
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("gives nothing for missing or unreadable JSON", () => {
|
|
142
|
+
expect(yearGroupSubsetFromSelection(undefined)).toBeUndefined();
|
|
143
|
+
expect(yearGroupSubsetFromSelection("{not json")).toBeUndefined();
|
|
144
|
+
});
|
|
145
|
+
});
|
package/src/surveys/selection.ts
CHANGED
|
@@ -19,8 +19,12 @@
|
|
|
19
19
|
* cycle stamped with the wrong year group shows up in another cohort's numbers.
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
|
+
import type {WhereFilterOp} from "firebase/firestore";
|
|
22
23
|
import type {DataViewerSelectionBackend} from "../typeDefinitions";
|
|
23
24
|
|
|
25
|
+
/** One clause of a DataViewer selection, as it is stored. */
|
|
26
|
+
export type SelectionFilter = {k: string, e?: WhereFilterOp, v: unknown};
|
|
27
|
+
|
|
24
28
|
/** The field a year group is filtered on. `resolveSelectionStudents` builds its own
|
|
25
29
|
* fallback selection with this key, so a year group is always a `details.year`
|
|
26
30
|
* clause however the selection was made. */
|
|
@@ -70,3 +74,49 @@ export const yearGroupFromSelection = (selectionJson?: string): string|undefined
|
|
|
70
74
|
const tag = clause.v.trim();
|
|
71
75
|
return tag || undefined;
|
|
72
76
|
};
|
|
77
|
+
|
|
78
|
+
/** A selection that names one year group, and the clauses that cut it down. */
|
|
79
|
+
export type YearGroupSubset = {
|
|
80
|
+
/** The year group it sits under, the same tag yearGroupFromSelection gives. */
|
|
81
|
+
yearGroup: string,
|
|
82
|
+
/** Every clause other than the year itself, in the order staff applied them. */
|
|
83
|
+
narrowing: SelectionFilter[],
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* The part of a year group a selection describes, when it is only part of one.
|
|
88
|
+
*
|
|
89
|
+
* A task sent to "Year 10, pupil premium" is stamped Year 10, because the year is
|
|
90
|
+
* the one thing the filter says for certain, and the cycle belongs on Year 10's
|
|
91
|
+
* timeline. It is NOT Year 10 though, and a row that draws it beside a survey sent
|
|
92
|
+
* to the whole year invites somebody to read two response rates as comparable when
|
|
93
|
+
* they have different denominators.
|
|
94
|
+
*
|
|
95
|
+
* So this answers the second question the first one leaves open: is this all of the
|
|
96
|
+
* year group, or some of it? `undefined` covers both the selections that are no
|
|
97
|
+
* year group at all and the ones that are the whole of it; a result means some of
|
|
98
|
+
* it, and carries what narrowed it so a page can say so.
|
|
99
|
+
*
|
|
100
|
+
* @param {string} [selectionJson] JSON-serialised DataViewerSelectionBackend.
|
|
101
|
+
* @return {YearGroupSubset|undefined} the year group and what narrows it, or
|
|
102
|
+
* nothing when the selection is a whole year group or not a year group at all.
|
|
103
|
+
*/
|
|
104
|
+
export const yearGroupSubsetFromSelection = (selectionJson?: string): YearGroupSubset|undefined => {
|
|
105
|
+
const yearGroup = yearGroupFromSelection(selectionJson);
|
|
106
|
+
// Not one year group, so there is no year group for it to be a subset OF. A
|
|
107
|
+
// hand-picked list of pupils is its own thing, not "some of Year 10".
|
|
108
|
+
if (!yearGroup) return undefined;
|
|
109
|
+
|
|
110
|
+
let selection: DataViewerSelectionBackend;
|
|
111
|
+
try {
|
|
112
|
+
// Safe to assume: yearGroupFromSelection only returns a tag for a string it
|
|
113
|
+
// parsed into an object with filters.
|
|
114
|
+
selection = JSON.parse(selectionJson as string) as DataViewerSelectionBackend;
|
|
115
|
+
} catch {
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
if (!selection || Array.isArray(selection)) return undefined;
|
|
119
|
+
|
|
120
|
+
const narrowing = (selection.filters ?? []).filter((f) => f?.k !== YEAR_GROUP_SELECTION_FIELD);
|
|
121
|
+
return narrowing.length ? {yearGroup, narrowing} : undefined;
|
|
122
|
+
};
|