lodash#head TypeScript Examples
The following examples show how to use
lodash#head.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: loaders-explorer.service.ts From nestjs-mercurius with MIT License | 6 votes |
private registerContextProvider<T = any>(request: T, contextId: ContextId) {
const coreModuleArray = [...this.modulesContainer.entries()]
.filter(
([key, { metatype }]) =>
metatype && metatype.name === InternalCoreModule.name,
)
.map(([key, value]) => value);
const coreModuleRef = head(coreModuleArray);
if (!coreModuleRef) {
return;
}
const wrapper = coreModuleRef.getProviderByKey(REQUEST);
wrapper.setInstanceByContextId(contextId, {
instance: request,
isResolved: true,
});
}
Example #2
Source File: BuildHeader.tsx From amplication with Apache License 2.0 | 6 votes |
BuildHeader = ({ build, deployments, isError }: Props) => {
const deployedClassName = `${CLASS_NAME}--deployed`;
const deployment = head(deployments);
const isDeployed =
deployment && deployment.status === models.EnumDeploymentStatus.Completed;
return (
<div className={`${CLASS_NAME} ${isDeployed && deployedClassName} `}>
<ClickableId
to={`/${build.appId}/builds/${build.id}`}
id={build.id}
label="Build ID"
eventData={{
eventName: "buildHeaderIdClick",
}}
/>
{isError ? (
<Link to={`/${build.appId}/builds/${build.id}`}>
<h3 className="error-message">Build Failed Check Logs</h3>
</Link>
) : null}
<span className="spacer" />
{deployment && isDeployed && (
<a href={deployment.environment.address} target="app">
<Icon icon="link_2" />
</a>
)}
</div>
);
}
Example #3
Source File: row-text-click.ts From S2 with MIT License | 6 votes |
private getRowIndex = (cellData: Node) => {
const isTree = this.spreadsheet.options.hierarchyType === 'tree';
if (isTree) {
let child = cellData;
while (!isEmpty(child.children)) {
child = head(child.children);
}
return cellData.rowIndex ?? child.rowIndex;
}
// if current cell has no row index, return dynamic computed value
const rowIndex = Math.floor(cellData.y / cellData.height);
return cellData.rowIndex ?? rowIndex;
};
Example #4
Source File: traceSummary.ts From erda-ui with GNU Affero General Public License v3.0 | 6 votes |
// What's the total duration of the spans in this trace?
// 最后结束的(时间戳+耗时最大)- 第一个调用的时间戳
export function traceDuration(spans: any) {
function makeList({ timestamp, duration }: any) {
if (!timestamp) {
return [];
} else if (!duration) {
return [timestamp];
}
return [timestamp, timestamp + duration];
}
// turns (timestamp, timestamp + duration) into an ordered list
const timestamps = fp.flow(fp.flatMap(makeList), fp.sortBy(identity))(spans);
if (timestamps.length < 2) {
return null;
}
const firstTime = head(timestamps);
const lastTime = last(timestamps);
return lastTime - firstTime;
}
Example #5
Source File: flow-right.spec.ts From s-libs with MIT License | 6 votes |
describe('flowRight()', () => {
//
// stolen from https://github.com/lodash/lodash
//
it('should supply each function with the return value of the previous', () => {
const increment = (x: number): number => x + 1;
const square = (x: number): number => x * x;
const fixed = (n: number): string => n.toFixed(1);
expect(flowRight(fixed, square, increment)(2)).toBe('9.0');
});
it('should return an identity function when no arguments are given', () => {
expect(flowRight()('a')).toBe('a');
});
it('should work with a curried function and `_.head`', () => {
const curried: any = curry(identity);
const combined: any = flowRight(head as any, curried);
expect(combined([1])).toBe(1);
});
});
Example #6
Source File: flow.spec.ts From s-libs with MIT License | 6 votes |
describe('flow()', () => {
//
// stolen from https://github.com/lodash/lodash
//
it('should supply each function with the return value of the previous', () => {
const increment = (x: number): number => x + 1;
const square = (x: number): number => x * x;
const fixed = (n: number): string => n.toFixed(1);
expect(flow(increment, square, fixed)(2)).toBe('9.0');
});
it('should return an identity function when no arguments are given', () => {
expect(flow()('a')).toBe('a');
});
it('should work with a curried function and `_.head`', () => {
const curried: any = curry(identity);
const combined: any = flow(head as any, curried);
expect(combined([1])).toBe(1);
});
});
Example #7
Source File: common-notify-group.tsx From erda-ui with GNU Affero General Public License v3.0 | 5 votes |
ListTargets = ({
targets = [],
roleMap,
}: {
targets: COMMON_STRATEGY_NOTIFY.INotifyTarget[];
roleMap: any;
}) => {
const userMap = useUserMap();
const { values = [], type } = targets[0] || {};
const firstValue = head(values)?.receiver as string;
let text = '';
let targetsEle = (
<>
<ErdaIcon fill="black-4" size="16" type="sidebarUser" className="color-text-desc mr-1" />
<Tooltip title={`${i18n.t('dop:group address')}: ${firstValue}`}>
<span className="group-address nowrap">{`${i18n.t('dop:group address')}: ${firstValue}`}</span>
</Tooltip>
</>
);
switch (type) {
case TargetType.USER:
text = `${userMap[firstValue] ? userMap[firstValue].nick : '--'} ${i18n.t('dop:and {length} others', {
length: values.length,
})}`;
targetsEle = (
<>
<div className="group-members mr-2 flex">
{map(take(values, 6), (obj: { receiver: string }) => (
<UserInfo.RenderWithAvatar id={obj.receiver} key={obj.receiver} showName={false} />
))}
</div>
<Tooltip title={text}>
<span className="nowrap">{text}</span>
</Tooltip>
</>
);
break;
case TargetType.EXTERNAL_USER:
text = `${JSON.parse(firstValue).username} ${i18n.t('dop:and {length} others', {
length: values.length,
})}`;
targetsEle = (
<>
<div className="group-members mr-2">
{map(take(values, 3), (obj: { receiver: string }) => {
const { username } = JSON.parse(obj.receiver);
return (
<Avatar size={'small'} key={username}>
{getAvatarChars(username)}
</Avatar>
);
})}
</div>
<Tooltip title={text}>
<span className="nowrap">{text}</span>
</Tooltip>
</>
);
break;
case TargetType.ROLE:
text = `${i18n.t('dop:notify role')}:${map(values, (obj) => roleMap[obj.receiver]).join(',')}`;
targetsEle = (
<>
<ErdaIcon fill="black-4" size="16" type="sidebarUser" className="mr-1" />
<Tooltip title={text}>
<span className="group-address nowrap">{text}</span>
</Tooltip>
</>
);
break;
default:
break;
}
return targetsEle;
}
Example #8
Source File: TestDetailsModal.tsx From frontend with Apache License 2.0 | 4 votes |
TestDetailsModal: React.FunctionComponent<{
testRun: TestRun;
touched: boolean;
handleClose: () => void;
}> = ({ testRun, touched, handleClose }) => {
const classes = useStyles();
const navigate = useNavigate();
const { enqueueSnackbar } = useSnackbar();
const testRunDispatch = useTestRunDispatch();
const stageWidth = (window.innerWidth / 2) * 0.8;
const stageHeigth = window.innerHeight * 0.6;
const stageScaleBy = 1.2;
const [stageScale, setStageScale] = React.useState(1);
const [stagePos, setStagePos] = React.useState(defaultStagePos);
const [stageInitPos, setStageInitPos] = React.useState(defaultStagePos);
const [stageOffset, setStageOffset] = React.useState(defaultStagePos);
const [processing, setProcessing] = React.useState(false);
const [isDrawMode, setIsDrawMode] = useState(false);
const [valueOfIgnoreOrCompare, setValueOfIgnoreOrCompare] = useState(
"Ignore Areas"
);
const [isDiffShown, setIsDiffShown] = useState(false);
const [selectedRectId, setSelectedRectId] = React.useState<string>();
const [ignoreAreas, setIgnoreAreas] = React.useState<IgnoreArea[]>([]);
const [applyIgnoreDialogOpen, setApplyIgnoreDialogOpen] = React.useState(
false
);
const toggleApplyIgnoreDialogOpen = () => {
setApplyIgnoreDialogOpen(!applyIgnoreDialogOpen);
};
const [image, imageStatus] = useImage(
staticService.getImage(testRun.imageName)
);
const [baselineImage, baselineImageStatus] = useImage(
staticService.getImage(testRun.baselineName)
);
const [diffImage, diffImageStatus] = useImage(
staticService.getImage(testRun.diffName)
);
const applyIgnoreAreaText =
"Apply selected ignore area to all images in this build.";
React.useEffect(() => {
fitStageToScreen();
// eslint-disable-next-line
}, [image]);
React.useEffect(() => {
setIsDiffShown(!!testRun.diffName);
}, [testRun.diffName]);
React.useEffect(() => {
setIgnoreAreas(JSON.parse(testRun.ignoreAreas));
}, [testRun]);
const isImageSizeDiffer = React.useMemo(
() =>
testRun.baselineName &&
testRun.imageName &&
(image?.height !== baselineImage?.height ||
image?.width !== baselineImage?.width),
[image, baselineImage, testRun.baselineName, testRun.imageName]
);
const handleIgnoreAreaChange = (ignoreAreas: IgnoreArea[]) => {
setIgnoreAreas(ignoreAreas);
testRunDispatch({
type: "touched",
payload: testRun.ignoreAreas !== JSON.stringify(ignoreAreas),
});
};
const removeSelection = (event: KonvaEventObject<MouseEvent>) => {
// deselect when clicked not on Rect
const isRectClicked = event.target.className === "Rect";
if (!isRectClicked) {
setSelectedRectId(undefined);
}
};
const deleteIgnoreArea = (id: string) => {
handleIgnoreAreaChange(ignoreAreas.filter((area) => area.id !== id));
setSelectedRectId(undefined);
};
const saveTestRun = (ignoreAreas: IgnoreArea[], successMessage: string) => {
testRunService
.updateIgnoreAreas({
ids: [testRun.id],
ignoreAreas,
})
.then(() => {
enqueueSnackbar(successMessage, {
variant: "success",
});
})
.catch((err) =>
enqueueSnackbar(err, {
variant: "error",
})
);
};
const saveIgnoreAreasOrCompareArea = () => {
if (valueOfIgnoreOrCompare.includes("Ignore")) {
saveTestRun(ignoreAreas, "Ignore areas are updated.");
} else {
const invertedIgnoreAreas = invertIgnoreArea(
image!.width,
image!.height,
head(ignoreAreas)
);
handleIgnoreAreaChange(invertedIgnoreAreas);
saveTestRun(
invertedIgnoreAreas,
"Selected area has been inverted to ignore areas and saved."
);
}
testRunDispatch({ type: "touched", payload: false });
};
const onIgnoreOrCompareSelectChange = (value: string) => {
if (value.includes("Compare")) {
setValueOfIgnoreOrCompare("Compare Area");
} else {
setValueOfIgnoreOrCompare("Ignore Areas");
}
};
const setOriginalSize = () => {
setStageScale(1);
resetPositioin();
};
const fitStageToScreen = () => {
const scale = image
? Math.min(
stageWidth < image.width ? stageWidth / image.width : 1,
stageHeigth < image.height ? stageHeigth / image.height : 1
)
: 1;
setStageScale(scale);
resetPositioin();
};
const resetPositioin = () => {
setStagePos(defaultStagePos);
setStageOffset(defaultStagePos);
};
const applyIgnoreArea = () => {
let newIgnoreArea = ignoreAreas.find((area) => selectedRectId! === area.id);
if (newIgnoreArea) {
setProcessing(true);
testRunService
.getList(testRun.buildId)
.then((testRuns: TestRun[]) => {
let allIds = testRuns.map((item) => item.id);
let data: UpdateIgnoreAreaDto = {
ids: allIds,
ignoreAreas: [newIgnoreArea!],
};
testRunService.addIgnoreAreas(data).then(() => {
setProcessing(false);
setSelectedRectId(undefined);
enqueueSnackbar(
"Ignore areas are updated in all images in this build.",
{
variant: "success",
}
);
});
})
.catch((error) => {
enqueueSnackbar("There was an error : " + error, {
variant: "error",
});
setProcessing(false);
});
} else {
enqueueSnackbar(
"There was an error determining which ignore area to apply.",
{ variant: "error" }
);
}
};
useHotkeys(
"d",
() => !!testRun.diffName && setIsDiffShown((isDiffShown) => !isDiffShown),
[testRun.diffName]
);
useHotkeys("ESC", handleClose, [handleClose]);
return (
<React.Fragment>
<AppBar position="sticky">
<Toolbar>
<Grid container justifyContent="space-between">
<Grid item>
<Typography variant="h6">{testRun.name}</Typography>
</Grid>
{testRun.diffName && (
<Grid item>
<Tooltip title={"Hotkey: D"}>
<Switch
checked={isDiffShown}
onChange={() => setIsDiffShown(!isDiffShown)}
name="Toggle diff"
/>
</Tooltip>
</Grid>
)}
{(testRun.status === TestStatus.unresolved ||
testRun.status === TestStatus.new) && (
<Grid item>
<ApproveRejectButtons testRun={testRun} />
</Grid>
)}
<Grid item>
<IconButton color="inherit" onClick={handleClose}>
<Close />
</IconButton>
</Grid>
</Grid>
</Toolbar>
</AppBar>
{processing && <LinearProgress />}
<Box m={1}>
<Grid container alignItems="center">
<Grid item xs={12}>
<Grid container alignItems="center">
<Grid item>
<TestRunDetails testRun={testRun} />
</Grid>
{isImageSizeDiffer && (
<Grid item>
<Tooltip
title={
"Image height/width differ from baseline! Cannot calculate diff!"
}
>
<IconButton>
<WarningRounded color="secondary" />
</IconButton>
</Tooltip>
</Grid>
)}
</Grid>
</Grid>
<Grid item>
<Grid container alignItems="center" spacing={2}>
<Grid item>
<Select
id="area-select"
labelId="areaSelect"
value={valueOfIgnoreOrCompare}
onChange={(event) =>
onIgnoreOrCompareSelectChange(event.target.value as string)
}
>
{["Ignore Areas", "Compare Area"].map((eachItem) => (
<MenuItem key={eachItem} value={eachItem}>
{eachItem}
</MenuItem>
))}
</Select>
</Grid>
<Grid item>
<ToggleButton
value={"drawMode"}
selected={isDrawMode}
onClick={() => {
setIsDrawMode(!isDrawMode);
}}
>
<Add />
</ToggleButton>
</Grid>
<Grid item>
<IconButton
disabled={!selectedRectId || ignoreAreas.length === 0}
onClick={() =>
selectedRectId && deleteIgnoreArea(selectedRectId)
}
>
<Delete />
</IconButton>
</Grid>
<Tooltip title="Clears all ignore areas." aria-label="reject">
<Grid item>
<IconButton
disabled={ignoreAreas.length === 0}
onClick={() => {
handleIgnoreAreaChange([]);
}}
>
<LayersClear />
</IconButton>
</Grid>
</Tooltip>
<Tooltip
title={applyIgnoreAreaText}
aria-label="apply ignore area"
>
<Grid item>
<IconButton
disabled={!selectedRectId || ignoreAreas.length === 0}
onClick={() => toggleApplyIgnoreDialogOpen()}
>
<Collections />
</IconButton>
</Grid>
</Tooltip>
<Grid item>
<IconButton
disabled={!touched}
onClick={() => saveIgnoreAreasOrCompareArea()}
>
<Save />
</IconButton>
</Grid>
</Grid>
</Grid>
<Grid item>
<Button
color="primary"
disabled={!testRun.testVariationId}
onClick={() => {
navigate(
`${routes.VARIATION_DETAILS_PAGE}/${testRun.testVariationId}`
);
}}
>
Baseline history
</Button>
</Grid>
<Grid item>
<CommentsPopper
text={testRun.comment}
onSave={(comment) =>
testRunService
.update(testRun.id, { comment })
.then(() =>
enqueueSnackbar("Comment updated", {
variant: "success",
})
)
.catch((err) =>
enqueueSnackbar(err, {
variant: "error",
})
)
}
/>
</Grid>
</Grid>
</Box>
<Box
overflow="hidden"
minHeight="65%"
className={classes.drawAreaContainer}
>
<Grid container style={{ height: "100%" }}>
<Grid item xs={6} className={classes.drawAreaItem}>
<DrawArea
type="Baseline"
imageName={testRun.baselineName}
branchName={testRun.baselineBranchName}
imageState={[baselineImage, baselineImageStatus]}
ignoreAreas={[]}
tempIgnoreAreas={[]}
setIgnoreAreas={handleIgnoreAreaChange}
selectedRectId={selectedRectId}
setSelectedRectId={setSelectedRectId}
onStageClick={removeSelection}
stageScaleState={[stageScale, setStageScale]}
stagePosState={[stagePos, setStagePos]}
stageInitPosState={[stageInitPos, setStageInitPos]}
stageOffsetState={[stageOffset, setStageOffset]}
drawModeState={[false, setIsDrawMode]}
/>
</Grid>
<Grid item xs={6} className={classes.drawAreaItem}>
{isDiffShown ? (
<DrawArea
type="Diff"
imageName={testRun.diffName}
branchName={testRun.branchName}
imageState={[diffImage, diffImageStatus]}
ignoreAreas={ignoreAreas}
tempIgnoreAreas={JSON.parse(testRun.tempIgnoreAreas)}
setIgnoreAreas={handleIgnoreAreaChange}
selectedRectId={selectedRectId}
setSelectedRectId={setSelectedRectId}
onStageClick={removeSelection}
stageScaleState={[stageScale, setStageScale]}
stagePosState={[stagePos, setStagePos]}
stageInitPosState={[stageInitPos, setStageInitPos]}
stageOffsetState={[stageOffset, setStageOffset]}
drawModeState={[isDrawMode, setIsDrawMode]}
/>
) : (
<DrawArea
type="Image"
imageName={testRun.imageName}
branchName={testRun.branchName}
imageState={[image, imageStatus]}
ignoreAreas={ignoreAreas}
tempIgnoreAreas={JSON.parse(testRun.tempIgnoreAreas)}
setIgnoreAreas={handleIgnoreAreaChange}
selectedRectId={selectedRectId}
setSelectedRectId={setSelectedRectId}
onStageClick={removeSelection}
stageScaleState={[stageScale, setStageScale]}
stagePosState={[stagePos, setStagePos]}
stageInitPosState={[stageInitPos, setStageInitPos]}
stageOffsetState={[stageOffset, setStageOffset]}
drawModeState={[isDrawMode, setIsDrawMode]}
/>
)}
</Grid>
</Grid>
</Box>
<ScaleActionsSpeedDial
onZoomInClick={() => setStageScale(stageScale * stageScaleBy)}
onZoomOutClick={() => setStageScale(stageScale / stageScaleBy)}
onOriginalSizeClick={setOriginalSize}
onFitIntoScreenClick={fitStageToScreen}
/>
<BaseModal
open={applyIgnoreDialogOpen}
title={applyIgnoreAreaText}
submitButtonText={"Yes"}
onCancel={toggleApplyIgnoreDialogOpen}
content={
<Typography>
{`All images in the current build will be re-compared with new ignore area taken into account. Are you sure?`}
</Typography>
}
onSubmit={() => {
toggleApplyIgnoreDialogOpen();
applyIgnoreArea();
}}
/>
</React.Fragment>
);
}
Example #9
Source File: BulkOperation.tsx From frontend with Apache License 2.0 | 4 votes |
BulkOperation: React.FunctionComponent = () => {
const props = useGridSlotComponentProps();
const { enqueueSnackbar } = useSnackbar();
const [approveDialogOpen, setApproveDialogOpen] = React.useState(false);
const [rejectDialogOpen, setRejectDialogOpen] = React.useState(false);
const [deleteDialogOpen, setDeleteDialogOpen] = React.useState(false);
const [downloadDialogOpen, setDownloadDialogOpen] = React.useState(false);
const [clearIgnoreDialogOpen, setClearIgnoreDialogOpen] = React.useState(false);
const [isProcessing, setIsProcessing] = React.useState(false);
const ids: GridRowId[] = React.useMemo(
() => Object.values(props.state.selection),
[props.state.selection]
);
const isMerge: boolean = React.useMemo(
() =>
!!head(
props.rows.filter((value: GridRowData) =>
ids.includes(value.id.toString())
)
)?.merge,
// eslint-disable-next-line
[ids]
);
const idsEligibleForApproveOrReject: string[] = React.useMemo(
() =>
props.rows
.filter(
(value: GridRowData) =>
ids.includes(value.id.toString()) &&
[TestStatus.new, TestStatus.unresolved].includes(
value.status.toString()
)
)
.map((value: GridRowData) => value.id.toString()),
// eslint-disable-next-line
[ids]
);
const selectedRows: GridSelectionModel = props.state.selection;
const count = Object.keys(selectedRows).length;
const toggleApproveDialogOpen = () => {
setApproveDialogOpen(!approveDialogOpen);
};
const toggleRejectDialogOpen = () => {
setRejectDialogOpen(!rejectDialogOpen);
};
const toggleDeleteDialogOpen = () => {
setDeleteDialogOpen(!deleteDialogOpen);
};
const toggleDownloadDialogOpen = () => {
setDownloadDialogOpen(!downloadDialogOpen);
};
const toggleClearIgnoreDialogOpen = () => {
setClearIgnoreDialogOpen(!clearIgnoreDialogOpen);
};
const getTitle = () => {
if (clearIgnoreDialogOpen) {
return "Clear Ignore Area For Selected Items";
}
return submitButtonText() + " Test Runs";
};
const submitButtonText = (): string => {
if (approveDialogOpen) {
return "Approve";
}
if (rejectDialogOpen) {
return "Reject";
}
if (deleteDialogOpen) {
return "Delete";
}
if (downloadDialogOpen) {
return "Download";
}
if (clearIgnoreDialogOpen) {
return "Clear";
}
return "";
};
const closeModal = () => {
if (deleteDialogOpen) {
return toggleDeleteDialogOpen();
}
if (downloadDialogOpen) {
return toggleDownloadDialogOpen();
}
if (approveDialogOpen) {
return toggleApproveDialogOpen();
}
if (rejectDialogOpen) {
return toggleRejectDialogOpen();
}
if (clearIgnoreDialogOpen) {
return toggleClearIgnoreDialogOpen();
}
};
const getBulkAction = () => {
if (deleteDialogOpen) {
return testRunService.removeBulk(ids);
}
if (downloadDialogOpen) {
let urlsToDownload: { download: string, filename: string }[] = [];
ids.forEach((id) => {
testRunService.getDetails(id.toString())
.then(
(e) => {
urlsToDownload.push({ "download": "static/imageUploads/" + e.imageName, "filename": e.name });
//Call getFile function only when all images names are pushed into the array.
if (urlsToDownload.length === ids.length) {
testRunService.getFiles(urlsToDownload);
}
});
});
}
if (rejectDialogOpen) {
return testRunService.rejectBulk(idsEligibleForApproveOrReject);
}
if (approveDialogOpen) {
return testRunService.approveBulk(idsEligibleForApproveOrReject, isMerge);
}
return testRunService.updateIgnoreAreas({
ids,
ignoreAreas: [],
});
};
const dismissDialog = () => {
if (deleteDialogOpen) {
return toggleDeleteDialogOpen();
}
if (downloadDialogOpen) {
return toggleDownloadDialogOpen();
}
if (approveDialogOpen) {
return toggleApproveDialogOpen();
}
if (clearIgnoreDialogOpen) {
return toggleClearIgnoreDialogOpen();
}
return toggleRejectDialogOpen();
};
return (
<>
<Tooltip
title="Approve unresolved in selected rows."
aria-label="approve"
>
<span>
<IconButton disabled={count === 0} onClick={toggleApproveDialogOpen}>
<ThumbUp />
</IconButton>
</span>
</Tooltip>
<Tooltip title="Reject unresolved in selected rows." aria-label="reject">
<span>
<IconButton disabled={count === 0} onClick={toggleRejectDialogOpen}>
<ThumbDown />
</IconButton>
</span>
</Tooltip>
<Tooltip title="Download images for selected rows." aria-label="download">
<span>
<IconButton disabled={count === 0} onClick={toggleDownloadDialogOpen}>
<CloudDownload />
</IconButton>
</span>
</Tooltip>
<Tooltip title="Delete selected rows." aria-label="delete">
<span>
<IconButton disabled={count === 0} onClick={toggleDeleteDialogOpen}>
<Delete />
</IconButton>
</span>
</Tooltip>
<Tooltip
title="Clear ignore areas for selected rows."
aria-label="clear ignore area"
>
<span>
<IconButton
disabled={count === 0}
onClick={toggleClearIgnoreDialogOpen}
>
<LayersClear />
</IconButton>
</span>
</Tooltip>
<BaseModal
open={
deleteDialogOpen ||
downloadDialogOpen ||
approveDialogOpen ||
rejectDialogOpen ||
clearIgnoreDialogOpen
}
title={getTitle()}
submitButtonText={submitButtonText()}
onCancel={dismissDialog}
content={
<Typography>
{`Are you sure you want to ${submitButtonText().toLowerCase()} ${count} items?`}
</Typography>
}
onSubmit={() => {
setIsProcessing(true);
getBulkAction()
.then(() => {
setIsProcessing(false);
enqueueSnackbar(`${count} test runs processed.`, {
variant: "success",
});
})
.catch((err) => {
setIsProcessing(false);
enqueueSnackbar(err, {
variant: "error",
});
});
closeModal();
}}
/>
{isProcessing && <LinearProgress />}
</>
);
}
Example #10
Source File: ChartWithTooltip.tsx From aqualink-app with MIT License | 4 votes |
function ChartWithTooltip({
chartSettings,
children,
className,
style,
...rest
}: PropsWithChildren<ChartWithTooltipProps>) {
const { siteId, surveys, timeZone, startDate, endDate, datasets } = rest;
const chartDataRef = useRef<Line>(null);
const [tooltipPosition, setTooltipPosition] = useState({ top: 0, left: 0 });
const [tooltipData, setTooltipData] = useState<TooltipData>({
siteId,
date: "",
datasets: [],
surveyId: null,
});
const [showTooltip, setShowTooltip] = useState<boolean>(false);
const customTooltip =
(ref: React.RefObject<Line>) => (tooltipModel: ChartTooltipModel) => {
const chart = ref.current;
if (!chart?.chartInstance.canvas) {
return;
}
const date = tooltipModel.dataPoints?.[0]?.xLabel;
if (typeof date !== "string") return;
const dateObject = new Date(date);
const surveyId = findSurveyFromDate(date, surveys);
const datasetsDates = getDatasetsTimestamps(datasets);
const minDataDate = minBy(datasetsDates, (item) => new Date(item));
const maxDataDate = maxBy(datasetsDates, (item) => new Date(item));
const closestDatasetData = getTooltipClosestData(dateObject, datasets);
const nValues = closestDatasetData
.map(({ data }) => head(data)?.value)
.filter(isNumber).length;
// Chart.js displays tooltips in a parallel to the X axis preference, meaning
// that it will appear right or left from the chart point. We want to change that,
// and display the tooltip in a Y axis preference, and more specifically, above the chart point.
const position = chart.chartInstance.canvas.getBoundingClientRect();
// We center the tooltip in the X axis by subtracting half its width.
const left = position.left + tooltipModel.caretX - TOOLTIP_WIDTH / 2;
// We increase the tooltip's top, so that it lands above the chart point. The amount by
// which we increase varies based on how many values we display and if there is a survey at that point,
// as we display a `VIEW SURVEY` button.
const top =
position.top +
tooltipModel.caretY -
((surveyId ? 30 : 0) + nValues * 20 + 50);
// We display the tooltip only if there are data to display at this point and it lands
// between the chart's X axis limits.
if (
nValues > 0 &&
moment(date).isBetween(
moment(startDate || minDataDate),
moment(endDate || maxDataDate),
undefined,
"[]"
)
) {
setTooltipPosition({ top, left });
setTooltipData({
...tooltipData,
date,
surveyId,
datasets: closestDatasetData,
});
setShowTooltip(true);
}
};
const hideTooltip = () => {
setShowTooltip(false);
};
// Hide tooltip on scroll to avoid dragging it on the page.
if (showTooltip) {
window.addEventListener("scroll", hideTooltip);
}
return (
<div className={className} style={style} onMouseLeave={hideTooltip}>
{children}
<Chart
{...rest}
chartRef={chartDataRef}
chartSettings={{
tooltips: {
enabled: false,
intersect: false,
custom: customTooltip(chartDataRef),
},
legend: {
display: false,
},
// we could use mergeWith here too, but currently nothing would use it.
...chartSettings,
}}
/>
{showTooltip ? (
<div
className="chart-tooltip"
id="chart-tooltip"
style={{
position: "fixed",
top: tooltipPosition.top,
left: tooltipPosition.left,
}}
>
<Tooltip
{...tooltipData}
siteTimeZone={timeZone}
userTimeZone={Intl.DateTimeFormat().resolvedOptions().timeZone}
/>
</div>
) : null}
</div>
);
}
Example #11
Source File: index.tsx From aqualink-app with MIT License | 4 votes |
WaterSamplingCard = ({ siteId }: WaterSamplingCardProps) => {
const classes = useStyles();
const [minDate, setMinDate] = useState<string>();
const [maxDate, setMaxDate] = useState<string>();
const [point, setPoint] = useState<SurveyPoint>();
const [timeSeriesData, setTimeSeriesData] = useState<TimeSeriesData>();
const meanValues = calculateSondeDataMeanValues(METRICS, timeSeriesData);
const isPointNameLong = (point?.name?.length || 0) > 24;
const surveyPointDisplayName = `${isPointNameLong ? "" : " Survey point:"} ${
point?.name || point?.id
}`;
const viewUploadButtonLink = `/sites/${siteId}${requests.generateUrlQueryParams(
{
start: minDate,
end: maxDate,
surveyPoint: point?.id,
}
)}`;
const lastUpload = maxDate ? moment(maxDate).format("MM/DD/YYYY") : undefined;
useEffect(() => {
const getCardData = async () => {
try {
const { data: uploadHistory } = await siteServices.getSiteUploadHistory(
parseInt(siteId, 10)
);
// Upload history is sorted by `maxDate`, so the first
// item is the most recent.
const {
minDate: from,
maxDate: to,
surveyPoint,
} = head(uploadHistory) || {};
if (typeof surveyPoint?.id === "number") {
const [data] = await timeSeriesRequest({
siteId,
pointId: surveyPoint.id.toString(),
start: from,
end: to,
metrics: METRICS,
hourly: true,
});
setMinDate(from);
setMaxDate(to);
setTimeSeriesData(data);
setPoint(surveyPoint);
}
} catch (err) {
console.error(err);
}
};
getCardData();
}, [siteId]);
return (
<Card className={classes.root}>
<CardHeader
className={classes.header}
title={
<Grid container>
<Grid item>
<Typography className={classes.cardTitle} variant="h6">
WATER SAMPLING
</Typography>
</Grid>
</Grid>
}
/>
<CardContent className={classes.content}>
<Box p="1rem" display="flex" flexGrow={1}>
<Grid container spacing={1}>
{metrics(meanValues).map(({ label, value, unit, xs }) => (
<Grid key={label} item xs={xs}>
<Typography
className={classes.contentTextTitles}
variant="subtitle2"
>
{label}
</Typography>
<Typography
className={classes.contentTextValues}
variant="h3"
display="inline"
>
{value}
</Typography>
<Typography
className={classes.contentUnits}
display="inline"
variant="h6"
>
{unit}
</Typography>
</Grid>
))}
</Grid>
</Box>
<UpdateInfo
relativeTime={lastUpload}
chipWidth={64}
timeText="Last data uploaded"
imageText="VIEW UPLOAD"
href={viewUploadButtonLink}
subtitle={point && surveyPointDisplayName}
/>
</CardContent>
</Card>
);
}
Example #12
Source File: addon-card-list.tsx From erda-ui with GNU Affero General Public License v3.0 | 4 votes |
AddonCardList = (props: IProps) => {
const [dataSource, setDataSource] = React.useState([] as any[]);
const [searchKey, setSearchKey] = React.useState('');
const [name, setName] = React.useState('');
const [dataSourceType, setDataSourceType] = React.useState('ALL');
const [env, setEnv] = React.useState('ALL');
const [activeCategory, setActiveCategory] = React.useState();
const categoryRefs = React.useRef([] as any[]);
const categoryCardsRef = React.useRef(null);
const { searchPlaceHolder, addonCategory, isFetching, onEitAddon, isPlatform } = props;
const debounceCheck = React.useCallback(
debounce(() => {
const categoryList = categoryCardsRef.current as any;
categoryRefs.current.forEach((categorySection: any) => {
const { current } = head(Object.values(categorySection)) as any;
if (current) {
const top = current.offsetTop - categoryList.scrollTop - 52;
const newActiveCategory = head(Object.keys(categorySection));
if (top <= 0) {
setActiveCategory(newActiveCategory);
}
}
});
}, 100),
[],
);
const operateScrollEvent = (isRemove?: boolean) => {
const categoryList = categoryCardsRef.current as any;
if (categoryList) {
!isRemove
? categoryList.addEventListener('scroll', debounceCheck)
: categoryList.removeEventListener('scroll', debounceCheck);
}
};
const scrollToTarget = (targetCategoryName: string) => {
const targetRef = categoryRefs.current.find((ref: any) => ref[targetCategoryName]);
const targetCategoryDom = targetRef[targetCategoryName].current;
if (!targetCategoryDom) {
return;
}
targetCategoryDom.parentNode.scrollTop = targetCategoryDom.offsetTop - 20;
};
React.useEffect(() => {
if (!isEmpty(props.addonCategory) && !isEmpty(dataSource)) {
const firstCategory = head(dataSource) as any[];
const targetCategory = head(firstCategory);
scrollToTarget(targetCategory);
}
}, [dataSource, props.addonCategory]);
React.useEffect(() => {
operateScrollEvent();
return () => {
operateScrollEvent(true);
};
});
useUpdateEffect(() => {
const { addonList = [], searchProps } = props;
if (!isEmpty(addonList)) {
setDataSource(
addonList.filter((addon: ADDON.Instance) => {
const isOtherFilter =
(addon.workspace === env || env === 'ALL') &&
searchFilter(addon, searchKey, searchProps) &&
searchFilter(addon, name, searchProps);
if (dataSourceType === 'custom') {
const isCustomDataSource = CustomDataSourceMap.includes(addon.displayName);
return isOtherFilter && isCustomDataSource;
}
return isOtherFilter && (addon.displayName?.toLowerCase() === dataSourceType || dataSourceType === 'ALL');
}),
);
} else if (!isEmpty(addonCategory)) {
const cloneAddonCategory = cloneDeep(addonCategory);
Object.keys(cloneAddonCategory).forEach((key: string) => {
const value = cloneAddonCategory[key];
cloneAddonCategory[key] = value.filter(
(addon: IAddon) => (addon.workspace === env || env === 'ALL') && searchFilter(addon, searchKey, searchProps),
);
});
const filterDataSource = Object.entries(pickBy(cloneAddonCategory, (x: any[]) => x.length > 0));
const resolvedFilterDataSource: any[] = [];
const categories = Object.keys(addonCategory);
forEach(categories, (v) => {
const targetItem = find(filterDataSource, (item) => item[0] === v);
targetItem && resolvedFilterDataSource.push(targetItem);
});
setDataSource(resolvedFilterDataSource);
categoryRefs.current = Object.keys(cloneAddonCategory).map((key: string) => {
return { [key]: React.createRef() };
});
setActiveCategory(head(head(resolvedFilterDataSource)));
} else {
setDataSource([]);
}
}, [env, searchKey, props.addonList, props.addonCategory, props, name, dataSourceType]);
const onSearchKeyChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const value = get(event, 'target.value');
setSearchKey(value);
};
const onNameChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const value = get(event, 'target.value');
setName(value);
};
const onEnvChange = (value: string) => {
setEnv(value);
};
const onDataSourceChange = (value: any) => {
setDataSourceType(value);
};
const onClickCategory = (event: React.MouseEvent) => {
const targetCategory = event.currentTarget.getAttribute('value') as string;
if (activeCategory === targetCategory) {
return;
}
setActiveCategory(targetCategory);
scrollToTarget(targetCategory);
};
const renderCategoryList = () => {
if (addonCategory) {
const categories = Object.keys(addonCategory);
const resolvedCategories = categories.filter((v) => CategoriesOrder.includes(v));
return resolvedCategories.map((key: string) => {
key = allWordsFirstLetterUpper(key);
return (
<li
key={key}
className={`category-item cursor-pointer ${activeCategory === key ? 'active' : ''}`}
value={key}
onClick={onClickCategory}
>
<Tooltip title={key}>{key}</Tooltip>
</li>
);
});
}
return null;
};
return (
<section className="addon-card-list">
<Spin wrapperClassName="full-spin-height" spinning={isFetching}>
<div className="addon-filter">
{!props.showDataSourceSearch && (
<Select className="env-select" defaultValue="ALL" onChange={onEnvChange}>
{envOptions}
</Select>
)}
{props.hideSearch ? null : (
<Search className="data-select" onChange={onSearchKeyChange} placeholder={searchPlaceHolder} />
)}
{props.showDataSourceSearch && (
<Search className="data-select mr-5" onChange={onNameChange} placeholder={searchPlaceHolder} />
)}
{props.showDataSourceSelect && (
<Select className="data-select" defaultValue="ALL" onChange={onDataSourceChange}>
{dataSourceTypeOptions}
</Select>
)}
</div>
<div className="addons-content">
<IF check={!isEmpty(addonCategory)}>
<div className="addon-menu">
<span className="content-title font-medium">{firstCharToUpper(i18n.t('dop:addon category'))}</span>
<ul className="menu-list">{renderCategoryList()}</ul>
</div>
</IF>
<AddonCards
forwardedRef={categoryCardsRef}
dataSource={dataSource}
onEitAddon={onEitAddon}
isPlatform={isPlatform}
categoryRefs={categoryRefs.current}
/>
</div>
</Spin>
</section>
);
}
Example #13
Source File: edit-stage.tsx From erda-ui with GNU Affero General Public License v3.0 | 4 votes |
EditStage = (props: IEditStageProps & FormProps) => {
const [form] = Form.useForm();
const [state, updater] = useUpdate({
task: {} as IStageTask | {},
actionConfig: {} as DEPLOY.ActionConfig | {},
resource: {},
originType: null as null | string,
originName: null as null | string,
});
const initialValue = React.useRef({});
const { task, actionConfig, resource, originName, originType } = state;
const { actions, otherTaskAlias, editing, isCreateTask, onSubmit: handleSubmit, task: PropsTask } = props;
const { getFieldValue } = form;
const actionConfigs = deployStore.useStore((s) => s.actionConfigs);
const { getActionConfigs } = deployStore.effects;
const [loading] = useLoading(deployStore, ['getActionConfigs']);
React.useEffect(() => {
if (!isEmpty(PropsTask)) {
updater.originName(PropsTask.alias);
updater.originType(PropsTask.type);
updater.task(PropsTask);
}
}, [PropsTask, updater]);
React.useEffect(() => {
let config;
if (actionConfigs.length > 0) {
config = PropsTask.version
? actionConfigs.find((c) => c.version === PropsTask.version)
: getDefaultVersionConfig(actionConfigs);
}
const newResource = getResource(PropsTask, config);
updater.resource(newResource);
updater.actionConfig(config as DEPLOY.ActionConfig);
}, [actionConfigs, PropsTask, updater]);
React.useEffect(() => {
if (isCreateTask) {
updater.actionConfig({});
}
}, [isCreateTask, updater]);
if (!isCreateTask && isEmpty(actionConfig)) {
return null;
}
const type = actionConfig.type || getFieldValue(['resource', 'type']);
const taskInitName =
originType === actionConfig.name
? originName
: otherTaskAlias.includes(actionConfig.name)
? undefined
: actionConfig.name;
const changeResourceType = (value: string) => {
const action = actions.find((a: any) => a.name === value);
if (action) {
getActionConfigs({ actionType: action.name }).then((result: DEPLOY.ActionConfig[]) => {
const config = getDefaultVersionConfig(result);
const mergedResource = mergeActionAndResource(config, {} as any);
updater.resource({
...resource,
...mergedResource,
});
});
}
};
const checkResourceName = (_rule: any, value: string, callback: any) => {
const name = form.getFieldValue(['resource', 'alias']);
if (!value) {
return callback(i18n.t('dop:please enter the task name'));
}
if (otherTaskAlias.includes(name)) {
return callback(i18n.t('dop:An Action with the same name exists.'));
}
callback();
};
const changeActionVersion = (version: string) => {
const selectConfig = actionConfigs.find((config) => config.version === version) as DEPLOY.ActionConfig;
updater.actionConfig(selectConfig);
updater.resource(getResource(task, selectConfig));
};
const taskType = (
<Item
name="resource.type"
initialValue={task.type}
rules={[
{
required: true,
message: `${i18n.t('dop:Please select')}Task Type`,
},
]}
>
<ActionSelect
disabled={!editing}
label={i18n.t('Task type')}
actions={actions}
onChange={changeResourceType}
placeholder={`${i18n.t('dop:Please choose the task type')}`}
/>
</Item>
);
const actionVersion = (
<Item
label="version"
name="resource.version"
initialValue={task.version || actionConfig.version}
rules={[
{
required: true,
message: `${i18n.t('dop:Please select')}Task Version`,
},
]}
>
<Select disabled={!editing} onChange={changeActionVersion} placeholder={`${i18n.t('dop:please choose version')}`}>
{actionConfigs.map((config) => (
<Option key={config.version} value={config.version}>
{config.version}
</Option>
))}
</Select>
</Item>
);
let alert;
if (!isCreateTask && isEmpty(resource)) {
return null;
}
if (!isCreateTask && !actionConfig.type) {
alert = (
<ErdaAlert
className="addon-error-tag"
message={i18n.t('dop:the current action does not exist, please re-select!')}
type="error"
/>
);
}
const taskName = (
<Item
label={i18n.t('dop:task name')}
name="resource.alias"
initialValue={taskInitName}
rules={[
{
required: true,
validator: checkResourceName,
},
]}
>
<Input autoFocus={!type} disabled={!editing} placeholder={i18n.t('dop:please enter the task name')} />
</Item>
);
const renderTaskTypeStructure = () => {
if (isEmpty(resource)) {
return null;
}
const { getFieldsValue } = form;
const resourceForm = getFieldsValue(['resource.alias', 'resource.type']);
if (!resourceForm.resource.type) {
return null;
}
return renderResource(resource, 'resource');
};
const getDataValue = (dataSource: any, key: string) => {
return dataSource ? dataSource[key] : null;
};
const renderResource = (resourceParam: any, parentKey?: string, dataSource?: any) => {
if (resourceParam.data instanceof Array) {
return resourceParam.data.map((item: any) => {
const inputKey = parentKey ? `${parentKey}.${item.name}` : `${item.name}`;
return renderObject(item, inputKey, getDataValue(dataSource, item.name));
});
}
const { params, image, resources } = resourceParam.data;
const parentObjectData = getDataValue(dataSource, 'params');
const paramsContent = map(params, (value: any, itemKey: string) => {
const inputKey = parentKey ? `${parentKey}.params.${itemKey}` : `params.${itemKey}`;
return renderObject(value, inputKey, getDataValue(parentObjectData, itemKey));
});
return (
<>
{actionConfig.name === 'custom-script' ? (
<div>{renderObject(image, 'resource.image', getDataValue(dataSource, 'image'))}</div>
) : null}
<div>
<div className="resource-input-group-title">params: </div>
{paramsContent}
</div>
<div>{renderObject(resources, 'resource.resources', getDataValue(dataSource, 'resources'))}</div>
</>
);
};
const renderObject = (value: any, parentKey: string, dataSource?: any) => {
if (!isObject(value.type)) {
return renderPropertyValue(value, parentKey, dataSource);
}
if (value.type === 'string_array') {
return renderStringArray(value, parentKey);
}
if (value.type === 'struct_array') {
return renderStructArray(value, parentKey);
}
if (value.type === 'map') {
return renderMap(value, parentKey, dataSource);
}
const content = renderResource({ data: value.struct }, parentKey, dataSource);
if (!content || !Object.values(content).some((c) => c)) return null;
return (
<div key={parentKey}>
<span className="resource-input-group-title">{value.name}: </span>
<div>{content}</div>
</div>
);
};
const renderMap = (value: any, parentKey: string, dataSource?: any) => {
let initialValue = isCreateTask ? value.default : value.value || value.default;
if (dataSource) {
initialValue = dataSource;
}
if (!editing && !initialValue) {
return null;
}
const inputField = (
<Item
key={parentKey}
name={parentKey}
initialValue
rules={[
{
required: value.required,
message: i18n.t('dop:this item cannot be empty'),
},
]}
>
{renderTooltip(value.desc, <VariableInput disabled={!editing} label={value.name} />)}
</Item>
);
return inputField;
};
const renderStringArray = (value: any, parentKey: string) => {
const inputField = (
<Item
key={parentKey}
name={parentKey}
initialValue={isCreateTask ? value.default : value.value || value.default}
rules={[
{
required: value.required,
message: i18n.t('dop:this item cannot be empty'),
},
]}
>
{renderTooltip(value.desc, <ListInput disabled={!editing} label={value.name} />)}
</Item>
);
return inputField;
};
const renderPropertyValue = (value: any, parentKey: string, dataSource?: any) => {
let input;
let initialValue = isCreateTask ? value.default : value.value || value.default;
if (dataSource) {
initialValue = dataSource;
}
if (!editing && !initialValue) {
return null;
}
const unit = value.unit ? <span>{value.unit}</span> : null;
switch (value.type) {
case 'float':
case 'int':
input = (
<InputNumber
disabled={!editing || value.readOnly}
className="w-full"
placeholder={i18n.t('dop:please enter data')}
/>
);
break;
default:
input = (
<Input
disabled={!editing || value.readOnly}
placeholder={i18n.t('dop:please enter data')}
addonAfter={unit}
/>
);
break;
}
const inputField = (
<Item
key={parentKey}
label={value.name}
name={parentKey}
initialValue
rules={[
{
required: value.required,
message: i18n.t('dop:this item cannot be empty'),
},
]}
>
{renderTooltip(value.desc, input)}
</Item>
);
return inputField;
};
const renderStructArray = (property: any, parentKey: string) => {
if ((!editing && !property.value) || (!editing && property.value && !property.value.length)) {
return null;
}
const addBtn = editing ? (
<ErdaIcon
type="plus"
className="cursor-pointer"
onClick={() => addNewItemToStructArray(property.value, property.struct[0])}
/>
) : null;
initialValue.current = {
[`${parentKey}-data`]: property.value || [],
};
const data = getFieldValue(`${parentKey}-data`);
const content = data.map((item: any, index: number) => {
const keys = Object.keys(item);
const header = (
<div>
<span>{typeof item[keys[0]] === 'string' ? item[keys[0]] : 'module'}</span>
{editing ? (
<CustomIcon
onClick={() => deleteItemFromStructArray(index, parentKey)}
className="icon-delete"
type="sc1"
/>
) : null}
</div>
);
return (
<Panel key={`${parentKey}.${item.name}`} header={header}>
{renderResource({ data: property.struct }, `${parentKey}[${index}]`, item)}
</Panel>
);
});
return (
<div key={parentKey}>
<span className="resource-input-group-title">
{property.name}:{addBtn}
</span>
{data.length ? (
<Collapse className="collapse-field" accordion>
{content}
</Collapse>
) : null}
</div>
);
};
const deleteItemFromStructArray = (index: number, parentKey: string) => {
const formDatas = form.getFieldValue(`${parentKey}-data`);
formDatas.splice(index, 1);
form.setFieldsValue({
[parentKey]: formDatas,
});
};
const addNewItemToStructArray = (list: any[], struct: any) => {
list.push({
[struct.name]: `module-${list.length + 1}`,
});
updater.resource(cloneDeep(resource));
};
const isObject = (inputType: string) => {
return ['map', 'string_array', 'struct_array', 'struct'].includes(inputType);
};
const renderTooltip = (message: string, text: any) => {
if (!message) {
return text;
}
const msgComp = <pre className="prop-popover">{message}</pre>;
return (
<Popover placement="leftTop" trigger={['focus']} content={msgComp}>
{text}
</Popover>
);
};
const onSubmit = () => {
form
.validateFields()
.then((values: any) => {
let data = cloneDeep(values);
const resources = head(filter(state.resource.data, (item) => item.name === 'resources'));
const originResource = transform(
get(resources, 'struct'),
(result, item: { name: string; default: string | number }) => {
const { name, default: d } = item;
// eslint-disable-next-line no-param-reassign
result[name] = +d;
},
{},
);
const editedResources = get(data, 'resource.resources');
forEach(Object.entries(editedResources), ([key, value]) => {
editedResources[key] = +(value as string);
});
const isResourceDefault = isEqual(editedResources, originResource);
if (isResourceDefault) {
data = omit(data, ['resource.resources']);
}
const filledFieldsData = clearEmptyField(data);
handleSubmit(filledFieldsData);
})
.catch(({ errorFields }: { errorFields: Array<{ name: any[]; errors: any[] }> }) => {
form.scrollToField(errorFields[0].name);
});
};
const clearEmptyField = (ObjData: any) => {
const filledFields: string[] = [];
const findData = (obj: any, parentArray: string[]) => {
Object.keys(obj).forEach((key) => {
const currentParent = [...parentArray, key];
const value = get(obj, key);
if (typeof value === 'object') {
findData(value, currentParent);
} else if (value || value === 0) {
filledFields.push(currentParent.join('.'));
}
});
};
findData(ObjData, []);
return pick(ObjData, filledFields);
};
return (
<Spin spinning={loading}>
<Form form={form} initialValues={initialValue.current} className="edit-service-container">
{alert}
{taskType}
{type ? taskName : null}
{actionVersion}
{renderTaskTypeStructure()}
{editing ? (
<Button type="primary" ghost onClick={onSubmit}>
{i18n.t('Save')}
</Button>
) : null}
</Form>
</Spin>
);
}
Example #14
Source File: pipeline-node-drawer.tsx From erda-ui with GNU Affero General Public License v3.0 | 4 votes |
PurePipelineNodeForm = (props: IEditStageProps & FormProps) => {
const [form] = Form.useForm();
const {
nodeData: propsNodeData,
editing,
isCreate,
otherTaskAlias = [],
onSubmit: handleSubmit = noop,
chosenActionName,
chosenAction,
actionSpec,
} = props;
const [actionConfigs] = appDeployStore.useStore((s) => [s.actionConfigs]);
const { getFieldValue } = form;
const [{ actionConfig, resource, originType, originName, task, changeKey }, updater, update] = useUpdate({
resource: {},
actionConfig: {} as DEPLOY.ActionConfig,
originType: null as null | string,
originName: null as null | string,
task: {} as IStageTask,
changeKey: 0,
});
useEffectOnce(() => {
handleActionSpec();
});
React.useEffect(() => {
if (propsNodeData && !isEmpty(propsNodeData)) {
update({
originName: propsNodeData.alias,
originType: propsNodeData.type,
task: propsNodeData,
});
}
}, [propsNodeData, update]);
React.useEffect(() => {
if (isCreate) {
updater.actionConfig({} as DEPLOY.ActionConfig);
}
}, [isCreate, updater]);
const taskInitName =
originType === actionConfig.name
? originName
: otherTaskAlias.includes(actionConfig.name)
? undefined
: actionConfig.name;
const taskInitVersion = task.version || actionConfig.version;
useUpdateEffect(() => {
const prevResource = form.getFieldValue('resource') || {};
form.setFieldsValue({
resource: {
...prevResource,
type: chosenActionName,
alias: taskInitName,
version: taskInitVersion,
},
});
updater.changeKey((prev: number) => prev + 1);
}, [taskInitName, taskInitVersion, chosenActionName]);
const handleActionSpec = () => {
let _config;
let _resource;
if (propsNodeData && !isEmpty(propsNodeData)) {
if (actionSpec.length > 0) {
_config = propsNodeData.version
? actionSpec.find((c) => c.version === propsNodeData.version)
: getDefaultVersionConfig(actionSpec);
}
_resource = getResource(propsNodeData, _config);
} else {
_config = getDefaultVersionConfig(actionSpec);
const mergedResource = mergeActionAndResource(_config, {});
_resource = { ...resource, ...mergedResource };
}
update({
resource: _resource,
actionConfig: _config || ({} as DEPLOY.ActionConfig),
});
};
useUpdateEffect(() => {
handleActionSpec();
}, [actionSpec]);
if (!isCreate && isEmpty(actionConfig)) {
return null;
}
const type = actionConfig.type || getFieldValue(['resource', 'type']);
const checkResourceName = (_rule: any, value: string, callback: any) => {
const name = form.getFieldValue(['resource', 'alias']);
if (!value) {
return callback(i18n.t('dop:please enter the task name'));
}
if (otherTaskAlias.includes(name)) {
return callback(i18n.t('dop:An Action with the same name exists.'));
}
callback();
};
const changeActionVersion = (version: any) => {
const selectConfig = actionConfigs.find((config) => config.version === version) as DEPLOY.ActionConfig;
updater.actionConfig(selectConfig);
updater.resource(getResource(task as IStageTask, selectConfig));
};
const taskType = (
<Item
className="hidden"
name={['resource', 'type']}
initialValue={chosenActionName}
rules={[
{
required: true,
message: `${i18n.t('dop:Please select')}Task Type`,
},
]}
>
<Input />
</Item>
);
const loopData = (
<Item className="hidden" name={['resource', 'loop']} initialValue={get(actionConfig, 'spec.loop')} />
);
const actionVersion = (
<Item
label={i18nMap.version}
name={['resource', 'version']}
initialValue={task.version || actionConfig.version}
rules={[
{
required: true,
message: `${i18n.t('dop:Please select')}Task Version`,
},
]}
>
<Select disabled={!editing} onChange={changeActionVersion} placeholder={`${i18n.t('dop:please choose version')}`}>
{actionConfigs.map((config) => (
<Option key={config.version} value={config.version}>
{config.version}
</Option>
))}
</Select>
</Item>
);
let alert;
if (!isCreate && !actionConfig.type) {
alert = (
<ErdaAlert
className="addon-error-tag"
showIcon
message={i18n.t('dop:the current action does not exist, please re-select!')}
type="error"
/>
);
}
const taskName = (
<Item
label={i18n.t('dop:task name')}
name={['resource', 'alias']}
initialValue={taskInitName}
rules={[
{
required: true,
validator: checkResourceName,
},
]}
>
<Input autoFocus={!type} disabled={!editing} placeholder={i18n.t('dop:please enter the task name')} />
</Item>
);
const renderTaskTypeStructure = () => {
if (isEmpty(resource)) {
return null;
}
const { getFieldsValue } = form;
const resourceForm = getFieldsValue([
['resource', 'alias'],
['resource', 'type'],
]);
if (!get(resourceForm, 'resource.type')) {
return null;
}
return renderResource(resource, 'resource');
};
const getDataValue = (dataSource: any, key: string) => {
return dataSource ? dataSource[key] : null;
};
const renderResource = (resourceParam: any, parentKey?: string, dataSource?: any) => {
if (resourceParam.data instanceof Array) {
return resourceParam.data.map((item: any) => {
const inputKey = parentKey ? `${parentKey}.${item.name}` : `${item.name}`;
return renderObject(item, inputKey, getDataValue(dataSource, item.name));
});
}
const { params, image, resources } = resourceParam.data;
const parentObjectData = getDataValue(dataSource, 'params');
const paramsContent = map(params, (value: any, itemKey: string) => {
const inputKey = parentKey ? `${parentKey}.params.${itemKey}` : `params.${itemKey}`;
return renderObject(value, inputKey, getDataValue(parentObjectData, itemKey));
});
return (
<>
{actionConfig.name === 'custom-script' ? (
<div>{renderObject(image, 'resource.image', getDataValue(dataSource, 'image'))}</div>
) : null}
<div>
<div className="resource-input-group-title">{i18nMap.params}: </div>
{paramsContent}
</div>
<div>{renderObject(resources, 'resource.resources', getDataValue(dataSource, 'resources'))}</div>
</>
);
};
const renderObject = (value: any, parentKey: string, dataSource?: any) => {
if (!isObject(value.type)) {
return renderPropertyValue(value, parentKey, dataSource);
}
if (value.type === 'string_array') {
return renderStringArray(value, parentKey);
}
if (value.type === 'struct_array') {
return renderStructArray(value, parentKey);
}
if (value.type === 'map') {
return renderMap(value, parentKey, dataSource);
}
const content = renderResource({ data: value.struct }, parentKey, dataSource);
if (!content || !Object.values(content).some((c) => c)) return null;
return (
<div key={parentKey}>
<span className="resource-input-group-title">{i18nMap[value.name] || value.name}: </span>
<div>{content}</div>
</div>
);
};
const renderMap = (value: any, parentKey: string, dataSource?: any) => {
let initialValue = isCreate ? value.default : value.value || value.default;
if (dataSource) {
initialValue = dataSource;
}
if (!editing && !initialValue) {
return null;
}
const inputField = (
<Item
key={parentKey}
name={formKeyFormat(parentKey)}
initialValue={initialValue}
rules={[
{
required: value.required,
message: i18n.t('dop:this item cannot be empty'),
},
]}
>
<VariableInput disabled={!editing} label={getLabel(value.name, value.desc)} />
</Item>
);
return inputField;
};
const renderStringArray = (value: any, parentKey: string) => {
const inputField = (
<Item
key={parentKey}
name={formKeyFormat(parentKey)}
initialValue={isCreate ? value.default : value.value || value.default}
rules={[
{
required: value.required,
message: i18n.t('dop:this item cannot be empty'),
},
]}
getValueFromEvent={(val: Array<{ value: string }>) => {
return val?.length ? val.map((v) => v.value) : val;
}}
>
<ListInput disabled={!editing} label={getLabel(value.name, value.desc)} />
</Item>
);
return inputField;
};
const renderPropertyValue = (value: any, parentKey: string, dataSource?: any) => {
let input;
let initialValue = isCreate ? value.default : value.value || value.default;
if (dataSource) {
initialValue = dataSource;
}
if (!editing && !initialValue) {
return null;
}
const unit = value.unit ? <span>{value.unit}</span> : null;
switch (value.type) {
case 'float':
case 'int':
input = (
<InputNumber
disabled={!editing || value.readOnly}
className="w-full"
placeholder={i18n.t('dop:please enter data')}
/>
);
break;
default:
input = (
<Input
disabled={!editing || value.readOnly}
placeholder={i18n.t('dop:please enter data')}
addonAfter={unit}
/>
);
break;
}
const inputField = (
<Item
key={parentKey}
label={getLabel(value.name, value.desc)}
name={formKeyFormat(parentKey)}
initialValue={initialValue}
rules={[
{
required: value.required,
message: i18n.t('dop:this item cannot be empty'),
},
]}
>
{input}
</Item>
);
return inputField;
};
const getLabel = (label: string, labelTip: string) => {
let _label: any = label;
if (labelTip) {
_label = (
<span>
{_label}
<Tooltip title={labelTip}>
<ErdaIcon type="help" size="14" className="mr-1 align-middle text-icon" />
</Tooltip>
</span>
);
}
return _label;
};
const renderStructArray = (property: any, parentKey: string) => {
if ((!editing && !property.value) || (!editing && property.value && !property.value.length)) {
return null;
}
const addBtn = editing ? (
<ErdaIcon
type="plus"
className="cursor-pointer align-middle"
onClick={() => addNewItemToStructArray(property, property.struct[0])}
/>
) : null;
// getFieldDecorator(`${parentKey}-data`, { initialValue: property.value || [] });
const data = property.value || []; // getFieldValue(`${parentKey}-data`);
const _val = form.getFieldsValue();
const realData = get(_val, `${parentKey}`) || [];
const content = data.map((item: any, index: number) => {
const keys = Object.keys(item);
const curItem = realData[index] || item;
const nameKey = get(property.struct, '[0].name');
const headName = curItem[nameKey] || (typeof curItem[keys[0]] === 'string' ? curItem[keys[0]] : 'module');
if (typeof headName === 'object') {
return (
<div className="p-2 text-black-4">
{i18n.t(
'dop:Rendering multi-layer nested structures is not supported at this time, please go to text mode.',
)}
</div>
);
}
const header = (
<div className="flex items-center justify-between">
<span className="truncate" title={headName}>
{headName}
</span>
{editing ? (
<CustomIcon
onClick={() => deleteItemFromStructArray(property, index, parentKey)}
className="icon-delete"
type="sc1"
/>
) : null}
</div>
);
return (
<Panel key={`${parentKey}.${item.key}-${String(index)}`} header={header} forceRender>
{renderResource({ data: property.struct }, `${parentKey}.[${index}]`, item)}
</Panel>
);
});
return (
<div key={`${parentKey}`}>
<span className="resource-input-group-title">
{property.name}:{addBtn}
</span>
{data.length ? (
<Collapse className="collapse-field my-2" accordion>
{content}
</Collapse>
) : null}
</div>
);
};
const deleteItemFromStructArray = (property: any, index: number, parentKey: string) => {
if (!property.value) {
// eslint-disable-next-line no-param-reassign
property.value = [];
}
property.value.splice(index, 1);
updater.resource(cloneDeep(resource));
const formDatas = form.getFieldValue(`${parentKey}`.split('.'));
formDatas?.splice(index, 1);
const curFormData = form.getFieldsValue();
set(curFormData, parentKey, formDatas);
form.setFieldsValue(curFormData);
};
const addNewItemToStructArray = (property: any, struct: any) => {
if (!property.value) {
// eslint-disable-next-line no-param-reassign
property.value = [];
}
property.value.push({
[struct.name]: `module-${property.value.length + 1}`,
});
updater.resource(cloneDeep(resource));
};
const isObject = (inputType: string) => {
return ['map', 'string_array', 'struct_array', 'struct'].includes(inputType);
};
const onSubmit = () => {
form
.validateFields()
.then((values: any) => {
let data = cloneDeep(values);
const resources = head(filter(resource.data, (item) => item.name === 'resources'));
const originResource = transform(
get(resources, 'struct'),
(result, item: { name: string; default: string | number }) => {
const { name, default: d } = item;
// eslint-disable-next-line no-param-reassign
result[name] = +d;
},
{},
);
const editedResources = get(data, 'resource.resources') || {};
forEach(Object.entries(editedResources), ([key, value]) => {
editedResources[key] = +(value as string);
});
const isResourceDefault = isEqual(editedResources, originResource);
if (isResourceDefault) {
data = omit(data, ['resource.resources']);
}
const filledFieldsData = clearEmptyField(data);
const resData = { ...filledFieldsData, action: chosenAction } as any;
if (data.executionCondition) resData.executionCondition = data.executionCondition;
handleSubmit(resData);
})
.catch(({ errorFields }: { errorFields: Array<{ name: any[]; errors: any[] }> }) => {
form.scrollToField(errorFields[0].name);
});
};
const clearEmptyField = (ObjData: any) => {
const filledFields: string[] = [];
const findData = (obj: any, parentArray: string[]) => {
Object.keys(obj).forEach((key) => {
const currentParent = [...parentArray, key];
const value = get(obj, key);
if (typeof value === 'object' && value !== null) {
findData(value, currentParent);
} else if (value || value === 0) {
filledFields.push(currentParent.join('.'));
}
});
};
findData(ObjData, []);
return pick(ObjData, filledFields);
};
const executionCondition = (
<Item
label={i18n.t('common:execution conditions')}
name={'executionCondition'}
initialValue={get(propsNodeData, 'if') || undefined}
rules={[
{
required: false,
},
]}
>
<Input disabled={!editing} placeholder={i18n.t('common:configure execution conditions')} />
</Item>
);
const onValuesChange = () => {
// use changeKey to tigger a rerender,
updater.changeKey((prev: number) => prev + 1);
};
return (
<Form form={form} onValuesChange={onValuesChange} layout="vertical" className="edit-service-container">
{alert}
{taskType}
{loopData}
{type ? taskName : null}
{actionVersion}
{executionCondition}
{renderTaskTypeStructure()}
{editing ? (
<Button type="primary" ghost onClick={onSubmit}>
{i18n.t('Save')}
</Button>
) : null}
</Form>
);
}