Skip to content

Commit

Permalink
use FE temporary uuid for services
Browse files Browse the repository at this point in the history
  • Loading branch information
iannbing committed Feb 11, 2022
1 parent 8f44bb9 commit 302c737
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 48 deletions.
52 changes: 37 additions & 15 deletions services/orchest-webserver/client/src/components/ServiceForm.tsx
Expand Up @@ -24,6 +24,7 @@ import Tooltip from "@mui/material/Tooltip";
import Typography from "@mui/material/Typography";
import { visuallyHidden } from "@mui/utils";
import {
hasValue,
makeCancelable,
makeRequest,
PromiseManager,
Expand Down Expand Up @@ -60,11 +61,12 @@ const FormSectionTitle: React.FC<{ title: string }> = ({ children, title }) => (

const ServiceForm: React.FC<{
service: Service;
services: Record<string, Service>;
serviceUuid: string;
project_uuid: string;
pipeline_uuid: string;
run_uuid: string;
updateService: (service: Service) => void;
nameChangeService: (oldName: string, newName: string) => void;
disabled: boolean;
}> = (props) => {
const environmentPrefix = "environment@";
Expand Down Expand Up @@ -100,9 +102,24 @@ const ServiceForm: React.FC<{
props.updateService(service);
};

const [serviceName, setServiceName] = React.useState(props.service.name);
const [serviceNameError, setServiceNameError] = React.useState<string | null>(
null
);
const hasServiceNameError = hasValue(serviceNameError);

const allServiceNames = React.useMemo(() => {
return Object.values(props.services).map((s) => s.name);
}, [props.services]);
const handleNameChange = (newName: string) => {
let oldName = props.service["name"];
props.nameChangeService(oldName, newName);
setServiceName(newName);

if (newName !== props.service.name && allServiceNames.includes(newName)) {
setServiceNameError("Same service name has been taken");
return;
}
setServiceNameError(null);
props.updateService({ ...props.service, name: newName });
};

const handleServiceBindsChange = (key: string, value: any) => {
Expand Down Expand Up @@ -194,19 +211,23 @@ const ServiceForm: React.FC<{
label="Name"
type="text"
disabled={props.disabled}
value={props.service.name}
error={hasServiceNameError}
value={serviceName}
onChange={(e) => {
handleNameChange(e.target.value);
}}
fullWidth
helperText="The name of the service. Up to 36 digits, letters or dashes are allowed."
helperText={
serviceNameError ||
"The name of the service. Up to 36 digits, letters or dashes are allowed."
}
aria-describedby="tooltip-name"
data-test-id={`service-${props.service.name}-name`}
/>
<TextField
label="Image"
type="text"
disabled={props.disabled}
disabled={props.disabled || hasServiceNameError}
onClick={() => {
// TODO: improve this
setShowImageDialog(true);
Expand All @@ -231,7 +252,7 @@ const ServiceForm: React.FC<{
<TextField
label="Entrypoint (optional)"
type="text"
disabled={props.disabled}
disabled={props.disabled || hasServiceNameError}
value={props.service.entrypoint}
onChange={(e) => {
handleServiceChange("entrypoint", e.target.value);
Expand All @@ -245,7 +266,7 @@ const ServiceForm: React.FC<{
<TextField
label="Command (optional)"
type="text"
disabled={props.disabled}
disabled={props.disabled || hasServiceNameError}
value={props.service.command}
onChange={(e) => {
handleServiceChange("command", e.target.value);
Expand All @@ -264,7 +285,7 @@ const ServiceForm: React.FC<{
<TextField
label="Project directory (optional)"
type="text"
disabled={props.disabled}
disabled={props.disabled || hasServiceNameError}
value={props.service.binds?.["/project-dir"]}
onChange={(e) => {
handleServiceBindsChange("/project-dir", e.target.value);
Expand All @@ -275,7 +296,7 @@ const ServiceForm: React.FC<{
<TextField
label="Data directory (optional)"
type="text"
disabled={props.disabled}
disabled={props.disabled || hasServiceNameError}
value={props.service?.binds?.["/data"]}
onChange={(e) => {
handleServiceBindsChange("/data", e.target.value);
Expand All @@ -299,7 +320,7 @@ const ServiceForm: React.FC<{
}))
: []
}
disabled={props.disabled}
disabled={props.disabled || hasServiceNameError}
onChange={(ports) => {
handleServiceChange(
"ports",
Expand Down Expand Up @@ -350,6 +371,7 @@ const ServiceForm: React.FC<{
control={
<Checkbox
checked={props.service?.preserve_base_path === true}
disabled={props.disabled || hasServiceNameError}
onChange={(e) => {
handleServiceChange(
"preserve_base_path",
Expand All @@ -368,7 +390,7 @@ const ServiceForm: React.FC<{
<FormGroup>
<FormControlLabel
label="Authentication required"
disabled={props.disabled}
disabled={props.disabled || hasServiceNameError}
control={
<Checkbox
checked={
Expand All @@ -394,7 +416,7 @@ const ServiceForm: React.FC<{
<FormGroup>
<FormControlLabel
label="Interactive sessions"
disabled={props.disabled}
disabled={props.disabled || hasServiceNameError}
control={
<Checkbox
checked={
Expand All @@ -411,7 +433,7 @@ const ServiceForm: React.FC<{
/>
<FormControlLabel
label="Job sessions"
disabled={props.disabled}
disabled={props.disabled || hasServiceNameError}
control={
<Checkbox
checked={
Expand Down Expand Up @@ -443,7 +465,7 @@ const ServiceForm: React.FC<{
value: env_variable.toString(),
}
)}
disabled={props.disabled}
disabled={props.disabled || hasServiceNameError}
onChange={(env_variables) => {
handleServiceChange(
"env_variables_inherit",
Expand Down
@@ -1,7 +1,7 @@
import { getOrderValue } from "@/pipeline-settings-view/common";
import { PipelineJson } from "@/types";
import { getPipelineJSONEndpoint } from "@/utils/webserver-utils";
import { fetcher } from "@orchest/lib-utils";
import { fetcher, uuidv4 } from "@orchest/lib-utils";
import React from "react";
import useSWR from "swr";
import { MutatorCallback } from "swr/dist/types";
Expand Down Expand Up @@ -46,6 +46,14 @@ export const useFetchPipelineJson = (props: FetchPipelineJsonProps | null) => {
pipelineObj.services = {};
}

// use temporary uuid for easier FE manipulation, will be cleaned up when saving
pipelineObj.services = Object.values(pipelineObj.services).reduce(
(all, curr) => {
return { ...all, [uuidv4()]: curr };
},
{}
);

// Augment services with order key
for (let service in pipelineObj.services) {
pipelineObj.services[service].order = getOrderValue();
Expand Down
Expand Up @@ -61,6 +61,7 @@ import {
makeCancelable,
makeRequest,
PromiseManager,
uuidv4,
} from "@orchest/lib-utils";
import "codemirror/mode/javascript/javascript";
import cloneDeep from "lodash.clonedeep";
Expand Down Expand Up @@ -214,43 +215,33 @@ const PipelineSettingsView: React.FC = () => {
const addServiceFromTemplate = (service: ServiceTemplate["config"]) => {
let clonedService = cloneDeep(service);

// Take care of service name collisions
let x = 1;
let baseServiceName = clonedService.name;
while (x < 100) {
if (pipelineJson.services[clonedService.name] == undefined) {
break;
}
clonedService.name = baseServiceName + x;
x++;
}
const newCount = Object.values(pipelineJson?.services || {}).reduce(
(count, service) => {
const newName = `${clonedService.name}${count === 0 ? "" : count}`;
return service.name === newName ? count + 1 : count;
},
0
);

clonedService.name = `${clonedService.name}${
newCount === 0 ? "" : newCount
}`;

onChangeService(clonedService);
onChangeService(uuidv4(), clonedService);
};

const onChangeService = (service: Service) => {
const onChangeService = (uuid: string, service: Service) => {
setPipelineJson((current) => {
// Maintain client side order key
if (service.order === undefined) service.order = getOrderValue();
current.services[service.name] = service;
current.services[uuid] = service;
return current;
});

setServicesChanged(true);
setAsSaved(false);
};

const nameChangeService = (oldName: string, newName: string) => {
setPipelineJson((current) => {
current.services[newName] = { ...current.services[oldName] };
current.services[newName].name = newName;
delete current.services[oldName];
return current;
});
setServicesChanged(true);
setAsSaved(false);
};

const deleteService = async (serviceName: string) => {
setPipelineJson((current) => {
delete current.services[serviceName];
Expand Down Expand Up @@ -325,8 +316,13 @@ const PipelineSettingsView: React.FC = () => {
pipelineJson: PipelineJson
): Omit<PipelineJson, "order"> => {
let pipelineCopy = cloneDeep(pipelineJson);
for (let serviceName in pipelineCopy.services) {
delete pipelineCopy.services[serviceName].order;
for (let uuid in pipelineCopy.services) {
const serviceName = pipelineCopy.services[uuid].name;
delete pipelineCopy.services[uuid].order;
pipelineCopy.services[serviceName] = {
...pipelineCopy.services[uuid],
};
delete pipelineCopy.services[uuid];
}
return pipelineCopy;
};
Expand Down Expand Up @@ -515,27 +511,26 @@ const PipelineSettingsView: React.FC = () => {
},
];

console.log("HM", pipelineJson?.services);

const serviceRows: DataTableRow<ServiceRow>[] = !pipelineJson
? []
: Object.entries(pipelineJson.services)
.sort((a, b) => a[1].order - b[1].order)
.map(([key, service]) => {
return {
uuid: key,
name: key,
name: service.name,
scope: service.scope
.map((scopeAsString) => scopeMap[scopeAsString])
.join(", "),
remove: key,
details: (
<ServiceForm
key={`ServiceForm-${key}`}
key={key}
serviceUuid={key}
service={service}
services={pipelineJson.services}
disabled={isReadOnly}
updateService={onChangeService}
nameChangeService={nameChangeService}
updateService={(updated) => onChangeService(key, updated)}
pipeline_uuid={pipelineUuid}
project_uuid={projectUuid}
run_uuid={runUuid}
Expand Down

0 comments on commit 302c737

Please sign in to comment.