@instructure/platform-cohorts 0.2.0 → 0.4.0
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/README.md +86 -0
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -28,3 +28,89 @@ pnpm --filter @instructure/platform-cohorts build # vite lib build + .d.ts
|
|
|
28
28
|
pnpm --filter @instructure/platform-cohorts type-check
|
|
29
29
|
pnpm lint # biome + oxlint + ast-grep
|
|
30
30
|
```
|
|
31
|
+
|
|
32
|
+
## The contract
|
|
33
|
+
|
|
34
|
+
`openapi.yaml` is OpenAPI 3.1, 26 operations across 17 paths, with no
|
|
35
|
+
server implementing it yet. `src/__tests__/openapi.test.ts` proves it's
|
|
36
|
+
structurally complete: the document validates against the OpenAPI 3.1
|
|
37
|
+
schema and every `$ref` resolves, every operation has a unique
|
|
38
|
+
`operationId`, a `403`, and a `404`, every `POST`/`PUT`/`DELETE` also has a
|
|
39
|
+
`400`, the path+method matrix matches exactly, the `Cohort`,
|
|
40
|
+
`WorkflowState`, and `ErrorCode` schemas carry the fields the rest of the
|
|
41
|
+
contract depends on, no path reaches into pseudonym, generator, or sync
|
|
42
|
+
territory, and no request body accepts a full user object where an id
|
|
43
|
+
would do. If a check fails, the fix belongs in the YAML, not the test.
|
|
44
|
+
|
|
45
|
+
The contract uses no custom (`x-*`) vendor extensions. Service limits live in
|
|
46
|
+
the schema and response descriptions that enforce them, and which operations
|
|
47
|
+
extend an existing Canvas API is documented in each operation's own
|
|
48
|
+
description.
|
|
49
|
+
|
|
50
|
+
Which permission an operation requires and which feature flag gates it are
|
|
51
|
+
determined by Canvas's real permission registry and feature-flag system,
|
|
52
|
+
not documented per-operation in this contract.
|
|
53
|
+
|
|
54
|
+
### Conventions consumers rely on
|
|
55
|
+
|
|
56
|
+
- **Pagination**: list operations return a `Link` header in RFC 5988 format,
|
|
57
|
+
matching the rest of the Canvas API.
|
|
58
|
+
- **Ids**: every id is a 64-bit integer, modelled as the shared `Id` schema.
|
|
59
|
+
Canvas serialises ids as JSON strings instead when the request carries
|
|
60
|
+
`Accept: application/json+canvas-string-ids`, so clients should accept
|
|
61
|
+
both forms.
|
|
62
|
+
- **Error payloads**: `Error` (message + optional machine-readable
|
|
63
|
+
`error_code`), `ValidationError` (per-attribute field errors), and
|
|
64
|
+
`UnauthorizedError` (the `403` shape) cover every failure case. The one
|
|
65
|
+
exception is `bulkEnroll`, whose pre-existing Canvas validation keeps its
|
|
66
|
+
string-bodied `LegacyStringError`.
|
|
67
|
+
- **Status codes follow Canvas**: creates return `200`, not `201`; an
|
|
68
|
+
authenticated caller without permission gets `403` with
|
|
69
|
+
`status: unauthorized`. No operation in this contract returns `409`.
|
|
70
|
+
- **List filters follow Canvas**: lifecycle filtering is the repeatable
|
|
71
|
+
`workflow_state[]` (`active`, `deleted`, `all`), `per_page` caps at 100,
|
|
72
|
+
and `search_term` needs at least two characters.
|
|
73
|
+
- **404 means three things on purpose**: a missing/deleted resource, the
|
|
74
|
+
`institutional_cohorts` flag being off for the root account, or the
|
|
75
|
+
resource belonging to a different root account than the caller's. The API
|
|
76
|
+
never distinguishes these, so it never reveals what it won't show you.
|
|
77
|
+
- **400 `feature_disabled`**: the three extended Canvas operations
|
|
78
|
+
(`bulkEnroll`, `createAccountNotification`, `updateAccountNotification`)
|
|
79
|
+
keep working when the `institutional_cohorts` flag is off, but reject their
|
|
80
|
+
cohort-specific parameters with `400` and error code `feature_disabled`,
|
|
81
|
+
rather than 404ing the whole operation.
|
|
82
|
+
- **Form encodings**: the three extended Canvas operations accept
|
|
83
|
+
`application/x-www-form-urlencoded` and `multipart/form-data` with the
|
|
84
|
+
same schema. `canvas-fetch` emits `FormData` natively, so multipart is the
|
|
85
|
+
path of least resistance for clients in this repo.
|
|
86
|
+
|
|
87
|
+
### Key contract decisions
|
|
88
|
+
|
|
89
|
+
- `createCohort` creates both kinds of cohort: pass `parent_cohort_id` for a
|
|
90
|
+
sub-cohort, or `null` for a root cohort at depth 1 with no parent. An
|
|
91
|
+
account may anchor several independent root cohorts — Canvas has no
|
|
92
|
+
`/root` route and neither does this contract. `listCohorts` finds them
|
|
93
|
+
with `roots=true`.
|
|
94
|
+
- Depth is 1-based (the root cohort is depth 1) and capped at 10.
|
|
95
|
+
- Deletion is soft: cohorts, memberships, and leader assignments move to
|
|
96
|
+
`workflow_state: deleted` rather than being removed.
|
|
97
|
+
- A Leader's rights always cascade to every cohort beneath the one the
|
|
98
|
+
assignment lives on — there's no way to scope a Leader to a single level.
|
|
99
|
+
- Adding a leader is idempotent: a duplicate `addCohortLeader` returns the
|
|
100
|
+
existing assignment with `200`, the way the Canvas admins API does.
|
|
101
|
+
- Leader grants carry no ceiling on which roles a leader may hand out. An
|
|
102
|
+
earlier revision refused, with `403`, any role other than the one that
|
|
103
|
+
authorised the caller. That rule appeared nowhere in the tech spec, so it
|
|
104
|
+
was dropped; Canvas instead enforces a subset rule on `AccountUser`
|
|
105
|
+
(`is_subset_of?`: the granted role's permissions must be a subset of the
|
|
106
|
+
caller's). If a ceiling is wanted, amend the spec first, and prefer the
|
|
107
|
+
Canvas subset rule over strict equality.
|
|
108
|
+
- `updateCohortLeader` changes a leader's role in place. The tech spec does
|
|
109
|
+
not describe it and the Canvas admins API has no equivalent (role change is
|
|
110
|
+
delete plus create there), so it stays pending a spec amendment.
|
|
111
|
+
- `getCohortTree` defaults to `depth=2` (the cohort and its direct children)
|
|
112
|
+
so a client expands one level at a time unless it asks for more; `depth=10`
|
|
113
|
+
returns the full hierarchy.
|
|
114
|
+
- Bulk course enrolment, program enrolment, and collection enrolment are all
|
|
115
|
+
one-shot actions: they resolve cohort membership once, when the job runs,
|
|
116
|
+
and nothing keeps the target roster in sync with the cohort afterwards.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@instructure/platform-cohorts",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@instructure/ui-text": "11.7.4",
|
|
29
29
|
"@instructure/ui-view": "11.7.4",
|
|
30
|
+
"@seriousme/openapi-schema-validator": "^2.9.1",
|
|
30
31
|
"@storybook/react": "^10.0.8",
|
|
31
32
|
"@tanstack/react-query": "^5.59.15",
|
|
32
33
|
"@testing-library/jest-dom": "^6.9.1",
|
|
@@ -41,6 +42,7 @@
|
|
|
41
42
|
"vite": "^6.0.0",
|
|
42
43
|
"vite-plugin-dts": "^4.0.0",
|
|
43
44
|
"vitest": "^4.0.0",
|
|
45
|
+
"yaml": "^2.8.2",
|
|
44
46
|
"@instructure/platform-provider": "0.5.2",
|
|
45
47
|
"@instructure/platform-test-utils": "0.2.5"
|
|
46
48
|
},
|