From 20f079d905fca1a088a8f15ade97fbb5c9a5d124 Mon Sep 17 00:00:00 2001 From: Jan Keromnes Date: Wed, 1 Dec 2021 14:45:44 +0000 Subject: [PATCH 1/5] [dashboard] Format Prebuild duration nicely --- components/dashboard/src/projects/Prebuilds.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/components/dashboard/src/projects/Prebuilds.tsx b/components/dashboard/src/projects/Prebuilds.tsx index 2bf8fb228b7899..22fc08e1afbcef 100644 --- a/components/dashboard/src/projects/Prebuilds.tsx +++ b/components/dashboard/src/projects/Prebuilds.tsx @@ -257,6 +257,11 @@ export function prebuildStatusIcon(prebuild?: PrebuildWithStatus) { } } +function formatDuration(milliseconds: number) { + const hours = Math.floor(milliseconds / (1000 * 60 * 60)); + return (hours > 0 ? `${hours}:` : '') + moment(milliseconds).format('mm:ss'); +} + export function PrebuildInstanceStatus(props: { prebuildInstance?: WorkspaceInstance }) { let status = <>; let details = <>; @@ -296,7 +301,7 @@ export function PrebuildInstanceStatus(props: { prebuildInstance?: WorkspaceInst details =
{!!props.prebuildInstance?.stoppedTime - ? `${Math.round(((new Date(props.prebuildInstance.stoppedTime).getTime()) - (new Date(props.prebuildInstance.creationTime).getTime())) / 1000)}s` + ? formatDuration((new Date(props.prebuildInstance.stoppedTime).getTime()) - (new Date(props.prebuildInstance.creationTime).getTime())) : '...'}
; break; From 713a33e4e679cb4a066efd041cac1ef2ec1acffc Mon Sep 17 00:00:00 2001 From: Jan Keromnes Date: Thu, 9 Dec 2021 13:37:05 +0000 Subject: [PATCH 2/5] [dashboard] Correctly show 'timed out' status on prebuild logs --- .../dashboard/src/projects/Prebuilds.tsx | 36 +++++++++++-------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/components/dashboard/src/projects/Prebuilds.tsx b/components/dashboard/src/projects/Prebuilds.tsx index 22fc08e1afbcef..89cf1bda1e00a7 100644 --- a/components/dashboard/src/projects/Prebuilds.tsx +++ b/components/dashboard/src/projects/Prebuilds.tsx @@ -242,18 +242,18 @@ export function prebuildStatusIcon(prebuild?: PrebuildWithStatus) { switch (prebuild?.status) { case undefined: // Fall through case "queued": - return ; + return ; case "building": - return ; + return ; case "aborted": - return ; + return ; case "timeout": - return ; + return ; case "available": if (prebuild?.error) { - return ; + return ; } - return ; + return ; } } @@ -272,11 +272,11 @@ export function PrebuildInstanceStatus(props: { prebuildInstance?: WorkspaceInst case 'creating': // Fall through case 'unknown': status =
- + PENDING
; details =
- + Preparing prebuild ...
; break; @@ -285,21 +285,21 @@ export function PrebuildInstanceStatus(props: { prebuildInstance?: WorkspaceInst case 'interrupted': // Fall through case 'stopping': status =
- + RUNNING
; details =
- + Prebuild in progress ...
; break; case 'stopped': status =
- + READY
; details =
- + {!!props.prebuildInstance?.stoppedTime ? formatDuration((new Date(props.prebuildInstance.stoppedTime).getTime()) - (new Date(props.prebuildInstance.creationTime).getTime())) : '...'} @@ -308,7 +308,7 @@ export function PrebuildInstanceStatus(props: { prebuildInstance?: WorkspaceInst } if (props.prebuildInstance?.status.conditions.stoppedByRequest) { status =
- + CANCELED
; details =
@@ -316,12 +316,20 @@ export function PrebuildInstanceStatus(props: { prebuildInstance?: WorkspaceInst
; } else if (props.prebuildInstance?.status.conditions.failed || props.prebuildInstance?.status.conditions.headlessTaskFailed) { status =
- + FAILED
; details =
Prebuild failed
; + } else if (props.prebuildInstance?.status.conditions.timeout) { + status =
+ + FAILED +
; + details =
+ Prebuild timed out +
; } return
{status}
From b404e72f8669fbf0cddb4c296e968917df2ac299 Mon Sep 17 00:00:00 2001 From: Jan Keromnes Date: Sat, 27 Nov 2021 11:50:04 +0000 Subject: [PATCH 3/5] Allow enabling/disabling Incremental Prebuilds in a Project's settings --- components/dashboard/src/App.tsx | 7 ++ components/dashboard/src/Menu.tsx | 34 +++---- .../dashboard/src/components/CheckBox.tsx | 2 +- .../dashboard/src/components/PrebuildLogs.tsx | 3 +- .../src/projects/ConfigureProject.tsx | 28 ++---- .../dashboard/src/projects/Prebuild.tsx | 17 ++-- .../src/projects/ProjectSettings.tsx | 89 +++++++++++++++++++ .../dashboard/src/teams/TeamSettings.tsx | 19 ++-- components/gitpod-db/src/project-db.ts | 3 +- .../src/typeorm/entity/db-project.ts | 5 +- .../1638025268018-ProjectSettings.ts | 21 +++++ .../gitpod-db/src/typeorm/project-db-impl.ts | 12 ++- .../gitpod-protocol/src/gitpod-service.ts | 3 +- .../src/teams-projects-protocol.ts | 6 ++ .../ee/src/prebuilds/prebuild-manager.ts | 7 +- .../ee/src/workspace/workspace-factory.ts | 1 + components/server/src/auth/rate-limiter.ts | 1 + .../server/src/projects/projects-service.ts | 15 +++- .../src/workspace/gitpod-server-impl.ts | 7 ++ 19 files changed, 220 insertions(+), 60 deletions(-) create mode 100644 components/dashboard/src/projects/ProjectSettings.tsx create mode 100644 components/gitpod-db/src/typeorm/migration/1638025268018-ProjectSettings.ts diff --git a/components/dashboard/src/App.tsx b/components/dashboard/src/App.tsx index 461f1894a478d7..81fc399944805b 100644 --- a/components/dashboard/src/App.tsx +++ b/components/dashboard/src/App.tsx @@ -21,6 +21,7 @@ import { trackButtonOrAnchor, trackPathChange, trackLocation } from './Analytics import { User } from '@gitpod/gitpod-protocol'; import * as GitpodCookie from '@gitpod/gitpod-protocol/lib/util/gitpod-cookie'; import { Experiment } from './experiments'; +import ProjectSettings from './projects/ProjectSettings'; const Setup = React.lazy(() => import(/* webpackPrefetch: true */ './Setup')); const Workspaces = React.lazy(() => import(/* webpackPrefetch: true */ './workspaces/Workspaces')); @@ -298,6 +299,9 @@ function App() { { const { resourceOrPrebuild } = props.match.params; + if (resourceOrPrebuild === "settings") { + return ; + } if (resourceOrPrebuild === "configure") { return ; } @@ -337,6 +341,9 @@ function App() { if (resourceOrPrebuild === "configure") { return ; } + if (resourceOrPrebuild === "settings") { + return ; + } if (resourceOrPrebuild === "workspaces") { return ; } diff --git a/components/dashboard/src/Menu.tsx b/components/dashboard/src/Menu.tsx index 12588619ed71ac..fc5e48e80addf1 100644 --- a/components/dashboard/src/Menu.tsx +++ b/components/dashboard/src/Menu.tsx @@ -4,7 +4,7 @@ * See License-AGPL.txt in the project root for license information. */ -import { User, TeamMemberInfo } from "@gitpod/gitpod-protocol"; +import { User, TeamMemberInfo, Project } from "@gitpod/gitpod-protocol"; import { useContext, useEffect, useState } from "react"; import { Link } from "react-router-dom"; import { useLocation, useRouteMatch } from "react-router"; @@ -21,6 +21,8 @@ import ContextMenu from "./components/ContextMenu"; import Separator from "./components/Separator"; import PillMenuItem from "./components/PillMenuItem"; import TabMenuItem from "./components/TabMenuItem"; +import { getTeamSettingsMenu } from "./teams/TeamSettings"; +import { getProjectSettingsMenu } from "./projects/ProjectSettings"; interface Entry { title: string, @@ -35,14 +37,14 @@ export default function Menu() { const location = useLocation(); const match = useRouteMatch<{ segment1?: string, segment2?: string, segment3?: string }>("/(t/)?:segment1/:segment2?/:segment3?"); - const projectName = (() => { + const projectSlug = (() => { const resource = match?.params?.segment2; if (resource && !["projects", "members", "users", "workspaces", "settings"].includes(resource)) { return resource; } })(); const prebuildId = (() => { - const resource = projectName && match?.params?.segment3; + const resource = projectSlug && match?.params?.segment3; if (resource !== "workspaces" && resource !== "prebuilds" && resource !== "settings" && resource !== "configure") { return resource; } @@ -89,24 +91,25 @@ export default function Menu() { const teamOrUserSlug = !!team ? '/t/' + team.slug : '/projects'; const leftMenu: Entry[] = (() => { // Project menu - if (projectName) { + if (projectSlug) { return [ { title: 'Branches', - link: `${teamOrUserSlug}/${projectName}` + link: `${teamOrUserSlug}/${projectSlug}`, }, { title: 'Workspaces', - link: `${teamOrUserSlug}/${projectName}/workspaces` + link: `${teamOrUserSlug}/${projectSlug}/workspaces`, }, { title: 'Prebuilds', - link: `${teamOrUserSlug}/${projectName}/prebuilds` + link: `${teamOrUserSlug}/${projectSlug}/prebuilds`, }, { - title: 'Configuration', - link: `${teamOrUserSlug}/${projectName}/configure` - } + title: 'Settings', + link: `${teamOrUserSlug}/${projectSlug}/settings`, + alternatives: getProjectSettingsMenu({ slug: projectSlug } as Project, team).flatMap(e => e.link), + }, ]; } // Team menu @@ -132,6 +135,7 @@ export default function Menu() { teamSettingsList.push({ title: 'Settings', link: `/t/${team.slug}/settings`, + alternatives: getTeamSettingsMenu(team).flatMap(e => e.link), }) } @@ -174,7 +178,7 @@ export default function Menu() { const renderTeamMenu = () => { return (
- { projectName &&
+ { projectSlug &&
{team?.name || userFullName} @@ -214,15 +218,15 @@ export default function Menu() { } ]}>
- { !projectName && {team?.name || userFullName}} + { !projectSlug && {team?.name || userFullName}}
- { projectName && ( + { projectSlug && (
- - {projectName} + + {projectSlug}
)} diff --git a/components/dashboard/src/components/CheckBox.tsx b/components/dashboard/src/components/CheckBox.tsx index ef806d2610ec63..2f813a145074ce 100644 --- a/components/dashboard/src/components/CheckBox.tsx +++ b/components/dashboard/src/components/CheckBox.tsx @@ -8,7 +8,7 @@ function CheckBox(props: { name?: string, title: string | React.ReactNode, - desc: string, + desc: string | React.ReactNode, checked: boolean, disabled?: boolean, onChange?: (e: React.ChangeEvent) => void diff --git a/components/dashboard/src/components/PrebuildLogs.tsx b/components/dashboard/src/components/PrebuildLogs.tsx index cc2e7aed5100d4..ebdf9048ace1f6 100644 --- a/components/dashboard/src/components/PrebuildLogs.tsx +++ b/components/dashboard/src/components/PrebuildLogs.tsx @@ -24,6 +24,7 @@ export default function PrebuildLogs(props: PrebuildLogsProps) { useEffect(() => { const disposables = new DisposableCollection(); + setWorkspaceInstance(undefined); (async () => { if (!props.workspaceId) { return; @@ -120,7 +121,7 @@ export default function PrebuildLogs(props: PrebuildLogsProps) { if (workspaceInstance?.status.conditions.failed) { setError(new Error(workspaceInstance.status.conditions.failed)); } - }, [ workspaceInstance?.status.phase ]); + }, [ props.workspaceId, workspaceInstance?.status.phase ]); return }> diff --git a/components/dashboard/src/projects/ConfigureProject.tsx b/components/dashboard/src/projects/ConfigureProject.tsx index 02fa8920d80cf7..143f6ffb8f2b10 100644 --- a/components/dashboard/src/projects/ConfigureProject.tsx +++ b/components/dashboard/src/projects/ConfigureProject.tsx @@ -11,7 +11,6 @@ import PrebuildLogs from "../components/PrebuildLogs"; import TabMenuItem from "../components/TabMenuItem"; import { getGitpodService } from "../service/service"; import { getCurrentTeam, TeamsContext } from "../teams/teams-context"; -import Header from "../components/Header"; import Spinner from "../icons/Spinner.svg"; import NoAccess from "../icons/NoAccess.svg"; import PrebuildLogsEmpty from "../images/prebuild-logs-empty.svg"; @@ -20,28 +19,12 @@ import { ThemeContext } from "../theme-context"; import { PrebuildInstanceStatus } from "./Prebuilds"; import { ErrorCodes } from "@gitpod/gitpod-protocol/lib/messaging/error"; import { openAuthorizeWindow } from "../provider-utils"; +import { PageWithSubMenu } from "../components/PageWithSubMenu"; +import { getProjectSettingsMenu } from "./ProjectSettings"; const MonacoEditor = React.lazy(() => import('../components/MonacoEditor')); const TASKS = { - NPM: `tasks: - - init: npm install - command: npm run start`, - Yarn: `tasks: - - init: yarn install - command: yarn run start`, - Go: `tasks: - - init: go get && go build ./... && go test ./... - command: go run`, - Rails: `tasks: - - init: bin/setup - command: bin/rails server`, - Rust: `tasks: - - init: cargo build - command: cargo watch -x run`, - Python: `tasks: - - init: pip install -r requirements.txt - command: python main.py`, Other: `tasks: - init: | echo 'TODO: build project' @@ -244,9 +227,8 @@ export default function () { redirectToNewWorkspace(); } - return <> -
-
+ return +
setSelectedEditor('.gitpod.yml')} /> @@ -301,7 +283,7 @@ export default function () {
- ; +
; } function EditorMessage(props: { heading: string, message: string, type: 'success' | 'warning' }) { diff --git a/components/dashboard/src/projects/Prebuild.tsx b/components/dashboard/src/projects/Prebuild.tsx index d957de2ef765f6..6ca07c1ee3191f 100644 --- a/components/dashboard/src/projects/Prebuild.tsx +++ b/components/dashboard/src/projects/Prebuild.tsx @@ -15,6 +15,7 @@ import { getGitpodService, gitpodHostUrl } from "../service/service"; import { TeamsContext, getCurrentTeam } from "../teams/teams-context"; import { PrebuildInstanceStatus } from "./Prebuilds"; import { shortCommitMessage } from "./render-utils"; +import { Link } from "react-router-dom"; export default function () { const history = useHistory(); @@ -41,21 +42,21 @@ export default function () { ? await getGitpodService().server.getTeamProjects(team.id) : await getGitpodService().server.getUserProjects()); - const project = projectSlug && projects.find( - p => p.slug ? p.slug === projectSlug : - p.name === projectSlug); - + const project = projectSlug && projects.find(p => !!p.slug + ? p.slug === projectSlug + : p.name === projectSlug); if (!project) { console.error(new Error(`Project not found! (teamId: ${team?.id}, projectName: ${projectSlug})`)); return; } + const prebuilds = await getGitpodService().server.findPrebuilds({ projectId: project.id, prebuildId }); setPrebuild(prebuilds[0]); })(); - }, [ teams ]); + }, [prebuildId, projectSlug, team, teams]); const renderTitle = () => { if (!prebuild) { @@ -77,6 +78,12 @@ export default function () {

{shortCommitMessage(prebuild.info.changeTitle)}

+ {!!prebuild.info.basedOnPrebuildId && <> +

·

+
+

Incremental Prebuild (base)

+
+ }
) }; diff --git a/components/dashboard/src/projects/ProjectSettings.tsx b/components/dashboard/src/projects/ProjectSettings.tsx new file mode 100644 index 00000000000000..00c88bd52bf014 --- /dev/null +++ b/components/dashboard/src/projects/ProjectSettings.tsx @@ -0,0 +1,89 @@ +/** + * Copyright (c) 2021 Gitpod GmbH. All rights reserved. + * Licensed under the GNU Affero General Public License (AGPL). + * See License-AGPL.txt in the project root for license information. + */ + +import { useContext, useEffect, useState } from "react"; +import { useLocation, useRouteMatch } from "react-router"; +import { Project, Team } from "@gitpod/gitpod-protocol"; +import CheckBox from "../components/CheckBox"; +import { getGitpodService } from "../service/service"; +import { getCurrentTeam, TeamsContext } from "../teams/teams-context"; +import { PageWithSubMenu } from "../components/PageWithSubMenu"; +import PillLabel from "../components/PillLabel"; + +export function getProjectSettingsMenu(project?: Project, team?: Team) { + const teamOrUserSlug = !!team ? 't/' + team.slug : 'projects'; + return [ + { + title: 'Configuration', + link: [`/${teamOrUserSlug}/${project?.slug || project?.name}/configure`], + }, + { + title: 'Project', + link: [`/${teamOrUserSlug}/${project?.slug || project?.name}/settings`], + }, + ]; +} + +export default function () { + const location = useLocation(); + const { teams } = useContext(TeamsContext); + const team = getCurrentTeam(location, teams); + const match = useRouteMatch<{ team: string, resource: string }>("/(t/)?:team/:resource"); + const projectSlug = match?.params?.resource; + const [ project, setProject ] = useState(); + + const [ isLoading, setIsLoading ] = useState(true); + const [ isIncrementalPrebuildsEnabled, setIsIncrementalPrebuildsEnabled ] = useState(false); + + useEffect(() => { + if (!teams || !projectSlug) { + return; + } + (async () => { + const projects = (!!team + ? await getGitpodService().server.getTeamProjects(team.id) + : await getGitpodService().server.getUserProjects()); + + // Find project matching with slug, otherwise with name + const project = projectSlug && projects.find(p => p.slug ? p.slug === projectSlug : p.name === projectSlug); + if (!project) { + return; + } + setProject(project); + })(); + }, [ projectSlug, team, teams ]); + + useEffect(() => { + if (!project) { + return; + } + setIsLoading(false); + setIsIncrementalPrebuildsEnabled(!!project.settings?.useIncrementalPrebuilds); + }, [ project ]); + + const toggleIncrementalPrebuilds = async () => { + if (!project) { + return; + } + setIsLoading(true); + try { + await getGitpodService().server.updateProjectSettings(project.id, { useIncrementalPrebuilds: !isIncrementalPrebuildsEnabled }); + setIsIncrementalPrebuildsEnabled(!isIncrementalPrebuildsEnabled); + } finally { + setIsLoading(false); + } + } + + return +

Incremental Prebuilds

+ Enable Incremental Prebuilds Beta} + desc={When possible, use an earlier successful prebuild as a base to create new prebuilds. This can make your prebuilds significantly faster, especially if they normally take longer than 10 minutes. Learn more} + checked={isIncrementalPrebuildsEnabled} + disabled={isLoading} + onChange={toggleIncrementalPrebuilds} /> +
; +} \ No newline at end of file diff --git a/components/dashboard/src/teams/TeamSettings.tsx b/components/dashboard/src/teams/TeamSettings.tsx index 1070a763cde288..435342e729a45e 100644 --- a/components/dashboard/src/teams/TeamSettings.tsx +++ b/components/dashboard/src/teams/TeamSettings.tsx @@ -4,6 +4,7 @@ * See License-AGPL.txt in the project root for license information. */ +import { Team } from "@gitpod/gitpod-protocol"; import { useContext, useEffect, useState } from "react"; import { Redirect, useLocation } from "react-router"; import CodeText from "../components/CodeText"; @@ -13,6 +14,15 @@ import { getGitpodService, gitpodHostUrl } from "../service/service"; import { UserContext } from "../user-context"; import { getCurrentTeam, TeamsContext } from "./teams-context"; +export function getTeamSettingsMenu(team?: Team) { + return [ + { + title: 'Team', + link: [`/t/${team?.slug}/settings`], + }, + ]; +} + export default function TeamSettings() { const [modal, setModal] = useState(false); const [teamSlug, setTeamSlug] = useState(''); @@ -44,15 +54,8 @@ export default function TeamSettings() { document.location.href = gitpodHostUrl.asDashboard().toString(); }; - const settingsMenu = [ - { - title: 'General', - link: [`/t/${team?.slug}/settings`] - } - ] - return <> - +

Delete Team

Deleting this team will also remove all associated data with this team, including projects and workspaces. Deleted teams cannot be restored!

diff --git a/components/gitpod-db/src/project-db.ts b/components/gitpod-db/src/project-db.ts index ab64e65184e4ef..ce8f94d31b2df6 100644 --- a/components/gitpod-db/src/project-db.ts +++ b/components/gitpod-db/src/project-db.ts @@ -4,7 +4,7 @@ * See License.enterprise.txt in the project root folder. */ -import { Project, ProjectConfig } from "@gitpod/gitpod-protocol"; +import { Project, ProjectConfig, ProjectSettings } from "@gitpod/gitpod-protocol"; export const ProjectDB = Symbol('ProjectDB'); export interface ProjectDB { @@ -15,5 +15,6 @@ export interface ProjectDB { findUserProjects(userId: string): Promise; storeProject(project: Project): Promise; setProjectConfiguration(projectId: string, config: ProjectConfig): Promise; + setProjectSettings(projectId: string, settings: ProjectSettings): Promise; markDeleted(projectId: string): Promise; } diff --git a/components/gitpod-db/src/typeorm/entity/db-project.ts b/components/gitpod-db/src/typeorm/entity/db-project.ts index 53e41e51ab3ba8..e49be8a93fbad3 100644 --- a/components/gitpod-db/src/typeorm/entity/db-project.ts +++ b/components/gitpod-db/src/typeorm/entity/db-project.ts @@ -6,7 +6,7 @@ import { Entity, Column, PrimaryColumn, Index } from "typeorm"; import { TypeORM } from "../typeorm"; -import { ProjectConfig } from "@gitpod/gitpod-protocol"; +import { ProjectConfig, ProjectSettings } from "@gitpod/gitpod-protocol"; import { Transformer } from "../transformer"; @Entity() @@ -44,6 +44,9 @@ export class DBProject { @Column("simple-json", { nullable: true }) config?: ProjectConfig; + @Column("simple-json", { nullable: true }) + settings?: ProjectSettings; + @Column("varchar") creationTime: string; diff --git a/components/gitpod-db/src/typeorm/migration/1638025268018-ProjectSettings.ts b/components/gitpod-db/src/typeorm/migration/1638025268018-ProjectSettings.ts new file mode 100644 index 00000000000000..4882bfd20c98b8 --- /dev/null +++ b/components/gitpod-db/src/typeorm/migration/1638025268018-ProjectSettings.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) 2021 Gitpod GmbH. All rights reserved. + * Licensed under the GNU Affero General Public License (AGPL). + * See License-AGPL.txt in the project root for license information. + */ + +import { MigrationInterface, QueryRunner } from "typeorm"; +import { columnExists } from "./helper/helper"; + +export class ProjectSettings1638025268018 implements MigrationInterface { + + public async up(queryRunner: QueryRunner): Promise { + if (!(await columnExists(queryRunner, "d_b_project", "settings"))) { + await queryRunner.query("ALTER TABLE d_b_project ADD COLUMN `settings` text NULL"); + } + } + + public async down(queryRunner: QueryRunner): Promise { + } + +} diff --git a/components/gitpod-db/src/typeorm/project-db-impl.ts b/components/gitpod-db/src/typeorm/project-db-impl.ts index b5c12110158b8e..4f388da7153711 100644 --- a/components/gitpod-db/src/typeorm/project-db-impl.ts +++ b/components/gitpod-db/src/typeorm/project-db-impl.ts @@ -9,7 +9,7 @@ import { TypeORM } from "./typeorm"; import { Repository } from "typeorm"; import { ProjectDB } from "../project-db"; import { DBProject } from "./entity/db-project"; -import { Project, ProjectConfig } from "@gitpod/gitpod-protocol"; +import { Project, ProjectConfig, ProjectSettings } from "@gitpod/gitpod-protocol"; @injectable() export class ProjectDBImpl implements ProjectDB { @@ -70,6 +70,16 @@ export class ProjectDBImpl implements ProjectDB { await repo.save(project); } + public async setProjectSettings(projectId: string, settings: ProjectSettings): Promise { + const repo = await this.getRepo(); + const project = await repo.findOne({ id: projectId, markedDeleted: false }); + if (!project) { + throw new Error('A project with this ID could not be found'); + } + project.settings = settings; + await repo.save(project); + } + public async markDeleted(projectId: string): Promise { const repo = await this.getRepo(); const project = await repo.findOne({ id: projectId }); diff --git a/components/gitpod-protocol/src/gitpod-service.ts b/components/gitpod-protocol/src/gitpod-service.ts index d30dbd75233ae9..82532dda38741a 100644 --- a/components/gitpod-protocol/src/gitpod-service.ts +++ b/components/gitpod-protocol/src/gitpod-service.ts @@ -13,7 +13,7 @@ import { } from './protocol'; import { Team, TeamMemberInfo, - TeamMembershipInvite, Project, TeamMemberRole, PrebuildWithStatus, StartPrebuildResult + TeamMembershipInvite, Project, TeamMemberRole, PrebuildWithStatus, StartPrebuildResult, ProjectSettings } from './teams-projects-protocol'; import { JsonRpcProxy, JsonRpcServer } from './messaging/proxy-factory'; import { Disposable, CancellationTokenSource } from 'vscode-jsonrpc'; @@ -141,6 +141,7 @@ export interface GitpodServer extends JsonRpcServer, AdminServer, guessProjectConfiguration(projectId: string): Promise; fetchRepositoryConfiguration(cloneUrl: string): Promise; guessRepositoryConfiguration(cloneUrl: string): Promise; + updateProjectSettings(projectId: string, partialSettings: Partial): Promise; // content service getContentBlobUploadUrl(name: string): Promise diff --git a/components/gitpod-protocol/src/teams-projects-protocol.ts b/components/gitpod-protocol/src/teams-projects-protocol.ts index ea3ad9a426535d..c09bf9ce258f91 100644 --- a/components/gitpod-protocol/src/teams-projects-protocol.ts +++ b/components/gitpod-protocol/src/teams-projects-protocol.ts @@ -11,6 +11,10 @@ export interface ProjectConfig { '.gitpod.yml': string; } +export interface ProjectSettings { + useIncrementalPrebuilds?: boolean; +} + export interface Project { id: string; name: string; @@ -20,6 +24,7 @@ export interface Project { userId?: string; appInstallationId: string; config?: ProjectConfig; + settings?: ProjectSettings; creationTime: string; /** This is a flag that triggers the HARD DELETION of this entity */ deleted?: boolean; @@ -64,6 +69,7 @@ export interface PrebuildWithStatus { export interface PrebuildInfo { id: string; buildWorkspaceId: string; + basedOnPrebuildId?: string; teamId?: string; userId?: string; diff --git a/components/server/ee/src/prebuilds/prebuild-manager.ts b/components/server/ee/src/prebuilds/prebuild-manager.ts index 99f68b3135474f..c21f87348e4d36 100644 --- a/components/server/ee/src/prebuilds/prebuild-manager.ts +++ b/components/server/ee/src/prebuilds/prebuild-manager.ts @@ -106,7 +106,7 @@ export class PrebuildManager { normalizedContextURL: actual.normalizedContextURL }; - if (this.shouldPrebuildIncrementally(actual.repository.cloneUrl)) { + if (this.shouldPrebuildIncrementally(actual.repository.cloneUrl, project)) { const maxDepth = this.config.incrementalPrebuilds.commitHistory; prebuildContext.commitHistory = await contextParser.fetchCommitHistory({ span }, user, contextURL, commit, maxDepth); } @@ -184,7 +184,10 @@ export class PrebuildManager { return true; } - protected shouldPrebuildIncrementally(cloneUrl: string): boolean { + protected shouldPrebuildIncrementally(cloneUrl: string, project?: Project): boolean { + if (project?.settings?.useIncrementalPrebuilds) { + return true; + } const trimRepoUrl = (url: string) => url.replace(/\/$/, '').replace(/\.git$/, ''); const repoUrl = trimRepoUrl(cloneUrl); return this.config.incrementalPrebuilds.repositoryPasslist.some(url => trimRepoUrl(url) === repoUrl); diff --git a/components/server/ee/src/workspace/workspace-factory.ts b/components/server/ee/src/workspace/workspace-factory.ts index ab2a7084a24ef6..ad4b0e5fde0404 100644 --- a/components/server/ee/src/workspace/workspace-factory.ts +++ b/components/server/ee/src/workspace/workspace-factory.ts @@ -169,6 +169,7 @@ export class WorkspaceFactoryEE extends WorkspaceFactory { await this.db.trace({span}).storePrebuildInfo({ id: pws.id, buildWorkspaceId: pws.buildWorkspaceId, + basedOnPrebuildId: ws.basedOnPrebuildId, teamId, userId, projectName, diff --git a/components/server/src/auth/rate-limiter.ts b/components/server/src/auth/rate-limiter.ts index ff1ffa11130919..a1d7d104a30d80 100644 --- a/components/server/src/auth/rate-limiter.ts +++ b/components/server/src/auth/rate-limiter.ts @@ -103,6 +103,7 @@ function getConfig(config: RateLimiterConfig): RateLimiterConfig { "guessProjectConfiguration": { group: "default", points: 1 }, "fetchRepositoryConfiguration": { group: "default", points: 1 }, "guessRepositoryConfiguration": { group: "default", points: 1 }, + "updateProjectSettings": { group: "default", points: 1 }, "getContentBlobUploadUrl": { group: "default", points: 1 }, "getContentBlobDownloadUrl": { group: "default", points: 1 }, "getGitpodTokens": { group: "default", points: 1 }, diff --git a/components/server/src/projects/projects-service.ts b/components/server/src/projects/projects-service.ts index 6688e110a24042..f81e7e025fe900 100644 --- a/components/server/src/projects/projects-service.ts +++ b/components/server/src/projects/projects-service.ts @@ -6,7 +6,7 @@ import { inject, injectable } from "inversify"; import { DBWithTracing, ProjectDB, TeamDB, TracedWorkspaceDB, UserDB, WorkspaceDB } from "@gitpod/gitpod-db/lib"; -import { Branch, CommitContext, PrebuildWithStatus, CreateProjectParams, FindPrebuildsParams, Project, ProjectConfig, User, WorkspaceConfig } from "@gitpod/gitpod-protocol"; +import { Branch, CommitContext, PrebuildWithStatus, CreateProjectParams, FindPrebuildsParams, Project, ProjectConfig, User, WorkspaceConfig, ProjectSettings } from "@gitpod/gitpod-protocol"; import { TraceContext } from "@gitpod/gitpod-protocol/lib/util/tracing"; import { HostContextProvider } from "../auth/host-context-provider"; import { FileProvider, RepoURL } from "../repohost"; @@ -229,4 +229,17 @@ export class ProjectsService { return configString; } + async updateProjectSettings(projectId: string, partialSettings: Partial): Promise { + const project = await this.getProject(projectId); + if (!project) { + throw new Error("Project not found"); + } + const settings = project.settings || {}; + let key: keyof ProjectSettings; + for (key in partialSettings) { + settings[key] = partialSettings[key]; + } + return this.projectDB.setProjectSettings(projectId, settings); + } + } diff --git a/components/server/src/workspace/gitpod-server-impl.ts b/components/server/src/workspace/gitpod-server-impl.ts index 1d67c179c199cf..53a863652288fa 100644 --- a/components/server/src/workspace/gitpod-server-impl.ts +++ b/components/server/src/workspace/gitpod-server-impl.ts @@ -53,6 +53,7 @@ import { LocalMessageBroker } from "../messaging/local-message-broker"; import { CachingBlobServiceClientProvider } from '@gitpod/content-service/lib/sugar'; import { IDEOptions } from '@gitpod/gitpod-protocol/lib/ide-protocol'; import { IDEConfigService } from '../ide-config'; +import { ProjectSettings } from '@gitpod/gitpod-protocol/src/teams-projects-protocol'; // shortcut export const traceWI = (ctx: TraceContext, wi: Omit) => TraceContext.setOWI(ctx, wi); // userId is already taken care of in WebsocketConnectionManager @@ -1713,6 +1714,12 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable { } } + public async updateProjectSettings(ctx: TraceContext, projectId: string, partialSettings: Partial): Promise { + const user = this.checkUser("updateProjectSettings"); + await this.guardProjectOperation(user, projectId, "update"); + await this.projectsService.updateProjectSettings(projectId, partialSettings); + } + public async getContentBlobUploadUrl(ctx: TraceContext, name: string): Promise { traceAPIParams(ctx, { name }); From 3c2d7d3cd8f4b3ed7f6bd41c5337ddcd58a80568 Mon Sep 17 00:00:00 2001 From: Jan Keromnes Date: Thu, 9 Dec 2021 17:13:11 +0000 Subject: [PATCH 4/5] =?UTF-8?q?[server]=20Refactor=20setProjectConfigurati?= =?UTF-8?q?on=20+=20updateProjectSettings=20=E2=86=92=20updateProjectParti?= =?UTF-8?q?al?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/projects/ProjectSettings.tsx | 7 ++- components/gitpod-db/src/project-db.ts | 5 +-- .../gitpod-db/src/typeorm/project-db-impl.ts | 21 +++------ .../gitpod-protocol/src/gitpod-service.ts | 6 +-- .../src/teams-projects-protocol.ts | 3 ++ components/server/src/auth/rate-limiter.ts | 4 +- .../server/src/projects/projects-service.ts | 20 ++------- .../src/workspace/gitpod-server-impl.ts | 43 ++++++++++++------- 8 files changed, 52 insertions(+), 57 deletions(-) diff --git a/components/dashboard/src/projects/ProjectSettings.tsx b/components/dashboard/src/projects/ProjectSettings.tsx index 00c88bd52bf014..e65edd69ce854f 100644 --- a/components/dashboard/src/projects/ProjectSettings.tsx +++ b/components/dashboard/src/projects/ProjectSettings.tsx @@ -70,7 +70,12 @@ export default function () { } setIsLoading(true); try { - await getGitpodService().server.updateProjectSettings(project.id, { useIncrementalPrebuilds: !isIncrementalPrebuildsEnabled }); + await getGitpodService().server.updateProjectPartial({ + id: project.id, + settings: { + useIncrementalPrebuilds: !isIncrementalPrebuildsEnabled, + } + }); setIsIncrementalPrebuildsEnabled(!isIncrementalPrebuildsEnabled); } finally { setIsLoading(false); diff --git a/components/gitpod-db/src/project-db.ts b/components/gitpod-db/src/project-db.ts index ce8f94d31b2df6..35c7948b23e4f0 100644 --- a/components/gitpod-db/src/project-db.ts +++ b/components/gitpod-db/src/project-db.ts @@ -4,7 +4,7 @@ * See License.enterprise.txt in the project root folder. */ -import { Project, ProjectConfig, ProjectSettings } from "@gitpod/gitpod-protocol"; +import { PartialProject, Project } from "@gitpod/gitpod-protocol"; export const ProjectDB = Symbol('ProjectDB'); export interface ProjectDB { @@ -14,7 +14,6 @@ export interface ProjectDB { findTeamProjects(teamId: string): Promise; findUserProjects(userId: string): Promise; storeProject(project: Project): Promise; - setProjectConfiguration(projectId: string, config: ProjectConfig): Promise; - setProjectSettings(projectId: string, settings: ProjectSettings): Promise; + updateProject(partialProject: PartialProject): Promise; markDeleted(projectId: string): Promise; } diff --git a/components/gitpod-db/src/typeorm/project-db-impl.ts b/components/gitpod-db/src/typeorm/project-db-impl.ts index 4f388da7153711..ea6abab77cf606 100644 --- a/components/gitpod-db/src/typeorm/project-db-impl.ts +++ b/components/gitpod-db/src/typeorm/project-db-impl.ts @@ -9,7 +9,7 @@ import { TypeORM } from "./typeorm"; import { Repository } from "typeorm"; import { ProjectDB } from "../project-db"; import { DBProject } from "./entity/db-project"; -import { Project, ProjectConfig, ProjectSettings } from "@gitpod/gitpod-protocol"; +import { PartialProject, Project } from "@gitpod/gitpod-protocol"; @injectable() export class ProjectDBImpl implements ProjectDB { @@ -60,24 +60,13 @@ export class ProjectDBImpl implements ProjectDB { return repo.save(project); } - public async setProjectConfiguration(projectId: string, config: ProjectConfig): Promise { + public async updateProject(partialProject: PartialProject): Promise { const repo = await this.getRepo(); - const project = await repo.findOne({ id: projectId, markedDeleted: false }); - if (!project) { + const count = await repo.count({ id: partialProject.id, markedDeleted: false }); + if (count < 1) { throw new Error('A project with this ID could not be found'); } - project.config = config; - await repo.save(project); - } - - public async setProjectSettings(projectId: string, settings: ProjectSettings): Promise { - const repo = await this.getRepo(); - const project = await repo.findOne({ id: projectId, markedDeleted: false }); - if (!project) { - throw new Error('A project with this ID could not be found'); - } - project.settings = settings; - await repo.save(project); + await repo.update(partialProject.id, partialProject); } public async markDeleted(projectId: string): Promise { diff --git a/components/gitpod-protocol/src/gitpod-service.ts b/components/gitpod-protocol/src/gitpod-service.ts index 82532dda38741a..a0911270311551 100644 --- a/components/gitpod-protocol/src/gitpod-service.ts +++ b/components/gitpod-protocol/src/gitpod-service.ts @@ -13,7 +13,7 @@ import { } from './protocol'; import { Team, TeamMemberInfo, - TeamMembershipInvite, Project, TeamMemberRole, PrebuildWithStatus, StartPrebuildResult, ProjectSettings + TeamMembershipInvite, Project, TeamMemberRole, PrebuildWithStatus, StartPrebuildResult, PartialProject } from './teams-projects-protocol'; import { JsonRpcProxy, JsonRpcServer } from './messaging/proxy-factory'; import { Disposable, CancellationTokenSource } from 'vscode-jsonrpc'; @@ -136,12 +136,12 @@ export interface GitpodServer extends JsonRpcServer, AdminServer, findPrebuilds(params: FindPrebuildsParams): Promise; triggerPrebuild(projectId: string, branchName: string | null): Promise; cancelPrebuild(projectId: string, prebuildId: string): Promise; - setProjectConfiguration(projectId: string, configString: string): Promise; fetchProjectRepositoryConfiguration(projectId: string): Promise; guessProjectConfiguration(projectId: string): Promise; fetchRepositoryConfiguration(cloneUrl: string): Promise; guessRepositoryConfiguration(cloneUrl: string): Promise; - updateProjectSettings(projectId: string, partialSettings: Partial): Promise; + setProjectConfiguration(projectId: string, configString: string): Promise; + updateProjectPartial(partialProject: PartialProject): Promise; // content service getContentBlobUploadUrl(name: string): Promise diff --git a/components/gitpod-protocol/src/teams-projects-protocol.ts b/components/gitpod-protocol/src/teams-projects-protocol.ts index c09bf9ce258f91..eae40284c65778 100644 --- a/components/gitpod-protocol/src/teams-projects-protocol.ts +++ b/components/gitpod-protocol/src/teams-projects-protocol.ts @@ -6,6 +6,7 @@ import { PrebuiltWorkspaceState } from "./protocol"; import { v4 as uuidv4 } from 'uuid'; +import { DeepPartial } from "./util/deep-partial"; export interface ProjectConfig { '.gitpod.yml': string; @@ -60,6 +61,8 @@ export namespace Project { } } +export type PartialProject = DeepPartial & Pick; + export interface PrebuildWithStatus { info: PrebuildInfo; status: PrebuiltWorkspaceState; diff --git a/components/server/src/auth/rate-limiter.ts b/components/server/src/auth/rate-limiter.ts index a1d7d104a30d80..f11996f6046837 100644 --- a/components/server/src/auth/rate-limiter.ts +++ b/components/server/src/auth/rate-limiter.ts @@ -98,12 +98,12 @@ function getConfig(config: RateLimiterConfig): RateLimiterConfig { "getProjectOverview": { group: "default", points: 1 }, "triggerPrebuild": { group: "default", points: 1 }, "cancelPrebuild": { group: "default", points: 1 }, - "setProjectConfiguration": { group: "default", points: 1 }, "fetchProjectRepositoryConfiguration": { group: "default", points: 1 }, "guessProjectConfiguration": { group: "default", points: 1 }, "fetchRepositoryConfiguration": { group: "default", points: 1 }, "guessRepositoryConfiguration": { group: "default", points: 1 }, - "updateProjectSettings": { group: "default", points: 1 }, + "setProjectConfiguration": { group: "default", points: 1 }, + "updateProjectPartial": { group: "default", points: 1 }, "getContentBlobUploadUrl": { group: "default", points: 1 }, "getContentBlobDownloadUrl": { group: "default", points: 1 }, "getGitpodTokens": { group: "default", points: 1 }, diff --git a/components/server/src/projects/projects-service.ts b/components/server/src/projects/projects-service.ts index f81e7e025fe900..182f6861282578 100644 --- a/components/server/src/projects/projects-service.ts +++ b/components/server/src/projects/projects-service.ts @@ -6,13 +6,14 @@ import { inject, injectable } from "inversify"; import { DBWithTracing, ProjectDB, TeamDB, TracedWorkspaceDB, UserDB, WorkspaceDB } from "@gitpod/gitpod-db/lib"; -import { Branch, CommitContext, PrebuildWithStatus, CreateProjectParams, FindPrebuildsParams, Project, ProjectConfig, User, WorkspaceConfig, ProjectSettings } from "@gitpod/gitpod-protocol"; +import { Branch, CommitContext, PrebuildWithStatus, CreateProjectParams, FindPrebuildsParams, Project, User, WorkspaceConfig } from "@gitpod/gitpod-protocol"; import { TraceContext } from "@gitpod/gitpod-protocol/lib/util/tracing"; import { HostContextProvider } from "../auth/host-context-provider"; import { FileProvider, RepoURL } from "../repohost"; import { log } from '@gitpod/gitpod-protocol/lib/util/logging'; import { ContextParser } from "../workspace/context-parser-service"; import { ConfigInferrer } from "./config-inferrer"; +import { PartialProject } from "@gitpod/gitpod-protocol/src/teams-projects-protocol"; @injectable() export class ProjectsService { @@ -178,10 +179,6 @@ export class ProjectsService { return result; } - async setProjectConfiguration(projectId: string, config: ProjectConfig): Promise { - return this.projectDB.setProjectConfiguration(projectId, config); - } - protected async getRepositoryFileProviderAndCommitContext(ctx: TraceContext, user: User, cloneUrl: string): Promise<{fileProvider: FileProvider, commitContext: CommitContext}> { const normalizedContextUrl = this.contextParser.normalizeContextURL(cloneUrl); const commitContext = (await this.contextParser.handle(ctx, user, normalizedContextUrl)) as CommitContext; @@ -229,17 +226,8 @@ export class ProjectsService { return configString; } - async updateProjectSettings(projectId: string, partialSettings: Partial): Promise { - const project = await this.getProject(projectId); - if (!project) { - throw new Error("Project not found"); - } - const settings = project.settings || {}; - let key: keyof ProjectSettings; - for (key in partialSettings) { - settings[key] = partialSettings[key]; - } - return this.projectDB.setProjectSettings(projectId, settings); + async updateProjectPartial(partialProject: PartialProject): Promise { + return this.projectDB.updateProject(partialProject); } } diff --git a/components/server/src/workspace/gitpod-server-impl.ts b/components/server/src/workspace/gitpod-server-impl.ts index 53a863652288fa..2682d55f635e3a 100644 --- a/components/server/src/workspace/gitpod-server-impl.ts +++ b/components/server/src/workspace/gitpod-server-impl.ts @@ -53,7 +53,7 @@ import { LocalMessageBroker } from "../messaging/local-message-broker"; import { CachingBlobServiceClientProvider } from '@gitpod/content-service/lib/sugar'; import { IDEOptions } from '@gitpod/gitpod-protocol/lib/ide-protocol'; import { IDEConfigService } from '../ide-config'; -import { ProjectSettings } from '@gitpod/gitpod-protocol/src/teams-projects-protocol'; +import { PartialProject } from '@gitpod/gitpod-protocol/src/teams-projects-protocol'; // shortcut export const traceWI = (ctx: TraceContext, wi: Omit) => TraceContext.setOWI(ctx, wi); // userId is already taken care of in WebsocketConnectionManager @@ -1636,18 +1636,6 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable { throw new ResponseError(ErrorCodes.EE_FEATURE, `Cancelling Prebuilds is implemented in Gitpod's Enterprise Edition`); } - public async setProjectConfiguration(ctx: TraceContext, projectId: string, configString: string): Promise { - traceAPIParams(ctx, { projectId }); // filter configString because of size - - const user = this.checkAndBlockUser("setProjectConfiguration"); - await this.guardProjectOperation(user, projectId, "update"); - const parseResult = this.gitpodParser.parse(configString); - if (parseResult.validationErrors) { - throw new Error(`This configuration could not be parsed: ${parseResult.validationErrors.join(', ')}`); - } - await this.projectsService.setProjectConfiguration(projectId, { '.gitpod.yml': configString }); - } - public async fetchRepositoryConfiguration(ctx: TraceContext, cloneUrl: string): Promise { traceAPIParams(ctx, { cloneUrl }); const user = this.checkUser("fetchRepositoryConfiguration"); @@ -1714,10 +1702,33 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable { } } - public async updateProjectSettings(ctx: TraceContext, projectId: string, partialSettings: Partial): Promise { - const user = this.checkUser("updateProjectSettings"); + public async setProjectConfiguration(ctx: TraceContext, projectId: string, configString: string): Promise { + traceAPIParams(ctx, { projectId }); // filter configString because of size + + const user = this.checkAndBlockUser("setProjectConfiguration"); await this.guardProjectOperation(user, projectId, "update"); - await this.projectsService.updateProjectSettings(projectId, partialSettings); + const parseResult = this.gitpodParser.parse(configString); + if (parseResult.validationErrors) { + throw new Error(`This configuration could not be parsed: ${parseResult.validationErrors.join(', ')}`); + } + await this.projectsService.updateProjectPartial({ + id: projectId, + config: { '.gitpod.yml': configString }, + }); + } + + public async updateProjectPartial(ctx: TraceContext, partialProject: PartialProject): Promise { + const user = this.checkUser("updateProjectPartial"); + await this.guardProjectOperation(user, partialProject.id, "update"); + + const partial: PartialProject = { id: partialProject.id }; + const allowedFields: (keyof Project)[] = ['settings']; // Don't add 'config' here! Please use `setProjectConfiguration` instead to parse & validate configs + for (const f of allowedFields) { + if (f in partialProject) { + (partial[f] as any) = partialProject[f]; + } + } + await this.projectsService.updateProjectPartial(partial); } public async getContentBlobUploadUrl(ctx: TraceContext, name: string): Promise { From c8c3da41df6277b8e3cd19e450bb1e782299e733 Mon Sep 17 00:00:00 2001 From: Jan Keromnes Date: Mon, 13 Dec 2021 15:24:44 +0000 Subject: [PATCH 5/5] Address review feedback Co-authored-by: George Tsiolis Co-authored-by: Gero Posmyk-Leinemann --- components/dashboard/src/Menu.tsx | 4 ++-- components/dashboard/src/components/CheckBox.tsx | 2 +- components/dashboard/src/projects/Prebuild.tsx | 3 +-- components/dashboard/src/projects/ProjectSettings.tsx | 10 +++++----- components/dashboard/src/teams/TeamSettings.tsx | 4 ++-- components/server/src/workspace/gitpod-server-impl.ts | 9 +++++++++ 6 files changed, 20 insertions(+), 12 deletions(-) diff --git a/components/dashboard/src/Menu.tsx b/components/dashboard/src/Menu.tsx index fc5e48e80addf1..0f0ad18379a641 100644 --- a/components/dashboard/src/Menu.tsx +++ b/components/dashboard/src/Menu.tsx @@ -219,7 +219,7 @@ export default function Menu() { ]}>
{ !projectSlug && {team?.name || userFullName}} - +
@@ -232,7 +232,7 @@ export default function Menu() { )} { prebuildId && (
- + {prebuildId}
)} diff --git a/components/dashboard/src/components/CheckBox.tsx b/components/dashboard/src/components/CheckBox.tsx index 2f813a145074ce..d16f2ea7cab92b 100644 --- a/components/dashboard/src/components/CheckBox.tsx +++ b/components/dashboard/src/components/CheckBox.tsx @@ -24,7 +24,7 @@ function CheckBox(props: { const checkboxId = `checkbox-${props.title}-${String(Math.random())}`; - return
+ return

·

-

Incremental Prebuild (base)

+

Incremental Prebuild (base)

}
) diff --git a/components/dashboard/src/projects/ProjectSettings.tsx b/components/dashboard/src/projects/ProjectSettings.tsx index e65edd69ce854f..a9811ed9b7d8a9 100644 --- a/components/dashboard/src/projects/ProjectSettings.tsx +++ b/components/dashboard/src/projects/ProjectSettings.tsx @@ -17,12 +17,12 @@ export function getProjectSettingsMenu(project?: Project, team?: Team) { const teamOrUserSlug = !!team ? 't/' + team.slug : 'projects'; return [ { - title: 'Configuration', - link: [`/${teamOrUserSlug}/${project?.slug || project?.name}/configure`], + title: 'General', + link: [`/${teamOrUserSlug}/${project?.slug || project?.name}/settings`], }, { - title: 'Project', - link: [`/${teamOrUserSlug}/${project?.slug || project?.name}/settings`], + title: 'Configuration', + link: [`/${teamOrUserSlug}/${project?.slug || project?.name}/configure`], }, ]; } @@ -82,7 +82,7 @@ export default function () { } } - return + return

Incremental Prebuilds

Enable Incremental Prebuilds Beta} diff --git a/components/dashboard/src/teams/TeamSettings.tsx b/components/dashboard/src/teams/TeamSettings.tsx index 435342e729a45e..8ac24586574c1f 100644 --- a/components/dashboard/src/teams/TeamSettings.tsx +++ b/components/dashboard/src/teams/TeamSettings.tsx @@ -17,7 +17,7 @@ import { getCurrentTeam, TeamsContext } from "./teams-context"; export function getTeamSettingsMenu(team?: Team) { return [ { - title: 'Team', + title: 'General', link: [`/t/${team?.slug}/settings`], }, ]; @@ -57,7 +57,7 @@ export default function TeamSettings() { return <>

Delete Team

-

Deleting this team will also remove all associated data with this team, including projects and workspaces. Deleted teams cannot be restored!

+

Deleting this team will also remove all associated data with this team, including projects and workspaces. Deleted teams cannot be restored!

diff --git a/components/server/src/workspace/gitpod-server-impl.ts b/components/server/src/workspace/gitpod-server-impl.ts index 2682d55f635e3a..04230fc55ae0e4 100644 --- a/components/server/src/workspace/gitpod-server-impl.ts +++ b/components/server/src/workspace/gitpod-server-impl.ts @@ -1707,6 +1707,7 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable { const user = this.checkAndBlockUser("setProjectConfiguration"); await this.guardProjectOperation(user, projectId, "update"); + const parseResult = this.gitpodParser.parse(configString); if (parseResult.validationErrors) { throw new Error(`This configuration could not be parsed: ${parseResult.validationErrors.join(', ')}`); @@ -1718,6 +1719,14 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable { } public async updateProjectPartial(ctx: TraceContext, partialProject: PartialProject): Promise { + traceAPIParams(ctx, { + // censor everything irrelevant + partialProject: { + id: partialProject.id, + settings: partialProject.settings, + } + }); + const user = this.checkUser("updateProjectPartial"); await this.guardProjectOperation(user, partialProject.id, "update");