Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add initial support for pipeline properties #122

Merged
merged 16 commits into from
Jun 4, 2021
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ clean: ## Make a clean source tree and unlink packages
lint: ## Run linters
yarn lint

install: lint ## Install dependencies and build packages
install: ## Install dependencies and build packages
yarn install && yarn build

dev-link: ## Link packages
Expand Down
5 changes: 5 additions & 0 deletions examples/create-react-app/src/theme.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,11 @@ const theme = {
<path d="M14.431 3.323l-8.47 10-.79-.036-3.35-4.77.818-.574 2.978 4.24 8.051-9.506.764.646z" />
</SvgIcon>
),
pipelineIcon: (
<SvgIcon>
<path d="M20,23 L11.86,23 C11.7609757,22.6493104 11.616411,22.3131134 11.43,22 L22,11.43 C22.6019656,11.7993928 23.293741,11.9965488 24,12 C26.0803346,12.0067496 27.8185594,10.4177446 27.9980602,8.34515757 C28.177561,6.27257049 26.7384074,4.4083819 24.6878883,4.05737018 C22.6373692,3.70635845 20.6600973,4.98571688 20.14,7 L11.86,7 C11.356433,5.04969328 9.48121328,3.77807479 7.48299948,4.03188121 C5.48478569,4.28568764 3.98701665,5.98573188 3.98701665,8 C3.98701665,10.0142681 5.48478569,11.7143124 7.48299948,11.9681188 C9.48121328,12.2219252 11.356433,10.9503067 11.86,9 L20.14,9 C20.2390243,9.35068963 20.383589,9.68688662 20.57,10 L10,20.57 C9.39803439,20.2006072 8.70625898,20.0034512 8,20 C5.91966537,19.9932504 4.18144061,21.5822554 4.00193981,23.6548424 C3.822439,25.7274295 5.26159259,27.5916181 7.31211167,27.9426298 C9.36263076,28.2936415 11.3399027,27.0142831 11.86,25 L20,25 L20,28 L28,28 L28,20 L20,20 L20,23 Z M8,10 C6.8954305,10 6,9.1045695 6,8 C6,6.8954305 6.8954305,6 8,6 C9.1045695,6 10,6.8954305 10,8 C10,8.53043298 9.78928632,9.03914081 9.41421356,9.41421356 C9.03914081,9.78928632 8.53043298,10 8,10 Z M24,6 C25.1045695,6 26,6.8954305 26,8 C26,9.1045695 25.1045695,10 24,10 C22.8954305,10 22,9.1045695 22,8 C22,6.8954305 22.8954305,6 24,6 Z M8,26 C6.8954305,26 6,25.1045695 6,24 C6,22.8954305 6.8954305,22 8,22 C9.1045695,22 10,22.8954305 10,24 C10,25.1045695 9.1045695,26 8,26 Z M22,22 L26,22 L26,26 L22,26 L22,22 Z" />
</SvgIcon>
),
},
};

Expand Down
25 changes: 25 additions & 0 deletions packages/pipeline-editor/src/CustomFormControls/DisplayControl.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright 2018-2021 Elyra Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { createControl, useControlState } from "./control";

function DisplayControl() {
const [value] = useControlState<string>();

return <p>{value}</p>;
}

export default createControl("DisplayControl", DisplayControl);
25 changes: 17 additions & 8 deletions packages/pipeline-editor/src/CustomFormControls/StringControl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -130,14 +130,23 @@ function StringControl({
return (
<Container className={errorMessages.length > 0 ? "error" : undefined}>
<InputContainer>
<input
type="text"
value={renderedValue}
placeholder={placeholder}
onChange={handleChange}
disabled={format === "file"}
onBlur={handleBlur}
/>
{format === "multiline" ? (
<textarea
value={renderedValue}
placeholder={placeholder}
onChange={handleChange}
onBlur={handleBlur}
/>
) : (
<input
type="text"
value={renderedValue}
placeholder={placeholder}
onChange={handleChange}
disabled={format === "file"}
onBlur={handleBlur}
/>
)}
{errorMessages[0] !== undefined && (
<ErrorMessage>{errorMessages[0]}</ErrorMessage>
)}
Expand Down
1 change: 1 addition & 0 deletions packages/pipeline-editor/src/CustomFormControls/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
/* istanbul ignore file */

export { default as StringControl } from "./StringControl";
export { default as DisplayControl } from "./DisplayControl";
export { default as StringArrayControl } from "./StringArrayControl";
export { default as BooleanControl } from "./BooleanControl";
export { default as EnumControl } from "./EnumControl";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export interface StringValidatorOptions {
patternErrorMessage?: string; // for giving a tailored error message when a pattern does not match
minLength?: number; // for restricting string length
maxLength?: number; // for restricting string length
format?: "file"; // for restricting strings to well-known formats. Potential future formats: "date" | "time" | "ipv4" | "email" | "uri"
format?: "file" | "multiline"; // for restricting strings to well-known formats. Potential future formats: "date" | "time" | "ipv4" | "email" | "uri"
}

export function getStringValidators<T extends string>({
Expand Down
42 changes: 37 additions & 5 deletions packages/pipeline-editor/src/PipelineEditor/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ import styled, { useTheme } from "styled-components";
import NodeTooltip from "../NodeTooltip";
import PalettePanel from "../PalettePanel";
import PipelineController from "../PipelineController";
import PropertiesPanel from "../PropertiesPanel";
import { NodeProperties, PipelineProperties } from "../properties-panels";
import SplitPanelLayout from "../SplitPanelLayout";
import TabbedPanelLayout from "../TabbedPanelLayout";
import { InternalThemeProvider } from "../ThemeProvider";
Expand All @@ -52,6 +52,7 @@ interface Props {
pipeline: any;
toolbar?: any;
nodes?: any;
pipelineProperties?: any;
onAction?: (action: { type: string; payload?: any }) => any;
onChange?: (pipeline: any) => any;
onDoubleClickNode?: (e: CanvasClickEvent) => any;
Expand Down Expand Up @@ -147,6 +148,7 @@ const PipelineEditor = forwardRef(
{
pipeline,
nodes,
pipelineProperties,
toolbar,
onAction,
onChange,
Expand Down Expand Up @@ -439,9 +441,9 @@ const PipelineEditor = forwardRef(
[onDoubleClickNode]
);

const [selectedNodes, setSelectedNodes] = useState<NodeTypeDef[]>();
const [selectedNodes, setSelectedNodes] = useState<string[]>();
const handleSelectionChange = useCallback((e: CanvasSelectionEvent) => {
setSelectedNodes(e.selectedNodes);
setSelectedNodes(e.selectedNodes.map((n: NodeTypeDef) => n.id));
}, []);

const handleEditAction = useCallback(
Expand Down Expand Up @@ -541,6 +543,18 @@ const PipelineEditor = forwardRef(
[onChange]
);

const handlePipelinePropertiesChange = useCallback(
(data) => {
const pipeline = controller.current.getPipelineFlow();
if (pipeline?.pipelines?.[0]?.app_data) {
pipeline.pipelines[0].app_data.properties = data;
controller.current.setPipelineFlow(pipeline);
onChange?.(controller.current.getPipelineFlow());
}
},
[onChange]
);

const handleTooltip = (tipType: string, e: TipEvent) => {
function isNodeTipEvent(type: string, _e: TipEvent): _e is TipNode {
return type === "tipTypeNode";
Expand Down Expand Up @@ -664,13 +678,31 @@ const PipelineEditor = forwardRef(
setPanelOpen(true);
}}
tabs={[
{
id: "pipeline-properties",
label: "Pipeline Properties",
icon: theme.overrides?.pipelineIcon,
content: (
<PipelineProperties
pipelineFlow={pipeline}
propertiesSchema={pipelineProperties}
onFileRequested={onFileRequested}
onPropertiesUpdateRequested={
onPropertiesUpdateRequested
}
onChange={handlePipelinePropertiesChange}
/>
),
},
{
id: "properties",
label: "Node Properties",
icon: theme.overrides?.propertiesIcon,
content: (
<PropertiesPanel
selectedNodes={selectedNodes}
<NodeProperties
selectedNodes={pipeline?.pipelines?.[0]?.nodes?.filter(
(n: NodeTypeDef) => selectedNodes?.includes(n.id)
)}
nodes={nodes}
onFileRequested={onFileRequested}
onPropertiesUpdateRequested={
Expand Down
13 changes: 12 additions & 1 deletion packages/pipeline-editor/src/ThemeProvider/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,8 @@ export const CanvasOverrides = css`
}

.properties-wrapper input[type="text" i],
.properties-wrapper input[type="number" i] {
.properties-wrapper input[type="number" i],
.properties-wrapper textarea {
box-sizing: border-box;
padding: 4px;
background-color: ${({ theme }) => theme.palette.background.input};
Expand All @@ -134,6 +135,16 @@ export const CanvasOverrides = css`
font-size: ${({ theme }) => theme.typography.fontSize};
}

.properties-wrapper p {
font-family: ${({ theme }) => theme.typography.fontFamily};
font-weight: ${({ theme }) => theme.typography.fontWeight};
font-size: ${({ theme }) => theme.typography.fontSize};
}

.properties-wrapper textarea {
resize: vertical;
}

.properties-wrapper input[type="text" i]:focus,
.properties-wrapper input[type="number" i]:focus {
outline: 1px solid ${({ theme }) => theme.palette.focus};
Expand Down
1 change: 1 addition & 0 deletions packages/pipeline-editor/src/common-canvas.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -596,6 +596,7 @@ declare module "@elyra/canvas" {
app_data?: {
// NOTE: elyra specific.
version: number;
properties: any;
/**
* Pipeline level UI information
*/
Expand Down
80 changes: 80 additions & 0 deletions packages/pipeline-editor/src/properties-panels/NodeProperties.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Copyright 2018-2021 Elyra Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { PropertiesPanel, Message } from "./PropertiesPanel";

interface Props {
selectedNodes?: any[];
nodes: any[];
onFileRequested?: (options: any) => any;
onPropertiesUpdateRequested?: (options: any) => any;
onChange?: (nodeID: string, data: any) => any;
}

function NodeProperties({
selectedNodes,
nodes,
onFileRequested,
onPropertiesUpdateRequested,
onChange,
}: Props) {
if (selectedNodes === undefined || selectedNodes.length === 0) {
return <Message>Select a node to edit its properties.</Message>;
}

if (selectedNodes.length > 1) {
return (
<Message>
Multiple nodes are selected. Select a single node to edit its
properties.
</Message>
);
}

const selectedNode = selectedNodes[0];

if (!selectedNode) {
return <Message>Select a node to edit its properties.</Message>;
}

if (selectedNode.type !== "execution_node") {
return (
<Message>This node type doesn't have any editable properties.</Message>
);
}

const nodePropertiesSchema = nodes.find((n: any) => n.op === selectedNode.op);

if (nodePropertiesSchema === undefined) {
return (
<Message>This node type doesn't have any editable properties.</Message>
);
}

return (
<PropertiesPanel
currentProperties={selectedNode.app_data}
propertiesSchema={nodePropertiesSchema}
onFileRequested={onFileRequested}
onChange={(data: any) => {
onChange?.(selectedNode.id, data);
}}
id={selectedNode.id}
/>
);
}

export default NodeProperties;
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright 2018-2021 Elyra Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { PropertiesPanel, Message } from "./PropertiesPanel";

interface Props {
pipelineFlow: any;
propertiesSchema?: any;
onFileRequested?: (options: any) => any;
onPropertiesUpdateRequested?: (options: any) => any;
onChange?: (data: any) => any;
}

function PipelineProperties({
pipelineFlow,
propertiesSchema,
onFileRequested,
onPropertiesUpdateRequested,
onChange,
}: Props) {
if (propertiesSchema === undefined) {
return <Message>No pipeline properties defined.</Message>;
}

return (
<PropertiesPanel
currentProperties={
pipelineFlow?.pipelines?.[0]?.app_data?.properties ?? {}
}
propertiesSchema={propertiesSchema}
onFileRequested={onFileRequested}
onChange={onChange}
id={pipelineFlow.id}
/>
);
}

export default PipelineProperties;
Loading