Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 30 additions & 21 deletions packages/app/src/app/graphql/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5113,6 +5113,33 @@ export type AllTeamsQuery = {
} | null;
};

export type SearchTeamSandboxFragment = {
__typename?: 'Sandbox';
id: string;
alias: string | null;
title: string | null;
description: string | null;
updatedAt: string;
viewCount: number;
isV2: boolean;
draft: boolean;
restricted: boolean;
privacy: number;
screenshotUrl: string | null;
source: { __typename?: 'Source'; template: string | null };
customTemplate: {
__typename?: 'Template';
id: any | null;
iconUrl: string | null;
} | null;
author: { __typename?: 'User'; username: string } | null;
collection: {
__typename?: 'Collection';
path: string;
id: any | null;
} | null;
};

export type _SearchTeamSandboxesQueryVariables = Exact<{
teamId: Scalars['UUID4'];
}>;
Expand All @@ -5130,43 +5157,25 @@ export type _SearchTeamSandboxesQuery = {
alias: string | null;
title: string | null;
description: string | null;
lastAccessedAt: any;
insertedAt: string;
updatedAt: string;
removedAt: string | null;
privacy: number;
isFrozen: boolean;
screenshotUrl: string | null;
viewCount: number;
likeCount: number;
isV2: boolean;
draft: boolean;
restricted: boolean;
authorId: any | null;
teamId: any | null;
privacy: number;
screenshotUrl: string | null;
source: { __typename?: 'Source'; template: string | null };
customTemplate: {
__typename?: 'Template';
id: any | null;
iconUrl: string | null;
} | null;
forkedTemplate: {
__typename?: 'Template';
id: any | null;
color: string | null;
iconUrl: string | null;
} | null;
author: { __typename?: 'User'; username: string } | null;
collection: {
__typename?: 'Collection';
path: string;
id: any | null;
} | null;
author: { __typename?: 'User'; username: string } | null;
permissions: {
__typename?: 'SandboxProtectionSettings';
preventSandboxLeaving: boolean;
preventSandboxExport: boolean;
} | null;
}>;
} | null;
} | null;
Expand Down
38 changes: 36 additions & 2 deletions packages/app/src/app/overmind/effects/gql/dashboard/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,40 @@ export const getTeams: Query<AllTeamsQuery, AllTeamsQueryVariables> = gql`
${teamFragmentDashboard}
`;

const SEARCH_TEAM_SANDBOX_FRAGMENT = gql`
fragment searchTeamSandbox on Sandbox {
id
alias
title
description
updatedAt
viewCount
isV2
draft
restricted
privacy
screenshotUrl

source {
template
}

customTemplate {
id
iconUrl
}

author {
username
}

collection {
path
id
}
}
`;

export const searchTeamSandboxes: Query<
_SearchTeamSandboxesQuery,
_SearchTeamSandboxesQueryVariables
Expand All @@ -313,12 +347,12 @@ export const searchTeamSandboxes: Query<

team(id: $teamId) {
sandboxes(orderBy: { field: "updated_at", direction: DESC }) {
...sandboxFragmentDashboard
...searchTeamSandbox
}
}
}
}
${sandboxFragmentDashboard}
${SEARCH_TEAM_SANDBOX_FRAGMENT}
`;

/**
Expand Down
131 changes: 94 additions & 37 deletions packages/app/src/app/overmind/namespaces/dashboard/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import { uniq } from 'lodash-es';
import {
TemplateFragmentDashboardFragment,
SandboxFragmentDashboardFragment,
SandboxByPathFragment,
DraftSandboxFragment,
RepoFragmentDashboardFragment,
ProjectFragment,
SearchTeamSandboxFragment,
} from 'app/graphql/types';
import {
sandboxUrl,
Expand All @@ -29,6 +31,19 @@ import {
import { OrderBy, PageTypes, sandboxesTypes } from './types';
import * as internalActions from './internalActions';

// Type guards to check if sandbox has specific properties
function hasIsFrozen(
sandbox: SandboxFragmentDashboardFragment | SandboxByPathFragment | DraftSandboxFragment | SearchTeamSandboxFragment
): sandbox is SandboxFragmentDashboardFragment | SandboxByPathFragment | DraftSandboxFragment {
return 'isFrozen' in sandbox;
}

function hasPermissions(
sandbox: SandboxFragmentDashboardFragment | SandboxByPathFragment | DraftSandboxFragment | SearchTeamSandboxFragment
): sandbox is SandboxFragmentDashboardFragment | SandboxByPathFragment | DraftSandboxFragment {
return 'permissions' in sandbox;
}

export const internal = internalActions;

export const dashboardMounted = withLoadApp();
Expand Down Expand Up @@ -751,7 +766,7 @@ export const getSearchSandboxes = async ({ state, effects }: Context) => {
try {
const activeTeam = state.activeTeam;

let sandboxes: SandboxFragmentDashboardFragment[] = [];
let sandboxes: SearchTeamSandboxFragment[] = [];
if (activeTeam) {
const data = await effects.gql.queries.searchTeamSandboxes({
teamId: activeTeam,
Expand Down Expand Up @@ -980,21 +995,35 @@ export const changeSandboxesFrozen = async (
changedSandboxes,
} = actions.dashboard.internal.changeSandboxesInState({
sandboxIds,
sandboxMutation: sandbox => ({ ...sandbox, isFrozen }),
sandboxMutation: sandbox => {
// Only update isFrozen if the property exists on the sandbox type
if (hasIsFrozen(sandbox)) {
return { ...sandbox, isFrozen };
}
return sandbox;
},
});

try {
await effects.gql.mutations.changeFrozen({ sandboxIds, isFrozen });
} catch (error) {
changedSandboxes.forEach(oldSandbox =>
actions.dashboard.internal.changeSandboxesInState({
sandboxIds: [oldSandbox.id],
sandboxMutation: sandbox => ({
...sandbox,
isFrozen: oldSandbox.isFrozen,
}),
})
);
changedSandboxes.forEach(oldSandbox => {
// Only rollback if the sandbox has isFrozen property
if (hasIsFrozen(oldSandbox)) {
actions.dashboard.internal.changeSandboxesInState({
sandboxIds: [oldSandbox.id],
sandboxMutation: sandbox => {
if (hasIsFrozen(sandbox)) {
return {
...sandbox,
isFrozen: oldSandbox.isFrozen,
};
}
return sandbox;
},
});
}
});

actions.internal.handleError({
message:
Expand Down Expand Up @@ -1128,10 +1157,16 @@ export const setPreventSandboxesLeavingWorkspace = async (
changedSandboxes,
} = actions.dashboard.internal.changeSandboxesInState({
sandboxIds,
sandboxMutation: sandbox => ({
...sandbox,
permissions: { ...sandbox.permissions, preventSandboxLeaving },
}),
sandboxMutation: sandbox => {
// Only update permissions if the property exists on the sandbox type
if (hasPermissions(sandbox) && sandbox.permissions) {
return {
...sandbox,
permissions: { ...sandbox.permissions, preventSandboxLeaving },
};
}
return sandbox;
},
});

effects.analytics.track(`Dashboard - Change sandbox permissions`, {
Expand All @@ -1146,15 +1181,23 @@ export const setPreventSandboxesLeavingWorkspace = async (

effects.notificationToast.success('Sandbox permissions updated.');
} catch (error) {
changedSandboxes.forEach(oldSandbox =>
actions.dashboard.internal.changeSandboxesInState({
sandboxIds: [oldSandbox.id],
sandboxMutation: sandbox => ({
...sandbox,
permissions: { ...oldSandbox.permissions },
}),
})
);
changedSandboxes.forEach(oldSandbox => {
// Only rollback if the sandbox has permissions property
if (hasPermissions(oldSandbox) && oldSandbox.permissions) {
actions.dashboard.internal.changeSandboxesInState({
sandboxIds: [oldSandbox.id],
sandboxMutation: sandbox => {
if (hasPermissions(sandbox) && sandbox.permissions) {
return {
...sandbox,
permissions: { ...oldSandbox.permissions },
};
}
return sandbox;
},
});
}
});
effects.notificationToast.error(
'There was a problem updating your sandbox permissions'
);
Expand All @@ -1176,10 +1219,16 @@ export const setPreventSandboxesExport = async (
changedSandboxes,
} = actions.dashboard.internal.changeSandboxesInState({
sandboxIds,
sandboxMutation: sandbox => ({
...sandbox,
permissions: { ...sandbox.permissions, preventSandboxExport },
}),
sandboxMutation: sandbox => {
// Only update permissions if the property exists on the sandbox type
if (hasPermissions(sandbox) && sandbox.permissions) {
return {
...sandbox,
permissions: { ...sandbox.permissions, preventSandboxExport },
};
}
return sandbox;
},
});

effects.analytics.track(`Dashboard - Change sandbox permissions`, {
Expand All @@ -1194,15 +1243,23 @@ export const setPreventSandboxesExport = async (

effects.notificationToast.success('Sandbox permissions updated.');
} catch (error) {
changedSandboxes.forEach(oldSandbox =>
actions.dashboard.internal.changeSandboxesInState({
sandboxIds: [oldSandbox.id],
sandboxMutation: sandbox => ({
...sandbox,
permissions: { ...oldSandbox.permissions },
}),
})
);
changedSandboxes.forEach(oldSandbox => {
// Only rollback if the sandbox has permissions property
if (hasPermissions(oldSandbox) && oldSandbox.permissions) {
actions.dashboard.internal.changeSandboxesInState({
sandboxIds: [oldSandbox.id],
sandboxMutation: sandbox => {
if (hasPermissions(sandbox) && sandbox.permissions) {
return {
...sandbox,
permissions: { ...oldSandbox.permissions },
};
}
return sandbox;
},
});
}
});
effects.notificationToast.error(
'There was a problem updating your sandbox permissions'
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
SandboxFragmentDashboardFragment,
SandboxByPathFragment,
DraftSandboxFragment,
SearchTeamSandboxFragment,
} from 'app/graphql/types';

/**
Expand All @@ -19,14 +20,14 @@ export const changeSandboxesInState = (
* The mutation that happens on the sandbox, make sure to return a *new* sandbox here, to make sure
* that we can still rollback easily in the future.
*/
sandboxMutation: <T extends SandboxFragmentDashboardFragment | SandboxByPathFragment | DraftSandboxFragment>(
sandboxMutation: <T extends SandboxFragmentDashboardFragment | SandboxByPathFragment | DraftSandboxFragment | SearchTeamSandboxFragment>(
sandbox: T
) => T;
}
) => {
const changedSandboxes: Set<ReturnType<typeof sandboxMutation>> = new Set();

const doMutateSandbox = <T extends SandboxFragmentDashboardFragment | SandboxByPathFragment | DraftSandboxFragment>(
const doMutateSandbox = <T extends SandboxFragmentDashboardFragment | SandboxByPathFragment | DraftSandboxFragment | SearchTeamSandboxFragment>(
sandbox: T
): T => {
changedSandboxes.add(sandbox);
Expand Down Expand Up @@ -110,7 +111,7 @@ export const deleteSandboxesFromState = (
ids: string[];
}
) => {
const sandboxFilter = <T extends SandboxFragmentDashboardFragment | SandboxByPathFragment | DraftSandboxFragment>(
const sandboxFilter = <T extends SandboxFragmentDashboardFragment | SandboxByPathFragment | DraftSandboxFragment | SearchTeamSandboxFragment>(
sandbox: T
): boolean => !ids.includes(sandbox.id);

Expand Down Expand Up @@ -165,7 +166,8 @@ export const deleteSandboxesFromState = (
} else if (type !== 'RECENT_BRANCHES') {
const newSandboxes = sandboxStructure[type].filter(sandboxFilter);
if (newSandboxes.length !== sandboxStructure[type].length) {
dashboard.sandboxes[type] = newSandboxes;
// TypeScript can't narrow the union type based on the key, so we need to assert
(dashboard.sandboxes as any)[type] = newSandboxes;
}
}
});
Expand Down
Loading
Loading