Skip to content
This repository has been archived by the owner on Jul 2, 2024. It is now read-only.

EVG-19950: Add host settings page #2014

Merged
merged 13 commits into from
Sep 8, 2023
57 changes: 57 additions & 0 deletions cypress/integration/distroSettings/host_section.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { save } from "./utils";

describe("host section", () => {
describe("using legacy ssh", () => {
beforeEach(() => {
cy.visit("/distro/localhost/settings/host");
});

it("shows the correct fields when distro has static provider", () => {
cy.dataCy("authorized-keys-input").should("exist");
cy.dataCy("minimum-hosts-input").should("not.exist");
cy.dataCy("maximum-hosts-input").should("not.exist");
cy.dataCy("idle-time-input").should("not.exist");
cy.dataCy("future-fraction-input").should("not.exist");
});

it("errors when selecting an incompatible host communication method", () => {
cy.selectLGOption("Host Communication Method", "RPC");
save();
cy.validateToast("error");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: does it make sense to populate the second parameter for description assertion

cy.selectLGOption("Host Communication Method", "Legacy SSH");
});

it("updates host fields", () => {
cy.selectLGOption("Agent Architecture", "Linux ARM 64-bit");
cy.getInputByLabel("Working Directory").clear();
cy.getInputByLabel("Working Directory").type("/usr/local/bin");
cy.getInputByLabel("SSH User").clear();
cy.getInputByLabel("SSH User").type("sudo");
cy.contains("button", "Add SSH option").click();
cy.getInputByLabel("SSH Option").type("BatchMode=yes");
cy.selectLGOption("Host Allocator Rounding Rule", "Round down");
cy.selectLGOption("Host Allocator Feedback Rule", "No feedback");
cy.selectLGOption(
"Host Overallocation Rule",
"Terminate hosts when overallocated"
);

save();
cy.validateToast("success");

// Reset fields
cy.selectLGOption("Agent Architecture", "Linux 64-bit");
cy.getInputByLabel("Working Directory").clear();
cy.getInputByLabel("Working Directory").type("/home/ubuntu/smoke");
cy.getInputByLabel("SSH User").clear();
cy.getInputByLabel("SSH User").type("ubuntu");
cy.dataCy("delete-item-button").click();
cy.selectLGOption("Host Allocator Rounding Rule", "Default");
cy.selectLGOption("Host Allocator Feedback Rule", "Default");
cy.selectLGOption("Host Overallocation Rule", "Default");

save();
cy.validateToast("success");
});
});
});
88 changes: 67 additions & 21 deletions src/gql/generated/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,21 +69,37 @@ export type Annotation = {
webhookConfigured: Scalars["Boolean"]["output"];
};

export enum Arch {
Linux_64Bit = "LINUX_64_BIT",
LinuxArm_64Bit = "LINUX_ARM_64_BIT",
LinuxPpc_64Bit = "LINUX_PPC_64_BIT",
LinuxZseries = "LINUX_ZSERIES",
Osx_64Bit = "OSX_64_BIT",
OsxArm_64Bit = "OSX_ARM_64_BIT",
Windows_64Bit = "WINDOWS_64_BIT",
}

export enum BannerTheme {
Announcement = "ANNOUNCEMENT",
Important = "IMPORTANT",
Information = "INFORMATION",
Warning = "WARNING",
}

export enum BootstrapMethod {
LegacySsh = "LEGACY_SSH",
Ssh = "SSH",
UserData = "USER_DATA",
}

export type BootstrapSettings = {
__typename?: "BootstrapSettings";
clientDir: Scalars["String"]["output"];
communication: Scalars["String"]["output"];
communication: CommunicationMethod;
env: Array<EnvVar>;
jasperBinaryDir: Scalars["String"]["output"];
jasperCredentialsPath: Scalars["String"]["output"];
method: Scalars["String"]["output"];
method: BootstrapMethod;
preconditionScripts: Array<PreconditionScript>;
resourceLimits: ResourceLimits;
rootDir: Scalars["String"]["output"];
Expand All @@ -93,11 +109,11 @@ export type BootstrapSettings = {

export type BootstrapSettingsInput = {
clientDir: Scalars["String"]["input"];
communication: Scalars["String"]["input"];
communication: CommunicationMethod;
env: Array<EnvVarInput>;
jasperBinaryDir: Scalars["String"]["input"];
jasperCredentialsPath: Scalars["String"]["input"];
method: Scalars["String"]["input"];
method: BootstrapMethod;
preconditionScripts: Array<PreconditionScriptInput>;
resourceLimits: ResourceLimitsInput;
rootDir: Scalars["String"]["input"];
Expand Down Expand Up @@ -236,6 +252,12 @@ export type CommitQueueParamsInput = {
message?: InputMaybe<Scalars["String"]["input"]>;
};

export enum CommunicationMethod {
LegacySsh = "LEGACY_SSH",
Rpc = "RPC",
Ssh = "SSH",
}

export type ContainerResources = {
__typename?: "ContainerResources";
cpu: Scalars["Int"]["output"];
Expand Down Expand Up @@ -328,7 +350,7 @@ export type DisplayTask = {
export type Distro = {
__typename?: "Distro";
aliases: Array<Scalars["String"]["output"]>;
arch: Scalars["String"]["output"];
arch: Arch;
authorizedKeysFile: Scalars["String"]["output"];
bootstrapSettings: BootstrapSettings;
cloneMethod: CloneMethod;
Expand Down Expand Up @@ -392,7 +414,7 @@ export type DistroInfo = {

export type DistroInput = {
aliases: Array<Scalars["String"]["input"]>;
arch: Scalars["String"]["input"];
arch: Arch;
authorizedKeysFile: Scalars["String"]["input"];
bootstrapSettings: BootstrapSettingsInput;
cloneMethod: CloneMethod;
Expand Down Expand Up @@ -512,6 +534,12 @@ export type ExternalLinkInput = {
urlTemplate: Scalars["String"]["input"];
};

export enum FeedbackRule {
Default = "DEFAULT",
NoFeedback = "NO_FEEDBACK",
WaitsOverThresh = "WAITS_OVER_THRESH",
}

export type File = {
__typename?: "File";
link: Scalars["String"]["output"];
Expand Down Expand Up @@ -670,26 +698,30 @@ export type Host = {
export type HostAllocatorSettings = {
__typename?: "HostAllocatorSettings";
acceptableHostIdleTime: Scalars["Duration"]["output"];
feedbackRule: Scalars["String"]["output"];
feedbackRule: FeedbackRule;
futureHostFraction: Scalars["Float"]["output"];
hostsOverallocatedRule: Scalars["String"]["output"];
hostsOverallocatedRule: OverallocatedRule;
maximumHosts: Scalars["Int"]["output"];
minimumHosts: Scalars["Int"]["output"];
roundingRule: Scalars["String"]["output"];
version: Scalars["String"]["output"];
roundingRule: RoundingRule;
version: HostAllocatorVersion;
};

export type HostAllocatorSettingsInput = {
acceptableHostIdleTime: Scalars["Int"]["input"];
feedbackRule: Scalars["String"]["input"];
feedbackRule: FeedbackRule;
futureHostFraction: Scalars["Float"]["input"];
hostsOverallocatedRule: Scalars["String"]["input"];
hostsOverallocatedRule: OverallocatedRule;
maximumHosts: Scalars["Int"]["input"];
minimumHosts: Scalars["Int"]["input"];
roundingRule: Scalars["String"]["input"];
version: Scalars["String"]["input"];
roundingRule: RoundingRule;
version: HostAllocatorVersion;
};

export enum HostAllocatorVersion {
Utilization = "UTILIZATION",
}

export type HostEventLogData = {
__typename?: "HostEventLogData";
agentBuild: Scalars["String"]["output"];
Expand Down Expand Up @@ -1322,6 +1354,12 @@ export type OomTrackerInfo = {
pids?: Maybe<Array<Maybe<Scalars["Int"]["output"]>>>;
};

export enum OverallocatedRule {
Default = "DEFAULT",
Ignore = "IGNORE",
Terminate = "TERMINATE",
}

export type Parameter = {
__typename?: "Parameter";
key: Scalars["String"]["output"];
Expand Down Expand Up @@ -2168,6 +2206,12 @@ export type ResourceLimitsInput = {
virtualMemoryKb: Scalars["Int"]["input"];
};

export enum RoundingRule {
Default = "DEFAULT",
Down = "DOWN",
Up = "UP",
}

/** SaveDistroInput is the input to the saveDistro mutation. */
export type SaveDistroInput = {
distro: DistroInput;
Expand Down Expand Up @@ -2711,6 +2755,7 @@ export type TriggerAlias = {
project: Scalars["String"]["output"];
status: Scalars["String"]["output"];
taskRegex: Scalars["String"]["output"];
unscheduleDownstreamVersions?: Maybe<Scalars["Boolean"]["output"]>;
};

export type TriggerAliasInput = {
Expand All @@ -2722,6 +2767,7 @@ export type TriggerAliasInput = {
project: Scalars["String"]["input"];
status: Scalars["String"]["input"];
taskRegex: Scalars["String"]["input"];
unscheduleDownstreamVersions?: InputMaybe<Scalars["Boolean"]["input"]>;
};

export type UiConfig = {
Expand Down Expand Up @@ -5141,7 +5187,7 @@ export type DistroQuery = {
distro?: {
__typename?: "Distro";
aliases: Array<string>;
arch: string;
arch: Arch;
authorizedKeysFile: string;
cloneMethod: CloneMethod;
containerPool: string;
Expand All @@ -5164,10 +5210,10 @@ export type DistroQuery = {
bootstrapSettings: {
__typename?: "BootstrapSettings";
clientDir: string;
communication: string;
communication: CommunicationMethod;
jasperBinaryDir: string;
jasperCredentialsPath: string;
method: string;
method: BootstrapMethod;
rootDir: string;
serviceUser: string;
shellPath: string;
Expand Down Expand Up @@ -5199,13 +5245,13 @@ export type DistroQuery = {
hostAllocatorSettings: {
__typename?: "HostAllocatorSettings";
acceptableHostIdleTime: number;
feedbackRule: string;
feedbackRule: FeedbackRule;
futureHostFraction: number;
hostsOverallocatedRule: string;
hostsOverallocatedRule: OverallocatedRule;
maximumHosts: number;
minimumHosts: number;
roundingRule: string;
version: string;
roundingRule: RoundingRule;
version: HostAllocatorVersion;
};
iceCreamSettings: {
__typename?: "IceCreamSettings";
Expand Down
26 changes: 11 additions & 15 deletions src/pages/distroSettings/HeaderButtons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
import { SAVE_DISTRO } from "gql/mutations";
import { useDistroSettingsContext } from "./Context";
import { formToGqlMap } from "./tabs/transformers";
import { WritableDistroSettingsType } from "./tabs/types";
import { FormToGqlFunction, WritableDistroSettingsType } from "./tabs/types";

interface Props {
distro: DistroQuery["distro"];
Expand Down Expand Up @@ -61,20 +61,16 @@ export const HeaderButtons: React.FC<Props> = ({ distro, tab }) => {
});

const handleSave = () => {
// Only perform the save operation is the tab is valid.
// eslint-disable-next-line no-prototype-builtins
if (formToGqlMap.hasOwnProperty(tab)) {
const formToGql = formToGqlMap[tab];
const changes = formToGql(formData, distro);
saveDistro({
variables: {
distro: changes,
onSave: onSaveOperation,
},
});
setModalOpen(false);
sendEvent({ name: "Save distro", section: tab });
}
const formToGql: FormToGqlFunction<typeof tab> = formToGqlMap[tab];
const changes = formToGql(formData, distro);
Fixed Show fixed Hide fixed
saveDistro({
variables: {
distro: changes,
onSave: onSaveOperation,
},
});
setModalOpen(false);
sendEvent({ name: "Save distro", section: tab });
};

return (
Expand Down
18 changes: 13 additions & 5 deletions src/pages/distroSettings/Tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ import { NavigationModal } from "./NavigationModal";
import {
EventLogTab,
GeneralTab,
HostTab,
ProjectTab,
ProviderTab,
TaskTab,
} from "./tabs/index";
import { gqlToFormMap } from "./tabs/transformers";
import { FormStateMap } from "./tabs/types";

interface Props {
distro: DistroQuery["distro"];
Expand All @@ -26,7 +28,6 @@ export const DistroSettingsTabs: React.FC<Props> = ({ distro }) => {
const tabData = useMemo(() => getTabData(distro), [distro]);

useEffect(() => {
// @ts-expect-error TODO: Type when all tabs have been implemented
setInitialData(tabData);
}, [setInitialData, tabData]);

Expand Down Expand Up @@ -59,6 +60,15 @@ export const DistroSettingsTabs: React.FC<Props> = ({ distro }) => {
<TaskTab distroData={tabData[DistroSettingsTabRoutes.Task]} />
}
/>
<Route
path={DistroSettingsTabRoutes.Host}
element={
<HostTab
distroData={tabData[DistroSettingsTabRoutes.Host]}
provider={distro.provider}
/>
}
/>
<Route
path={DistroSettingsTabRoutes.Project}
element={
Expand All @@ -74,15 +84,13 @@ export const DistroSettingsTabs: React.FC<Props> = ({ distro }) => {
);
};

/* Map data from query to the tab to which it will be passed */
// TODO: Type when all tabs have been implemented
const getTabData = (data: Props["distro"]) =>
const getTabData = (data: Props["distro"]): FormStateMap =>
Object.keys(gqlToFormMap).reduce(
(obj, tab) => ({
...obj,
[tab]: gqlToFormMap[tab](data),
}),
{}
{} as FormStateMap
);

const Container = styled.div`
Expand Down
14 changes: 14 additions & 0 deletions src/pages/distroSettings/tabs/HostTab/HostTab.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { useMemo } from "react";
import { BaseTab } from "../BaseTab";
import { getFormSchema } from "./getFormSchema";
import { TabProps } from "./types";

export const HostTab: React.FC<TabProps> = ({ distroData, provider }) => {
const initialFormState = distroData;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const initialFormState = distroData;


const formSchema = useMemo(() => getFormSchema({ provider }), [provider]);

return (
<BaseTab formSchema={formSchema} initialFormState={initialFormState} />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<BaseTab formSchema={formSchema} initialFormState={initialFormState} />
<BaseTab formSchema={formSchema} initialFormState={distroData} />

);
};
Loading