@datalayer/core 1.1.66 → 1.2.12
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 +43 -20
- package/lib/api/constants.d.ts +1 -1
- package/lib/api/constants.js +1 -1
- package/lib/api/contents/attachments.js +3 -7
- package/lib/api/contents/datasets.d.ts +48 -0
- package/lib/api/contents/datasets.js +44 -7
- package/lib/api/contents/runtimeMounts.js +1 -1
- package/lib/api/contents/sandboxUid.d.ts +13 -45
- package/lib/api/contents/sandboxUid.js +14 -65
- package/lib/api/evals/client.d.ts +402 -0
- package/lib/api/evals/client.js +359 -0
- package/lib/api/evals/index.d.ts +10 -0
- package/lib/api/evals/index.js +18 -0
- package/lib/api/evals/request.d.ts +43 -0
- package/lib/api/evals/request.js +67 -0
- package/lib/api/evals/status.d.ts +27 -0
- package/lib/api/evals/status.js +122 -0
- package/lib/api/evals/types.d.ts +1335 -0
- package/lib/api/evals/types.js +5 -0
- package/lib/api/iam/authentication.js +6 -4
- package/lib/api/iam/connectedAgents.d.ts +29 -0
- package/lib/api/iam/connectedAgents.js +35 -0
- package/lib/api/iam/healthz.js +2 -2
- package/lib/api/iam/identityProviders.d.ts +76 -0
- package/lib/api/iam/identityProviders.js +187 -0
- package/lib/api/iam/index.d.ts +1 -0
- package/lib/api/iam/index.js +1 -0
- package/lib/api/iam/mcpPolicy.d.ts +102 -1
- package/lib/api/iam/mcpPolicy.js +38 -2
- package/lib/api/iam/oauth2.js +18 -12
- package/lib/api/iam/profile.d.ts +17 -1
- package/lib/api/iam/profile.js +27 -0
- package/lib/api/iam/scimTokens.d.ts +79 -0
- package/lib/api/iam/scimTokens.js +141 -0
- package/lib/api/iam/secrets.js +10 -23
- package/lib/api/iam/trials.d.ts +38 -0
- package/lib/api/iam/trials.js +66 -0
- package/lib/api/index.d.ts +4 -0
- package/lib/api/index.js +4 -0
- package/lib/api/mcp/observability.d.ts +34 -11
- package/lib/api/mcp/observability.js +97 -59
- package/lib/api/orchestration/client.d.ts +53 -0
- package/lib/api/orchestration/client.js +105 -0
- package/lib/api/orchestration/events.d.ts +88 -0
- package/lib/api/orchestration/events.js +245 -0
- package/lib/api/orchestration/generated.d.ts +693 -0
- package/lib/api/orchestration/generated.js +687 -0
- package/lib/api/orchestration/index.d.ts +30 -0
- package/lib/api/orchestration/index.js +34 -0
- package/lib/api/orchestration/lifecycle.d.ts +39 -0
- package/lib/api/orchestration/lifecycle.js +57 -0
- package/lib/api/orchestration/measures.d.ts +55 -0
- package/lib/api/orchestration/measures.js +99 -0
- package/lib/api/otel/dashboards.d.ts +56 -0
- package/lib/api/otel/dashboards.js +50 -0
- package/lib/api/otel/index.d.ts +1 -0
- package/lib/api/otel/index.js +1 -0
- package/lib/api/otel/metrics.d.ts +16 -0
- package/lib/api/otel/metrics.js +15 -0
- package/lib/api/scheduler/client.d.ts +32 -0
- package/lib/api/scheduler/client.js +42 -0
- package/lib/api/scheduler/index.d.ts +8 -0
- package/lib/api/scheduler/index.js +16 -0
- package/lib/api/scheduler/request.d.ts +28 -0
- package/lib/api/scheduler/request.js +50 -0
- package/lib/api/scheduler/types.d.ts +88 -0
- package/lib/api/scheduler/types.js +5 -0
- package/lib/api/spacer/comments.d.ts +97 -0
- package/lib/api/spacer/comments.js +45 -0
- package/lib/api/spacer/index.d.ts +9 -0
- package/lib/api/spacer/index.js +17 -0
- package/lib/api/spacer/notebooks.d.ts +33 -0
- package/lib/api/spacer/notebooks.js +39 -0
- package/lib/api/spacer/request.d.ts +25 -0
- package/lib/api/spacer/request.js +46 -0
- package/lib/api/spacer/spaces.d.ts +68 -0
- package/lib/api/spacer/spaces.js +45 -0
- package/lib/api/utils/validation.d.ts +4 -4
- package/lib/api/utils/validation.js +10 -7
- package/lib/client/auth/strategies.js +1 -1
- package/lib/client/constants.d.ts +1 -0
- package/lib/client/constants.js +1 -0
- package/lib/client/mixins/IAMMixin.js +2 -2
- package/lib/components/animation/AnimatedText.d.ts +1 -44
- package/lib/components/animation/AnimatedText.js +6 -122
- package/lib/components/anonymous/AnonymousKeyExpired.d.ts +56 -0
- package/lib/components/anonymous/AnonymousKeyExpired.js +96 -0
- package/lib/components/anonymous/AnonymousKeyTimer.d.ts +63 -0
- package/lib/components/anonymous/AnonymousKeyTimer.js +140 -0
- package/lib/components/anonymous/index.d.ts +7 -0
- package/lib/components/anonymous/index.js +15 -0
- package/lib/components/avatars/UserAvatar.d.ts +7 -1
- package/lib/components/avatars/UserAvatar.js +15 -1
- package/lib/components/billing/BillingEntitySelect.d.ts +6 -0
- package/lib/components/billing/BillingEntitySelect.js +49 -10
- package/lib/components/billing/eligibility.d.ts +22 -0
- package/lib/components/billing/eligibility.js +27 -0
- package/lib/components/billing/index.d.ts +1 -0
- package/lib/components/billing/index.js +1 -0
- package/lib/components/checkout/StripeCheckout.d.ts +1 -1
- package/lib/components/checkout/StripeCheckout.js +1 -1
- package/lib/components/collaboration/LiveEditorCollaborators.d.ts +5 -0
- package/lib/components/collaboration/LiveEditorCollaborators.js +3 -1
- package/lib/components/display/DatalayerBox.d.ts +2 -3
- package/lib/components/display/DatalayerBox.js +1 -1
- package/lib/components/display/NavLink.js +3 -2
- package/lib/components/display/VisuallyHidden.d.ts +1 -2
- package/lib/components/labels/StatusLabels.d.ts +2 -10
- package/lib/components/labels/StatusLabels.js +2 -26
- package/lib/components/principal/PrincipalAppearance.d.ts +21 -42
- package/lib/components/principal/PrincipalAppearance.js +232 -174
- package/lib/components/principal/PrincipalAvatar.d.ts +8 -1
- package/lib/components/principal/PrincipalAvatar.js +2 -2
- package/lib/components/principal/PrincipalDetailsOverlay.d.ts +8 -1
- package/lib/components/principal/PrincipalDetailsOverlay.js +2 -2
- package/lib/components/screencapture/Screencapture.js +46 -3
- package/lib/components/sharing/ShareAccessComponent.d.ts +45 -4
- package/lib/components/sharing/ShareAccessComponent.js +110 -78
- package/lib/components/sharing/SharingEditor.d.ts +5 -1
- package/lib/components/sharing/SharingEditor.js +9 -4
- package/lib/components/sharing/index.d.ts +1 -0
- package/lib/components/sharing/index.js +1 -0
- package/lib/components/sharing/sandboxSharing.d.ts +26 -0
- package/lib/components/sharing/sandboxSharing.js +47 -0
- package/lib/components/spaces/SpaceDetailsOverlay.d.ts +39 -0
- package/lib/components/spaces/SpaceDetailsOverlay.js +80 -0
- package/lib/components/spaces/SpaceDisplay.d.ts +38 -0
- package/lib/components/spaces/SpaceDisplay.js +92 -0
- package/lib/components/spaces/index.d.ts +4 -0
- package/lib/components/spaces/index.js +7 -0
- package/lib/components/spaces/spaceDisplayModel.d.ts +93 -0
- package/lib/components/spaces/spaceDisplayModel.js +170 -0
- package/lib/config/Configuration.d.ts +4 -4
- package/lib/config/Configuration.js +7 -7
- package/lib/config/index.d.ts +1 -0
- package/lib/config/index.js +1 -0
- package/lib/config/planes.d.ts +45 -0
- package/lib/config/planes.js +53 -0
- package/lib/hooks/cacheConverters.d.ts +0 -7
- package/lib/hooks/cacheConverters.js +0 -25
- package/lib/hooks/index.d.ts +1 -0
- package/lib/hooks/index.js +1 -0
- package/lib/hooks/useBackdrop.d.ts +4 -5
- package/lib/hooks/useBillingEntityStore.js +3 -0
- package/lib/hooks/useCache.d.ts +197 -44
- package/lib/hooks/useCache.js +1586 -494
- package/lib/hooks/useContents.d.ts +22 -1
- package/lib/hooks/useContents.js +90 -21
- package/lib/hooks/useMcp.d.ts +87 -1
- package/lib/hooks/useMcp.js +248 -13
- package/lib/hooks/useNavigate.d.ts +41 -3
- package/lib/hooks/useNavigate.js +116 -52
- package/lib/hooks/usePrincipalCacheStore.js +3 -0
- package/lib/hooks/usePrincipalStore.js +3 -0
- package/lib/hooks/useSpaceCacheStore.d.ts +50 -0
- package/lib/hooks/useSpaceCacheStore.js +85 -0
- package/lib/hooks/useWindowSize.js +1 -1
- package/lib/models/CreditsDTO.d.ts +6 -5
- package/lib/models/CreditsDTO.js +7 -5
- package/lib/models/IAMProvidersSpecs.d.ts +10 -5
- package/lib/models/IAMProvidersSpecs.js +0 -23
- package/lib/models/McpBinding.d.ts +6 -0
- package/lib/models/Organization.d.ts +69 -0
- package/lib/models/Organization.js +96 -0
- package/lib/models/Secret.d.ts +1 -2
- package/lib/models/Secret.js +2 -18
- package/lib/models/StartedBy.d.ts +34 -0
- package/lib/models/StartedBy.js +36 -0
- package/lib/models/Team.d.ts +2 -0
- package/lib/models/Team.js +2 -0
- package/lib/models/User.d.ts +4 -0
- package/lib/models/User.js +3 -0
- package/lib/models/UserSettings.d.ts +13 -0
- package/lib/models/UserSettings.js +9 -0
- package/lib/models/contents/__fixtures__/v1-contracts.json +158 -30
- package/lib/navigation/adapters/react-router.d.ts +1 -1
- package/lib/navigation/adapters/react-router.js +3 -0
- package/lib/navigation/components.js +2 -3
- package/lib/routes/publicPaths.js +4 -0
- package/lib/state/index.d.ts +1 -0
- package/lib/state/index.js +1 -0
- package/lib/state/sessionEnd.d.ts +55 -0
- package/lib/state/sessionEnd.js +162 -0
- package/lib/state/substates/CoreState.js +3 -16
- package/lib/state/substates/IAMState.d.ts +3 -3
- package/lib/state/substates/IAMState.js +7 -0
- package/lib/state/substates/LayoutState.js +12 -0
- package/lib/state/substates/NavigationState.d.ts +82 -0
- package/lib/state/substates/NavigationState.js +255 -0
- package/lib/state/substates/ProfileState.d.ts +1 -0
- package/lib/state/substates/ProfileState.js +1 -0
- package/lib/state/substates/index.d.ts +1 -0
- package/lib/state/substates/index.js +1 -0
- package/lib/utils/Lazy.d.ts +20 -0
- package/lib/utils/Lazy.js +32 -6
- package/lib/utils/Screencapture.d.ts +15 -0
- package/lib/utils/Screencapture.js +26 -20
- package/lib/utils/WithSuspense.d.ts +11 -1
- package/lib/utils/WithSuspense.js +11 -1
- package/lib/views/mcp/AdmittedClients.d.ts +44 -0
- package/lib/views/mcp/AdmittedClients.js +59 -0
- package/lib/views/mcp/ApprovalQueue.d.ts +66 -0
- package/lib/views/mcp/ApprovalQueue.js +95 -0
- package/lib/views/mcp/ConnectedAgents.js +38 -3
- package/lib/views/mcp/EnterpriseConsole.d.ts +1 -1
- package/lib/views/mcp/EnterpriseConsole.js +23 -1
- package/lib/views/mcp/IdentityProviders.d.ts +33 -0
- package/lib/views/mcp/IdentityProviders.js +299 -0
- package/lib/views/mcp/McpDashboard.js +90 -7
- package/lib/views/mcp/McpHome.js +1 -1
- package/lib/views/mcp/McpObservability.d.ts +30 -1
- package/lib/views/mcp/McpObservability.js +105 -19
- package/lib/views/mcp/NotebookRuns.d.ts +39 -0
- package/lib/views/mcp/NotebookRuns.js +44 -0
- package/lib/views/mcp/OrganizationPolicy.js +2 -1
- package/lib/views/mcp/PersonalPolicy.js +2 -1
- package/lib/views/mcp/PolicyForm.d.ts +17 -1
- package/lib/views/mcp/PolicyForm.js +25 -3
- package/lib/views/mcp/PolicyHistory.d.ts +13 -0
- package/lib/views/mcp/PolicyHistory.js +14 -1
- package/lib/views/mcp/RunDetail.d.ts +87 -0
- package/lib/views/mcp/RunDetail.js +186 -0
- package/lib/views/mcp/ScimProvisioning.d.ts +54 -0
- package/lib/views/mcp/ScimProvisioning.js +173 -0
- package/lib/views/mcp/TeamPolicies.js +66 -5
- package/lib/views/mcp/ToolAccess.d.ts +60 -0
- package/lib/views/mcp/ToolAccess.js +101 -0
- package/lib/views/mcp/TraceTimeline.d.ts +81 -0
- package/lib/views/mcp/TraceTimeline.js +132 -0
- package/lib/views/mcp/index.d.ts +5 -0
- package/lib/views/mcp/index.js +5 -0
- package/lib/views/otel/simpleAuthStore.js +3 -0
- package/lib/views/secrets/SecretEdit.js +3 -2
- package/lib/views/secrets/SecretNew.js +5 -1
- package/package.json +271 -264
- package/scripts/generate-mcp-types.py +16 -10
- package/scripts/generate-orchestration-types.py +920 -0
package/lib/hooks/useCache.js
CHANGED
|
@@ -38,13 +38,14 @@
|
|
|
38
38
|
*/
|
|
39
39
|
import { useQuery, useQueries, useMutation, useQueryClient, } from '@tanstack/react-query';
|
|
40
40
|
import { BOOTSTRAP_USER_ONBOARDING, asContact, asInvite, asOrganization, asSecret, asTeam, asToken, asUser, } from '../models/index.js';
|
|
41
|
-
import {
|
|
41
|
+
import { asSpace } from './cacheConverters.js';
|
|
42
42
|
import { useCoreStore, useIAMStore, profileStore } from '../state/index.js';
|
|
43
43
|
import { asDisplayName, namesAsInitials, asArray } from '../utils/index.js';
|
|
44
44
|
import { useDatalayer } from './useDatalayer.js';
|
|
45
45
|
import { useAuthorization } from './useAuthorization.js';
|
|
46
46
|
import { useUploadForm } from './useUpload.js';
|
|
47
47
|
import { OUTPUTSHOT_PLACEHOLDER_DEFAULT_SVG } from './assets/index.js';
|
|
48
|
+
import { forgetSessionState } from '../state/sessionEnd.js';
|
|
48
49
|
// Kept for potential future use
|
|
49
50
|
// Default query options for all queries
|
|
50
51
|
const DEFAULT_QUERY_OPTIONS = {
|
|
@@ -57,6 +58,53 @@ const DEFAULT_QUERY_OPTIONS = {
|
|
|
57
58
|
// Ensure queries prioritize cache over network when data is fresh
|
|
58
59
|
networkMode: 'online',
|
|
59
60
|
};
|
|
61
|
+
/**
|
|
62
|
+
* Refresh what a visitor sees, not just what the editor is looking at.
|
|
63
|
+
*
|
|
64
|
+
* `invalidateQueries` marks queries stale and refetches the **active** ones —
|
|
65
|
+
* the ones some mounted component is watching. Everything else is left to
|
|
66
|
+
* refetch when it is next mounted, which is the usual and correct behaviour
|
|
67
|
+
* and does not work here.
|
|
68
|
+
*
|
|
69
|
+
* It does not work because the app's `QueryClient` is built with
|
|
70
|
+
* `refetchOnMount: false`. A query that mounts holding stale data renders it
|
|
71
|
+
* and asks for nothing. So an administrator on `/admin/library/featured`
|
|
72
|
+
* would reorder the ribbon, watch the admin list reorder itself correctly —
|
|
73
|
+
* that query is active — and then find the home page still showing the old
|
|
74
|
+
* order, because the ribbon was not mounted when the invalidation happened
|
|
75
|
+
* and would not ask again when it was. Only a full page reload, which throws
|
|
76
|
+
* the whole cache away, put it right.
|
|
77
|
+
*
|
|
78
|
+
* `refetchType: 'all'` fetches the inactive ones too, there and then. It
|
|
79
|
+
* belongs on the handful of mutations that change what somebody *else's*
|
|
80
|
+
* screen shows, and nowhere else: for an ordinary edit, refetching every
|
|
81
|
+
* dormant query in the cache is a burst of requests for pages nobody is on.
|
|
82
|
+
*/
|
|
83
|
+
const invalidateEverywhere = (client, queryKey) => {
|
|
84
|
+
void client.invalidateQueries({ queryKey, refetchType: 'all' });
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* Forget everything a session was told, without orphaning what is on screen.
|
|
88
|
+
*
|
|
89
|
+
* `queryClient.clear()` is the obvious way to end a session and the wrong
|
|
90
|
+
* one. It destroys the query objects themselves, and an observer whose query
|
|
91
|
+
* is destroyed while its fetch is in flight is never answered and never asks
|
|
92
|
+
* again: it sits at `isLoading` for as long as the page stays open.
|
|
93
|
+
*
|
|
94
|
+
* Signing out is exactly that race. Ending the session swaps the whole tree
|
|
95
|
+
* for the anonymous one, which mounts the home page and starts fetching the
|
|
96
|
+
* featured ribbon; the logout response then lands on top of that fetch. The
|
|
97
|
+
* ribbon stayed a row of empty placeholders until the reader reloaded.
|
|
98
|
+
*
|
|
99
|
+
* So: drop what nobody is looking at, and reset what is on screen. A reset
|
|
100
|
+
* forgets the answer the person who has left was given and asks the question
|
|
101
|
+
* again as whoever is here now, which is what signing out means for a page
|
|
102
|
+
* that is still open.
|
|
103
|
+
*/
|
|
104
|
+
export const forgetSession = (client) => {
|
|
105
|
+
client.removeQueries({ type: 'inactive' });
|
|
106
|
+
client.resetQueries({ type: 'active' });
|
|
107
|
+
};
|
|
60
108
|
// ============================================================================
|
|
61
109
|
// Query Key Factories
|
|
62
110
|
// ============================================================================
|
|
@@ -132,14 +180,26 @@ export const queryKeys = {
|
|
|
132
180
|
mcpCall: (sessionUid, callUid) => [...queryKeys.contents.mcpCalls(sessionUid), callUid],
|
|
133
181
|
mcpApprovals: () => [...queryKeys.contents.all(), 'mcp-approvals'],
|
|
134
182
|
mcpApprovalList: (filters) => [...queryKeys.contents.mcpApprovals(), 'list', filters ?? {}],
|
|
135
|
-
datasourceSchema: (sourceUid) => [
|
|
136
|
-
|
|
183
|
+
datasourceSchema: (sourceUid) => [
|
|
184
|
+
...queryKeys.contents.source(sourceUid),
|
|
185
|
+
'datasource',
|
|
186
|
+
'schema',
|
|
187
|
+
],
|
|
188
|
+
datasourceCapabilities: (sourceUid) => [
|
|
189
|
+
...queryKeys.contents.source(sourceUid),
|
|
190
|
+
'datasource',
|
|
191
|
+
'capabilities',
|
|
192
|
+
],
|
|
137
193
|
datasourceQueries: (sourceUid) => [...queryKeys.contents.source(sourceUid), 'queries'],
|
|
138
194
|
// A query is addressed by its own uid, not under its source: a
|
|
139
195
|
// notebook reconnects to one by uid alone.
|
|
140
196
|
queries: () => [...queryKeys.contents.all(), 'queries'],
|
|
141
197
|
query: (queryUid) => [...queryKeys.contents.queries(), queryUid],
|
|
142
|
-
dataserverStatus: (sourceUid) => [
|
|
198
|
+
dataserverStatus: (sourceUid) => [
|
|
199
|
+
...queryKeys.contents.source(sourceUid),
|
|
200
|
+
'dataserver',
|
|
201
|
+
'status',
|
|
202
|
+
],
|
|
143
203
|
},
|
|
144
204
|
// The Jupyter MCP Server: what the agents did, and what they may do
|
|
145
205
|
mcp: {
|
|
@@ -147,7 +207,12 @@ export const queryKeys = {
|
|
|
147
207
|
tasks: () => [...queryKeys.mcp.all(), 'tasks'],
|
|
148
208
|
taskList: (filters) => [...queryKeys.mcp.tasks(), 'list', filters ?? {}],
|
|
149
209
|
task: (taskUid) => [...queryKeys.mcp.tasks(), taskUid],
|
|
150
|
-
notebookTasks: (notebookUid, filters) => [
|
|
210
|
+
notebookTasks: (notebookUid, filters) => [
|
|
211
|
+
...queryKeys.mcp.tasks(),
|
|
212
|
+
'notebook',
|
|
213
|
+
notebookUid,
|
|
214
|
+
filters ?? {},
|
|
215
|
+
],
|
|
151
216
|
bindings: () => [...queryKeys.mcp.all(), 'bindings'],
|
|
152
217
|
bindingList: (filters) => [...queryKeys.mcp.bindings(), 'list', filters ?? {}],
|
|
153
218
|
activity: (filters) => [...queryKeys.mcp.all(), 'activity', filters ?? {}],
|
|
@@ -163,6 +228,12 @@ export const queryKeys = {
|
|
|
163
228
|
// shared entry would show one under the other's name.
|
|
164
229
|
policyLayer: (scope, subjectUid) => [...queryKeys.mcp.all(), 'policy-layer', scope, subjectUid],
|
|
165
230
|
alertRules: (orgUid) => [...queryKeys.mcp.all(), 'alert-rules', orgUid],
|
|
231
|
+
identityProviders: (orgUid) => [...queryKeys.mcp.all(), 'identity-providers', orgUid],
|
|
232
|
+
scimTokens: (orgUid) => [...queryKeys.mcp.all(), 'scim-tokens', orgUid],
|
|
233
|
+
// Keyed by scope *and* subject, like `policyLayer`: an organization's
|
|
234
|
+
// admitted clients and a team's are different lists, and a shared entry
|
|
235
|
+
// would show one under the other's name.
|
|
236
|
+
admittedClients: (scope, subjectUid) => [...queryKeys.mcp.all(), 'admitted-clients', scope, subjectUid],
|
|
166
237
|
forwarding: (orgUid) => [...queryKeys.mcp.all(), 'audit-forwarding', orgUid],
|
|
167
238
|
auditSettings: (orgUid) => [...queryKeys.mcp.all(), 'audit-settings', orgUid],
|
|
168
239
|
organizationTeams: (orgUid) => [...queryKeys.mcp.all(), 'organization-teams', orgUid],
|
|
@@ -177,8 +248,20 @@ export const queryKeys = {
|
|
|
177
248
|
workflows: () => [...queryKeys.mcp.operations(), 'workflows'],
|
|
178
249
|
gatewayVersion: () => [...queryKeys.mcp.all(), 'version'],
|
|
179
250
|
// The Enterprise console: one answer per organization, narrowed by team.
|
|
180
|
-
orgOverview: (orgUid, filters) => [
|
|
181
|
-
|
|
251
|
+
orgOverview: (orgUid, filters) => [
|
|
252
|
+
...queryKeys.mcp.all(),
|
|
253
|
+
'organizations',
|
|
254
|
+
orgUid,
|
|
255
|
+
'overview',
|
|
256
|
+
filters ?? {},
|
|
257
|
+
],
|
|
258
|
+
orgUsage: (orgUid, filters) => [
|
|
259
|
+
...queryKeys.mcp.all(),
|
|
260
|
+
'organizations',
|
|
261
|
+
orgUid,
|
|
262
|
+
'usage',
|
|
263
|
+
filters ?? {},
|
|
264
|
+
],
|
|
182
265
|
},
|
|
183
266
|
// Authentication & Profile
|
|
184
267
|
auth: {
|
|
@@ -262,6 +345,14 @@ export const queryKeys = {
|
|
|
262
345
|
bySpace: (spaceId) => [...queryKeys.documents.all(), 'space', spaceId],
|
|
263
346
|
model: (documentId) => [...queryKeys.documents.detail(documentId), 'model'],
|
|
264
347
|
},
|
|
348
|
+
// Decks
|
|
349
|
+
decks: {
|
|
350
|
+
all: () => ['decks'],
|
|
351
|
+
details: () => [...queryKeys.decks.all(), 'detail'],
|
|
352
|
+
detail: (id) => [...queryKeys.decks.details(), id],
|
|
353
|
+
bySpace: (spaceId) => [...queryKeys.decks.all(), 'space', spaceId],
|
|
354
|
+
model: (deckId) => [...queryKeys.decks.detail(deckId), 'model'],
|
|
355
|
+
},
|
|
265
356
|
// Cells
|
|
266
357
|
cells: {
|
|
267
358
|
all: () => ['cells'],
|
|
@@ -308,12 +399,6 @@ export const queryKeys = {
|
|
|
308
399
|
detail: (id) => [...queryKeys.environments.details(), id],
|
|
309
400
|
bySpace: (spaceId) => [...queryKeys.environments.all(), 'space', spaceId],
|
|
310
401
|
},
|
|
311
|
-
// Pages
|
|
312
|
-
pages: {
|
|
313
|
-
all: () => ['pages'],
|
|
314
|
-
details: () => [...queryKeys.pages.all(), 'detail'],
|
|
315
|
-
detail: (id) => [...queryKeys.pages.details(), id],
|
|
316
|
-
},
|
|
317
402
|
// Secrets
|
|
318
403
|
secrets: {
|
|
319
404
|
all: () => ['secrets'],
|
|
@@ -371,8 +456,16 @@ export const queryKeys = {
|
|
|
371
456
|
items: {
|
|
372
457
|
all: () => ['items'],
|
|
373
458
|
public: () => [...queryKeys.items.all(), 'public'],
|
|
459
|
+
publicItem: (uid) => [...queryKeys.items.all(), 'public', uid],
|
|
374
460
|
bySpace: (spaceId) => [...queryKeys.items.all(), 'space', spaceId],
|
|
375
461
|
search: (opts) => [...queryKeys.items.all(), 'search', opts],
|
|
462
|
+
// The library: what the home page, the profile pages and "my orbits"
|
|
463
|
+
// read, each invalidated by the mutation that changes it.
|
|
464
|
+
featured: () => [...queryKeys.items.all(), 'featured'],
|
|
465
|
+
orbits: () => [...queryKeys.items.all(), 'orbits'],
|
|
466
|
+
orbiters: (uid) => [...queryKeys.items.all(), 'orbiters', uid],
|
|
467
|
+
derivations: (uid) => [...queryKeys.items.all(), 'derivations', uid],
|
|
468
|
+
byOwner: (handle) => [...queryKeys.items.all(), 'owner', handle],
|
|
376
469
|
},
|
|
377
470
|
// Layout
|
|
378
471
|
layout: {
|
|
@@ -453,6 +546,8 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
|
|
|
453
546
|
const { isLoading: notebookUploadLoading, uploadAndSubmit: uploadNotebook, progress: notebookUploadProgress, reset: resetNotebookUpload, } = useUploadForm(`${coreStore.configuration.spacerUrl}/api/spacer/v1/notebooks`);
|
|
454
547
|
// Hook for document upload/creation
|
|
455
548
|
const { isLoading: documentUploadLoading, uploadAndSubmit: uploadDocument, progress: documentUploadProgress, reset: resetDocumentUpload, } = useUploadForm(`${coreStore.configuration.spacerUrl}/api/spacer/v1/lexicals`);
|
|
549
|
+
// Hook for deck upload/creation
|
|
550
|
+
const { isLoading: deckUploadLoading, uploadAndSubmit: uploadDeck, progress: deckUploadProgress, reset: resetDeckUpload, } = useUploadForm(`${coreStore.configuration.spacerUrl}/api/spacer/v1/decks`);
|
|
456
551
|
// ============================================================================
|
|
457
552
|
// Transformation Functions (kept from original useCache)
|
|
458
553
|
// Note: These functions use 'any' because they handle dynamic API responses
|
|
@@ -552,11 +647,6 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
|
|
|
552
647
|
const toSpace = (spc) => {
|
|
553
648
|
return asSpace(spc);
|
|
554
649
|
};
|
|
555
|
-
const toPage = (s) => {
|
|
556
|
-
if (s) {
|
|
557
|
-
return asPage(s);
|
|
558
|
-
}
|
|
559
|
-
};
|
|
560
650
|
const toSecret = (s) => {
|
|
561
651
|
if (s) {
|
|
562
652
|
return asSecret(s);
|
|
@@ -582,6 +672,232 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
|
|
|
582
672
|
* is available so the UI hydration hooks (e.g. `useUsersByUids`) can
|
|
583
673
|
* still fill in display data downstream.
|
|
584
674
|
*/
|
|
675
|
+
/**
|
|
676
|
+
* What the library knows about an artifact, on top of what its own service
|
|
677
|
+
* stores: how many people orbit it, whether this visitor does, how often it
|
|
678
|
+
* has been reused, whether it is featured, and what it looks like.
|
|
679
|
+
*
|
|
680
|
+
* Applied in one place — `toItem`, the library and search path — rather than
|
|
681
|
+
* in each mapper: a notebook read from its space has none of this, and
|
|
682
|
+
* inventing zeros there would say "nobody orbits it" where the truth is
|
|
683
|
+
* "this was not the library answering".
|
|
684
|
+
*/
|
|
685
|
+
const withLibraryMeta = (raw) => {
|
|
686
|
+
if (!raw || typeof raw !== 'object') {
|
|
687
|
+
return {};
|
|
688
|
+
}
|
|
689
|
+
const meta = {};
|
|
690
|
+
if (raw.orbits !== undefined)
|
|
691
|
+
meta.orbits = Number(raw.orbits) || 0;
|
|
692
|
+
if (raw.orbited !== undefined)
|
|
693
|
+
meta.orbited = Boolean(raw.orbited);
|
|
694
|
+
if (raw.clones !== undefined)
|
|
695
|
+
meta.clones = Number(raw.clones) || 0;
|
|
696
|
+
if (raw.instantiations !== undefined)
|
|
697
|
+
meta.instantiations = Number(raw.instantiations) || 0;
|
|
698
|
+
if (raw.featured !== undefined)
|
|
699
|
+
meta.featured = Boolean(raw.featured);
|
|
700
|
+
if (raw.featuredRank !== undefined)
|
|
701
|
+
meta.featuredRank = Number(raw.featuredRank);
|
|
702
|
+
if (raw.reuse !== undefined)
|
|
703
|
+
meta.reuse = raw.reuse;
|
|
704
|
+
if (raw.publisher !== undefined)
|
|
705
|
+
meta.publisher = raw.publisher;
|
|
706
|
+
if (raw.image !== undefined)
|
|
707
|
+
meta.image = raw.image;
|
|
708
|
+
/*
|
|
709
|
+
* Where the artifact can be opened for editing.
|
|
710
|
+
*
|
|
711
|
+
* An item is a nested child of its space and carries no handle of its
|
|
712
|
+
* own, so the library resolves the space when it projects one. Without
|
|
713
|
+
* these two a page can link to the public reading of an artifact and has
|
|
714
|
+
* no way to offer the editable one.
|
|
715
|
+
*/
|
|
716
|
+
if (raw.space_handle_s)
|
|
717
|
+
meta.spaceHandle = raw.space_handle_s;
|
|
718
|
+
if (raw.space_owner_handle_s)
|
|
719
|
+
meta.spaceOwnerHandle = raw.space_owner_handle_s;
|
|
720
|
+
/*
|
|
721
|
+
* The absolute address anyone can read it at.
|
|
722
|
+
*
|
|
723
|
+
* Built by the library, not here, because it needs the host the site is
|
|
724
|
+
* served from and the browser only knows the host it happens to be on —
|
|
725
|
+
* which on a development server is `localhost:3063`, the one address that
|
|
726
|
+
* is no use to whoever the link is being copied for.
|
|
727
|
+
*/
|
|
728
|
+
if (raw.publicUrl)
|
|
729
|
+
meta.publicUrl = raw.publicUrl;
|
|
730
|
+
if (raw.shareCardUrl)
|
|
731
|
+
meta.shareCardUrl = raw.shareCardUrl;
|
|
732
|
+
if (raw.shareCardVersion)
|
|
733
|
+
meta.shareCardVersion = raw.shareCardVersion;
|
|
734
|
+
return meta;
|
|
735
|
+
};
|
|
736
|
+
/**
|
|
737
|
+
* A published dataset. Keyed on the dataset it is a publication of, not on
|
|
738
|
+
* the publication: republishing mints a new immutable revision, and the
|
|
739
|
+
* library entry — with the orbits it has collected — is the dataset's.
|
|
740
|
+
*/
|
|
741
|
+
const toDataset = (raw) => {
|
|
742
|
+
const owner = toItemOwner(raw);
|
|
743
|
+
return {
|
|
744
|
+
id: raw.uid,
|
|
745
|
+
type: 'dataset',
|
|
746
|
+
name: raw.name_t,
|
|
747
|
+
description: raw.description_t,
|
|
748
|
+
tags: Array.isArray(raw.tags_ss) ? raw.tags_ss : [],
|
|
749
|
+
public: raw.is_public_b ?? true,
|
|
750
|
+
license: raw.license_s,
|
|
751
|
+
fileCount: raw.file_count_i,
|
|
752
|
+
totalSize: raw.total_size_l,
|
|
753
|
+
publicationId: raw.latest_publication_uid_s,
|
|
754
|
+
revisionId: raw.revision_uid_s,
|
|
755
|
+
creationDate: raw.creation_ts_dt
|
|
756
|
+
? new Date(raw.creation_ts_dt)
|
|
757
|
+
: undefined,
|
|
758
|
+
owner,
|
|
759
|
+
};
|
|
760
|
+
};
|
|
761
|
+
/**
|
|
762
|
+
* An agent as a library artifact: published by Datalayer rather than by a
|
|
763
|
+
* user, and reused by instantiating a runtime from its specification rather
|
|
764
|
+
* than by cloning a document. The specification itself lives in the
|
|
765
|
+
* application's own build; this is only what the library shows of it.
|
|
766
|
+
*/
|
|
767
|
+
/**
|
|
768
|
+
* A published Data Server: its catalog, never its address.
|
|
769
|
+
*
|
|
770
|
+
* What the library holds is what it answers for — the connector kinds and
|
|
771
|
+
* the relations they name. Reusing one attaches it as a Datasource; there
|
|
772
|
+
* is nothing to copy, because a Data Server is a process somebody runs.
|
|
773
|
+
*/
|
|
774
|
+
const toDataserver = (raw) => {
|
|
775
|
+
const owner = toItemOwner(raw);
|
|
776
|
+
return {
|
|
777
|
+
id: raw.uid,
|
|
778
|
+
type: 'dataserver',
|
|
779
|
+
name: raw.name_t,
|
|
780
|
+
description: raw.description_t,
|
|
781
|
+
tags: Array.isArray(raw.tags_ss) ? raw.tags_ss : [],
|
|
782
|
+
public: raw.is_public_b ?? true,
|
|
783
|
+
connectors: Array.isArray(raw.connectors_ss) ? raw.connectors_ss : [],
|
|
784
|
+
relations: Array.isArray(raw.relations_ss) ? raw.relations_ss : [],
|
|
785
|
+
publicationId: raw.latest_publication_uid_s,
|
|
786
|
+
creationDate: raw.creation_ts_dt
|
|
787
|
+
? new Date(raw.creation_ts_dt)
|
|
788
|
+
: undefined,
|
|
789
|
+
owner,
|
|
790
|
+
};
|
|
791
|
+
};
|
|
792
|
+
const toAgent = (raw) => {
|
|
793
|
+
return {
|
|
794
|
+
id: raw.uid,
|
|
795
|
+
type: 'agent',
|
|
796
|
+
name: raw.name_t,
|
|
797
|
+
description: raw.description_t,
|
|
798
|
+
tags: Array.isArray(raw.tags_ss) ? raw.tags_ss : [],
|
|
799
|
+
public: raw.is_public_b ?? true,
|
|
800
|
+
domain: raw.domain_s,
|
|
801
|
+
icon: raw.icon_s,
|
|
802
|
+
emoji: raw.emoji_s,
|
|
803
|
+
model: raw.model_s,
|
|
804
|
+
specId: String(raw.uid || '').replace(/^agent:/, ''),
|
|
805
|
+
specVersion: raw.spec_version_s,
|
|
806
|
+
publisher: raw.publisher_s || 'datalayer',
|
|
807
|
+
creationDate: raw.creation_ts_dt
|
|
808
|
+
? new Date(raw.creation_ts_dt)
|
|
809
|
+
: undefined,
|
|
810
|
+
};
|
|
811
|
+
};
|
|
812
|
+
/**
|
|
813
|
+
* A published benchmark report (B5-01): what it is about and where it
|
|
814
|
+
* stands, and the document it is written in, which the library reads for it
|
|
815
|
+
* and hands over as `model_s` — the field a document's reader looks for.
|
|
816
|
+
*/
|
|
817
|
+
const toReport = (raw) => {
|
|
818
|
+
return {
|
|
819
|
+
id: raw.uid,
|
|
820
|
+
type: 'report',
|
|
821
|
+
name: raw.name_t,
|
|
822
|
+
description: raw.description_t,
|
|
823
|
+
tags: Array.isArray(raw.tags_ss) ? raw.tags_ss : [],
|
|
824
|
+
public: raw.is_public_b ?? true,
|
|
825
|
+
state: raw.state_s,
|
|
826
|
+
version: raw.version_i,
|
|
827
|
+
runCount: raw.run_count_i,
|
|
828
|
+
evalsetId: raw.evalset_uid_s,
|
|
829
|
+
report: raw.report,
|
|
830
|
+
model_s: raw.model_s,
|
|
831
|
+
creationDate: raw.creation_ts_dt
|
|
832
|
+
? new Date(raw.creation_ts_dt)
|
|
833
|
+
: undefined,
|
|
834
|
+
lastPublicationDate: raw.published_ts_dt
|
|
835
|
+
? new Date(raw.published_ts_dt)
|
|
836
|
+
: undefined,
|
|
837
|
+
owner: toItemOwner(raw),
|
|
838
|
+
};
|
|
839
|
+
};
|
|
840
|
+
/**
|
|
841
|
+
* A published investigation (B5-01): its scope, where it stands and what was
|
|
842
|
+
* decided, and the document it is written in, as a report's is.
|
|
843
|
+
*/
|
|
844
|
+
const toInvestigation = (raw) => {
|
|
845
|
+
return {
|
|
846
|
+
id: raw.uid,
|
|
847
|
+
type: 'investigation',
|
|
848
|
+
name: raw.name_t,
|
|
849
|
+
description: raw.description_t,
|
|
850
|
+
tags: Array.isArray(raw.tags_ss) ? raw.tags_ss : [],
|
|
851
|
+
public: raw.is_public_b ?? true,
|
|
852
|
+
scope: raw.scope_s,
|
|
853
|
+
status: raw.status_s,
|
|
854
|
+
evalsetId: raw.evalset_uid_s,
|
|
855
|
+
investigation: raw.investigation,
|
|
856
|
+
model_s: raw.model_s,
|
|
857
|
+
creationDate: raw.creation_ts_dt
|
|
858
|
+
? new Date(raw.creation_ts_dt)
|
|
859
|
+
: undefined,
|
|
860
|
+
lastPublicationDate: raw.published_ts_dt
|
|
861
|
+
? new Date(raw.published_ts_dt)
|
|
862
|
+
: undefined,
|
|
863
|
+
owner: toItemOwner(raw),
|
|
864
|
+
};
|
|
865
|
+
};
|
|
866
|
+
/** An evaluator of the platform's catalogue (B5-01), published as an agent is. */
|
|
867
|
+
const toEvaluator = (raw) => {
|
|
868
|
+
return {
|
|
869
|
+
id: raw.uid,
|
|
870
|
+
type: 'evaluator',
|
|
871
|
+
name: raw.name_t,
|
|
872
|
+
description: raw.description_t,
|
|
873
|
+
tags: Array.isArray(raw.tags_ss) ? raw.tags_ss : [],
|
|
874
|
+
public: raw.is_public_b ?? true,
|
|
875
|
+
specId: String(raw.uid || '').replace(/^evaluator:/, ''),
|
|
876
|
+
specVersion: raw.spec_version_s,
|
|
877
|
+
publisher: raw.publisher_s || 'datalayer',
|
|
878
|
+
creationDate: raw.creation_ts_dt
|
|
879
|
+
? new Date(raw.creation_ts_dt)
|
|
880
|
+
: undefined,
|
|
881
|
+
};
|
|
882
|
+
};
|
|
883
|
+
/**
|
|
884
|
+
* A card of the site's own in the featured ribbon — the run the home page is
|
|
885
|
+
* written about, say. The library knows its name and whether it is
|
|
886
|
+
* featured; what it draws and where it leads are in the application's build,
|
|
887
|
+
* keyed by `cardId`.
|
|
888
|
+
*/
|
|
889
|
+
const toSiteCard = (raw) => {
|
|
890
|
+
return {
|
|
891
|
+
id: raw.uid,
|
|
892
|
+
type: 'sitecard',
|
|
893
|
+
name: raw.name_t,
|
|
894
|
+
description: raw.description_t,
|
|
895
|
+
tags: Array.isArray(raw.tags_ss) ? raw.tags_ss : [],
|
|
896
|
+
public: raw.is_public_b ?? true,
|
|
897
|
+
cardId: String(raw.uid || '').replace(/^sitecard:/, ''),
|
|
898
|
+
publisher: raw.publisher_s || 'datalayer',
|
|
899
|
+
};
|
|
900
|
+
};
|
|
585
901
|
const toItemOwner = (raw) => {
|
|
586
902
|
const uid = String(raw?.creator_uid_s ??
|
|
587
903
|
raw?.creator_uid ??
|
|
@@ -593,6 +909,15 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
|
|
|
593
909
|
const lastName = String(raw?.creator_last_name_t ?? raw?.last_name_t ?? '').trim();
|
|
594
910
|
const email = String(raw?.creator_email_s ?? raw?.email_s ?? '').trim();
|
|
595
911
|
const displayName = firstName || lastName ? asDisplayName(firstName, lastName) : handle;
|
|
912
|
+
/*
|
|
913
|
+
* The face, which the library has been projecting all along and nothing
|
|
914
|
+
* was reading. Without it every published artifact was attributed to a
|
|
915
|
+
* pair of initials, on its own public page as much as anywhere else —
|
|
916
|
+
* the owner is right there in the projection, and this is the one place
|
|
917
|
+
* it was being dropped.
|
|
918
|
+
*/
|
|
919
|
+
const avatarUrl = String(raw?.creator_avatar_url_s ?? raw?.avatar_url_s ?? '').trim();
|
|
920
|
+
const avatarIcon = String(raw?.creator_avatar_icon_s ?? raw?.avatar_icon_s ?? '').trim();
|
|
596
921
|
return {
|
|
597
922
|
id: uid,
|
|
598
923
|
handle,
|
|
@@ -601,6 +926,8 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
|
|
|
601
926
|
lastName,
|
|
602
927
|
initials: namesAsInitials(firstName, lastName),
|
|
603
928
|
displayName,
|
|
929
|
+
avatarUrl: avatarUrl || undefined,
|
|
930
|
+
avatarIcon: avatarIcon || undefined,
|
|
604
931
|
roles: [],
|
|
605
932
|
iamProviders: [],
|
|
606
933
|
setRoles: () => { },
|
|
@@ -619,6 +946,17 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
|
|
|
619
946
|
type: 'cell',
|
|
620
947
|
name: cl.name_t,
|
|
621
948
|
description: cl.description_t,
|
|
949
|
+
/*
|
|
950
|
+
* The words its owner filed it under.
|
|
951
|
+
*
|
|
952
|
+
* Every artifact type the library holds carries `tags_ss`, and the six
|
|
953
|
+
* space items — notebook, document, cell, lesson, exercise, assignment
|
|
954
|
+
* — were the six that never read it. So a notebook could be published
|
|
955
|
+
* under tags, be found by them in a search, and arrive at the card that
|
|
956
|
+
* shows them with none: the field was dropped here, one layer below
|
|
957
|
+
* anything that could notice.
|
|
958
|
+
*/
|
|
959
|
+
tags: Array.isArray(cl.tags_ss) ? cl.tags_ss : [],
|
|
622
960
|
source: cl.source_t,
|
|
623
961
|
creationDate: new Date(cl.creation_ts_dt),
|
|
624
962
|
public: cl.is_public_b ?? false,
|
|
@@ -644,6 +982,7 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
|
|
|
644
982
|
type: 'notebook',
|
|
645
983
|
name: raw_notebook.name_t,
|
|
646
984
|
description: raw_notebook.description_t,
|
|
985
|
+
tags: Array.isArray(raw_notebook.tags_ss) ? raw_notebook.tags_ss : [],
|
|
647
986
|
nbformat: raw_notebook.model_s
|
|
648
987
|
? JSON.parse(raw_notebook.model_s)
|
|
649
988
|
: undefined,
|
|
@@ -666,6 +1005,49 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
|
|
|
666
1005
|
},
|
|
667
1006
|
};
|
|
668
1007
|
};
|
|
1008
|
+
/**
|
|
1009
|
+
* Put a deck in the detail cache — but only a whole one.
|
|
1010
|
+
*
|
|
1011
|
+
* A listing answers with the space's items, and an item carries no
|
|
1012
|
+
* `model_s`: the specification lives in S3 and only `GET /decks/{uid}`
|
|
1013
|
+
* inlines it. Seeding the detail cache from such a row left `useDeck`
|
|
1014
|
+
* answering instantly with a deck whose `spec` is undefined — and since
|
|
1015
|
+
* these queries stay fresh for five minutes and do not refetch on mount,
|
|
1016
|
+
* an editor opened from the table drew nothing until the tab was reloaded.
|
|
1017
|
+
* A deck without its specification is not the deck this cache promises.
|
|
1018
|
+
*/
|
|
1019
|
+
const seedDeckDetail = (deck) => {
|
|
1020
|
+
if (deck?.spec) {
|
|
1021
|
+
queryClient.setQueryData(queryKeys.decks.detail(deck.id), deck);
|
|
1022
|
+
}
|
|
1023
|
+
};
|
|
1024
|
+
const toDeck = (raw) => {
|
|
1025
|
+
const owner = toItemOwner(raw);
|
|
1026
|
+
return {
|
|
1027
|
+
id: raw.uid,
|
|
1028
|
+
type: 'deck',
|
|
1029
|
+
name: raw.name_t,
|
|
1030
|
+
description: raw.description_t,
|
|
1031
|
+
tags: Array.isArray(raw.tags_ss) ? raw.tags_ss : [],
|
|
1032
|
+
spec: raw.model_s ? JSON.parse(raw.model_s) : undefined,
|
|
1033
|
+
public: raw.is_public_b ?? false,
|
|
1034
|
+
creationDate: new Date(raw.creation_ts_dt),
|
|
1035
|
+
lastUpdateDate: raw.last_update_ts_dt
|
|
1036
|
+
? new Date(raw.last_update_ts_dt)
|
|
1037
|
+
: undefined,
|
|
1038
|
+
lastPublicationDate: raw.creation_ts_dt
|
|
1039
|
+
? new Date(raw.creation_ts_dt)
|
|
1040
|
+
: undefined,
|
|
1041
|
+
owner,
|
|
1042
|
+
space: {
|
|
1043
|
+
handle: raw.handle_s,
|
|
1044
|
+
},
|
|
1045
|
+
organization: {
|
|
1046
|
+
id: raw.organization_uid_s,
|
|
1047
|
+
handle: raw.organization_handle_s,
|
|
1048
|
+
},
|
|
1049
|
+
};
|
|
1050
|
+
};
|
|
669
1051
|
const toDocument = (doc) => {
|
|
670
1052
|
const owner = toItemOwner(doc);
|
|
671
1053
|
return {
|
|
@@ -673,6 +1055,7 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
|
|
|
673
1055
|
type: 'document',
|
|
674
1056
|
name: doc.name_t,
|
|
675
1057
|
description: doc.description_t,
|
|
1058
|
+
tags: Array.isArray(doc.tags_ss) ? doc.tags_ss : [],
|
|
676
1059
|
model: doc.model_s ? JSON.parse(doc.model_s) : undefined,
|
|
677
1060
|
public: doc.is_public_b ?? false,
|
|
678
1061
|
creationDate: new Date(doc.creation_ts_dt),
|
|
@@ -699,6 +1082,7 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
|
|
|
699
1082
|
type: 'lesson',
|
|
700
1083
|
name: raw_lesson.name_t,
|
|
701
1084
|
description: raw_lesson.description_t,
|
|
1085
|
+
tags: Array.isArray(raw_lesson.tags_ss) ? raw_lesson.tags_ss : [],
|
|
702
1086
|
nbformat: raw_lesson.model_s ? JSON.parse(raw_lesson.model_s) : undefined,
|
|
703
1087
|
public: raw_lesson.is_public_b ?? false,
|
|
704
1088
|
creationDate: new Date(raw_lesson.creation_ts_dt),
|
|
@@ -726,6 +1110,7 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
|
|
|
726
1110
|
type: 'exercise',
|
|
727
1111
|
name: ex.name_t,
|
|
728
1112
|
description: ex.description_t,
|
|
1113
|
+
tags: Array.isArray(ex.tags_ss) ? ex.tags_ss : [],
|
|
729
1114
|
help: ex.help_t,
|
|
730
1115
|
codePre: ex.code_pre_t,
|
|
731
1116
|
codeQuestion: ex.code_question_t,
|
|
@@ -771,6 +1156,7 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
|
|
|
771
1156
|
type: 'assignment',
|
|
772
1157
|
name: raw_assignment.name_t,
|
|
773
1158
|
description: raw_assignment.description_t,
|
|
1159
|
+
tags: Array.isArray(raw_assignment.tags_ss) ? raw_assignment.tags_ss : [],
|
|
774
1160
|
nbformat: raw_assignment.model_s
|
|
775
1161
|
? JSON.parse(raw_assignment.model_s)
|
|
776
1162
|
: undefined,
|
|
@@ -818,6 +1204,13 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
|
|
|
818
1204
|
};
|
|
819
1205
|
const toEvalset = (raw_evalset) => {
|
|
820
1206
|
const owner = toItemOwner(raw_evalset);
|
|
1207
|
+
const numberOrUndefined = (value) => {
|
|
1208
|
+
if (value === null || value === undefined || value === '') {
|
|
1209
|
+
return undefined;
|
|
1210
|
+
}
|
|
1211
|
+
const parsed = Number(value);
|
|
1212
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
1213
|
+
};
|
|
821
1214
|
return {
|
|
822
1215
|
id: raw_evalset.uid,
|
|
823
1216
|
type: 'evalset',
|
|
@@ -831,6 +1224,70 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
|
|
|
831
1224
|
? new Date(raw_evalset.last_update_ts_dt)
|
|
832
1225
|
: undefined,
|
|
833
1226
|
tags: Array.isArray(raw_evalset.tags_ss) ? raw_evalset.tags_ss : [],
|
|
1227
|
+
/*
|
|
1228
|
+
* What the runs of a benchmark say about it, projected onto its
|
|
1229
|
+
* library card whenever a run moves (BENCHMARK.md, B2-10 and B1-09).
|
|
1230
|
+
*
|
|
1231
|
+
* These were dropped here, so a card in the Library or on the home
|
|
1232
|
+
* page could show a name and a description and nothing that would
|
|
1233
|
+
* help anybody choose between two benchmarks — how big it is, what it
|
|
1234
|
+
* has been run on, how well anyone has done, what a run tends to
|
|
1235
|
+
* cost. The projection has been writing them all along.
|
|
1236
|
+
*/
|
|
1237
|
+
category: raw_evalset.category_s || undefined,
|
|
1238
|
+
caseCount: numberOrUndefined(raw_evalset.case_count_i),
|
|
1239
|
+
runCount: numberOrUndefined(raw_evalset.run_count_i),
|
|
1240
|
+
latestPassRate: numberOrUndefined(raw_evalset.latest_pass_rate_f),
|
|
1241
|
+
bestPassRate: numberOrUndefined(raw_evalset.best_pass_rate_f),
|
|
1242
|
+
lastRunStatus: raw_evalset.last_run_status_s || undefined,
|
|
1243
|
+
lastRunAt: raw_evalset.last_run_at_dt
|
|
1244
|
+
? new Date(raw_evalset.last_run_at_dt)
|
|
1245
|
+
: undefined,
|
|
1246
|
+
estimatedCost: numberOrUndefined(raw_evalset.estimated_cost_f),
|
|
1247
|
+
environment: raw_evalset.environment_s || undefined,
|
|
1248
|
+
subjectRefs: Array.isArray(raw_evalset.subject_refs_ss)
|
|
1249
|
+
? raw_evalset.subject_refs_ss
|
|
1250
|
+
: [],
|
|
1251
|
+
owner,
|
|
1252
|
+
};
|
|
1253
|
+
};
|
|
1254
|
+
/**
|
|
1255
|
+
* A published environment version as a library artifact (PLAN_ENV E2-16).
|
|
1256
|
+
*
|
|
1257
|
+
* Keyed on the version uid, so one published version is one card, and read
|
|
1258
|
+
* from the publication's immutable snapshot: its name, title, README (the
|
|
1259
|
+
* description), the licenses its SBOM names (the tags), its size class and
|
|
1260
|
+
* the variants it was built for. Republishing mints a new version — and a
|
|
1261
|
+
* new card — rather than moving this one.
|
|
1262
|
+
*/
|
|
1263
|
+
const toEnvironmentArtifact = (raw) => {
|
|
1264
|
+
const owner = toItemOwner(raw);
|
|
1265
|
+
const numberOrUndefined = (value) => {
|
|
1266
|
+
if (value === null || value === undefined || value === '') {
|
|
1267
|
+
return undefined;
|
|
1268
|
+
}
|
|
1269
|
+
const parsed = Number(value);
|
|
1270
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
1271
|
+
};
|
|
1272
|
+
return {
|
|
1273
|
+
id: raw.uid,
|
|
1274
|
+
type: 'environment',
|
|
1275
|
+
name: raw.name_t,
|
|
1276
|
+
title: raw.title_t || undefined,
|
|
1277
|
+
description: raw.description_t,
|
|
1278
|
+
tags: Array.isArray(raw.tags_ss) ? raw.tags_ss : [],
|
|
1279
|
+
licenses: Array.isArray(raw.tags_ss) ? raw.tags_ss : [],
|
|
1280
|
+
public: raw.is_public_b ?? true,
|
|
1281
|
+
environmentUid: raw.environment_uid_s || undefined,
|
|
1282
|
+
sizeClass: raw.size_class_s || undefined,
|
|
1283
|
+
variants: Array.isArray(raw.variants_ss) ? raw.variants_ss : [],
|
|
1284
|
+
versionNumber: numberOrUndefined(raw.version_number_i),
|
|
1285
|
+
creationDate: raw.creation_ts_dt
|
|
1286
|
+
? new Date(raw.creation_ts_dt)
|
|
1287
|
+
: undefined,
|
|
1288
|
+
lastUpdateDate: raw.source_update_ts_dt
|
|
1289
|
+
? new Date(raw.source_update_ts_dt)
|
|
1290
|
+
: undefined,
|
|
834
1291
|
owner,
|
|
835
1292
|
};
|
|
836
1293
|
};
|
|
@@ -839,6 +1296,13 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
|
|
|
839
1296
|
console.error('No type_s found on item', item);
|
|
840
1297
|
return {};
|
|
841
1298
|
}
|
|
1299
|
+
// The library answers with the same field names a space item has, plus
|
|
1300
|
+
// what only it knows; the type mapper reads the former and
|
|
1301
|
+
// `withLibraryMeta` adds the latter, so one artifact has one shape
|
|
1302
|
+
// wherever it was searched.
|
|
1303
|
+
return { ...toTypedItem(item), ...withLibraryMeta(item) };
|
|
1304
|
+
};
|
|
1305
|
+
const toTypedItem = (item) => {
|
|
842
1306
|
switch (item.type_s) {
|
|
843
1307
|
case 'assignment':
|
|
844
1308
|
return toAssignment(item);
|
|
@@ -883,6 +1347,10 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
|
|
|
883
1347
|
},
|
|
884
1348
|
};
|
|
885
1349
|
}
|
|
1350
|
+
// Without this the mapper answered `{}` for a deck: the card then
|
|
1351
|
+
// read no type and no name, and drew itself as a nameless notebook.
|
|
1352
|
+
case 'deck':
|
|
1353
|
+
return toDeck(item);
|
|
886
1354
|
case 'document':
|
|
887
1355
|
return toDocument(item);
|
|
888
1356
|
case 'exercise':
|
|
@@ -891,10 +1359,24 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
|
|
|
891
1359
|
return toLesson(item);
|
|
892
1360
|
case 'notebook':
|
|
893
1361
|
return toNotebook(item);
|
|
894
|
-
case 'page':
|
|
895
|
-
return toPage(item);
|
|
896
1362
|
case 'evalset':
|
|
897
1363
|
return toEvalset(item);
|
|
1364
|
+
case 'environment':
|
|
1365
|
+
return toEnvironmentArtifact(item);
|
|
1366
|
+
case 'dataset':
|
|
1367
|
+
return toDataset(item);
|
|
1368
|
+
case 'agent':
|
|
1369
|
+
return toAgent(item);
|
|
1370
|
+
case 'dataserver':
|
|
1371
|
+
return toDataserver(item);
|
|
1372
|
+
case 'report':
|
|
1373
|
+
return toReport(item);
|
|
1374
|
+
case 'investigation':
|
|
1375
|
+
return toInvestigation(item);
|
|
1376
|
+
case 'evaluator':
|
|
1377
|
+
return toEvaluator(item);
|
|
1378
|
+
case 'sitecard':
|
|
1379
|
+
return toSiteCard(item);
|
|
898
1380
|
default:
|
|
899
1381
|
return {};
|
|
900
1382
|
}
|
|
@@ -1020,8 +1502,8 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
|
|
|
1020
1502
|
});
|
|
1021
1503
|
},
|
|
1022
1504
|
onSuccess: () => {
|
|
1023
|
-
|
|
1024
|
-
|
|
1505
|
+
forgetSession(queryClient);
|
|
1506
|
+
forgetSessionState();
|
|
1025
1507
|
},
|
|
1026
1508
|
});
|
|
1027
1509
|
};
|
|
@@ -1058,11 +1540,11 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
|
|
|
1058
1540
|
*/
|
|
1059
1541
|
const useUpdateMe = () => {
|
|
1060
1542
|
return useMutation({
|
|
1061
|
-
mutationFn: async ({ email, firstName, lastName, avatarIcon, banner, }) => {
|
|
1543
|
+
mutationFn: async ({ email, firstName, lastName, avatarIcon, banner, showOrbits, }) => {
|
|
1062
1544
|
return requestDatalayer({
|
|
1063
1545
|
url: `${configuration.iamUrl}/api/iam/v1/me`,
|
|
1064
1546
|
method: 'PUT',
|
|
1065
|
-
body: { email, firstName, lastName, avatarIcon, banner },
|
|
1547
|
+
body: { email, firstName, lastName, avatarIcon, banner, showOrbits },
|
|
1066
1548
|
});
|
|
1067
1549
|
},
|
|
1068
1550
|
onSuccess: () => {
|
|
@@ -1296,19 +1778,33 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
|
|
|
1296
1778
|
const useUpdateUserSettings = () => {
|
|
1297
1779
|
return useMutation({
|
|
1298
1780
|
mutationFn: async ({ userId, settings, }) => {
|
|
1781
|
+
// Only the keys the caller actually set. IAM merges what it
|
|
1782
|
+
// receives into the settings document, so sending a key means
|
|
1783
|
+
// changing it — an undefined one is dropped by `JSON.stringify`
|
|
1784
|
+
// and leaves the stored value alone.
|
|
1785
|
+
const body = {};
|
|
1786
|
+
if (settings.aiAgentsUrl !== undefined) {
|
|
1787
|
+
body.aiAgents_url_s = settings.aiAgentsUrl;
|
|
1788
|
+
}
|
|
1789
|
+
if (settings.canInvite !== undefined) {
|
|
1790
|
+
body.can_invite_b = settings.canInvite;
|
|
1791
|
+
}
|
|
1792
|
+
if (settings.docsInPlace !== undefined) {
|
|
1793
|
+
body.docs_in_place_b = settings.docsInPlace;
|
|
1794
|
+
}
|
|
1299
1795
|
return requestDatalayer({
|
|
1300
1796
|
url: `${configuration.iamUrl}/api/iam/v1/users/${userId}/settings`,
|
|
1301
1797
|
method: 'PUT',
|
|
1302
|
-
body
|
|
1303
|
-
aiAgents_url_s: settings.aiAgentsUrl,
|
|
1304
|
-
can_invite_b: settings.canInvite || false,
|
|
1305
|
-
},
|
|
1798
|
+
body,
|
|
1306
1799
|
});
|
|
1307
1800
|
},
|
|
1308
1801
|
onSuccess: (_, variables) => {
|
|
1309
1802
|
queryClient.invalidateQueries({
|
|
1310
1803
|
queryKey: queryKeys.users.detail(variables.userId),
|
|
1311
1804
|
});
|
|
1805
|
+
// The signed-in user carries these settings, and the application
|
|
1806
|
+
// shell reads them from there.
|
|
1807
|
+
queryClient.invalidateQueries({ queryKey: queryKeys.auth.me() });
|
|
1312
1808
|
},
|
|
1313
1809
|
});
|
|
1314
1810
|
};
|
|
@@ -1938,6 +2434,148 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
|
|
|
1938
2434
|
},
|
|
1939
2435
|
});
|
|
1940
2436
|
};
|
|
2437
|
+
/**
|
|
2438
|
+
* Get a deck by id, its specification inlined as `spec`.
|
|
2439
|
+
*/
|
|
2440
|
+
const useDeck = (deckId) => {
|
|
2441
|
+
return useQuery({
|
|
2442
|
+
queryKey: queryKeys.decks.detail(deckId),
|
|
2443
|
+
queryFn: async () => {
|
|
2444
|
+
const resp = await requestDatalayer({
|
|
2445
|
+
url: `${configuration.spacerUrl}/api/spacer/v1/decks/${deckId}`,
|
|
2446
|
+
method: 'GET',
|
|
2447
|
+
});
|
|
2448
|
+
if (resp.success && resp.deck) {
|
|
2449
|
+
return toDeck(resp.deck);
|
|
2450
|
+
}
|
|
2451
|
+
if (resp?.code === 'FORBIDDEN') {
|
|
2452
|
+
throw new Error(resp.message || 'Not authorized to access this deck');
|
|
2453
|
+
}
|
|
2454
|
+
throw new Error(resp.message || 'Failed to fetch deck');
|
|
2455
|
+
},
|
|
2456
|
+
...DEFAULT_QUERY_OPTIONS,
|
|
2457
|
+
enabled: !!deckId,
|
|
2458
|
+
});
|
|
2459
|
+
};
|
|
2460
|
+
/**
|
|
2461
|
+
* Get decks by space
|
|
2462
|
+
*/
|
|
2463
|
+
const useDecksBySpace = (spaceId) => {
|
|
2464
|
+
return useQuery({
|
|
2465
|
+
queryKey: queryKeys.decks.bySpace(spaceId),
|
|
2466
|
+
queryFn: async () => {
|
|
2467
|
+
const resp = await requestDatalayer({
|
|
2468
|
+
url: `${configuration.spacerUrl}/api/spacer/v1/spaces/${spaceId}/items/types/deck`,
|
|
2469
|
+
method: 'GET',
|
|
2470
|
+
});
|
|
2471
|
+
if (resp.success && resp.items) {
|
|
2472
|
+
return resp.items.map((raw) => {
|
|
2473
|
+
const deck = toDeck(raw);
|
|
2474
|
+
seedDeckDetail(deck);
|
|
2475
|
+
return deck;
|
|
2476
|
+
});
|
|
2477
|
+
}
|
|
2478
|
+
return [];
|
|
2479
|
+
},
|
|
2480
|
+
...DEFAULT_QUERY_OPTIONS,
|
|
2481
|
+
enabled: !!spaceId,
|
|
2482
|
+
});
|
|
2483
|
+
};
|
|
2484
|
+
/**
|
|
2485
|
+
* Create a deck — from an uploaded specification, or the starter one.
|
|
2486
|
+
*/
|
|
2487
|
+
const useCreateDeck = () => {
|
|
2488
|
+
return useMutation({
|
|
2489
|
+
mutationFn: async ({ spaceId, name, description, deckType = 'deck', file, }) => {
|
|
2490
|
+
const formData = new FormData();
|
|
2491
|
+
formData.append('space_id', spaceId);
|
|
2492
|
+
// Backward compatibility for endpoints still expecting camelCase.
|
|
2493
|
+
formData.append('spaceId', spaceId);
|
|
2494
|
+
formData.append('deckType', deckType);
|
|
2495
|
+
formData.append('name', name);
|
|
2496
|
+
formData.append('description', description || '');
|
|
2497
|
+
if (file) {
|
|
2498
|
+
formData.append('file', file);
|
|
2499
|
+
}
|
|
2500
|
+
return uploadDeck(formData);
|
|
2501
|
+
},
|
|
2502
|
+
onSuccess: resp => {
|
|
2503
|
+
if (resp.success && resp.deck) {
|
|
2504
|
+
const deck = toDeck(resp.deck);
|
|
2505
|
+
seedDeckDetail(deck);
|
|
2506
|
+
queryClient.refetchQueries({ queryKey: queryKeys.decks.all() });
|
|
2507
|
+
queryClient.refetchQueries({ queryKey: queryKeys.items.all() });
|
|
2508
|
+
}
|
|
2509
|
+
},
|
|
2510
|
+
});
|
|
2511
|
+
};
|
|
2512
|
+
/**
|
|
2513
|
+
* Update a deck's name and description
|
|
2514
|
+
*/
|
|
2515
|
+
const useUpdateDeck = () => {
|
|
2516
|
+
return useMutation({
|
|
2517
|
+
mutationFn: async ({ id, name, description, }) => {
|
|
2518
|
+
return requestDatalayer({
|
|
2519
|
+
url: `${configuration.spacerUrl}/api/spacer/v1/decks/${id}`,
|
|
2520
|
+
method: 'PUT',
|
|
2521
|
+
body: { name, description },
|
|
2522
|
+
});
|
|
2523
|
+
},
|
|
2524
|
+
onSuccess: (resp, deck) => {
|
|
2525
|
+
if (resp.success) {
|
|
2526
|
+
queryClient.invalidateQueries({
|
|
2527
|
+
queryKey: queryKeys.decks.detail(deck.id),
|
|
2528
|
+
});
|
|
2529
|
+
queryClient.invalidateQueries({ queryKey: queryKeys.decks.all() });
|
|
2530
|
+
}
|
|
2531
|
+
},
|
|
2532
|
+
});
|
|
2533
|
+
};
|
|
2534
|
+
/**
|
|
2535
|
+
* Replace a deck's specification, whole.
|
|
2536
|
+
*/
|
|
2537
|
+
const useUpdateDeckModel = () => {
|
|
2538
|
+
return useMutation({
|
|
2539
|
+
mutationFn: async ({ deckId, spec, }) => {
|
|
2540
|
+
return requestDatalayer({
|
|
2541
|
+
url: `${configuration.spacerUrl}/api/spacer/v1/decks/${deckId}/model`,
|
|
2542
|
+
method: 'PUT',
|
|
2543
|
+
body: { spec },
|
|
2544
|
+
});
|
|
2545
|
+
},
|
|
2546
|
+
onSuccess: (_, variables) => {
|
|
2547
|
+
queryClient.invalidateQueries({
|
|
2548
|
+
queryKey: queryKeys.decks.detail(variables.deckId),
|
|
2549
|
+
});
|
|
2550
|
+
queryClient.invalidateQueries({
|
|
2551
|
+
queryKey: queryKeys.decks.model(variables.deckId),
|
|
2552
|
+
});
|
|
2553
|
+
queryClient.invalidateQueries({ queryKey: queryKeys.decks.all() });
|
|
2554
|
+
},
|
|
2555
|
+
});
|
|
2556
|
+
};
|
|
2557
|
+
/**
|
|
2558
|
+
* Clone a deck into the caller's library (or a space they may change).
|
|
2559
|
+
* The spacer writes the specification again under the new uid.
|
|
2560
|
+
*/
|
|
2561
|
+
const useCloneDeck = () => {
|
|
2562
|
+
return useMutation({
|
|
2563
|
+
mutationFn: async (deckId) => {
|
|
2564
|
+
return requestDatalayer({
|
|
2565
|
+
url: `${configuration.spacerUrl}/api/spacer/v1/decks/${deckId}/clone`,
|
|
2566
|
+
method: 'POST',
|
|
2567
|
+
});
|
|
2568
|
+
},
|
|
2569
|
+
onSuccess: resp => {
|
|
2570
|
+
if (resp.success && resp.deck) {
|
|
2571
|
+
const deck = toDeck(resp.deck);
|
|
2572
|
+
seedDeckDetail(deck);
|
|
2573
|
+
queryClient.refetchQueries({ queryKey: queryKeys.decks.all() });
|
|
2574
|
+
queryClient.refetchQueries({ queryKey: queryKeys.items.all() });
|
|
2575
|
+
}
|
|
2576
|
+
},
|
|
2577
|
+
});
|
|
2578
|
+
};
|
|
1941
2579
|
/**
|
|
1942
2580
|
* Clone notebook
|
|
1943
2581
|
*/
|
|
@@ -2133,191 +2771,51 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
|
|
|
2133
2771
|
},
|
|
2134
2772
|
});
|
|
2135
2773
|
};
|
|
2136
|
-
// ============================================================================
|
|
2137
|
-
// Page Hooks
|
|
2138
|
-
// ============================================================================
|
|
2139
|
-
/**
|
|
2140
|
-
* Get page by ID
|
|
2141
|
-
*/
|
|
2142
|
-
const usePage = (pageId) => {
|
|
2143
|
-
return useQuery({
|
|
2144
|
-
queryKey: queryKeys.pages.detail(pageId),
|
|
2145
|
-
queryFn: async () => {
|
|
2146
|
-
const resp = await requestDatalayer({
|
|
2147
|
-
url: `${configuration.libraryUrl}/api/library/v1/pages/${pageId}`,
|
|
2148
|
-
method: 'GET',
|
|
2149
|
-
});
|
|
2150
|
-
if (resp.success && resp.page) {
|
|
2151
|
-
return toPage(resp.page);
|
|
2152
|
-
}
|
|
2153
|
-
throw new Error(resp.message || 'Failed to fetch page');
|
|
2154
|
-
},
|
|
2155
|
-
...DEFAULT_QUERY_OPTIONS,
|
|
2156
|
-
enabled: !!pageId,
|
|
2157
|
-
});
|
|
2158
|
-
};
|
|
2159
2774
|
/**
|
|
2160
|
-
* Get all
|
|
2775
|
+
* Get all secrets
|
|
2161
2776
|
*/
|
|
2162
|
-
|
|
2777
|
+
// TODO: Previously this hook pre-populated individual secret caches with setQueryData,
|
|
2778
|
+
// but this prevented useSecret from fetching fresh data (e.g., the value field).
|
|
2779
|
+
// Consider re-adding cache pre-population if the list endpoint returns full secret data,
|
|
2780
|
+
// or use a different query key pattern for partial vs full secret data.
|
|
2781
|
+
const useSecrets = (options) => {
|
|
2782
|
+
const principalUid = options?.principalUid;
|
|
2783
|
+
const principalKind = options?.principalKind;
|
|
2163
2784
|
return useQuery({
|
|
2164
2785
|
queryKey: [
|
|
2165
|
-
...queryKeys.
|
|
2166
|
-
|
|
2167
|
-
|
|
2786
|
+
...queryKeys.secrets.all(),
|
|
2787
|
+
principalUid || 'self',
|
|
2788
|
+
principalKind || '',
|
|
2168
2789
|
],
|
|
2169
2790
|
queryFn: async () => {
|
|
2170
|
-
const params = new URLSearchParams();
|
|
2171
|
-
if (scope?.selectedPrincipalUid) {
|
|
2172
|
-
params.set('selected_principal_uid', scope.selectedPrincipalUid);
|
|
2173
|
-
}
|
|
2174
|
-
if (scope?.selectedPrincipalKind) {
|
|
2175
|
-
params.set('selected_principal_kind', scope.selectedPrincipalKind);
|
|
2176
|
-
}
|
|
2177
|
-
const query = params.toString();
|
|
2178
2791
|
const resp = await requestDatalayer({
|
|
2179
|
-
url: `${configuration.
|
|
2792
|
+
url: withAccountUidQuery(`${configuration.iamUrl}/api/iam/v1/secrets`, principalUid, principalKind),
|
|
2180
2793
|
method: 'GET',
|
|
2181
2794
|
});
|
|
2182
|
-
if (resp.success && resp.
|
|
2183
|
-
const
|
|
2184
|
-
.map((
|
|
2185
|
-
const page = toPage(p);
|
|
2186
|
-
if (page) {
|
|
2187
|
-
queryClient.setQueryData(queryKeys.pages.detail(page.id), page);
|
|
2188
|
-
}
|
|
2189
|
-
return page;
|
|
2190
|
-
})
|
|
2795
|
+
if (resp.success && resp.secrets) {
|
|
2796
|
+
const secrets = resp.secrets
|
|
2797
|
+
.map((s) => toSecret(s))
|
|
2191
2798
|
.filter(Boolean);
|
|
2192
|
-
return
|
|
2799
|
+
return secrets;
|
|
2193
2800
|
}
|
|
2194
2801
|
return [];
|
|
2195
2802
|
},
|
|
2196
2803
|
...DEFAULT_QUERY_OPTIONS,
|
|
2804
|
+
refetchOnMount: true, // Override to refetch when stale after invalidation
|
|
2197
2805
|
});
|
|
2198
2806
|
};
|
|
2199
2807
|
/**
|
|
2200
|
-
* Create
|
|
2808
|
+
* Create secret
|
|
2201
2809
|
*/
|
|
2202
|
-
const
|
|
2810
|
+
const useCreateSecret = (options) => {
|
|
2811
|
+
const principalUid = options?.principalUid;
|
|
2812
|
+
const principalKind = options?.principalKind;
|
|
2203
2813
|
return useMutation({
|
|
2204
|
-
mutationFn: async (
|
|
2814
|
+
mutationFn: async (secret) => {
|
|
2205
2815
|
return requestDatalayer({
|
|
2206
|
-
url: `${configuration.
|
|
2816
|
+
url: withAccountUidQuery(`${configuration.iamUrl}/api/iam/v1/secrets`, principalUid, principalKind),
|
|
2207
2817
|
method: 'POST',
|
|
2208
|
-
body: { ...
|
|
2209
|
-
});
|
|
2210
|
-
},
|
|
2211
|
-
onSuccess: resp => {
|
|
2212
|
-
if (resp.success && resp.page) {
|
|
2213
|
-
const page = toPage(resp.page);
|
|
2214
|
-
if (page) {
|
|
2215
|
-
// Set detail cache
|
|
2216
|
-
queryClient.setQueryData(queryKeys.pages.detail(page.id), page);
|
|
2217
|
-
// Invalidate list to refetch
|
|
2218
|
-
queryClient.invalidateQueries({
|
|
2219
|
-
queryKey: queryKeys.pages.all(),
|
|
2220
|
-
});
|
|
2221
|
-
}
|
|
2222
|
-
}
|
|
2223
|
-
},
|
|
2224
|
-
});
|
|
2225
|
-
};
|
|
2226
|
-
/**
|
|
2227
|
-
* Update page
|
|
2228
|
-
*/
|
|
2229
|
-
const useUpdatePage = () => {
|
|
2230
|
-
return useMutation({
|
|
2231
|
-
mutationFn: async (page) => {
|
|
2232
|
-
return requestDatalayer({
|
|
2233
|
-
url: `${configuration.libraryUrl}/api/library/v1/pages/${page.id}`,
|
|
2234
|
-
method: 'PUT',
|
|
2235
|
-
body: {
|
|
2236
|
-
name: page.name,
|
|
2237
|
-
description: page.description,
|
|
2238
|
-
tags: page.tags,
|
|
2239
|
-
},
|
|
2240
|
-
});
|
|
2241
|
-
},
|
|
2242
|
-
onSuccess: (resp, page) => {
|
|
2243
|
-
if (resp.success) {
|
|
2244
|
-
// Invalidate detail and list queries
|
|
2245
|
-
queryClient.invalidateQueries({
|
|
2246
|
-
queryKey: queryKeys.pages.detail(page.id),
|
|
2247
|
-
});
|
|
2248
|
-
queryClient.invalidateQueries({
|
|
2249
|
-
queryKey: queryKeys.pages.all(),
|
|
2250
|
-
});
|
|
2251
|
-
}
|
|
2252
|
-
},
|
|
2253
|
-
});
|
|
2254
|
-
};
|
|
2255
|
-
/**
|
|
2256
|
-
* Delete page
|
|
2257
|
-
*/
|
|
2258
|
-
const useDeletePage = () => {
|
|
2259
|
-
return useMutation({
|
|
2260
|
-
mutationFn: async (pageId) => {
|
|
2261
|
-
return requestDatalayer({
|
|
2262
|
-
url: `${configuration.libraryUrl}/api/library/v1/pages/${pageId}`,
|
|
2263
|
-
method: 'DELETE',
|
|
2264
|
-
});
|
|
2265
|
-
},
|
|
2266
|
-
onSuccess: (_, pageId) => {
|
|
2267
|
-
// Remove from detail cache
|
|
2268
|
-
queryClient.removeQueries({ queryKey: queryKeys.pages.detail(pageId) });
|
|
2269
|
-
// Invalidate list to refetch
|
|
2270
|
-
queryClient.invalidateQueries({
|
|
2271
|
-
queryKey: queryKeys.pages.all(),
|
|
2272
|
-
});
|
|
2273
|
-
},
|
|
2274
|
-
});
|
|
2275
|
-
};
|
|
2276
|
-
/**
|
|
2277
|
-
* Get all secrets
|
|
2278
|
-
*/
|
|
2279
|
-
// TODO: Previously this hook pre-populated individual secret caches with setQueryData,
|
|
2280
|
-
// but this prevented useSecret from fetching fresh data (e.g., the value field).
|
|
2281
|
-
// Consider re-adding cache pre-population if the list endpoint returns full secret data,
|
|
2282
|
-
// or use a different query key pattern for partial vs full secret data.
|
|
2283
|
-
const useSecrets = (options) => {
|
|
2284
|
-
const principalUid = options?.principalUid;
|
|
2285
|
-
const principalKind = options?.principalKind;
|
|
2286
|
-
return useQuery({
|
|
2287
|
-
queryKey: [
|
|
2288
|
-
...queryKeys.secrets.all(),
|
|
2289
|
-
principalUid || 'self',
|
|
2290
|
-
principalKind || '',
|
|
2291
|
-
],
|
|
2292
|
-
queryFn: async () => {
|
|
2293
|
-
const resp = await requestDatalayer({
|
|
2294
|
-
url: withAccountUidQuery(`${configuration.iamUrl}/api/iam/v1/secrets`, principalUid, principalKind),
|
|
2295
|
-
method: 'GET',
|
|
2296
|
-
});
|
|
2297
|
-
if (resp.success && resp.secrets) {
|
|
2298
|
-
const secrets = resp.secrets
|
|
2299
|
-
.map((s) => toSecret(s))
|
|
2300
|
-
.filter(Boolean);
|
|
2301
|
-
return secrets;
|
|
2302
|
-
}
|
|
2303
|
-
return [];
|
|
2304
|
-
},
|
|
2305
|
-
...DEFAULT_QUERY_OPTIONS,
|
|
2306
|
-
refetchOnMount: true, // Override to refetch when stale after invalidation
|
|
2307
|
-
});
|
|
2308
|
-
};
|
|
2309
|
-
/**
|
|
2310
|
-
* Create secret
|
|
2311
|
-
*/
|
|
2312
|
-
const useCreateSecret = (options) => {
|
|
2313
|
-
const principalUid = options?.principalUid;
|
|
2314
|
-
const principalKind = options?.principalKind;
|
|
2315
|
-
return useMutation({
|
|
2316
|
-
mutationFn: async (secret) => {
|
|
2317
|
-
return requestDatalayer({
|
|
2318
|
-
url: withAccountUidQuery(`${configuration.iamUrl}/api/iam/v1/secrets`, principalUid, principalKind),
|
|
2319
|
-
method: 'POST',
|
|
2320
|
-
body: { ...secret },
|
|
2818
|
+
body: { ...secret },
|
|
2321
2819
|
});
|
|
2322
2820
|
},
|
|
2323
2821
|
onSuccess: resp => {
|
|
@@ -2565,6 +3063,7 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
|
|
|
2565
3063
|
queryClient.removeQueries({
|
|
2566
3064
|
queryKey: queryKeys.documents.detail(itemId),
|
|
2567
3065
|
});
|
|
3066
|
+
queryClient.removeQueries({ queryKey: queryKeys.decks.detail(itemId) });
|
|
2568
3067
|
queryClient.removeQueries({ queryKey: queryKeys.cells.detail(itemId) });
|
|
2569
3068
|
queryClient.removeQueries({
|
|
2570
3069
|
queryKey: queryKeys.lessons.detail(itemId),
|
|
@@ -2578,6 +3077,7 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
|
|
|
2578
3077
|
// Invalidate all artifact type queries
|
|
2579
3078
|
queryClient.invalidateQueries({ queryKey: queryKeys.notebooks.all() });
|
|
2580
3079
|
queryClient.invalidateQueries({ queryKey: queryKeys.documents.all() });
|
|
3080
|
+
queryClient.invalidateQueries({ queryKey: queryKeys.decks.all() });
|
|
2581
3081
|
queryClient.invalidateQueries({ queryKey: queryKeys.cells.all() });
|
|
2582
3082
|
queryClient.invalidateQueries({ queryKey: queryKeys.lessons.all() });
|
|
2583
3083
|
queryClient.invalidateQueries({ queryKey: queryKeys.exercises.all() });
|
|
@@ -3525,25 +4025,6 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
|
|
|
3525
4025
|
},
|
|
3526
4026
|
});
|
|
3527
4027
|
};
|
|
3528
|
-
/**
|
|
3529
|
-
* Get public courses
|
|
3530
|
-
*/
|
|
3531
|
-
const usePublicCourses = () => {
|
|
3532
|
-
return useQuery({
|
|
3533
|
-
queryKey: ['courses', 'public'],
|
|
3534
|
-
queryFn: async () => {
|
|
3535
|
-
const resp = await requestDatalayer({
|
|
3536
|
-
url: `${configuration.libraryUrl}/api/library/v1/courses/public`,
|
|
3537
|
-
method: 'GET',
|
|
3538
|
-
});
|
|
3539
|
-
if (resp.success && resp.courses) {
|
|
3540
|
-
return resp.courses.map((course) => toCourse(course));
|
|
3541
|
-
}
|
|
3542
|
-
return [];
|
|
3543
|
-
},
|
|
3544
|
-
...DEFAULT_QUERY_OPTIONS,
|
|
3545
|
-
});
|
|
3546
|
-
};
|
|
3547
4028
|
/**
|
|
3548
4029
|
* Get instructor courses
|
|
3549
4030
|
*/
|
|
@@ -5830,284 +6311,936 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
|
|
|
5830
6311
|
/**
|
|
5831
6312
|
* Search public items (notebooks, documents, etc.)
|
|
5832
6313
|
*/
|
|
6314
|
+
/**
|
|
6315
|
+
* Every artifact type a library card is drawn for. One list, so a caller
|
|
6316
|
+
* that wants "everything" cannot accidentally ask for the six types someone
|
|
6317
|
+
* wrote out by hand two releases ago.
|
|
6318
|
+
*
|
|
6319
|
+
* `deck` is here because the administration page's "not in the ribbon" list
|
|
6320
|
+
* asks for this default: without it a published deck was in the library and
|
|
6321
|
+
* could be found by searching, but was never offered to be featured.
|
|
6322
|
+
*/
|
|
6323
|
+
const LIBRARY_ARTIFACT_TYPES = [
|
|
6324
|
+
'notebook',
|
|
6325
|
+
'document',
|
|
6326
|
+
'deck',
|
|
6327
|
+
'cell',
|
|
6328
|
+
'lesson',
|
|
6329
|
+
'exercise',
|
|
6330
|
+
'assignment',
|
|
6331
|
+
'course',
|
|
6332
|
+
'evalset',
|
|
6333
|
+
'dataset',
|
|
6334
|
+
'agent',
|
|
6335
|
+
];
|
|
6336
|
+
const librarySearchQueryString = ({ q, types, max = 20, offset = 0, sort = 'relevance', featured = false, owner, }) => {
|
|
6337
|
+
const normalizedTypes = Array.from(new Set((types || [])
|
|
6338
|
+
.map(type => String(type || '')
|
|
6339
|
+
.trim()
|
|
6340
|
+
.toLowerCase())
|
|
6341
|
+
.filter(Boolean)
|
|
6342
|
+
.map(type => (type === 'eval' ? 'evalset' : type))));
|
|
6343
|
+
const params = {
|
|
6344
|
+
q: q || '*',
|
|
6345
|
+
types: (normalizedTypes.length > 0
|
|
6346
|
+
? normalizedTypes
|
|
6347
|
+
: LIBRARY_ARTIFACT_TYPES).join(' '),
|
|
6348
|
+
max: String(max),
|
|
6349
|
+
offset: String(offset),
|
|
6350
|
+
sort,
|
|
6351
|
+
};
|
|
6352
|
+
if (featured) {
|
|
6353
|
+
params.featured = 'true';
|
|
6354
|
+
}
|
|
6355
|
+
if (owner) {
|
|
6356
|
+
params.owner = owner;
|
|
6357
|
+
}
|
|
6358
|
+
return Object.entries(params)
|
|
6359
|
+
.map(([key, value]) => `${key}=${encodeURIComponent(value)}`)
|
|
6360
|
+
.join('&');
|
|
6361
|
+
};
|
|
6362
|
+
const searchLibrary = async (args) => {
|
|
6363
|
+
const resp = await requestDatalayer({
|
|
6364
|
+
url: `${configuration.libraryUrl}/api/library/v1/search?${librarySearchQueryString(args)}`,
|
|
6365
|
+
method: 'GET',
|
|
6366
|
+
});
|
|
6367
|
+
if (!resp.success) {
|
|
6368
|
+
throw new Error(resp.message || 'Failed to search the library');
|
|
6369
|
+
}
|
|
6370
|
+
return {
|
|
6371
|
+
items: (resp.items || [])
|
|
6372
|
+
.map((item) => toItem(item))
|
|
6373
|
+
.filter(Boolean),
|
|
6374
|
+
total: Number(resp.total ?? 0),
|
|
6375
|
+
offset: Number(resp.offset ?? args.offset ?? 0),
|
|
6376
|
+
};
|
|
6377
|
+
};
|
|
6378
|
+
/**
|
|
6379
|
+
* Search the library: one request, whatever mix of artifact types is asked
|
|
6380
|
+
* for, with the total so a caller can page without asking twice.
|
|
6381
|
+
*
|
|
6382
|
+
* Answers `{ items, total, offset }` rather than a bare array — a result
|
|
6383
|
+
* page without its total cannot be paged, and every caller was inventing
|
|
6384
|
+
* "there is probably more" from the length it got.
|
|
6385
|
+
*/
|
|
5833
6386
|
const useSearchPublicItems = () => {
|
|
5834
6387
|
return useMutation({
|
|
5835
|
-
mutationFn: async (
|
|
5836
|
-
|
|
5837
|
-
|
|
5838
|
-
|
|
5839
|
-
|
|
5840
|
-
|
|
5841
|
-
|
|
5842
|
-
|
|
5843
|
-
|
|
5844
|
-
|
|
5845
|
-
|
|
5846
|
-
|
|
5847
|
-
|
|
5848
|
-
|
|
5849
|
-
|
|
5850
|
-
|
|
6388
|
+
mutationFn: async (args) => searchLibrary(args),
|
|
6389
|
+
});
|
|
6390
|
+
};
|
|
6391
|
+
/**
|
|
6392
|
+
* Everything the library holds, as a query rather than a mutation.
|
|
6393
|
+
*
|
|
6394
|
+
* For the administration page, which has to show what *could* be featured
|
|
6395
|
+
* as well as what is. Publishing the agent catalogue puts artifacts in the
|
|
6396
|
+
* library; without this the page reported "6 agents published" and then
|
|
6397
|
+
* listed none of them, because the only list it had was the featured one.
|
|
6398
|
+
*/
|
|
6399
|
+
const useLibraryArtifacts = (max = 100, options) => {
|
|
6400
|
+
return useQuery({
|
|
6401
|
+
queryKey: [...queryKeys.items.all(), 'library-artifacts', max],
|
|
6402
|
+
queryFn: async () => searchLibrary({ max, sort: 'recent' }),
|
|
6403
|
+
...options,
|
|
6404
|
+
});
|
|
6405
|
+
};
|
|
6406
|
+
/**
|
|
6407
|
+
* The featured artifacts, for the home page's ribbon.
|
|
6408
|
+
*
|
|
6409
|
+
* Anonymous-safe: the library answers this one without a token, and the
|
|
6410
|
+
* service holds it for a minute, so a visit costs the platform nothing.
|
|
6411
|
+
*/
|
|
6412
|
+
const useFeaturedItems = (max = 12, options) => {
|
|
6413
|
+
return useQuery({
|
|
6414
|
+
queryKey: [...queryKeys.items.featured(), max],
|
|
6415
|
+
queryFn: async () => {
|
|
5851
6416
|
const resp = await requestDatalayer({
|
|
5852
|
-
url: `${configuration.libraryUrl}/api/library/v1/
|
|
6417
|
+
url: `${configuration.libraryUrl}/api/library/v1/featured?max=${max}`,
|
|
5853
6418
|
method: 'GET',
|
|
5854
6419
|
});
|
|
5855
6420
|
if (!resp.success) {
|
|
5856
|
-
throw new Error(resp.message || 'Failed to
|
|
6421
|
+
throw new Error(resp.message || 'Failed to get the featured artifacts');
|
|
5857
6422
|
}
|
|
5858
6423
|
return (resp.items || [])
|
|
5859
6424
|
.map((item) => toItem(item))
|
|
5860
6425
|
.filter(Boolean);
|
|
5861
6426
|
},
|
|
6427
|
+
staleTime: 60_000,
|
|
6428
|
+
...options,
|
|
5862
6429
|
});
|
|
5863
6430
|
};
|
|
5864
|
-
// ============================================================================
|
|
5865
|
-
// Social Media Integrations
|
|
5866
|
-
// ============================================================================
|
|
5867
6431
|
/**
|
|
5868
|
-
*
|
|
5869
|
-
|
|
5870
|
-
|
|
5871
|
-
|
|
5872
|
-
|
|
5873
|
-
|
|
6432
|
+
* Everything an administrator has actually featured, and nothing else.
|
|
6433
|
+
*
|
|
6434
|
+
* Not `useFeaturedItems`. That one answers for the home page's ribbon: a
|
|
6435
|
+
* public request, capped at what a row can show, ordered for reading. This
|
|
6436
|
+
* one is what the administration page edits — everything featured, in rank
|
|
6437
|
+
* order, however long the list is and whoever it belongs to — so that the
|
|
6438
|
+
* page showing the ribbon's contents is showing the ribbon's contents and
|
|
6439
|
+
* not a page of it. `platform_admin` only.
|
|
6440
|
+
*/
|
|
6441
|
+
const useAllFeaturedItems = (options) => {
|
|
6442
|
+
return useQuery({
|
|
6443
|
+
queryKey: [...queryKeys.items.featured(), 'all'],
|
|
6444
|
+
queryFn: async () => {
|
|
6445
|
+
const resp = await requestDatalayer({
|
|
6446
|
+
url: `${configuration.libraryUrl}/api/library/v1/featured/all`,
|
|
5874
6447
|
method: 'GET',
|
|
5875
|
-
headers: {
|
|
5876
|
-
Accept: 'application/vnd.github+json',
|
|
5877
|
-
Authorization: `Bearer ${accessToken}`,
|
|
5878
|
-
'X-GitHub-Api-Version': '2022-11-28',
|
|
5879
|
-
},
|
|
5880
6448
|
});
|
|
5881
|
-
|
|
6449
|
+
if (!resp.success) {
|
|
6450
|
+
throw new Error(resp.message || 'Failed to get the featured artifacts');
|
|
6451
|
+
}
|
|
6452
|
+
return (resp.items || [])
|
|
6453
|
+
.map((item) => toItem(item))
|
|
6454
|
+
.filter(Boolean);
|
|
5882
6455
|
},
|
|
6456
|
+
...options,
|
|
5883
6457
|
});
|
|
5884
6458
|
};
|
|
5885
6459
|
/**
|
|
5886
|
-
*
|
|
6460
|
+
* One public artifact by uid, with its content and its library metadata.
|
|
6461
|
+
*
|
|
6462
|
+
* The public pages used to `fetch` this endpoint themselves, which meant no
|
|
6463
|
+
* token was ever sent and the page could not know whether the visitor
|
|
6464
|
+
* orbits what they are reading.
|
|
5887
6465
|
*/
|
|
5888
|
-
const
|
|
5889
|
-
return
|
|
5890
|
-
|
|
5891
|
-
|
|
6466
|
+
const usePublicItem = (itemId, options) => {
|
|
6467
|
+
return useQuery({
|
|
6468
|
+
queryKey: queryKeys.items.publicItem(itemId || ''),
|
|
6469
|
+
queryFn: async () => {
|
|
6470
|
+
const resp = await requestDatalayer({
|
|
6471
|
+
url: `${configuration.libraryUrl}/api/library/v1/public/items/${encodeURIComponent(itemId)}`,
|
|
5892
6472
|
method: 'GET',
|
|
5893
|
-
headers: {
|
|
5894
|
-
Accept: 'application/json',
|
|
5895
|
-
Authorization: `Bearer ${accessToken}`,
|
|
5896
|
-
},
|
|
5897
6473
|
});
|
|
5898
|
-
|
|
6474
|
+
if (!resp.success || !resp.item) {
|
|
6475
|
+
throw new Error(resp.message || 'Failed to get the public artifact');
|
|
6476
|
+
}
|
|
6477
|
+
return toItem(resp.item);
|
|
5899
6478
|
},
|
|
6479
|
+
enabled: Boolean(itemId),
|
|
6480
|
+
...options,
|
|
5900
6481
|
});
|
|
5901
6482
|
};
|
|
5902
|
-
/**
|
|
5903
|
-
|
|
5904
|
-
|
|
5905
|
-
|
|
5906
|
-
|
|
5907
|
-
|
|
5908
|
-
|
|
5909
|
-
|
|
5910
|
-
|
|
5911
|
-
|
|
5912
|
-
|
|
5913
|
-
|
|
5914
|
-
|
|
5915
|
-
|
|
6483
|
+
/** The artifacts this account has published: a public profile page. */
|
|
6484
|
+
const useAccountPublications = (handle, options) => {
|
|
6485
|
+
return useQuery({
|
|
6486
|
+
queryKey: queryKeys.items.byOwner(handle || ''),
|
|
6487
|
+
queryFn: async () => searchLibrary({ owner: handle, max: 100, sort: 'recent' }),
|
|
6488
|
+
enabled: Boolean(handle),
|
|
6489
|
+
...options,
|
|
6490
|
+
});
|
|
6491
|
+
};
|
|
6492
|
+
/** The artifacts the signed-in visitor orbits. */
|
|
6493
|
+
const useMyOrbits = (offset = 0, max = 20, options) => {
|
|
6494
|
+
return useQuery({
|
|
6495
|
+
queryKey: [...queryKeys.items.orbits(), offset, max],
|
|
6496
|
+
queryFn: async () => {
|
|
6497
|
+
const resp = await requestDatalayer({
|
|
6498
|
+
url: `${configuration.libraryUrl}/api/library/v1/orbits?offset=${offset}&max=${max}`,
|
|
6499
|
+
method: 'GET',
|
|
5916
6500
|
});
|
|
6501
|
+
if (!resp.success) {
|
|
6502
|
+
throw new Error(resp.message || 'Failed to get the orbits');
|
|
6503
|
+
}
|
|
6504
|
+
return {
|
|
6505
|
+
items: (resp.items || [])
|
|
6506
|
+
.map((item) => toItem(item))
|
|
6507
|
+
.filter(Boolean),
|
|
6508
|
+
total: Number(resp.total ?? 0),
|
|
6509
|
+
};
|
|
5917
6510
|
},
|
|
6511
|
+
...options,
|
|
5918
6512
|
});
|
|
5919
6513
|
};
|
|
5920
6514
|
/**
|
|
5921
|
-
*
|
|
6515
|
+
* What one account orbits, on its public profile.
|
|
6516
|
+
*
|
|
6517
|
+
* Whether it publishes them at all is the service's answer, not this
|
|
6518
|
+
* caller's: `shown` says so, and an account that has not opted in reads the
|
|
6519
|
+
* same as one that orbits nothing.
|
|
5922
6520
|
*/
|
|
5923
|
-
const
|
|
5924
|
-
return
|
|
5925
|
-
|
|
5926
|
-
|
|
5927
|
-
|
|
5928
|
-
|
|
5929
|
-
|
|
5930
|
-
|
|
5931
|
-
|
|
5932
|
-
|
|
5933
|
-
|
|
5934
|
-
}
|
|
5935
|
-
|
|
5936
|
-
|
|
5937
|
-
|
|
6521
|
+
const useAccountOrbits = (handle, offset = 0, max = 12, options) => {
|
|
6522
|
+
return useQuery({
|
|
6523
|
+
queryKey: [
|
|
6524
|
+
...queryKeys.items.orbits(),
|
|
6525
|
+
'account',
|
|
6526
|
+
handle || '',
|
|
6527
|
+
offset,
|
|
6528
|
+
max,
|
|
6529
|
+
],
|
|
6530
|
+
queryFn: async () => {
|
|
6531
|
+
const resp = await requestDatalayer({
|
|
6532
|
+
url: `${configuration.libraryUrl}/api/library/v1/accounts/${encodeURIComponent(handle)}/orbits?offset=${offset}&max=${max}`,
|
|
6533
|
+
method: 'GET',
|
|
6534
|
+
});
|
|
6535
|
+
if (!resp.success) {
|
|
6536
|
+
throw new Error(resp.message || 'Failed to get the orbits');
|
|
6537
|
+
}
|
|
6538
|
+
return {
|
|
6539
|
+
items: (resp.items || [])
|
|
6540
|
+
.map((item) => toItem(item))
|
|
6541
|
+
.filter(Boolean),
|
|
6542
|
+
total: Number(resp.total ?? 0),
|
|
6543
|
+
shown: Boolean(resp.shown),
|
|
5938
6544
|
};
|
|
5939
|
-
|
|
5940
|
-
|
|
5941
|
-
|
|
5942
|
-
|
|
5943
|
-
|
|
5944
|
-
|
|
5945
|
-
|
|
5946
|
-
|
|
5947
|
-
|
|
6545
|
+
},
|
|
6546
|
+
enabled: Boolean(handle),
|
|
6547
|
+
...options,
|
|
6548
|
+
});
|
|
6549
|
+
};
|
|
6550
|
+
/** Who orbits an artifact. */
|
|
6551
|
+
const useItemOrbiters = (itemId, offset = 0, max = 20, options) => {
|
|
6552
|
+
return useQuery({
|
|
6553
|
+
queryKey: [
|
|
6554
|
+
...queryKeys.items.orbiters(itemId || ''),
|
|
6555
|
+
offset,
|
|
6556
|
+
max,
|
|
6557
|
+
],
|
|
6558
|
+
queryFn: async () => {
|
|
6559
|
+
const resp = await requestDatalayer({
|
|
6560
|
+
url: `${configuration.libraryUrl}/api/library/v1/items/${encodeURIComponent(itemId)}/orbiters?offset=${offset}&max=${max}`,
|
|
6561
|
+
method: 'GET',
|
|
5948
6562
|
});
|
|
6563
|
+
if (!resp.success) {
|
|
6564
|
+
throw new Error(resp.message || 'Failed to get the orbiters');
|
|
6565
|
+
}
|
|
6566
|
+
return {
|
|
6567
|
+
orbiters: resp.orbiters || [],
|
|
6568
|
+
total: Number(resp.total ?? 0),
|
|
6569
|
+
};
|
|
5949
6570
|
},
|
|
6571
|
+
enabled: Boolean(itemId),
|
|
6572
|
+
...options,
|
|
5950
6573
|
});
|
|
5951
6574
|
};
|
|
5952
6575
|
/**
|
|
5953
|
-
*
|
|
6576
|
+
* What was made of a public artifact (B5-04): every derivation counted by
|
|
6577
|
+
* kind, and the derived artifacts that are public themselves, newest first.
|
|
5954
6578
|
*/
|
|
5955
|
-
const
|
|
5956
|
-
return
|
|
5957
|
-
|
|
5958
|
-
|
|
5959
|
-
|
|
5960
|
-
|
|
5961
|
-
|
|
5962
|
-
|
|
5963
|
-
|
|
5964
|
-
|
|
5965
|
-
|
|
5966
|
-
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
},
|
|
5970
|
-
],
|
|
5971
|
-
},
|
|
5972
|
-
};
|
|
5973
|
-
const registerResp = await requestDatalayer({
|
|
5974
|
-
url: `${configuration.iamUrl}/api/iam/v1/proxy/request`,
|
|
5975
|
-
method: 'POST',
|
|
5976
|
-
body: {
|
|
5977
|
-
request_method: 'POST',
|
|
5978
|
-
request_url: 'https://api.linkedin.com/v2/assets?action=registerUpload',
|
|
5979
|
-
request_token: accessToken,
|
|
5980
|
-
request_body: registerUploadRequest,
|
|
5981
|
-
},
|
|
5982
|
-
});
|
|
5983
|
-
const asset = registerResp.response.value.asset;
|
|
5984
|
-
const uploadUrl = registerResp.response.value.uploadMechanism['com.linkedin.digitalmedia.uploading.MediaUploadHttpRequest'].uploadUrl;
|
|
5985
|
-
// Step 2: Upload image
|
|
5986
|
-
await requestDatalayer({
|
|
5987
|
-
url: `${configuration.iamUrl}/api/iam/v1/proxy/request`,
|
|
5988
|
-
method: 'POST',
|
|
5989
|
-
body: {
|
|
5990
|
-
request_method: 'PUT',
|
|
5991
|
-
request_url: uploadUrl,
|
|
5992
|
-
request_token: accessToken,
|
|
5993
|
-
request_body: {
|
|
5994
|
-
uploadURL: uploadUrl,
|
|
5995
|
-
content: uploadObject,
|
|
5996
|
-
userURN: linkedinUserUrn,
|
|
5997
|
-
},
|
|
5998
|
-
},
|
|
5999
|
-
});
|
|
6000
|
-
// Step 3: Create share with media
|
|
6001
|
-
shareRequest = {
|
|
6002
|
-
author: linkedinUserUrn,
|
|
6003
|
-
lifecycleState: 'PUBLISHED',
|
|
6004
|
-
specificContent: {
|
|
6005
|
-
'com.linkedin.ugc.ShareContent': {
|
|
6006
|
-
shareCommentary: { text: postText },
|
|
6007
|
-
shareMediaCategory: 'IMAGE',
|
|
6008
|
-
media: [
|
|
6009
|
-
{
|
|
6010
|
-
status: 'READY',
|
|
6011
|
-
description: { text: 'Datalayer Notebook' },
|
|
6012
|
-
media: asset,
|
|
6013
|
-
title: { text: 'Datalayer Notebook' },
|
|
6014
|
-
},
|
|
6015
|
-
],
|
|
6016
|
-
},
|
|
6017
|
-
},
|
|
6018
|
-
visibility: {
|
|
6019
|
-
'com.linkedin.ugc.MemberNetworkVisibility': 'PUBLIC',
|
|
6020
|
-
},
|
|
6021
|
-
};
|
|
6579
|
+
const useItemDerivations = (itemId, offset = 0, max = 20, options) => {
|
|
6580
|
+
return useQuery({
|
|
6581
|
+
queryKey: [
|
|
6582
|
+
...queryKeys.items.derivations(itemId || ''),
|
|
6583
|
+
offset,
|
|
6584
|
+
max,
|
|
6585
|
+
],
|
|
6586
|
+
queryFn: async () => {
|
|
6587
|
+
const resp = await requestDatalayer({
|
|
6588
|
+
url: `${configuration.libraryUrl}/api/library/v1/items/${encodeURIComponent(itemId)}/derivations?offset=${offset}&max=${max}`,
|
|
6589
|
+
method: 'GET',
|
|
6590
|
+
});
|
|
6591
|
+
if (!resp.success) {
|
|
6592
|
+
throw new Error(resp.message || 'Failed to get the derivations');
|
|
6022
6593
|
}
|
|
6023
|
-
|
|
6024
|
-
|
|
6025
|
-
|
|
6026
|
-
|
|
6027
|
-
|
|
6028
|
-
|
|
6029
|
-
|
|
6030
|
-
|
|
6031
|
-
|
|
6032
|
-
|
|
6033
|
-
|
|
6034
|
-
|
|
6035
|
-
|
|
6036
|
-
|
|
6037
|
-
|
|
6594
|
+
return {
|
|
6595
|
+
derivations: (resp.derivations || []).map((derivation) => ({
|
|
6596
|
+
kind: String(derivation.kind || ''),
|
|
6597
|
+
fromVersion: derivation.fromVersion ?? null,
|
|
6598
|
+
derivedAt: derivation.derivedAt,
|
|
6599
|
+
item: toItem(derivation.item),
|
|
6600
|
+
})),
|
|
6601
|
+
counts: (resp.counts || {}),
|
|
6602
|
+
total: Number(resp.total ?? 0),
|
|
6603
|
+
};
|
|
6604
|
+
},
|
|
6605
|
+
enabled: Boolean(itemId),
|
|
6606
|
+
...options,
|
|
6607
|
+
});
|
|
6608
|
+
};
|
|
6609
|
+
/**
|
|
6610
|
+
* Show an artifact as orbited before the server has said so, and put it
|
|
6611
|
+
* back if the server disagrees.
|
|
6612
|
+
*
|
|
6613
|
+
* A star that waits for a round trip feels broken, and the count is on the
|
|
6614
|
+
* button the visitor just pressed. The server answers with a recount, which
|
|
6615
|
+
* is what finally lands in the cache.
|
|
6616
|
+
*/
|
|
6617
|
+
const applyOrbitToCaches = (itemId, orbited, orbits) => {
|
|
6618
|
+
const patch = (item) => {
|
|
6619
|
+
if (!item || String(item.id ?? item.uid) !== String(itemId)) {
|
|
6620
|
+
return item;
|
|
6621
|
+
}
|
|
6622
|
+
const nextOrbits = orbits !== undefined
|
|
6623
|
+
? orbits
|
|
6624
|
+
: Math.max(0, Number(item.orbits || 0) + (orbited ? 1 : -1));
|
|
6625
|
+
return { ...item, orbited, orbits: nextOrbits };
|
|
6626
|
+
};
|
|
6627
|
+
queryClient.setQueriesData({ queryKey: queryKeys.items.all() }, (data) => {
|
|
6628
|
+
if (!data) {
|
|
6629
|
+
return data;
|
|
6630
|
+
}
|
|
6631
|
+
if (Array.isArray(data)) {
|
|
6632
|
+
return data.map(patch);
|
|
6633
|
+
}
|
|
6634
|
+
if (Array.isArray(data.items)) {
|
|
6635
|
+
return { ...data, items: data.items.map(patch) };
|
|
6636
|
+
}
|
|
6637
|
+
if (data.id || data.uid) {
|
|
6638
|
+
return patch(data);
|
|
6639
|
+
}
|
|
6640
|
+
return data;
|
|
6641
|
+
});
|
|
6642
|
+
};
|
|
6643
|
+
const useOrbitItem = () => {
|
|
6644
|
+
return useMutation({
|
|
6645
|
+
mutationFn: async (itemId) => {
|
|
6646
|
+
const resp = await requestDatalayer({
|
|
6647
|
+
url: `${configuration.libraryUrl}/api/library/v1/items/${encodeURIComponent(itemId)}/orbit`,
|
|
6648
|
+
method: 'PUT',
|
|
6649
|
+
});
|
|
6650
|
+
if (!resp.success) {
|
|
6651
|
+
throw new Error(resp.message || 'Failed to orbit the artifact');
|
|
6038
6652
|
}
|
|
6039
|
-
return
|
|
6040
|
-
|
|
6653
|
+
return { itemId, orbits: Number(resp.orbits ?? 0), orbited: true };
|
|
6654
|
+
},
|
|
6655
|
+
onMutate: async (itemId) => {
|
|
6656
|
+
applyOrbitToCaches(itemId, true);
|
|
6657
|
+
return { itemId };
|
|
6658
|
+
},
|
|
6659
|
+
onError: (_error, itemId) => {
|
|
6660
|
+
applyOrbitToCaches(itemId, false);
|
|
6661
|
+
},
|
|
6662
|
+
onSuccess: result => {
|
|
6663
|
+
applyOrbitToCaches(result.itemId, true, result.orbits);
|
|
6664
|
+
queryClient.invalidateQueries({ queryKey: queryKeys.items.orbits() });
|
|
6665
|
+
},
|
|
6666
|
+
});
|
|
6667
|
+
};
|
|
6668
|
+
const useUnorbitItem = () => {
|
|
6669
|
+
return useMutation({
|
|
6670
|
+
mutationFn: async (itemId) => {
|
|
6671
|
+
const resp = await requestDatalayer({
|
|
6672
|
+
url: `${configuration.libraryUrl}/api/library/v1/items/${encodeURIComponent(itemId)}/orbit`,
|
|
6673
|
+
method: 'DELETE',
|
|
6674
|
+
});
|
|
6675
|
+
if (!resp.success) {
|
|
6676
|
+
throw new Error(resp.message || 'Failed to leave the orbit');
|
|
6677
|
+
}
|
|
6678
|
+
return { itemId, orbits: Number(resp.orbits ?? 0), orbited: false };
|
|
6679
|
+
},
|
|
6680
|
+
onMutate: async (itemId) => {
|
|
6681
|
+
applyOrbitToCaches(itemId, false);
|
|
6682
|
+
return { itemId };
|
|
6683
|
+
},
|
|
6684
|
+
onError: (_error, itemId) => {
|
|
6685
|
+
applyOrbitToCaches(itemId, true);
|
|
6686
|
+
},
|
|
6687
|
+
onSuccess: result => {
|
|
6688
|
+
applyOrbitToCaches(result.itemId, false, result.orbits);
|
|
6689
|
+
queryClient.invalidateQueries({ queryKey: queryKeys.items.orbits() });
|
|
6690
|
+
},
|
|
6691
|
+
});
|
|
6692
|
+
};
|
|
6693
|
+
/** Feature an artifact on the home page. `platform_admin` only. */
|
|
6694
|
+
const useFeatureItem = () => {
|
|
6695
|
+
return useMutation({
|
|
6696
|
+
mutationFn: async ({ itemId, rank, }) => {
|
|
6697
|
+
const resp = await requestDatalayer({
|
|
6698
|
+
url: `${configuration.libraryUrl}/api/library/v1/items/${encodeURIComponent(itemId)}/featured`,
|
|
6699
|
+
method: 'PUT',
|
|
6700
|
+
body: { rank: rank ?? null },
|
|
6701
|
+
// One toast, not two: the caller says which artifact failed and
|
|
6702
|
+
// why, so the generic report underneath is a duplicate.
|
|
6703
|
+
notifyOnError: false,
|
|
6704
|
+
});
|
|
6705
|
+
if (!resp.success) {
|
|
6706
|
+
throw new Error(resp.message || 'Failed to feature the artifact');
|
|
6707
|
+
}
|
|
6708
|
+
return resp;
|
|
6709
|
+
},
|
|
6710
|
+
onSuccess: () => {
|
|
6711
|
+
invalidateEverywhere(queryClient, queryKeys.items.all());
|
|
6712
|
+
},
|
|
6713
|
+
});
|
|
6714
|
+
};
|
|
6715
|
+
const useUnfeatureItem = () => {
|
|
6716
|
+
return useMutation({
|
|
6717
|
+
mutationFn: async (itemId) => {
|
|
6718
|
+
const resp = await requestDatalayer({
|
|
6719
|
+
url: `${configuration.libraryUrl}/api/library/v1/items/${encodeURIComponent(itemId)}/featured`,
|
|
6720
|
+
method: 'DELETE',
|
|
6721
|
+
// One toast, not two: the caller says which artifact failed and
|
|
6722
|
+
// why, so the generic report underneath is a duplicate.
|
|
6723
|
+
notifyOnError: false,
|
|
6724
|
+
});
|
|
6725
|
+
if (!resp.success) {
|
|
6726
|
+
throw new Error(resp.message || 'Failed to unfeature the artifact');
|
|
6727
|
+
}
|
|
6728
|
+
return resp;
|
|
6729
|
+
},
|
|
6730
|
+
onSuccess: () => {
|
|
6731
|
+
invalidateEverywhere(queryClient, queryKeys.items.all());
|
|
6732
|
+
},
|
|
6733
|
+
});
|
|
6734
|
+
};
|
|
6735
|
+
const useSetFeaturedRank = () => {
|
|
6736
|
+
return useMutation({
|
|
6737
|
+
mutationFn: async ({ itemId, rank, }) => {
|
|
6738
|
+
const resp = await requestDatalayer({
|
|
6739
|
+
url: `${configuration.libraryUrl}/api/library/v1/items/${encodeURIComponent(itemId)}/featured`,
|
|
6740
|
+
method: 'PATCH',
|
|
6741
|
+
body: { rank },
|
|
6742
|
+
// One toast, not two: the caller says which artifact failed and
|
|
6743
|
+
// why, so the generic report underneath is a duplicate.
|
|
6744
|
+
notifyOnError: false,
|
|
6745
|
+
});
|
|
6746
|
+
if (!resp.success) {
|
|
6747
|
+
throw new Error(resp.message || 'Failed to move the artifact');
|
|
6748
|
+
}
|
|
6749
|
+
return resp;
|
|
6750
|
+
},
|
|
6751
|
+
onSuccess: () => {
|
|
6752
|
+
invalidateEverywhere(queryClient, queryKeys.items.featured());
|
|
6753
|
+
},
|
|
6754
|
+
});
|
|
6755
|
+
};
|
|
6756
|
+
/**
|
|
6757
|
+
* Refresh everything that carries an artifact's public flag or its image.
|
|
6758
|
+
*
|
|
6759
|
+
* `items` and `spaces` cover the library listings, but an artifact opened in
|
|
6760
|
+
* an editor is read through its own per-type query — `notebooks.detail(id)`
|
|
6761
|
+
* for the editor, `notebooks.bySpace(id)` for the table — and neither
|
|
6762
|
+
* shares a prefix with either of those. Leaving them alone is what made the
|
|
6763
|
+
* Publish button keep saying "Publish" after publishing, and "Published"
|
|
6764
|
+
* after withdrawing: the mutation succeeded, the cached copy still held the
|
|
6765
|
+
* old `is_public_b`, and nothing asked for it again for five minutes.
|
|
6766
|
+
*
|
|
6767
|
+
* A course is a space, but it is not read through the `spaces` queries: it
|
|
6768
|
+
* has a namespace of its own, so publishing one from its header left the
|
|
6769
|
+
* course page holding the `public` flag it loaded with and the button
|
|
6770
|
+
* saying the opposite of what had just happened.
|
|
6771
|
+
*
|
|
6772
|
+
* **`refetchType: 'all'`, which is what makes any of the above work.**
|
|
6773
|
+
*
|
|
6774
|
+
* Naming a query is not the same as making it ask again. The default,
|
|
6775
|
+
* `'active'`, refetches the queries something is currently watching and
|
|
6776
|
+
* leaves the rest marked stale — right nearly everywhere, and wrong here,
|
|
6777
|
+
* because this application sets `refetchOnMount: false` (see
|
|
6778
|
+
* `DEFAULT_QUERY_OPTIONS`, and the client the web application builds). A
|
|
6779
|
+
* query that mounts holding stale data renders it and asks for nothing.
|
|
6780
|
+
*
|
|
6781
|
+
* So publishing worked exactly once. The editor it was published from was
|
|
6782
|
+
* mounted and flipped its button; the notebooks table, the library, the
|
|
6783
|
+
* search page and the artifact's own public page were not, and kept the
|
|
6784
|
+
* answer they already had. Come back to the editor after that and *it* is
|
|
6785
|
+
* now the page that was not mounted for the next withdrawal, so its button
|
|
6786
|
+
* is wrong too — which is what "it works the first time" means.
|
|
6787
|
+
*
|
|
6788
|
+
* The whole of each type's namespace, not only its `details()`. A table
|
|
6789
|
+
* lists its artifacts under `bySpace(spaceId)`, which shares no prefix with
|
|
6790
|
+
* `detail(id)` — so publishing from an editor and walking back to the table
|
|
6791
|
+
* showed the row exactly as it was, switch and all.
|
|
6792
|
+
*
|
|
6793
|
+
* The `model` queries are the one thing held out. A notebook's model is its
|
|
6794
|
+
* content, megabytes of it, and it lives *under* `detail(id)` — so
|
|
6795
|
+
* refetching a namespace would pull every open notebook's content over the
|
|
6796
|
+
* wire each time somebody pressed Publish. Whether an artifact is public
|
|
6797
|
+
* says nothing about what is in it.
|
|
6798
|
+
*/
|
|
6799
|
+
const invalidateArtifactVisibility = () => {
|
|
6800
|
+
const refetchType = 'all';
|
|
6801
|
+
/** Everything about an artifact except what is written in it. */
|
|
6802
|
+
const notItsContent = (query) => query.queryKey[query.queryKey.length - 1] !== 'model';
|
|
6803
|
+
queryClient.invalidateQueries({
|
|
6804
|
+
queryKey: queryKeys.items.all(),
|
|
6805
|
+
refetchType,
|
|
6806
|
+
});
|
|
6807
|
+
queryClient.invalidateQueries({ queryKey: ['spaces'], refetchType });
|
|
6808
|
+
for (const namespace of [
|
|
6809
|
+
queryKeys.notebooks.all(),
|
|
6810
|
+
queryKeys.documents.all(),
|
|
6811
|
+
queryKeys.decks.all(),
|
|
6812
|
+
queryKeys.cells.all(),
|
|
6813
|
+
queryKeys.lessons.all(),
|
|
6814
|
+
queryKeys.exercises.all(),
|
|
6815
|
+
queryKeys.assignments.all(),
|
|
6816
|
+
queryKeys.courses.all(),
|
|
6817
|
+
]) {
|
|
6818
|
+
queryClient.invalidateQueries({
|
|
6819
|
+
queryKey: namespace,
|
|
6820
|
+
refetchType,
|
|
6821
|
+
predicate: notItsContent,
|
|
6822
|
+
});
|
|
6823
|
+
}
|
|
6824
|
+
};
|
|
6825
|
+
/**
|
|
6826
|
+
* What an artifact looks like in the library: a capture the publisher took,
|
|
6827
|
+
* the box drawn for its kind, or an icon from the Datalayer set.
|
|
6828
|
+
*/
|
|
6829
|
+
const useSetItemImage = () => {
|
|
6830
|
+
return useMutation({
|
|
6831
|
+
mutationFn: async ({ itemId, kind, ref, data, }) => {
|
|
6832
|
+
const resp = await requestDatalayer({
|
|
6833
|
+
url: `${configuration.libraryUrl}/api/library/v1/items/${encodeURIComponent(itemId)}/image`,
|
|
6834
|
+
method: 'PUT',
|
|
6835
|
+
body: { kind, ref, data },
|
|
6836
|
+
});
|
|
6837
|
+
if (!resp.success) {
|
|
6838
|
+
throw new Error(resp.message || 'Failed to set the image');
|
|
6839
|
+
}
|
|
6840
|
+
return resp.image;
|
|
6841
|
+
},
|
|
6842
|
+
onSuccess: invalidateArtifactVisibility,
|
|
6843
|
+
});
|
|
6844
|
+
};
|
|
6845
|
+
const useResetItemImage = () => {
|
|
6846
|
+
return useMutation({
|
|
6847
|
+
mutationFn: async (itemId) => {
|
|
6848
|
+
const resp = await requestDatalayer({
|
|
6849
|
+
url: `${configuration.libraryUrl}/api/library/v1/items/${encodeURIComponent(itemId)}/image`,
|
|
6850
|
+
method: 'DELETE',
|
|
6851
|
+
});
|
|
6852
|
+
if (!resp.success) {
|
|
6853
|
+
throw new Error(resp.message || 'Failed to reset the image');
|
|
6854
|
+
}
|
|
6855
|
+
return resp.image;
|
|
6856
|
+
},
|
|
6857
|
+
onSuccess: invalidateArtifactVisibility,
|
|
6858
|
+
});
|
|
6859
|
+
};
|
|
6860
|
+
/** Publish a public artifact natively to LinkedIn through Library's saga. */
|
|
6861
|
+
const usePublishLinkedInShare = () => {
|
|
6862
|
+
return useMutation({
|
|
6863
|
+
mutationFn: async ({ itemId, commentary, idempotencyKey, imageDataUrl, imageAltText, }) => {
|
|
6864
|
+
const resp = await requestDatalayer({
|
|
6865
|
+
url: `${configuration.libraryUrl}/api/library/v1/items/${encodeURIComponent(itemId)}/shares/linkedin`,
|
|
6866
|
+
method: 'POST',
|
|
6867
|
+
headers: { 'Idempotency-Key': idempotencyKey },
|
|
6868
|
+
body: { commentary, imageDataUrl, imageAltText },
|
|
6869
|
+
});
|
|
6870
|
+
if (!resp.success) {
|
|
6871
|
+
throw new Error(resp.message || 'Failed to publish to LinkedIn');
|
|
6872
|
+
}
|
|
6873
|
+
return resp.share;
|
|
6874
|
+
},
|
|
6875
|
+
});
|
|
6876
|
+
};
|
|
6877
|
+
const useUnpublishLinkedInShare = () => {
|
|
6878
|
+
return useMutation({
|
|
6879
|
+
mutationFn: async (idempotencyKey) => {
|
|
6880
|
+
const resp = await requestDatalayer({
|
|
6881
|
+
url: `${configuration.libraryUrl}/api/library/v1/shares/linkedin/${encodeURIComponent(idempotencyKey)}`,
|
|
6882
|
+
method: 'DELETE',
|
|
6883
|
+
});
|
|
6884
|
+
if (!resp.success) {
|
|
6885
|
+
throw new Error(resp.message || 'Failed to unpublish from LinkedIn');
|
|
6886
|
+
}
|
|
6887
|
+
return resp.share;
|
|
6888
|
+
},
|
|
6889
|
+
});
|
|
6890
|
+
};
|
|
6891
|
+
const useLinkedInShares = (itemId, enabled = true) => {
|
|
6892
|
+
return useQuery({
|
|
6893
|
+
queryKey: ['library', 'social-shares', 'linkedin', itemId],
|
|
6894
|
+
queryFn: async () => {
|
|
6895
|
+
const resp = await requestDatalayer({
|
|
6896
|
+
url: `${configuration.libraryUrl}/api/library/v1/items/${encodeURIComponent(itemId)}/shares/linkedin`,
|
|
6897
|
+
method: 'GET',
|
|
6898
|
+
notifyOnError: false,
|
|
6899
|
+
});
|
|
6900
|
+
if (!resp.success) {
|
|
6901
|
+
throw new Error(resp.message || 'Failed to load LinkedIn publications');
|
|
6902
|
+
}
|
|
6903
|
+
return Array.isArray(resp.shares) ? resp.shares : [];
|
|
6904
|
+
},
|
|
6905
|
+
...DEFAULT_QUERY_OPTIONS,
|
|
6906
|
+
enabled: Boolean(configuration.libraryUrl) && Boolean(itemId) && enabled,
|
|
6907
|
+
});
|
|
6908
|
+
};
|
|
6909
|
+
/** Publish one selected public result through the durable Bluesky saga. */
|
|
6910
|
+
const usePublishBlueskyShare = () => {
|
|
6911
|
+
return useMutation({
|
|
6912
|
+
mutationFn: async ({ itemId, finding, cellId, outputIndex, idempotencyKey, imageDataUrl, imageAltText, }) => {
|
|
6913
|
+
const resp = await requestDatalayer({
|
|
6914
|
+
url: `${configuration.libraryUrl}/api/library/v1/items/${encodeURIComponent(itemId)}/shares/bluesky`,
|
|
6915
|
+
method: 'POST',
|
|
6916
|
+
headers: { 'Idempotency-Key': idempotencyKey },
|
|
6917
|
+
body: { finding, cellId, outputIndex, imageDataUrl, imageAltText },
|
|
6918
|
+
});
|
|
6919
|
+
if (!resp.success) {
|
|
6920
|
+
throw new Error(resp.message || 'Failed to publish to Bluesky');
|
|
6921
|
+
}
|
|
6922
|
+
return resp.share;
|
|
6923
|
+
},
|
|
6924
|
+
});
|
|
6925
|
+
};
|
|
6926
|
+
const useUnpublishBlueskyShare = () => {
|
|
6927
|
+
return useMutation({
|
|
6928
|
+
mutationFn: async (idempotencyKey) => {
|
|
6929
|
+
const resp = await requestDatalayer({
|
|
6930
|
+
url: `${configuration.libraryUrl}/api/library/v1/shares/bluesky/${encodeURIComponent(idempotencyKey)}`,
|
|
6931
|
+
method: 'DELETE',
|
|
6932
|
+
});
|
|
6933
|
+
if (!resp.success) {
|
|
6934
|
+
throw new Error(resp.message || 'Failed to unpublish from Bluesky');
|
|
6935
|
+
}
|
|
6936
|
+
return resp.share;
|
|
6937
|
+
},
|
|
6938
|
+
});
|
|
6939
|
+
};
|
|
6940
|
+
const useBlueskyShares = (itemId, enabled = true) => {
|
|
6941
|
+
return useQuery({
|
|
6942
|
+
queryKey: ['library', 'social-shares', 'bluesky', itemId],
|
|
6943
|
+
queryFn: async () => {
|
|
6944
|
+
const resp = await requestDatalayer({
|
|
6945
|
+
url: `${configuration.libraryUrl}/api/library/v1/items/${encodeURIComponent(itemId)}/shares/bluesky`,
|
|
6946
|
+
method: 'GET',
|
|
6947
|
+
notifyOnError: false,
|
|
6948
|
+
});
|
|
6949
|
+
if (!resp.success) {
|
|
6950
|
+
throw new Error(resp.message || 'Failed to load Bluesky publications');
|
|
6951
|
+
}
|
|
6952
|
+
return Array.isArray(resp.shares) ? resp.shares : [];
|
|
6953
|
+
},
|
|
6954
|
+
...DEFAULT_QUERY_OPTIONS,
|
|
6955
|
+
enabled: Boolean(configuration.libraryUrl) && Boolean(itemId) && enabled,
|
|
6956
|
+
});
|
|
6957
|
+
};
|
|
6958
|
+
/**
|
|
6959
|
+
* Tell the library that an artifact was reused: cloned into a space, or
|
|
6960
|
+
* instantiated as a runtime. The reuse itself belongs to the service that
|
|
6961
|
+
* performed it, so this is a count, not the act.
|
|
6962
|
+
*/
|
|
6963
|
+
const useCountItemReuse = () => {
|
|
6964
|
+
return useMutation({
|
|
6965
|
+
mutationFn: async ({ itemId, kind, signupSource, shareRef, }) => {
|
|
6966
|
+
const resp = await requestDatalayer({
|
|
6967
|
+
url: `${configuration.libraryUrl}/api/library/v1/items/${encodeURIComponent(itemId)}/reused`,
|
|
6041
6968
|
method: 'POST',
|
|
6042
6969
|
body: {
|
|
6043
|
-
|
|
6044
|
-
|
|
6045
|
-
request_token: accessToken,
|
|
6046
|
-
request_body: shareRequest,
|
|
6970
|
+
kind,
|
|
6971
|
+
...(signupSource && shareRef ? { signupSource, shareRef } : {}),
|
|
6047
6972
|
},
|
|
6048
6973
|
});
|
|
6974
|
+
if (!resp.success) {
|
|
6975
|
+
throw new Error(resp.message || 'Failed to count the reuse');
|
|
6976
|
+
}
|
|
6977
|
+
return resp;
|
|
6049
6978
|
},
|
|
6050
6979
|
});
|
|
6051
6980
|
};
|
|
6052
|
-
// ============================================================================
|
|
6053
|
-
// Proxy Operations
|
|
6054
|
-
// ============================================================================
|
|
6055
6981
|
/**
|
|
6056
|
-
*
|
|
6982
|
+
* Publish the platform's own catalogue — the agents — into the library.
|
|
6983
|
+
* `platform_admin` only; the catalogue lives in this application's build.
|
|
6057
6984
|
*/
|
|
6058
|
-
const
|
|
6985
|
+
const useSyncArtifactCatalogue = () => {
|
|
6059
6986
|
return useMutation({
|
|
6060
|
-
mutationFn: async ({
|
|
6061
|
-
|
|
6062
|
-
url: `${configuration.
|
|
6063
|
-
method: '
|
|
6987
|
+
mutationFn: async ({ artifacts, artifactType = 'agent', }) => {
|
|
6988
|
+
const resp = await requestDatalayer({
|
|
6989
|
+
url: `${configuration.libraryUrl}/api/library/v1/artifacts/sync`,
|
|
6990
|
+
method: 'PUT',
|
|
6991
|
+
body: { artifact_type: artifactType, artifacts },
|
|
6992
|
+
// One toast, not two: the caller says which artifact failed and
|
|
6993
|
+
// why, so the generic report underneath is a duplicate.
|
|
6994
|
+
notifyOnError: false,
|
|
6995
|
+
});
|
|
6996
|
+
if (!resp.success) {
|
|
6997
|
+
throw new Error(resp.message || 'Failed to synchronize the catalogue');
|
|
6998
|
+
}
|
|
6999
|
+
return resp;
|
|
7000
|
+
},
|
|
7001
|
+
onSuccess: () => {
|
|
7002
|
+
invalidateEverywhere(queryClient, queryKeys.items.all());
|
|
7003
|
+
},
|
|
7004
|
+
});
|
|
7005
|
+
};
|
|
7006
|
+
/**
|
|
7007
|
+
* Publish an artifact in the library: choose its image, then make it
|
|
7008
|
+
* public.
|
|
7009
|
+
*
|
|
7010
|
+
* The one place that publishes. It used to be a `ToggleSwitch` in each of
|
|
7011
|
+
* seven tables and three editors, each with its own copy of the mutation
|
|
7012
|
+
* and its own idea of what to roll back; publishing is an act with
|
|
7013
|
+
* consequences a switch cannot express — a picture to choose, a public page
|
|
7014
|
+
* that appears, a search it enters.
|
|
7015
|
+
*
|
|
7016
|
+
* The image is set first: a failure there leaves the artifact private, which
|
|
7017
|
+
* is recoverable by trying again, where the reverse would publish something
|
|
7018
|
+
* showing the wrong picture.
|
|
7019
|
+
*/
|
|
7020
|
+
const usePublishArtifact = () => {
|
|
7021
|
+
const setImage = useSetItemImage();
|
|
7022
|
+
return useMutation({
|
|
7023
|
+
mutationFn: async ({ itemId, image, tags, description, }) => {
|
|
7024
|
+
/*
|
|
7025
|
+
* Publish first, then dress it.
|
|
7026
|
+
*
|
|
7027
|
+
* The image used to be set before the artifact was made public, so
|
|
7028
|
+
* that a picture that would not take left it private rather than
|
|
7029
|
+
* published with the wrong one. That order could never work: the
|
|
7030
|
+
* library indexes public artifacts, so a private one has no
|
|
7031
|
+
* projection, and setting an image on it answered "The artifact is
|
|
7032
|
+
* not found" — every first publish with a picture failed.
|
|
7033
|
+
*
|
|
7034
|
+
* The intent survives the reordering. The artifact goes public, which
|
|
7035
|
+
* is what creates the projection; if the picture then will not take,
|
|
7036
|
+
* it is withdrawn again and the message still tells the truth.
|
|
7037
|
+
*/
|
|
7038
|
+
const resp = await requestDatalayer({
|
|
7039
|
+
url: `${configuration.libraryUrl}/api/library/v1/items/${encodeURIComponent(itemId)}/public`,
|
|
7040
|
+
method: 'PATCH',
|
|
6064
7041
|
body: {
|
|
6065
|
-
|
|
6066
|
-
|
|
6067
|
-
|
|
7042
|
+
is_public: true,
|
|
7043
|
+
// Absent, not null: the service reads "leave it alone" from a
|
|
7044
|
+
// missing key, and a caller that does not know an artifact's
|
|
7045
|
+
// words must not be able to clear them by omission.
|
|
7046
|
+
...(tags === undefined ? {} : { tags }),
|
|
7047
|
+
...(description === undefined ? {} : { description }),
|
|
6068
7048
|
},
|
|
6069
7049
|
});
|
|
7050
|
+
if (!resp.success) {
|
|
7051
|
+
throw new Error(resp.message || 'Failed to publish the artifact');
|
|
7052
|
+
}
|
|
7053
|
+
if (image) {
|
|
7054
|
+
try {
|
|
7055
|
+
await setImage.mutateAsync({ itemId, ...image });
|
|
7056
|
+
}
|
|
7057
|
+
catch (error) {
|
|
7058
|
+
// Back to private, so the artifact is never left public wearing
|
|
7059
|
+
// the drawing the publisher did not choose.
|
|
7060
|
+
try {
|
|
7061
|
+
await requestDatalayer({
|
|
7062
|
+
url: `${configuration.libraryUrl}/api/library/v1/items/${encodeURIComponent(itemId)}/public`,
|
|
7063
|
+
method: 'PATCH',
|
|
7064
|
+
body: { is_public: false },
|
|
7065
|
+
});
|
|
7066
|
+
}
|
|
7067
|
+
catch {
|
|
7068
|
+
// The withdrawal failed too; the error below is still the one
|
|
7069
|
+
// worth showing, and it is the more actionable of the two.
|
|
7070
|
+
}
|
|
7071
|
+
throw new Error(`The image could not be set, so the artifact stays private: ${error?.message || 'unknown error'}`, { cause: error });
|
|
7072
|
+
}
|
|
7073
|
+
}
|
|
7074
|
+
return resp;
|
|
6070
7075
|
},
|
|
7076
|
+
onSuccess: invalidateArtifactVisibility,
|
|
6071
7077
|
});
|
|
6072
7078
|
};
|
|
6073
7079
|
/**
|
|
6074
|
-
*
|
|
7080
|
+
* Withdraw an artifact from the library. Its orbits are kept: publishing it
|
|
7081
|
+
* again brings them back.
|
|
6075
7082
|
*/
|
|
6076
|
-
|
|
7083
|
+
/**
|
|
7084
|
+
* Rename a published artifact, or change what it says about itself.
|
|
7085
|
+
*
|
|
7086
|
+
* The words belong to the artifact rather than to the library — the
|
|
7087
|
+
* library holds a projection of them — so the library service writes the
|
|
7088
|
+
* artifact's own source and that write carries the change into the index.
|
|
7089
|
+
* Which is why this is one request and not two.
|
|
7090
|
+
*
|
|
7091
|
+
* Every field is optional and every one means "leave it alone" when
|
|
7092
|
+
* omitted, so a caller may change one without knowing the others.
|
|
7093
|
+
*/
|
|
7094
|
+
const useUpdateArtifactDetails = () => {
|
|
6077
7095
|
return useMutation({
|
|
6078
|
-
mutationFn: async ({
|
|
6079
|
-
|
|
6080
|
-
url: `${configuration.
|
|
6081
|
-
method: '
|
|
7096
|
+
mutationFn: async ({ itemId, name, description, tags, }) => {
|
|
7097
|
+
const resp = await requestDatalayer({
|
|
7098
|
+
url: `${configuration.libraryUrl}/api/library/v1/items/${encodeURIComponent(itemId)}`,
|
|
7099
|
+
method: 'PATCH',
|
|
6082
7100
|
body: {
|
|
6083
|
-
|
|
6084
|
-
|
|
6085
|
-
|
|
6086
|
-
|
|
7101
|
+
...(name === undefined ? {} : { name }),
|
|
7102
|
+
...(description === undefined ? {} : { description }),
|
|
7103
|
+
...(tags === undefined ? {} : { tags }),
|
|
7104
|
+
},
|
|
7105
|
+
});
|
|
7106
|
+
if (!resp.success) {
|
|
7107
|
+
throw new Error(resp.message || 'Failed to update the artifact');
|
|
7108
|
+
}
|
|
7109
|
+
return resp;
|
|
7110
|
+
},
|
|
7111
|
+
// The same fan-out publishing uses: a rename changes the card in every
|
|
7112
|
+
// listing, the editor's own copy, and the artifact's public page.
|
|
7113
|
+
onSuccess: invalidateArtifactVisibility,
|
|
7114
|
+
});
|
|
7115
|
+
};
|
|
7116
|
+
const useUnpublishArtifact = () => {
|
|
7117
|
+
return useMutation({
|
|
7118
|
+
mutationFn: async (itemId) => {
|
|
7119
|
+
const resp = await requestDatalayer({
|
|
7120
|
+
url: `${configuration.libraryUrl}/api/library/v1/items/${encodeURIComponent(itemId)}/public`,
|
|
7121
|
+
method: 'PATCH',
|
|
7122
|
+
body: { is_public: false },
|
|
7123
|
+
});
|
|
7124
|
+
if (!resp.success) {
|
|
7125
|
+
throw new Error(resp.message || 'Failed to withdraw the artifact');
|
|
7126
|
+
}
|
|
7127
|
+
return resp;
|
|
7128
|
+
},
|
|
7129
|
+
onSuccess: invalidateArtifactVisibility,
|
|
7130
|
+
});
|
|
7131
|
+
};
|
|
7132
|
+
// ============================================================================
|
|
7133
|
+
// Social Media Integrations
|
|
7134
|
+
// ============================================================================
|
|
7135
|
+
/**
|
|
7136
|
+
* Get GitHub profile from access token
|
|
7137
|
+
*/
|
|
7138
|
+
const useGetGitHubProfile = () => {
|
|
7139
|
+
return useMutation({
|
|
7140
|
+
mutationFn: async (accessToken) => {
|
|
7141
|
+
const response = await fetch('https://api.github.com/user', {
|
|
7142
|
+
method: 'GET',
|
|
7143
|
+
headers: {
|
|
7144
|
+
Accept: 'application/vnd.github+json',
|
|
7145
|
+
Authorization: `Bearer ${accessToken}`,
|
|
7146
|
+
'X-GitHub-Api-Version': '2022-11-28',
|
|
6087
7147
|
},
|
|
6088
7148
|
});
|
|
7149
|
+
return response.json();
|
|
6089
7150
|
},
|
|
6090
7151
|
});
|
|
6091
7152
|
};
|
|
6092
7153
|
/**
|
|
6093
|
-
*
|
|
7154
|
+
* Get Google profile from access token
|
|
6094
7155
|
*/
|
|
6095
|
-
const
|
|
7156
|
+
const useGetGoogleProfile = () => {
|
|
7157
|
+
return useMutation({
|
|
7158
|
+
mutationFn: async (accessToken) => {
|
|
7159
|
+
const response = await fetch('https://openidconnect.googleapis.com/v1/userinfo', {
|
|
7160
|
+
method: 'GET',
|
|
7161
|
+
headers: {
|
|
7162
|
+
Accept: 'application/json',
|
|
7163
|
+
Authorization: `Bearer ${accessToken}`,
|
|
7164
|
+
},
|
|
7165
|
+
});
|
|
7166
|
+
return response.json();
|
|
7167
|
+
},
|
|
7168
|
+
});
|
|
7169
|
+
};
|
|
7170
|
+
/** Get the server-connected LinkedIn profile. */
|
|
7171
|
+
const useGetLinkedinProfile = () => {
|
|
7172
|
+
return useMutation({
|
|
7173
|
+
mutationFn: async () => {
|
|
7174
|
+
return requestDatalayer({
|
|
7175
|
+
url: `${configuration.iamUrl}/api/iam/v1/social/linkedin/profile`,
|
|
7176
|
+
method: 'GET',
|
|
7177
|
+
});
|
|
7178
|
+
},
|
|
7179
|
+
});
|
|
7180
|
+
};
|
|
7181
|
+
/** Publish a LinkedIn text post through the server-held connection. */
|
|
7182
|
+
const usePostLinkedinShare = () => {
|
|
7183
|
+
return useMutation({
|
|
7184
|
+
mutationFn: async ({ postText }) => {
|
|
7185
|
+
return requestDatalayer({
|
|
7186
|
+
url: `${configuration.iamUrl}/api/iam/v1/social/linkedin/posts`,
|
|
7187
|
+
method: 'POST',
|
|
7188
|
+
body: { commentary: postText },
|
|
7189
|
+
});
|
|
7190
|
+
},
|
|
7191
|
+
});
|
|
7192
|
+
};
|
|
7193
|
+
/** Publish a LinkedIn post, optionally with one uploaded image. */
|
|
7194
|
+
const usePostLinkedinShareWithUpload = () => {
|
|
6096
7195
|
return useMutation({
|
|
6097
|
-
mutationFn: async ({
|
|
7196
|
+
mutationFn: async ({ postText, uploadObject, }) => {
|
|
6098
7197
|
return requestDatalayer({
|
|
6099
|
-
url: `${configuration.iamUrl}/api/iam/v1/
|
|
7198
|
+
url: `${configuration.iamUrl}/api/iam/v1/social/linkedin/posts`,
|
|
6100
7199
|
method: 'POST',
|
|
6101
7200
|
body: {
|
|
6102
|
-
|
|
6103
|
-
|
|
6104
|
-
request_token: token,
|
|
6105
|
-
request_body: body,
|
|
7201
|
+
commentary: postText,
|
|
7202
|
+
image_data_url: uploadObject,
|
|
6106
7203
|
},
|
|
6107
7204
|
});
|
|
6108
7205
|
},
|
|
6109
7206
|
});
|
|
6110
7207
|
};
|
|
7208
|
+
/** Connect-only AT Protocol account operations. */
|
|
7209
|
+
const useGetBlueskyConnection = () => useMutation({
|
|
7210
|
+
mutationFn: async () => requestDatalayer({
|
|
7211
|
+
url: `${configuration.iamUrl}/api/iam/v1/social/bluesky/profile`,
|
|
7212
|
+
method: 'GET',
|
|
7213
|
+
notifyOnError: false,
|
|
7214
|
+
}),
|
|
7215
|
+
});
|
|
7216
|
+
/**
|
|
7217
|
+
* Begin a Bluesky connection.
|
|
7218
|
+
*
|
|
7219
|
+
* `returnOrigin` is where the reader should come back to — their own origin,
|
|
7220
|
+
* so a connection begun on a developer's machine does not end on
|
|
7221
|
+
* production. IAM takes the origin only and builds the address itself, so
|
|
7222
|
+
* this cannot ask to be sent anywhere.
|
|
7223
|
+
*/
|
|
7224
|
+
const useConnectBluesky = () => useMutation({
|
|
7225
|
+
mutationFn: async (asked) => {
|
|
7226
|
+
const { identifier, returnOrigin } = typeof asked === 'string'
|
|
7227
|
+
? { identifier: asked, returnOrigin: undefined }
|
|
7228
|
+
: asked;
|
|
7229
|
+
return requestDatalayer({
|
|
7230
|
+
url: `${configuration.iamUrl}/api/iam/v1/social/bluesky/connect`,
|
|
7231
|
+
method: 'POST',
|
|
7232
|
+
body: returnOrigin
|
|
7233
|
+
? { identifier, return_origin: returnOrigin }
|
|
7234
|
+
: { identifier },
|
|
7235
|
+
});
|
|
7236
|
+
},
|
|
7237
|
+
});
|
|
7238
|
+
const useDisconnectBluesky = () => useMutation({
|
|
7239
|
+
mutationFn: async () => requestDatalayer({
|
|
7240
|
+
url: `${configuration.iamUrl}/api/iam/v1/social/bluesky/connection`,
|
|
7241
|
+
method: 'DELETE',
|
|
7242
|
+
}),
|
|
7243
|
+
});
|
|
6111
7244
|
// ============================================================================
|
|
6112
7245
|
// Waiting List & Growth
|
|
6113
7246
|
// ============================================================================
|
|
@@ -6215,28 +7348,6 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
|
|
|
6215
7348
|
...options,
|
|
6216
7349
|
});
|
|
6217
7350
|
};
|
|
6218
|
-
/**
|
|
6219
|
-
* Refresh public courses list
|
|
6220
|
-
* @param options - Mutation options
|
|
6221
|
-
*/
|
|
6222
|
-
const useRefreshPublicCourses = (options) => {
|
|
6223
|
-
const queryClient = useQueryClient();
|
|
6224
|
-
return useMutation({
|
|
6225
|
-
mutationFn: async () => {
|
|
6226
|
-
const resp = await requestDatalayer({
|
|
6227
|
-
url: `${configuration.libraryUrl}/api/library/v1/courses/public`,
|
|
6228
|
-
method: 'GET',
|
|
6229
|
-
});
|
|
6230
|
-
return resp;
|
|
6231
|
-
},
|
|
6232
|
-
onSuccess: () => {
|
|
6233
|
-
queryClient.invalidateQueries({
|
|
6234
|
-
queryKey: queryKeys.courses.public(),
|
|
6235
|
-
});
|
|
6236
|
-
},
|
|
6237
|
-
...options,
|
|
6238
|
-
});
|
|
6239
|
-
};
|
|
6240
7351
|
/**
|
|
6241
7352
|
* Refresh instructor courses list
|
|
6242
7353
|
* @param options - Mutation options
|
|
@@ -6465,28 +7576,6 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
|
|
|
6465
7576
|
...options,
|
|
6466
7577
|
});
|
|
6467
7578
|
};
|
|
6468
|
-
/**
|
|
6469
|
-
* Get public items (query hook)
|
|
6470
|
-
* @param options - Query options
|
|
6471
|
-
*/
|
|
6472
|
-
const usePublicItems = (options) => {
|
|
6473
|
-
return useQuery({
|
|
6474
|
-
queryKey: queryKeys.items.public(),
|
|
6475
|
-
queryFn: async () => {
|
|
6476
|
-
const resp = await requestDatalayer({
|
|
6477
|
-
url: `${configuration.libraryUrl}/api/library/v1/search?q=*&max=-1`,
|
|
6478
|
-
method: 'GET',
|
|
6479
|
-
});
|
|
6480
|
-
return {
|
|
6481
|
-
...resp,
|
|
6482
|
-
items: (resp.items || [])
|
|
6483
|
-
.map((item) => toItem(item))
|
|
6484
|
-
.filter(Boolean),
|
|
6485
|
-
};
|
|
6486
|
-
},
|
|
6487
|
-
...options,
|
|
6488
|
-
});
|
|
6489
|
-
};
|
|
6490
7579
|
/**
|
|
6491
7580
|
* Get the current user's own public items (publications).
|
|
6492
7581
|
* @param options - Query options
|
|
@@ -6509,31 +7598,6 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
|
|
|
6509
7598
|
...options,
|
|
6510
7599
|
});
|
|
6511
7600
|
};
|
|
6512
|
-
/**
|
|
6513
|
-
* Refresh public items
|
|
6514
|
-
* @param options - Mutation options
|
|
6515
|
-
*/
|
|
6516
|
-
const useRefreshPublicItems = (options) => {
|
|
6517
|
-
const queryClient = useQueryClient();
|
|
6518
|
-
return useMutation({
|
|
6519
|
-
mutationFn: async () => {
|
|
6520
|
-
const resp = await requestDatalayer({
|
|
6521
|
-
url: `${configuration.libraryUrl}/api/library/v1/search?q=*&max=-1`,
|
|
6522
|
-
method: 'GET',
|
|
6523
|
-
});
|
|
6524
|
-
return {
|
|
6525
|
-
...resp,
|
|
6526
|
-
items: (resp.items || [])
|
|
6527
|
-
.map((item) => toItem(item))
|
|
6528
|
-
.filter(Boolean),
|
|
6529
|
-
};
|
|
6530
|
-
},
|
|
6531
|
-
onSuccess: () => {
|
|
6532
|
-
queryClient.invalidateQueries({ queryKey: queryKeys.items.public() });
|
|
6533
|
-
},
|
|
6534
|
-
...options,
|
|
6535
|
-
});
|
|
6536
|
-
};
|
|
6537
7601
|
/**
|
|
6538
7602
|
* Refresh space items
|
|
6539
7603
|
* @param options - Mutation options
|
|
@@ -7225,11 +8289,16 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
|
|
|
7225
8289
|
useGetLinkedinProfile,
|
|
7226
8290
|
usePostLinkedinShare,
|
|
7227
8291
|
usePostLinkedinShareWithUpload,
|
|
8292
|
+
useGetBlueskyConnection,
|
|
8293
|
+
useConnectBluesky,
|
|
8294
|
+
useDisconnectBluesky,
|
|
8295
|
+
usePublishLinkedInShare,
|
|
8296
|
+
useUnpublishLinkedInShare,
|
|
8297
|
+
useLinkedInShares,
|
|
8298
|
+
usePublishBlueskyShare,
|
|
8299
|
+
useUnpublishBlueskyShare,
|
|
8300
|
+
useBlueskyShares,
|
|
7228
8301
|
useRegisterToWaitingList,
|
|
7229
|
-
// Proxy
|
|
7230
|
-
useProxyGET,
|
|
7231
|
-
useProxyPOST,
|
|
7232
|
-
useProxyPUT,
|
|
7233
8302
|
// Users
|
|
7234
8303
|
useUser,
|
|
7235
8304
|
useUserByHandle,
|
|
@@ -7311,7 +8380,6 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
|
|
|
7311
8380
|
// Courses
|
|
7312
8381
|
useCourse,
|
|
7313
8382
|
useUpdateCourse,
|
|
7314
|
-
usePublicCourses,
|
|
7315
8383
|
useInstructorCourses,
|
|
7316
8384
|
useCourseEnrollments,
|
|
7317
8385
|
useEnrollStudentToCourse,
|
|
@@ -7321,7 +8389,6 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
|
|
|
7321
8389
|
useConfirmCourseItemCompletion,
|
|
7322
8390
|
useSetCourseItems,
|
|
7323
8391
|
useRefreshCourse,
|
|
7324
|
-
useRefreshPublicCourses,
|
|
7325
8392
|
useRefreshInstructorCourses,
|
|
7326
8393
|
useRefreshCoursesEnrollments,
|
|
7327
8394
|
useRefreshStudent,
|
|
@@ -7334,6 +8401,13 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
|
|
|
7334
8401
|
useCloneNotebook,
|
|
7335
8402
|
useRefreshNotebook,
|
|
7336
8403
|
useRefreshSpaceNotebooks,
|
|
8404
|
+
// Decks
|
|
8405
|
+
useDeck,
|
|
8406
|
+
useDecksBySpace,
|
|
8407
|
+
useCreateDeck,
|
|
8408
|
+
useUpdateDeck,
|
|
8409
|
+
useUpdateDeckModel,
|
|
8410
|
+
useCloneDeck,
|
|
7337
8411
|
// Documents
|
|
7338
8412
|
useDocument,
|
|
7339
8413
|
useDocumentsBySpace,
|
|
@@ -7387,18 +8461,33 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
|
|
|
7387
8461
|
useMakeItemPublic,
|
|
7388
8462
|
useMakeItemPrivate,
|
|
7389
8463
|
useSearchPublicItems,
|
|
7390
|
-
|
|
8464
|
+
useLibraryArtifacts,
|
|
7391
8465
|
usePublications,
|
|
7392
|
-
|
|
8466
|
+
// Library (search, orbits, featuring, images, reuse)
|
|
8467
|
+
useFeaturedItems,
|
|
8468
|
+
useAllFeaturedItems,
|
|
8469
|
+
usePublicItem,
|
|
8470
|
+
useAccountPublications,
|
|
8471
|
+
useMyOrbits,
|
|
8472
|
+
useAccountOrbits,
|
|
8473
|
+
useItemOrbiters,
|
|
8474
|
+
useItemDerivations,
|
|
8475
|
+
useOrbitItem,
|
|
8476
|
+
useUnorbitItem,
|
|
8477
|
+
useFeatureItem,
|
|
8478
|
+
useUnfeatureItem,
|
|
8479
|
+
useSetFeaturedRank,
|
|
8480
|
+
useSetItemImage,
|
|
8481
|
+
useResetItemImage,
|
|
8482
|
+
useCountItemReuse,
|
|
8483
|
+
useSyncArtifactCatalogue,
|
|
8484
|
+
usePublishArtifact,
|
|
8485
|
+
useUnpublishArtifact,
|
|
8486
|
+
useUpdateArtifactDetails,
|
|
7393
8487
|
useRefreshSpaceItems,
|
|
7394
8488
|
useClearCachedPublicItems,
|
|
7395
8489
|
useClearCachedItems,
|
|
7396
8490
|
// Pages
|
|
7397
|
-
usePage,
|
|
7398
|
-
usePages,
|
|
7399
|
-
useCreatePage,
|
|
7400
|
-
useUpdatePage,
|
|
7401
|
-
useDeletePage,
|
|
7402
8491
|
// Secrets
|
|
7403
8492
|
useSecret,
|
|
7404
8493
|
useSecrets,
|
|
@@ -7497,6 +8586,9 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
|
|
|
7497
8586
|
documentUploadLoading,
|
|
7498
8587
|
documentUploadProgress,
|
|
7499
8588
|
resetDocumentUpload,
|
|
8589
|
+
deckUploadLoading,
|
|
8590
|
+
deckUploadProgress,
|
|
8591
|
+
resetDeckUpload,
|
|
7500
8592
|
};
|
|
7501
8593
|
};
|
|
7502
8594
|
export default useCache;
|