Skip to content

Commit

Permalink
DEVPROD-5994: Implement Token Permission Restrictions section on GitH…
Browse files Browse the repository at this point in the history
…ub App Settings tab (#258)
  • Loading branch information
minnakt authored Jul 25, 2024
1 parent 0e85267 commit e5928bf
Show file tree
Hide file tree
Showing 25 changed files with 470 additions and 60 deletions.
16 changes: 16 additions & 0 deletions apps/parsley/src/gql/generated/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,17 @@ export type DeleteDistroPayload = {
deletedDistroId: Scalars["String"]["output"];
};

/** DeleteGithubAppCredentialsInput is the input to the deleteGithubAppCredentials mutation. */
export type DeleteGithubAppCredentialsInput = {
projectId: Scalars["String"]["input"];
};

/** DeleteGithubAppCredentialsPayload is returned by the deleteGithubAppCredentials mutation. */
export type DeleteGithubAppCredentialsPayload = {
__typename?: "DeleteGithubAppCredentialsPayload";
oldAppId: Scalars["Int"]["output"];
};

export type Dependency = {
__typename?: "Dependency";
buildVariant: Scalars["String"]["output"];
Expand Down Expand Up @@ -1070,6 +1081,7 @@ export type Mutation = {
deactivateStepbackTask: Scalars["Boolean"]["output"];
defaultSectionToRepo?: Maybe<Scalars["String"]["output"]>;
deleteDistro: DeleteDistroPayload;
deleteGithubAppCredentials?: Maybe<DeleteGithubAppCredentialsPayload>;
deleteProject: Scalars["Boolean"]["output"];
deleteSubscriptions: Scalars["Int"]["output"];
detachProjectFromRepo: Project;
Expand Down Expand Up @@ -1182,6 +1194,10 @@ export type MutationDeleteDistroArgs = {
opts: DeleteDistroInput;
};

export type MutationDeleteGithubAppCredentialsArgs = {
opts: DeleteGithubAppCredentialsInput;
};

export type MutationDeleteProjectArgs = {
projectId: Scalars["String"]["input"];
};
Expand Down
3 changes: 3 additions & 0 deletions apps/spruce/cypress/integration/projectSettings/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ export const getPluginsRoute = (identifier: string) =>
export const getContainersRoute = (identifier: string) =>
`${getSettingsRoute(identifier)}/containers`;

export const getAppSettingsRoute = (identifier: string) =>
`${getSettingsRoute(identifier)}/github-app-settings`;

export const getPermissionGroupsRoute = (identifier: string) =>
`${getSettingsRoute(identifier)}/github-permission-groups`;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { clickSave } from "../../utils";
import { getAppSettingsRoute, saveButtonEnabled } from "./constants";

describe("GitHub app settings", () => {
const destination = getAppSettingsRoute("spruce");
const selectMenu = "[role='listbox']";
const permissionGroups = {
all: "All app permissions",
readPRs: "Read Pull Requests",
writeIssues: "Write Issues",
};

beforeEach(() => {
cy.visit(destination);
// Wait for page content to finish loading.
cy.contains("Token Permission Restrictions");
});

it("save button should be disabled by default", () => {
saveButtonEnabled(false);
});

it("should be able to save different permission groups for requesters, then return to defaults", () => {
cy.dataCy("permission-group-input").should("have.length", 7);
cy.dataCy("permission-group-input").eq(0).as("permission-group-input-0");
cy.dataCy("permission-group-input").eq(4).as("permission-group-input-4");

// Save different permission groups.
cy.get("@permission-group-input-0").click();
cy.get(selectMenu).within(() => {
cy.contains(permissionGroups.readPRs).click();
});
cy.get("@permission-group-input-4").click();
cy.get(selectMenu).within(() => {
cy.contains(permissionGroups.writeIssues).click();
});
cy.dataCy("save-settings-button").scrollIntoView();
saveButtonEnabled(true);
clickSave();
cy.validateToast("success", "Successfully updated project");

// Changes should persist on the page.
cy.reload();
cy.get("@permission-group-input-0").contains(permissionGroups.readPRs);
cy.get("@permission-group-input-4").contains(permissionGroups.writeIssues);

// Return to and save defaults.
cy.get("@permission-group-input-0").click();
cy.get(selectMenu).within(() => {
cy.contains(permissionGroups.all).click();
});
cy.get("@permission-group-input-4").click();
cy.get(selectMenu).within(() => {
cy.contains(permissionGroups.all).click();
});
cy.dataCy("save-settings-button").scrollIntoView();
saveButtonEnabled(true);
clickSave();
cy.validateToast("success", "Successfully updated project");
});
});
14 changes: 7 additions & 7 deletions apps/spruce/src/components/PatchesPage/RequesterSelector.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Combobox, ComboboxOption } from "@leafygreen-ui/combobox";
import { githubPRRequester, patchRequester } from "constants/patch";
import { Requester } from "constants/requesters";
import { requesterSubscriberOptions } from "constants/triggers";
import { useStatusesFilter } from "hooks";
import { PatchPageQueryParams } from "types/patch";
Expand Down Expand Up @@ -32,13 +32,13 @@ export const RequesterSelector: React.FC = () => {

const options = [
{
displayName: requesterSubscriberOptions[githubPRRequester],
value: githubPRRequester,
key: githubPRRequester,
displayName: requesterSubscriberOptions[Requester.GitHubPR],
value: Requester.GitHubPR,
key: Requester.GitHubPR,
},
{
displayName: requesterSubscriberOptions[patchRequester],
value: patchRequester,
key: patchRequester,
displayName: requesterSubscriberOptions[Requester.Patch],
value: Requester.Patch,
key: Requester.Patch,
},
];
Original file line number Diff line number Diff line change
Expand Up @@ -164,9 +164,9 @@ export const FieldRow: React.FC<

const RowContainer = styled.div`
display: flex;
margin-bottom: ${size.s};
justify-content: space-between;
gap: ${size.s};
align-items: center;
gap: ${size.l};
`;

const AccordionTitle = styled(Subtitle)`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
SegmentedControlOption,
SegmentedControlProps,
} from "@leafygreen-ui/segmented-control";
import { Option, Select } from "@leafygreen-ui/select";
import { Option, Select, Size as SelectSize } from "@leafygreen-ui/select";
import TextArea from "@leafygreen-ui/text-area";
import TextInput, { State as TextInputState } from "@leafygreen-ui/text-input";
import Toggle from "@leafygreen-ui/toggle";
Expand Down Expand Up @@ -217,6 +217,7 @@ export const LeafyGreenSelect: React.FC<
elementWrapperCSS,
enumDisabled,
enumOptions,
sizeVariant,
} = options;
const { hasError } = processErrors(rawErrors);

Expand All @@ -240,6 +241,7 @@ export const LeafyGreenSelect: React.FC<
state={hasError && !disabled ? "error" : "none"}
errorMessage={hasError ? rawErrors?.join(", ") : ""}
popoverZIndex={zIndex.dropdown}
size={sizeVariant as SelectSize}
>
{enumOptions.map((o) => {
// LG Select doesn't handle disabled options well. So we need to ensure the selected option is not disabled
Expand Down
5 changes: 5 additions & 0 deletions apps/spruce/src/constants/externalResources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ export const githubChecksAliasesDocumentationUrl = `${projectDistroSettingsDocum
export const githubPermissionsDocumentationUrl =
"https://docs.github.com/en/rest/apps/apps#create-an-installation-access-token-for-an-app";

/**
* TODO: Documentation must be written by app team. Update this URL in DEVPROD-8967.
*/
export const githubTokenPermissionRestrictionsUrl = "";

export const ignoredFilesDocumentationUrl = `${wikiBaseUrl}/Project-Configuration/Project-Configuration-Files#ignoring-changes-to-certain-files`;

export const cliDocumentationUrl = `${wikiBaseUrl}/CLI`;
Expand Down
4 changes: 0 additions & 4 deletions apps/spruce/src/constants/patch.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,2 @@
export const commitQueueAlias = "__commit_queue";
export const commitQueueRequester = "merge_test";
export const githubMergeRequester = "github_merge_request";
export const githubPRRequester = "github_pull_request";
export const patchRequester = "patch_request";
export const unlinkedPRUsers = new Set(["github_pull_request", "parent_patch"]);
38 changes: 38 additions & 0 deletions apps/spruce/src/constants/requesters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { PartialRecord } from "types/utils";

// Not included in Requester enum because it will be deprecated.
const commitQueueRequester = "merge_test";

enum Requester {
AdHoc = "ad_hoc",
GitHubMergeQueue = "github_merge_request",
GitHubPR = "github_pull_request",
GitTag = "git_tag_request",
Gitter = "gitter_request",
Patch = "patch_request",
Trigger = "trigger_request",
}

const requesterToTitle: PartialRecord<Requester, string> = {
[Requester.AdHoc]: "Ad Hoc Request",
[Requester.GitHubMergeQueue]: "GitHub Merge Request",
[Requester.GitHubPR]: "GitHub Pull Request",
[Requester.GitTag]: "Git Tag Request",
[Requester.Gitter]: "Gitter Request",
[Requester.Patch]: "Patch Request",
[Requester.Trigger]: "Trigger Request",
};

const requesterToDescription: PartialRecord<Requester, string> = {
[Requester.AdHoc]: "Periodic build versions",
[Requester.Gitter]: "Repotracker versions",
[Requester.Patch]: "Manual patches made via CLI or API",
[Requester.Trigger]: "Downstream trigger versions",
};

export {
Requester,
commitQueueRequester,
requesterToTitle,
requesterToDescription,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
fragment ProjectAppSettings on Project {
githubPermissionGroupByRequester
}
2 changes: 2 additions & 0 deletions apps/spruce/src/gql/fragments/projectSettings/index.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#import "./containers.graphql"
#import "./viewsAndFilters.graphql"
#import "./permissionGroups.graphql"
#import "./appSettings.graphql"

fragment ProjectSettingsFields on ProjectSettings {
aliases {
Expand All @@ -22,6 +23,7 @@ fragment ProjectSettingsFields on ProjectSettings {
id
identifier
...ProjectAccessSettings
...ProjectAppSettings
...ProjectContainerSettings
...ProjectGeneralSettings
...ProjectNotificationSettings
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#import "./periodicBuilds.graphql"
#import "./viewsAndFilters.graphql"
#import "./permissionGroups.graphql"
#import "./appSettings.graphql"

fragment ProjectEventSettings on ProjectEventSettings {
aliases {
Expand All @@ -21,6 +22,7 @@ fragment ProjectEventSettings on ProjectEventSettings {
hidden
identifier
...ProjectAccessSettings
...ProjectAppSettings
...ProjectGeneralSettings
...ProjectNotificationSettings
...ProjectPatchAliasSettings
Expand Down
28 changes: 28 additions & 0 deletions apps/spruce/src/gql/generated/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,17 @@ export type DeleteDistroPayload = {
deletedDistroId: Scalars["String"]["output"];
};

/** DeleteGithubAppCredentialsInput is the input to the deleteGithubAppCredentials mutation. */
export type DeleteGithubAppCredentialsInput = {
projectId: Scalars["String"]["input"];
};

/** DeleteGithubAppCredentialsPayload is returned by the deleteGithubAppCredentials mutation. */
export type DeleteGithubAppCredentialsPayload = {
__typename?: "DeleteGithubAppCredentialsPayload";
oldAppId: Scalars["Int"]["output"];
};

export type Dependency = {
__typename?: "Dependency";
buildVariant: Scalars["String"]["output"];
Expand Down Expand Up @@ -1070,6 +1081,7 @@ export type Mutation = {
deactivateStepbackTask: Scalars["Boolean"]["output"];
defaultSectionToRepo?: Maybe<Scalars["String"]["output"]>;
deleteDistro: DeleteDistroPayload;
deleteGithubAppCredentials?: Maybe<DeleteGithubAppCredentialsPayload>;
deleteProject: Scalars["Boolean"]["output"];
deleteSubscriptions: Scalars["Int"]["output"];
detachProjectFromRepo: Project;
Expand Down Expand Up @@ -1182,6 +1194,10 @@ export type MutationDeleteDistroArgs = {
opts: DeleteDistroInput;
};

export type MutationDeleteGithubAppCredentialsArgs = {
opts: DeleteGithubAppCredentialsInput;
};

export type MutationDeleteProjectArgs = {
projectId: Scalars["String"]["input"];
};
Expand Down Expand Up @@ -3567,6 +3583,11 @@ export type AliasFragment = {
parameters: Array<{ __typename?: "Parameter"; key: string; value: string }>;
};

export type ProjectAppSettingsFragment = {
__typename?: "Project";
githubPermissionGroupByRequester?: { [key: string]: any } | null;
};

export type ProjectContainerSettingsFragment = {
__typename?: "Project";
containerSizeDefinitions?: Array<{
Expand Down Expand Up @@ -3735,6 +3756,7 @@ export type ProjectSettingsFieldsFragment = {
repoRefId: string;
admins?: Array<string> | null;
restricted?: boolean | null;
githubPermissionGroupByRequester?: { [key: string]: any } | null;
batchTime: number;
branch: string;
deactivatePrevious?: boolean | null;
Expand Down Expand Up @@ -4332,6 +4354,7 @@ export type ProjectEventSettingsFragment = {
versionControlEnabled?: boolean | null;
admins?: Array<string> | null;
restricted?: boolean | null;
githubPermissionGroupByRequester?: { [key: string]: any } | null;
batchTime: number;
branch: string;
deactivatePrevious?: boolean | null;
Expand Down Expand Up @@ -6795,6 +6818,7 @@ export type ProjectEventLogsQuery = {
versionControlEnabled?: boolean | null;
admins?: Array<string> | null;
restricted?: boolean | null;
githubPermissionGroupByRequester?: { [key: string]: any } | null;
batchTime: number;
branch: string;
deactivatePrevious?: boolean | null;
Expand Down Expand Up @@ -7007,6 +7031,7 @@ export type ProjectEventLogsQuery = {
versionControlEnabled?: boolean | null;
admins?: Array<string> | null;
restricted?: boolean | null;
githubPermissionGroupByRequester?: { [key: string]: any } | null;
batchTime: number;
branch: string;
deactivatePrevious?: boolean | null;
Expand Down Expand Up @@ -7292,6 +7317,7 @@ export type ProjectSettingsQuery = {
repoRefId: string;
admins?: Array<string> | null;
restricted?: boolean | null;
githubPermissionGroupByRequester?: { [key: string]: any } | null;
batchTime: number;
branch: string;
deactivatePrevious?: boolean | null;
Expand Down Expand Up @@ -7559,6 +7585,7 @@ export type RepoEventLogsQuery = {
versionControlEnabled?: boolean | null;
admins?: Array<string> | null;
restricted?: boolean | null;
githubPermissionGroupByRequester?: { [key: string]: any } | null;
batchTime: number;
branch: string;
deactivatePrevious?: boolean | null;
Expand Down Expand Up @@ -7771,6 +7798,7 @@ export type RepoEventLogsQuery = {
versionControlEnabled?: boolean | null;
admins?: Array<string> | null;
restricted?: boolean | null;
githubPermissionGroupByRequester?: { [key: string]: any } | null;
batchTime: number;
branch: string;
deactivatePrevious?: boolean | null;
Expand Down
6 changes: 6 additions & 0 deletions apps/spruce/src/pages/projectSettings/Tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,12 @@ export const ProjectSettingsTabs: React.FC<Props> = ({
tabData[ProjectSettingsTabRoutes.GithubAppSettings]
.projectData
}
githubPermissionGroups={
projectData?.projectRef?.githubDynamicTokenPermissionGroups ??
[]
}
// @ts-expect-error: FIXME. This comment was added by an automated script.
identifier={identifier}
/>
}
/>
Expand Down
Loading

0 comments on commit e5928bf

Please sign in to comment.