Skip to content

Commit

Permalink
Merge pull request #809 from kinvolk/apply-multiple-resources
Browse files Browse the repository at this point in the history
Apply multiple resources at once
  • Loading branch information
joaquimrocha authored Nov 22, 2022
2 parents e6cd349 + 5752fab commit f7461c7
Show file tree
Hide file tree
Showing 5 changed files with 179 additions and 113 deletions.
75 changes: 45 additions & 30 deletions frontend/src/components/common/Resource/CreateButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,42 +15,57 @@ export default function CreateButton() {
const location = useLocation();
const { t } = useTranslation(['resource', 'frequent']);

const applyFunc = async (newItem: KubeObjectInterface) => {
try {
await apply(newItem);
} catch (err) {
let msg = t('Something went wrong…');
if (err instanceof Error) {
msg = err.message;
}
setErrorMessage(msg);
setOpenDialog(true);
throw err;
}
const applyFunc = async (newItems: KubeObjectInterface[]) => {
await Promise.allSettled(newItems.map(newItem => apply(newItem))).then((values: any) => {
values.forEach((value: any, index: number) => {
if (value.status === 'rejected') {
let msg;
const kind = newItems[index].kind;
const name = newItems[index].metadata.name;
const apiVersion = newItems[index].apiVersion;
if (newItems.length === 1) {
msg = t('Failed to create {{ kind }} {{ name }}.', { kind, name });
} else {
msg = t('Failed to create {{ kind }} {{ name }} in {{ apiVersion }}.', {
kind,
name,
apiVersion,
});
}
setErrorMessage(msg);
setOpenDialog(true);
throw msg;
}
});
});
};

function handleSave(newItemDef: KubeObjectInterface) {
function handleSave(newItemDefs: KubeObjectInterface[]) {
const cancelUrl = location.pathname;

if (!newItemDef.metadata?.name) {
setErrorMessage(t('Please set a name to the resource!'));
return;
}

if (!newItemDef.kind) {
setErrorMessage(t('Please set a kind to the resource!'));
return;
// check if all yaml objects are valid
for (let i = 0; i < newItemDefs.length; i++) {
if (!newItemDefs[i].metadata?.name) {
setErrorMessage(t(`Invalid: One or more of the resource doesn't have a name property`));
return;
}
if (!newItemDefs[i].kind) {
setErrorMessage(t('Invalid: Please set a kind to the resource!'));
return;
}
}

const newItemName = newItemDef.metadata.name;

// all resources name
const resourceNames = newItemDefs.map(newItemDef => newItemDef.metadata.name);
setOpenDialog(false);
dispatch(
clusterAction(() => applyFunc(newItemDef), {
startMessage: t('Applying {{ newItemName }}…', { newItemName }),
cancelledMessage: t('Cancelled applying {{ newItemName }}.', { newItemName }),
successMessage: t('Applied {{ newItemName }}.', { newItemName }),
errorMessage: t('Failed to apply {{ newItemName }}.', { newItemName }),
clusterAction(() => applyFunc(newItemDefs), {
startMessage: t('Applying {{ newItemName }}…', { newItemName: resourceNames.join(',') }),
cancelledMessage: t('Cancelled applying {{ newItemName }}.', {
newItemName: resourceNames.join(','),
}),
successMessage: t('Applied {{ newItemName }}.', { newItemName: resourceNames.join(',') }),
errorMessage: t('Failed to apply {{ newItemName }}.', {
newItemName: resourceNames.join(','),
}),
cancelUrl,
})
);
Expand Down
129 changes: 82 additions & 47 deletions frontend/src/components/common/Resource/DocsViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { makeStyles } from '@material-ui/core/styles';
import Typography from '@material-ui/core/Typography';
import TreeItem from '@material-ui/lab/TreeItem';
import TreeView from '@material-ui/lab/TreeView';
import _ from 'lodash';
import React from 'react';
import { useTranslation } from 'react-i18next';
import getDocDefinitions from '../../../lib/docs';
Expand All @@ -13,7 +12,6 @@ import Loader from '../Loader';

const useStyles = makeStyles(() => ({
root: {
height: 216,
flexGrow: 1,
maxWidth: 400,
},
Expand All @@ -23,32 +21,53 @@ const useStyles = makeStyles(() => ({
function DocsViewer(props: { docSpecs: any }) {
const { docSpecs } = props;
const classes = useStyles();
const [docs, setDocs] = React.useState<object | null>(null);
const [docsError, setDocsError] = React.useState<string | null>(null);
const [docs, setDocs] = React.useState<
(
| {
data: null;
error: any;
kind: string;
}
| {
data: any;
error: null;
kind: string;
}
| undefined
)[]
>([]);
const [docsLoading, setDocsLoading] = React.useState(false);
const { t } = useTranslation('resource');

React.useEffect(() => {
setDocsError(null);

if (docSpecs.error) {
t('Cannot load documentation: {{ docsError }}', { docsError: docSpecs.error });
return;
}
if (!docSpecs.apiVersion || !docSpecs.kind) {
setDocsError(
t(
'Cannot load documentation: Please make sure the YAML is valid and has the kind and apiVersion set.'
)
);
return;
}

getDocDefinitions(docSpecs.apiVersion, docSpecs.kind)
.then(result => {
setDocs(result?.properties || {});
setDocsLoading(true);
// fetch docSpecs for all the resources specified
Promise.allSettled(
docSpecs.map((docSpec: { apiVersion: string; kind: string }) => {
return getDocDefinitions(docSpec.apiVersion, docSpec.kind);
}) as PromiseSettledResult<any>[]
)
.then(values => {
const docSpecsFromApi = values.map((value, index) => {
if (value.status === 'fulfilled') {
return {
data: value.value,
error: null,
kind: docSpecs[index].kind,
};
} else if (value.status === 'rejected') {
return {
data: null,
error: value.reason,
kind: docSpecs[index].kind,
};
}
});
setDocsLoading(false);
setDocs(docSpecsFromApi);
})
.catch(err => {
setDocsError(t('Cannot load documentation: {{err}}', { err }));
.catch(() => {
setDocsLoading(false);
});
}, [docSpecs]);

Expand All @@ -74,29 +93,45 @@ function DocsViewer(props: { docSpecs: any }) {
);
}

return docs === null && docsError === null ? (
<Loader title={t('Loading documentation')} />
) : !_.isEmpty(docsError) ? (
<Empty color="error">{docsError}</Empty>
) : _.isEmpty(docs) ? (
<Empty>
{t('No documentation for type {{ docsType }}.', { docsType: docSpecs.kind.trim() })}
</Empty>
) : (
<Box p={4}>
<Typography>
{t('Showing documentation for: {{ docsType }}', {
docsType: docSpecs.kind.trim(),
})}
</Typography>
<TreeView
className={classes.root}
defaultCollapseIcon={<Icon icon="mdi:chevron-down" />}
defaultExpandIcon={<Icon icon="mdi:chevron-right" />}
>
{Object.entries(docs || {}).map(([name, value], i) => makeItems(name, value, i.toString()))}
</TreeView>
</Box>
return (
<>
{docsLoading ? (
<Loader title={t('Loading documentation')} />
) : (
docs.map((docSpec: any) => {
if (!docSpec.error && !docSpec.data) {
return (
<Empty>
{t('No documentation for type {{ docsType }}.', { docsType: docSpec.kind.trim() })}
</Empty>
);
}
if (docSpec.error) {
return <Empty color="error">{docSpec.error.message}</Empty>;
}
if (docSpec.data) {
return (
<Box p={2}>
<Typography>
{t('Showing documentation for: {{ docsType }}', {
docsType: docSpec.kind.trim(),
})}
</Typography>
<TreeView
className={classes.root}
defaultCollapseIcon={<Icon icon="mdi:chevron-down" />}
defaultExpandIcon={<Icon icon="mdi:chevron-right" />}
>
{Object.entries(docSpec.data.properties || {}).map(([name, value], i) =>
makeItems(name, value, i.toString())
)}
</TreeView>
</Box>
);
}
})
)}
</>
);
}

Expand Down
Loading

0 comments on commit f7461c7

Please sign in to comment.