@rm3l/plugin-scaffolder-frontend-module-devfile-field
Version:
Devfile Field Extensions (Frontend Plugin)
233 lines (229 loc) • 8.93 kB
JavaScript
import { useApi, configApiRef, createPlugin } from '@backstage/core-plugin-api';
import { makeFieldSchemaFromZod, scaffolderPlugin } from '@backstage/plugin-scaffolder';
import { createScaffolderFieldExtension } from '@backstage/plugin-scaffolder-react';
import React, { useState } from 'react';
import { FormControl, TextField } from '@material-ui/core';
import { z } from 'zod';
import { useAsync } from 'react-use';
import Autocomplete from '@material-ui/lab/Autocomplete';
import { makeStyles } from '@material-ui/core/styles';
const DevfileSelectorExtensionWithOptionsFieldSchema = makeFieldSchemaFromZod(
z.object({
devfile: z.string().describe("Devfile name"),
version: z.string().describe("Devfile Stack version"),
starter_project: z.string().optional().describe("Devfile Stack starter project")
})
);
const DevfileSelectorExtensionWithOptionsSchema = DevfileSelectorExtensionWithOptionsFieldSchema.schema;
const useStyles = makeStyles({
option: {
fontSize: 15,
"& > span": {
marginRight: 10,
fontSize: 18
}
}
});
const DevfileSelectorExtension = ({
onChange,
rawErrors,
required,
formData,
idSchema,
schema: { description }
}) => {
var _a, _b, _c, _d;
const config = useApi(configApiRef);
const [loading, setLoading] = useState(true);
const [data, setData] = useState([]);
const [selectedStack, setSelectedStack] = useState("");
const [versions, setVersions] = useState([]);
const [selectedVersion, setSelectedVersion] = useState("");
const [starterprojects, setStarterprojects] = useState([]);
const backendUrl = config.getString("backend.baseUrl");
const registryApiEndpoint = `${backendUrl}/api/proxy/devfile-registry/v2index`;
useAsync(async () => {
const req = await fetch(registryApiEndpoint, {
headers: {
Accept: "application/json"
}
});
const resp = await req.json();
resp.sort(
(a, b) => {
var _a2, _b2;
return ((_a2 = a.displayName) != null ? _a2 : "").localeCompare((_b2 = b.displayName) != null ? _b2 : "");
}
);
setData(resp);
setVersions([]);
setStarterprojects([]);
setLoading(false);
});
const handleDevfileStack = (value) => {
var _a2;
const filteredStacks = data.filter((stack) => stack.name === (value == null ? void 0 : value.name));
const versionList = filteredStacks.flatMap((stack) => stack.versions);
const filteredVersions = versionList.map((v) => v.version);
filteredVersions.sort((a, b) => a.localeCompare(b));
let filteredStarterProjects = [];
if (versionList.length > 0) {
filteredStarterProjects = (_a2 = versionList[0].starterProjects) != null ? _a2 : [];
}
setSelectedStack(value.name);
setSelectedVersion((filteredVersions == null ? void 0 : filteredVersions.length) > 0 ? filteredVersions[0] : "");
setVersions(filteredVersions);
setStarterprojects(filteredStarterProjects);
onChange({
devfile: value.name,
version: versionList.length > 0 ? versionList[0].version : "",
starter_project: filteredStarterProjects.length > 0 ? filteredStarterProjects[0] : ""
});
};
const handleDevfileStackVersion = (value) => {
const filteredResult = data.filter((stack) => stack.name === selectedStack).flatMap((stack) => stack.versions).filter((v) => v.version === value).flatMap((v) => {
var _a2;
return (_a2 = v.starterProjects) != null ? _a2 : [];
});
filteredResult.sort((a, b) => a.localeCompare(b));
setSelectedVersion(value);
setStarterprojects(filteredResult);
onChange({
devfile: selectedStack,
version: value,
starter_project: filteredResult.length > 0 ? filteredResult[0] : ""
});
};
const handleDevfileStarterProject = (value) => {
onChange({
devfile: selectedStack,
version: selectedVersion,
starter_project: value
});
};
const classes = useStyles();
return /* @__PURE__ */ React.createElement(
FormControl,
{
margin: "normal",
required,
error: (rawErrors == null ? void 0 : rawErrors.length) > 0
},
/* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(
Autocomplete,
{
id: `devfile-selector-${idSchema == null ? void 0 : idSchema.$id}`,
loading,
noOptionsText: "No Devfile Stacks available from registry",
value: (
// dummy DevfileStack object with the name set, so that getOptionSelected can resolve the right item from data
{ name: (_a = formData == null ? void 0 : formData.devfile) != null ? _a : selectedStack, icon: "", displayName: "", versions: [] }
),
classes: {
option: classes.option
},
options: data,
renderOption: (option) => option.icon ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("span", null, /* @__PURE__ */ React.createElement(
"img",
{
style: { width: 50, height: 50 },
src: option.icon,
alt: `icon for ${option.name}`
}
)), option.displayName) : /* @__PURE__ */ React.createElement(React.Fragment, null, option.displayName),
getOptionLabel: (option) => option.name,
renderInput: (params) => /* @__PURE__ */ React.createElement(
TextField,
{
...params,
label: "Devfile Stack",
variant: "outlined",
required,
error: (rawErrors == null ? void 0 : rawErrors.length) > 0 && !formData,
inputProps: {
...params.inputProps,
autoComplete: "new-password"
// disable autocomplete and autofill
},
helperText: description
}
),
onChange: (_, value) => handleDevfileStack(value),
getOptionSelected: (option, value) => option.name === value.name,
disableClearable: true
}
)),
/* @__PURE__ */ React.createElement("br", null),
/* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(
Autocomplete,
{
id: `devfile-version-selector-${idSchema == null ? void 0 : idSchema.$id}`,
loading,
value: (_c = (_b = formData == null ? void 0 : formData.version) != null ? _b : selectedVersion) != null ? _c : versions.length > 0 ? versions[0] : null,
noOptionsText: "No version available in Devfile Stack",
renderInput: (params) => /* @__PURE__ */ React.createElement(
TextField,
{
...params,
label: "Version",
variant: "outlined",
required,
error: (rawErrors == null ? void 0 : rawErrors.length) > 0 && !formData,
helperText: description,
inputProps: {
...params.inputProps,
autoComplete: "new-password"
// disable autocomplete and autofill
}
}
),
options: versions,
onChange: (_, value) => handleDevfileStackVersion(value),
getOptionSelected: (option, value) => option === value,
disableClearable: true
}
)),
/* @__PURE__ */ React.createElement("br", null),
/* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(
Autocomplete,
{
id: `devfile-starter-project-selector-${idSchema == null ? void 0 : idSchema.$id}`,
loading,
value: (_d = formData == null ? void 0 : formData.starter_project) != null ? _d : starterprojects.length > 0 ? starterprojects[0] : "",
noOptionsText: "No starter project available in Devfile Stack",
renderInput: (params) => /* @__PURE__ */ React.createElement(
TextField,
{
...params,
label: "Starter Project",
variant: "outlined",
required: false,
error: (rawErrors == null ? void 0 : rawErrors.length) > 0 && !formData,
inputProps: {
...params.inputProps,
autoComplete: "new-password"
// disable autocomplete and autofill
},
helperText: description
}
),
options: starterprojects,
onChange: (_, value) => handleDevfileStarterProject(value),
getOptionSelected: (option, value) => option === value,
disableClearable: true
}
))
);
};
const devfileSelectorExtensionPlugin = createPlugin({
id: "devfile-selector-extension"
});
const DevfileSelectorFieldExtension = scaffolderPlugin.provide(
createScaffolderFieldExtension({
name: "DevfileSelectorExtension",
component: DevfileSelectorExtension,
schema: DevfileSelectorExtensionWithOptionsSchema
})
);
export { DevfileSelectorFieldExtension, devfileSelectorExtensionPlugin };
//# sourceMappingURL=index.esm.js.map