@finos/legend-application-studio
Version:
Legend Studio application core
231 lines • 10.9 kB
JavaScript
import { SerializationFactory, returnUndefOnError, usingModelSchema, } from '@finos/legend-shared';
import { WorkspaceType } from '@finos/legend-server-sdlc';
import { createModelSchema, list, optional, primitive } from 'serializr';
/**
* Copyright (c) 2020-present, Goldman Sachs
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export var LEGEND_STUDIO_USER_DATA_KEY;
(function (LEGEND_STUDIO_USER_DATA_KEY) {
LEGEND_STUDIO_USER_DATA_KEY["GLOBAL_TEST_RUNNER_SHOW_DEPENDENCIES"] = "studio-editor.global-test-runner-showDependencyPanel";
// Per-user theme preference for the database editor. Scoped to this one
// editor since the wider Studio app is dark-mode-only today — the rest of
// the app does not honor this value.
// TODO: when Studio adopts app-wide theming via `LayoutService` (the
// mechanism Query already uses with setting key
// `application.layout.colorTheme`), retire this key and have the database
// editor inherit `applicationStore.layoutService.currentColorTheme`
// instead. Migration is mechanical: delete this key + the helper getters,
// drop the toggle button in the tab header, and retarget the SCSS
// `.database-editor--light` block at the framework's color-theme tokens.
LEGEND_STUDIO_USER_DATA_KEY["DATABASE_EDITOR_THEME"] = "studio-editor.database-editor.theme";
// Recently-opened projects and (non-patch) workspaces shown on the
// workspace setup screen to speed up re-opening common work.
LEGEND_STUDIO_USER_DATA_KEY["WORKSPACE_SETUP_RECENTS"] = "studio-editor.workspace-setup.recents";
// Per-user cache of the sandbox-access boolean + the sandbox project id,
// so the workspace setup screen can render the sandbox UI without waiting
// on the `userHasPrototypeProjectAccess` graph manager call AND a
// sandbox-tag project search on every mount. Revalidated against SDLC in
// the background; invalidated automatically on 404 or after the TTL.
LEGEND_STUDIO_USER_DATA_KEY["WORKSPACE_SETUP_SANDBOX_INFO"] = "studio-editor.workspace-setup.sandboxInfo";
})(LEGEND_STUDIO_USER_DATA_KEY || (LEGEND_STUDIO_USER_DATA_KEY = {}));
// --- Workspace setup recents -------------------------------------------------
const WORKSPACE_SETUP_RECENTS_VERSION = 1;
const MAX_RECENT_PROJECTS = 10;
const MAX_RECENT_WORKSPACES = 20;
export class RecentProjectEntry {
projectId;
name;
description;
webUrl;
tags;
lastOpenedAt;
static serialization = new SerializationFactory(createModelSchema(RecentProjectEntry, {
description: primitive(),
lastOpenedAt: primitive(),
name: primitive(),
projectId: primitive(),
tags: list(primitive()),
webUrl: primitive(),
}));
}
export class RecentWorkspaceEntry {
projectId;
workspaceId;
workspaceType;
lastOpenedAt;
static serialization = new SerializationFactory(createModelSchema(RecentWorkspaceEntry, {
lastOpenedAt: primitive(),
projectId: primitive(),
workspaceId: primitive(),
// WorkspaceType is a string enum; stored/loaded as a plain string and
// validated when the entry is consumed.
workspaceType: primitive(),
}));
}
export class WorkspaceSetupRecents {
version = WORKSPACE_SETUP_RECENTS_VERSION;
projects = [];
workspaces = [];
static serialization = new SerializationFactory(createModelSchema(WorkspaceSetupRecents, {
projects: list(usingModelSchema(RecentProjectEntry.serialization.schema)),
version: primitive(),
workspaces: list(usingModelSchema(RecentWorkspaceEntry.serialization.schema)),
}));
}
const isValidWorkspaceType = (v) => v === WorkspaceType.USER || v === WorkspaceType.GROUP;
const emptyRecents = () => new WorkspaceSetupRecents();
const readRecents = (service) => {
const raw = returnUndefOnError(() => service.getObjectValue(LEGEND_STUDIO_USER_DATA_KEY.WORKSPACE_SETUP_RECENTS));
if (!raw) {
return emptyRecents();
}
const parsed = returnUndefOnError(() => WorkspaceSetupRecents.serialization.fromJson(raw));
if (!parsed) {
return emptyRecents();
}
// Defensive post-checks: drop entries with invalid enum values and enforce
// the LRU caps in case the persisted blob was tampered with.
parsed.workspaces = parsed.workspaces
.filter((w) => isValidWorkspaceType(w.workspaceType))
.slice(0, MAX_RECENT_WORKSPACES);
parsed.projects = parsed.projects.slice(0, MAX_RECENT_PROJECTS);
return parsed;
};
const writeRecents = (service, recents) => {
service.persistValue(LEGEND_STUDIO_USER_DATA_KEY.WORKSPACE_SETUP_RECENTS, WorkspaceSetupRecents.serialization.toJson(recents));
};
// --- Cached sandbox info -----------------------------------------------------
// Sandbox access and the sandbox project id rarely change for a given user,
// but they're costly to look up on every setup mount (one graph manager call
// + one tagged-project search). Cache the result for a day and revalidate
// in the background on the fast path.
const SANDBOX_INFO_TTL_MS = 24 * 60 * 60 * 1000;
export class CachedSandboxInfo {
userId;
hasAccess;
/** undefined when the user has access but hasn't created a sandbox yet. */
projectId;
fetchedAt;
static serialization = new SerializationFactory(createModelSchema(CachedSandboxInfo, {
fetchedAt: primitive(),
hasAccess: primitive(),
projectId: optional(primitive()),
userId: primitive(),
}));
}
export class LegendStudioUserDataHelper {
static globalTestRunner_getShowDependencyPanel(service) {
return returnUndefOnError(() => service.getBooleanValue(LEGEND_STUDIO_USER_DATA_KEY.GLOBAL_TEST_RUNNER_SHOW_DEPENDENCIES));
}
static globalTestRunner_setShowDependencyPanel(service, val) {
service.persistValue(LEGEND_STUDIO_USER_DATA_KEY.GLOBAL_TEST_RUNNER_SHOW_DEPENDENCIES, val);
}
static databaseEditor_getTheme(service) {
const val = returnUndefOnError(() => service.getStringValue(LEGEND_STUDIO_USER_DATA_KEY.DATABASE_EDITOR_THEME));
return val === 'light' || val === 'dark' ? val : undefined;
}
static databaseEditor_setTheme(service, val) {
service.persistValue(LEGEND_STUDIO_USER_DATA_KEY.DATABASE_EDITOR_THEME, val);
}
// --- Workspace setup recents ---------------------------------------------
static workspaceSetup_getRecentProjects(service) {
return readRecents(service).projects;
}
static workspaceSetup_getRecentWorkspaces(service) {
return readRecents(service).workspaces;
}
static workspaceSetup_recordRecentProject(service, entry) {
const recents = readRecents(service);
const next = new RecentProjectEntry();
next.projectId = entry.projectId;
next.name = entry.name;
next.description = entry.description;
next.webUrl = entry.webUrl;
next.tags = entry.tags;
next.lastOpenedAt = Date.now();
recents.projects = [
next,
...recents.projects.filter((p) => p.projectId !== entry.projectId),
].slice(0, MAX_RECENT_PROJECTS);
writeRecents(service, recents);
return recents.projects;
}
static workspaceSetup_recordRecentWorkspace(service, entry) {
const recents = readRecents(service);
const next = new RecentWorkspaceEntry();
next.projectId = entry.projectId;
next.workspaceId = entry.workspaceId;
next.workspaceType = entry.workspaceType;
next.lastOpenedAt = Date.now();
recents.workspaces = [
next,
...recents.workspaces.filter((w) => !(w.projectId === entry.projectId &&
w.workspaceId === entry.workspaceId &&
w.workspaceType === entry.workspaceType)),
].slice(0, MAX_RECENT_WORKSPACES);
writeRecents(service, recents);
return recents.workspaces;
}
static workspaceSetup_removeRecentProject(service, projectId) {
const recents = readRecents(service);
recents.projects = recents.projects.filter((p) => p.projectId !== projectId);
recents.workspaces = recents.workspaces.filter((w) => w.projectId !== projectId);
writeRecents(service, recents);
return recents;
}
static workspaceSetup_removeRecentWorkspace(service, entry) {
const recents = readRecents(service);
recents.workspaces = recents.workspaces.filter((w) => !(w.projectId === entry.projectId &&
w.workspaceId === entry.workspaceId &&
w.workspaceType === entry.workspaceType));
writeRecents(service, recents);
return recents.workspaces;
}
static workspaceSetup_clearRecents(service) {
writeRecents(service, emptyRecents());
}
// --- Cached sandbox info -------------------------------------------------
static workspaceSetup_getCachedSandboxInfo(service, currentUserId) {
const raw = returnUndefOnError(() => service.getObjectValue(LEGEND_STUDIO_USER_DATA_KEY.WORKSPACE_SETUP_SANDBOX_INFO));
if (!raw) {
return undefined;
}
const parsed = returnUndefOnError(() => CachedSandboxInfo.serialization.fromJson(raw));
if (!parsed) {
return undefined;
}
// Discard entries that don't belong to the user looking at the screen
// (e.g., after a user switch on a shared machine) or that have aged out.
if (parsed.userId !== currentUserId) {
return undefined;
}
if (Date.now() - parsed.fetchedAt > SANDBOX_INFO_TTL_MS) {
return undefined;
}
return parsed;
}
static workspaceSetup_recordSandboxInfo(service, info) {
const entry = new CachedSandboxInfo();
entry.userId = info.userId;
entry.hasAccess = info.hasAccess;
entry.projectId = info.projectId;
entry.fetchedAt = Date.now();
service.persistValue(LEGEND_STUDIO_USER_DATA_KEY.WORKSPACE_SETUP_SANDBOX_INFO, CachedSandboxInfo.serialization.toJson(entry));
}
static workspaceSetup_clearSandboxInfo(service) {
service.persistValue(LEGEND_STUDIO_USER_DATA_KEY.WORKSPACE_SETUP_SANDBOX_INFO, undefined);
}
}
//# sourceMappingURL=LegendStudioUserDataHelper.js.map