Merge pull request #117 from ctk-hq/feat/depends-on-spec

Update `depends_on` field to adhere the specification
master
Artem Golub 2 years ago committed by GitHub
commit 4cf2ecdd37
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -8,7 +8,8 @@ import {
IServiceNodeItem,
IVolumeNodeItem,
IServiceNodePosition,
IProject
IProject,
IEditServiceFormDependsOn
} from "../../types";
import eventBus from "../../events/eventBus";
import { useMutation } from "react-query";
@ -40,6 +41,7 @@ import EditVolumeModal from "../modals/docker-compose/volume/EditVolumeModal";
import CodeEditor from "../CodeEditor";
import { useTitle } from "../../hooks";
import VisibilitySwitch from "../global/VisibilitySwitch";
import _ from "lodash";
interface IProjectProps {
isAuthenticated: boolean;
@ -248,6 +250,22 @@ export default function Project(props: IProjectProps) {
setNetworks({ ..._networks });
};
const getDependsOnKeys = (dependsOn: any): string[] => {
let dependsOnKeys: string[] = [];
if (dependsOn) {
if (dependsOn.constructor === Object) {
dependsOnKeys = Object.keys(dependsOn);
}
if (Array.isArray(dependsOn)) {
dependsOnKeys = dependsOn as [];
}
}
return dependsOnKeys;
};
const onUpdateEndpoint = (nodeItem: IServiceNodeItem) => {
const key = nodeItem.key;
@ -266,11 +284,10 @@ export default function Project(props: IProjectProps) {
});
}
if (
nodeItem.serviceConfig?.depends_on &&
Array.isArray(nodeItem.serviceConfig.depends_on)
) {
nodeItem.serviceConfig.depends_on.forEach((dep: string) => {
const dependsOnData = nodeItem.serviceConfig.depends_on;
const dependsOnKeys = getDependsOnKeys(dependsOnData);
dependsOnKeys.forEach((dep: string) => {
const depObject = Object.keys(nodes).find((key: string) => {
const node = nodes[key];
if (node.canvasConfig.node_name === dep) {
@ -282,7 +299,6 @@ export default function Project(props: IProjectProps) {
onConnectionAttached([key, depObject]);
}
});
}
setNodes({ ...nodes, [nodeItem.key]: nodeItem });
};
@ -301,19 +317,25 @@ export default function Project(props: IProjectProps) {
} as IServiceNodeItem;
const targetNode = stateNodesRef.current[data[1]];
const targetServiceName = targetNode.canvasConfig.node_name;
const sourceDependsOn = sourceNode.serviceConfig.depends_on as string[];
const sourceDependsOn = sourceNode.serviceConfig.depends_on as any;
if (sourceDependsOn && sourceDependsOn.length) {
if (targetServiceName) {
const filtered = sourceDependsOn.filter(
(nodeName: string) => nodeName !== targetServiceName
);
const dependsOnKeys = getDependsOnKeys(sourceDependsOn);
if (filtered.length) {
sourceNode.serviceConfig.depends_on = filtered;
} else {
delete sourceNode.serviceConfig.depends_on;
dependsOnKeys.forEach((key: string) => {
if (key === targetServiceName) {
if (Array.isArray(sourceDependsOn)) {
_.remove(sourceDependsOn, (key) => key === targetServiceName);
}
if (sourceDependsOn && sourceDependsOn.constructor === Object) {
delete sourceDependsOn[key];
}
}
});
if (!getDependsOnKeys(sourceDependsOn).length) {
delete sourceNode.serviceConfig.depends_on;
}
}
}
@ -337,13 +359,22 @@ export default function Project(props: IProjectProps) {
} as IServiceNodeItem;
const targetNode = stateNodesRef.current[data[1]];
const targetServiceName = targetNode.canvasConfig.node_name;
let sourceDependsOn = sourceNode.serviceConfig.depends_on as string[];
let sourceDependsOn = sourceNode.serviceConfig.depends_on as any;
const dependsOnKeys = getDependsOnKeys(sourceDependsOn);
if (sourceDependsOn && sourceDependsOn.length) {
if (sourceDependsOn) {
if (targetServiceName) {
if (!sourceDependsOn.includes(targetServiceName)) {
if (!dependsOnKeys.includes(targetServiceName)) {
if (Array.isArray(sourceDependsOn)) {
sourceDependsOn.push(targetServiceName);
}
if (sourceDependsOn.constructor === Object) {
sourceDependsOn[targetServiceName] = {
condition: "service_healthy"
};
}
}
}
} else {
if (targetServiceName) {

@ -222,14 +222,38 @@ const General = () => {
title: "Depends on",
fields: (index: number): TFinalFormField[] => [
{
id: `dependsOn[${index}]`,
id: `dependsOn[${index}].serviceName`,
type: "text",
name: `dependsOn[${index}]`,
name: `dependsOn[${index}].serviceName`,
placeholder: "Service name",
required: false
required: true
},
{
id: `dependsOn[${index}].condition`,
type: "toggle",
name: `dependsOn[${index}].condition`,
label: "Condition",
required: true,
options: [
{
value: "service_started",
text: "Service started"
},
{
value: "service_healthy",
text: "Service healthy"
},
{
value: "service_completed_successfully",
text: "Service completed successfully"
}
]
}
],
newValue: ""
newValue: {
serviceName: "",
condition: "service_started"
}
},
{
id: "networks",

@ -1,4 +1,8 @@
import type { IEditServiceForm, IServiceNodeItem } from "../../../../types";
import type {
IEditServiceForm,
IEditServiceFormDependsOn,
IServiceNodeItem
} from "../../../../types";
import * as yup from "yup";
import {
checkArray,
@ -278,9 +282,43 @@ export const validationSchema = yup.object({
key: yup.string().required("Key is required"),
value: yup.string()
})
),
dependsOn: yup.array(
yup.object({
serviceName: yup.string().required("Service name is required"),
condition: yup
.string()
.oneOf(
[
"service_started",
"service_healthy",
"service_completed_successfully"
],
"Condition should be one of: service_started, service_healthy, or service_completed_successfully."
)
})
),
workingDir: yup.string()
});
const extractDependsOn = (object: string[] | any) => {
if (!object) {
return object;
}
if (Array.isArray(object)) {
return object.map((item) => ({
serviceName: item,
condition: "service_started"
}));
}
return Object.keys(object).map((key) => ({
serviceName: key,
condition: object[key].condition
}));
};
export const getInitialValues = (node?: IServiceNodeItem): IEditServiceForm => {
if (!node) {
return {
@ -456,8 +494,7 @@ export const getInitialValues = (node?: IServiceNodeItem): IEditServiceForm => {
initialValues.deploy.labels
}
: initialValues.deploy,
dependsOn:
(depends_on as string[]) ?? (initialValues.dependsOn as string[]),
dependsOn: extractDependsOn(depends_on) ?? initialValues.dependsOn,
entrypoint: (entrypoint as string) ?? (initialValues.entrypoint as string),
envFile: (env_file as string) ?? (initialValues.envFile as string),
imageName,
@ -500,6 +537,31 @@ export const getInitialValues = (node?: IServiceNodeItem): IEditServiceForm => {
};
};
const getFinalDependsOn = (dependsOn: IEditServiceFormDependsOn[]) => {
let shortForm = true;
for (const item of dependsOn) {
if (item.condition !== "service_started") {
shortForm = false;
break;
}
}
if (shortForm) {
return pruneArray(dependsOn.map((item) => item.serviceName));
}
return pruneObject(
Object.fromEntries(
dependsOn.map((item) => [
item.serviceName,
{
condition: item.condition
}
])
)
);
};
export const getFinalValues = (
values: IEditServiceForm,
previous?: IServiceNodeItem
@ -606,7 +668,7 @@ export const getFinalValues = (
}),
labels: packArrayAsObject(deploy.labels, "key", "value")
}),
depends_on: pruneArray(dependsOn),
depends_on: getFinalDependsOn(dependsOn),
entrypoint: pruneString(entrypoint),
env_file: pruneString(envFile),
image: pruneString(

@ -149,11 +149,15 @@ export interface IService {
credential_spec: KeyValPair;
depends_on:
| string[]
| {
[key: string]: {
condition: string;
};
};
| Record<
string,
{
condition:
| "service_started"
| "service_healthy"
| "service_completed_successfully";
}
>;
deploy?: {
endpoint_mode?: "vip" | "dnsrr";
labels?: string[] | Record<string, string>;
@ -480,10 +484,18 @@ export interface IEditServiceForm {
key: string;
value: string;
}[];
dependsOn: string[];
dependsOn: IEditServiceFormDependsOn[];
workingDir: string;
}
export interface IEditServiceFormDependsOn {
serviceName: string;
condition:
| "service_started"
| "service_healthy"
| "service_completed_successfully";
}
export interface IEditVolumeForm {
entryName: string;
volumeName: string;

Loading…
Cancel
Save