Skip to main content
Glama
workspaces.store.ts11 kB
import { defineStore } from "pinia"; import * as _ from "lodash-es"; import { watch } from "vue"; import { addStoreHooks, ApiRequest } from "@si/vue-lib/pinia"; import { useRealtimeStore } from "@/store/realtime/realtime.store"; import router from "@/router"; import { ModuleId } from "@/api/sdf/dal/module"; import { WorkspacePk } from "@/api/sdf/dal/workspace"; import { useAuthStore, UserId } from "./auth.store"; import { useRouterStore } from "./router.store"; import handleStoreError from "./errors"; import { AuthApiRequest } from "."; type WorkspaceExportId = string; type WorkspaceExportSummary = { id: WorkspaceExportId; createdAt: IsoDateString; }; type InstanceEnvType = "LOCAL" | "PRIVATE" | "SI"; type AuthApiWorkspace = { creatorUserId: string; displayName: string; id: WorkspacePk; pk: WorkspacePk; // not actually in the response, but we backfill instanceEnvType: InstanceEnvType; instanceUrl: string; role: "OWNER" | "EDITOR"; token: string; isHidden: boolean; approvalsEnabled: boolean; }; export type WorkspaceImportSummary = { importRequestedByUserPk: UserId; workspaceExportCreatedAt: IsoDateString; workspaceExportCreatedBy: string; importedWorkspaceName: string; }; export type WorkspaceIntegration = { workspaceId: WorkspacePk; slackWebhookUrl?: string; }; // Note(victor): The workspace import exists outside a change set context // (since change sets exists inside tenancies) - So no endpoints in this store // should use a visibility. If one seems like it should, then it belongs // in a different store. export const useWorkspacesStore = () => { const realtimeStore = useRealtimeStore(); return addStoreHooks( undefined, undefined, defineStore("workspaces", { state: () => ({ workspacesByPk: {} as Record<WorkspacePk, AuthApiWorkspace>, workspaceExports: [] as WorkspaceExportSummary[], workspaceImportSummary: null as WorkspaceImportSummary | null, workspaceApprovals: {} as Record<UserId, string>, importCompletedAt: null as IsoDateString | null, importCancelledAt: null as IsoDateString | null, importId: null as string | null, importLoading: false as boolean, importError: undefined as string | undefined, integrations: null as WorkspaceIntegration | null, }), getters: { allWorkspaces: (state) => _.values(state.workspacesByPk), selectedWorkspacePk(): WorkspacePk | null { const pk = this.selectedWorkspace?.pk || null; return pk; }, urlSelectedWorkspaceId: () => { const route = useRouterStore().currentRoute; return route?.params?.workspacePk as WorkspacePk | undefined; }, selectedWorkspace(): AuthApiWorkspace | null { return _.get( this.workspacesByPk, this.urlSelectedWorkspaceId || "", null, ); }, workspaceApprovalsEnabled(): boolean { const thisWorkspace = this.selectedWorkspace || null; if (!thisWorkspace) return false; return thisWorkspace.approvalsEnabled; }, getIntegrations(): WorkspaceIntegration | null { return this.integrations || null; }, getWorkspaceToken(): string { const thisWorkspace = this.selectedWorkspace || null; if (!thisWorkspace) return ""; return thisWorkspace.token; }, }, actions: { async FETCH_USER_WORKSPACES() { return new AuthApiRequest<AuthApiWorkspace[]>({ url: "/workspaces", onSuccess: (response) => { const renameIdList = _.map(response, (w) => ({ ...w, pk: w.id, })); this.workspacesByPk = _.keyBy(renameIdList, "pk"); // NOTE - we could cache this stuff in localstorage too to avoid showing loading state // but this is a small optimization to make later... }, }); }, async BEGIN_WORKSPACE_IMPORT(moduleId: ModuleId) { this.workspaceApprovals = {}; this.importId = null; this.importLoading = true; this.importError = undefined; return new ApiRequest<{ id: string }>({ method: "post", url: `/v2/workspaces/${moduleId}/install`, onSuccess: (data) => { this.workspaceImportSummary = null; this.importId = data.id; }, onFail: () => { this.importId = null; this.importLoading = false; }, }); }, async BEGIN_APPROVAL_PROCESS(moduleId: ModuleId) { return new ApiRequest({ method: "post", url: "/module/begin_approval_process", params: { id: moduleId, }, }); }, async CANCEL_APPROVAL_PROCESS() { this.workspaceImportSummary = null; return new ApiRequest({ method: "post", url: "/module/cancel_approval_process", params: {}, onSuccess: (_response) => { this.workspaceImportSummary = null; }, }); }, async IMPORT_WORKSPACE_VOTE(vote: string) { return new ApiRequest({ method: "post", url: "/module/import_workspace_vote", params: { vote, }, }); }, async GET_INTEGRATIONS() { if ( this.selectedWorkspacePk === null || this.selectedWorkspacePk === "" ) return; return new ApiRequest({ method: "get", url: `v2/workspaces/${this.selectedWorkspacePk}/integrations`, onSuccess: (response) => { if (response) this.integrations = { workspaceId: response.integration.workspacePk, slackWebhookUrl: response.integration.slackWebhookUrl, }; }, }); }, async UPDATE_INTEGRATION(webhookUrl: string) { if ( this.selectedWorkspacePk === null || this.selectedWorkspacePk === "" ) return; return new ApiRequest({ method: "post", url: `v2/workspaces/${this.selectedWorkspacePk}/integrations`, params: { slackWebhookUrl: webhookUrl, }, onSuccess: (response) => { if (response.integration) { this.integrations = { workspaceId: response.integration.workspacePk, slackWebhookUrl: response.integration.slackWebhookUrl, }; } }, }); }, registerRequestsBegin(requestUlid: string, actionName: string) { realtimeStore.inflightRequests.set(requestUlid, actionName); }, registerRequestsEnd(requestUlid: string) { realtimeStore.inflightRequests.delete(requestUlid); }, }, onActivated() { const authStore = useAuthStore(); watch( () => authStore.userIsLoggedInAndInitialized, (loggedIn) => { if (loggedIn) { this.FETCH_USER_WORKSPACES(); } }, { immediate: true }, ); // Since there is only one workspace store instance, // we need to resubscribe when the workspace pk changes watch( () => this.selectedWorkspacePk, () => { this.GET_INTEGRATIONS(); realtimeStore.subscribe( this.$id, `workspace/${this.selectedWorkspacePk}`, [ { eventType: "WorkspaceImportBeginApprovalProcess", callback: (data) => { this.importCancelledAt = null; this.importCompletedAt = null; this.workspaceImportSummary = { importRequestedByUserPk: data.userPk, workspaceExportCreatedAt: data.createdAt, workspaceExportCreatedBy: data.createdBy, importedWorkspaceName: data.name, }; }, }, { eventType: "WorkspaceImportCancelApprovalProcess", callback: () => { this.workspaceApprovals = {}; this.workspaceImportSummary = null; this.importCancelledAt = new Date().toISOString(); this.importCompletedAt = null; }, }, { eventType: "ImportWorkspaceVote", callback: (data) => { if (this.selectedWorkspacePk === data.workspacePk) { this.workspaceApprovals[data.userPk] = data.vote; } }, }, { eventType: "WorkspaceImported", callback: () => { this.workspaceApprovals = {}; this.workspaceImportSummary = null; this.importCompletedAt = new Date().toISOString(); this.importCancelledAt = null; }, }, { eventType: "AsyncFinish", callback: ({ id }: { id: string }) => { if (id === this.importId) { this.importLoading = false; this.importCompletedAt = new Date().toISOString(); this.importError = undefined; this.importId = null; const route = router.currentRoute.value; router.push({ name: "workspace-compose", params: { workspacePk: route.params.workspacePk, changeSetId: "head", }, }); } }, }, { eventType: "AsyncError", callback: ({ id, error }: { id: string; error: string }) => { if (id === this.importId) { this.importLoading = false; this.importError = error; this.importId = null; } }, }, ], ); }, { immediate: true }, ); this.$onAction(handleStoreError); // NOTE - don't need to clean up here, since there is only one workspace // store, and it will always be loaded }, }), )(); };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/systeminit/si'

If you have feedback or need assistance with the MCP directory API, please join our Discord server