Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/components/dashboard/src/data/ide-options/ide-options-query.ts
2501 views
1
/**
2
* Copyright (c) 2023 Gitpod GmbH. All rights reserved.
3
* Licensed under the GNU Affero General Public License (AGPL).
4
* See License.AGPL.txt in the project root for license information.
5
*/
6
7
import { useQuery } from "@tanstack/react-query";
8
import { getGitpodService } from "../../service/service";
9
import { IDEOption, IDEOptions } from "@gitpod/gitpod-protocol/lib/ide-protocol";
10
import { DisableScope, Scope } from "../workspaces/workspace-classes-query";
11
import { useOrgSettingsQuery } from "../organizations/org-settings-query";
12
import { OrganizationSettings } from "@gitpod/public-api/lib/gitpod/v1/organization_pb";
13
import { useMemo } from "react";
14
import { useConfiguration } from "../configurations/configuration-queries";
15
import { useDeepCompareMemoize } from "use-deep-compare-effect";
16
17
const DEFAULT_WS_EDITOR = "code";
18
19
export const useIDEOptions = () => {
20
return useQuery(
21
["ide-options"],
22
async () => {
23
return await getGitpodService().server.getIDEOptions();
24
},
25
{
26
staleTime: 1000 * 60 * 60 * 1, // 1h
27
cacheTime: 1000 * 60 * 60 * 1, // 1h
28
},
29
);
30
};
31
32
export const useIDEVersionsQuery = (pinnableIdeIdList?: string[]) => {
33
return useQuery(
34
["ide-versions", pinnableIdeIdList?.join(",")],
35
async () => {
36
const updatedVal: Record<string, string[]> = {};
37
if (!pinnableIdeIdList) {
38
return updatedVal;
39
}
40
const ideVersionsResult = await Promise.all(
41
pinnableIdeIdList.map((ide) => getGitpodService().server.getIDEVersions(ide)),
42
);
43
for (let i = 0; i < pinnableIdeIdList.length; i++) {
44
const versions = ideVersionsResult[i]!;
45
updatedVal[pinnableIdeIdList[i]] = versions;
46
}
47
return updatedVal;
48
},
49
{
50
staleTime: 1000 * 60 * 10, // 10m
51
cacheTime: 1000 * 60 * 10, // 10m
52
},
53
);
54
};
55
56
export type AllowedWorkspaceEditor = IDEOption & {
57
id: string;
58
isDisabledInScope?: boolean;
59
disableScope?: DisableScope;
60
isComputedDefault?: boolean;
61
};
62
63
interface FilterOptions {
64
filterOutDisabled: boolean;
65
userDefault?: string;
66
ignoreScope?: DisableScope[];
67
}
68
export const useAllowedWorkspaceEditorsMemo = (configurationId: string | undefined, options?: FilterOptions) => {
69
const { data: orgSettings, isLoading: isLoadingOrgSettings } = useOrgSettingsQuery();
70
const { data: installationOptions, isLoading: isLoadingInstallationCls } = useIDEOptions();
71
const { data: configuration, isLoading: isLoadingConfiguration } = useConfiguration(configurationId);
72
const isLoading = isLoadingOrgSettings || isLoadingInstallationCls || isLoadingConfiguration;
73
const depItems = [
74
installationOptions,
75
options?.ignoreScope,
76
orgSettings,
77
configuration?.workspaceSettings?.restrictedEditorNames,
78
];
79
const data = useMemo(() => {
80
return getAllowedWorkspaceEditors(
81
installationOptions,
82
orgSettings,
83
configuration?.workspaceSettings?.restrictedEditorNames,
84
options,
85
);
86
// react useMemo is using `Object.is` to compare dependencies so array / object will make re-render re-call useMemo,
87
// see also https://react.dev/reference/react/useMemo#every-time-my-component-renders-the-calculation-in-usememo-re-runs
88
//
89
// eslint-disable-next-line react-hooks/exhaustive-deps
90
}, [useDeepCompareMemoize(depItems)]);
91
return { ...data, isLoading, usingConfigurationId: configuration?.id };
92
};
93
94
const getAllowedWorkspaceEditors = (
95
installationOptions: IDEOptions | undefined,
96
orgSettings: Pick<OrganizationSettings, "restrictedEditorNames"> | undefined,
97
repoRestrictedEditorNames: string[] | undefined,
98
options?: FilterOptions,
99
) => {
100
let data: AllowedWorkspaceEditor[] = [];
101
const baseDefault = options?.userDefault ?? DEFAULT_WS_EDITOR;
102
103
if (installationOptions?.options) {
104
data = Object.entries(installationOptions.options)
105
.map(([key, value]) => ({
106
...value,
107
id: key,
108
}))
109
.sort(IdeOptionsSorter);
110
}
111
let scope: Scope = "installation";
112
if (data.length === 0) {
113
return { data, scope, computedDefault: baseDefault, availableOptions: [] };
114
}
115
if (
116
!options?.ignoreScope?.includes("organization") &&
117
orgSettings?.restrictedEditorNames &&
118
orgSettings.restrictedEditorNames.length > 0
119
) {
120
data = data.map((d) => ({
121
...d,
122
isDisabledInScope: orgSettings.restrictedEditorNames.includes(d.id),
123
disableScope: "organization",
124
}));
125
scope = "organization";
126
}
127
if (
128
!options?.ignoreScope?.includes("configuration") &&
129
repoRestrictedEditorNames &&
130
repoRestrictedEditorNames.length > 0
131
) {
132
data = data.map((d) => {
133
if (d.isDisabledInScope) {
134
return d;
135
}
136
return {
137
...d,
138
isDisabledInScope: repoRestrictedEditorNames.includes(d.id),
139
disableScope: "configuration",
140
};
141
});
142
scope = "configuration";
143
}
144
145
let computedDefault = options?.userDefault;
146
const allowedList = data.filter((e) => !e.isDisabledInScope);
147
if (!allowedList.some((d) => d.id === options?.userDefault && !d.isDisabledInScope)) {
148
computedDefault = allowedList.length > 0 ? allowedList[0].id : baseDefault;
149
}
150
data = data.map((e) => {
151
if (e.id === computedDefault) {
152
e.isComputedDefault = true;
153
}
154
return e;
155
});
156
const availableOptions = allowedList.map((e) => e.id);
157
if (options?.filterOutDisabled) {
158
return { data: allowedList, scope, computedDefault, availableOptions };
159
}
160
return { data, scope, computedDefault, availableOptions };
161
};
162
163
function IdeOptionsSorter(
164
a: Pick<IDEOption, "experimental" | "orderKey">,
165
b: Pick<IDEOption, "experimental" | "orderKey">,
166
) {
167
// Prefer experimental options
168
if (a.experimental && !b.experimental) {
169
return -1;
170
}
171
if (!a.experimental && b.experimental) {
172
return 1;
173
}
174
175
if (!a.orderKey || !b.orderKey) {
176
return 0;
177
}
178
179
return parseInt(a.orderKey, 10) - parseInt(b.orderKey, 10);
180
}
181
182