From 6c692bc217ebedab8deb6d06f66f19473dd7deab Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Thu, 5 Jan 2023 11:21:34 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=AA=9F=F0=9F=8E=89=20Connector=20builder:?= =?UTF-8?q?=20Add=20copy=20to/from=20stream=20buttons=20(#20816)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds buttons to copy pagination and slicing settings between streams --- .../connectorBuilder/Builder/Builder.tsx | 6 +- .../Builder/BuilderCard.module.scss | 32 ++++ .../connectorBuilder/Builder/BuilderCard.tsx | 158 +++++++++++++++++- .../Builder/BuilderConfigView.tsx | 10 +- .../Builder/PaginationSection.tsx | 11 +- .../Builder/StreamConfigView.module.scss | 4 + .../Builder/StreamConfigView.tsx | 12 +- .../Builder/StreamSlicerSection.tsx | 11 +- airbyte-webapp/src/locales/en.json | 6 +- 9 files changed, 237 insertions(+), 13 deletions(-) diff --git a/airbyte-webapp/src/components/connectorBuilder/Builder/Builder.tsx b/airbyte-webapp/src/components/connectorBuilder/Builder/Builder.tsx index ff06c79c07b0c..6596005a883c6 100644 --- a/airbyte-webapp/src/components/connectorBuilder/Builder/Builder.tsx +++ b/airbyte-webapp/src/components/connectorBuilder/Builder/Builder.tsx @@ -17,14 +17,14 @@ interface BuilderProps { toggleYamlEditor: () => void; } -function getView(selectedView: BuilderView) { +function getView(selectedView: BuilderView, hasMultipleStreams: boolean) { switch (selectedView) { case "global": return ; case "inputs": return ; default: - return ; + return ; } } @@ -47,7 +47,7 @@ export const Builder: React.FC = ({ values, toggleYamlEditor, vali return (
-
{getView(selectedView)}
+
{getView(selectedView, values.streams.length > 1)}
); }; diff --git a/airbyte-webapp/src/components/connectorBuilder/Builder/BuilderCard.module.scss b/airbyte-webapp/src/components/connectorBuilder/Builder/BuilderCard.module.scss index 552148a02cf6c..5ae39c3ada5dd 100644 --- a/airbyte-webapp/src/components/connectorBuilder/Builder/BuilderCard.module.scss +++ b/airbyte-webapp/src/components/connectorBuilder/Builder/BuilderCard.module.scss @@ -1,13 +1,45 @@ @use "scss/variables"; +@use "scss/colors"; .card { padding: variables.$spacing-xl; display: flex; flex-direction: column; gap: variables.$spacing-xl; + position: relative; } .toggleContainer { display: flex; gap: variables.$spacing-md; } + +.copyButtonContainer { + position: absolute; + right: -50px; + top: 0; + display: flex; + flex-direction: column; + gap: variables.$spacing-md; +} + +.modalStreamListContainer { + display: flex; + flex-direction: column; + gap: variables.$spacing-md; + align-items: stretch; +} + +.streamItem { + border-radius: variables.$border-radius-sm; + padding: variables.$spacing-md; + border: none; + background: none; + display: block; + text-align: left; + + &:hover { + cursor: pointer; + background: colors.$grey-50; + } +} diff --git a/airbyte-webapp/src/components/connectorBuilder/Builder/BuilderCard.tsx b/airbyte-webapp/src/components/connectorBuilder/Builder/BuilderCard.tsx index deec879b8c917..c6dbbdd9ec5de 100644 --- a/airbyte-webapp/src/components/connectorBuilder/Builder/BuilderCard.tsx +++ b/airbyte-webapp/src/components/connectorBuilder/Builder/BuilderCard.tsx @@ -1,9 +1,17 @@ +import { faArrowUpRightFromSquare } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import classNames from "classnames"; -import React from "react"; +import { useField, useFormikContext } from "formik"; +import get from "lodash/get"; +import React, { useState } from "react"; +import { FormattedMessage } from "react-intl"; +import { Button } from "components/ui/Button"; import { Card } from "components/ui/Card"; import { CheckBox } from "components/ui/CheckBox"; +import { Modal, ModalBody, ModalFooter } from "components/ui/Modal"; +import { BuilderStream } from "../types"; import styles from "./BuilderCard.module.scss"; interface BuilderCardProps { @@ -13,13 +21,24 @@ interface BuilderCardProps { toggledOn: boolean; onToggle: (newToggleValue: boolean) => void; }; + copyConfig?: { + path: string; + currentStreamIndex: number; + copyToLabel: string; + copyFromLabel: string; + }; } export const BuilderCard: React.FC> = ({ children, className, toggleConfig, + copyConfig, }) => { + const { setFieldValue, getFieldMeta } = useFormikContext(); + const [isCopyToOpen, setCopyToOpen] = useState(false); + const [isCopyFromOpen, setCopyFromOpen] = useState(false); + const streams = getFieldMeta("streams").value; return ( {toggleConfig && ( @@ -33,7 +52,144 @@ export const BuilderCard: React.FC> = {toggleConfig.label} )} + {copyConfig && streams.length > 1 && ( +
+
+ )} {(!toggleConfig || toggleConfig.toggledOn) && children}
); }; + +function getStreamName(stream: BuilderStream) { + return stream.name || ; +} + +const CopyToModal: React.FC<{ + onCancel: () => void; + onApply: (selectedStreamIndices: number[]) => void; + title: string; + currentStreamIndex: number; +}> = ({ onCancel, onApply, title, currentStreamIndex }) => { + const [streams] = useField("streams"); + const [selectMap, setSelectMap] = useState>({}); + return ( + +
{ + onApply( + Object.entries(selectMap) + .filter(([, selected]) => selected) + .map(([index]) => Number(index)) + ); + }} + > + + {streams.value.map((stream, index) => + index === currentStreamIndex ? null : ( + + ) + )} + + + + + +
+
+ ); +}; + +const CopyFromModal: React.FC<{ + onCancel: () => void; + onSelect: (selectedStreamIndex: number) => void; + title: string; + currentStreamIndex: number; +}> = ({ onCancel, onSelect, title, currentStreamIndex }) => { + const [streams] = useField("streams"); + return ( + + + {streams.value.map((stream, index) => + currentStreamIndex === index ? null : ( + + ) + )} + + + ); +}; diff --git a/airbyte-webapp/src/components/connectorBuilder/Builder/BuilderConfigView.tsx b/airbyte-webapp/src/components/connectorBuilder/Builder/BuilderConfigView.tsx index e064a9e1bbded..ff6fc0e3589c3 100644 --- a/airbyte-webapp/src/components/connectorBuilder/Builder/BuilderConfigView.tsx +++ b/airbyte-webapp/src/components/connectorBuilder/Builder/BuilderConfigView.tsx @@ -1,3 +1,4 @@ +import classNames from "classnames"; import React from "react"; import { Heading } from "components/ui/Heading"; @@ -6,11 +7,16 @@ import styles from "./BuilderConfigView.module.scss"; interface BuilderConfigViewProps { heading: string; + className?: string; } -export const BuilderConfigView: React.FC> = ({ children, heading }) => { +export const BuilderConfigView: React.FC> = ({ + children, + heading, + className, +}) => { return ( -
+
{heading} diff --git a/airbyte-webapp/src/components/connectorBuilder/Builder/PaginationSection.tsx b/airbyte-webapp/src/components/connectorBuilder/Builder/PaginationSection.tsx index 062fbb1f80e8e..0a42c59f1dac6 100644 --- a/airbyte-webapp/src/components/connectorBuilder/Builder/PaginationSection.tsx +++ b/airbyte-webapp/src/components/connectorBuilder/Builder/PaginationSection.tsx @@ -1,5 +1,6 @@ import { useField } from "formik"; import capitalize from "lodash/capitalize"; +import { useIntl } from "react-intl"; import GroupControls from "components/GroupControls"; import { ControlLabels } from "components/LabeledControl"; @@ -15,9 +16,11 @@ import { ToggleGroupField } from "./ToggleGroupField"; interface PaginationSectionProps { streamFieldPath: (fieldPath: string) => string; + currentStreamIndex: number; } -export const PaginationSection: React.FC = ({ streamFieldPath }) => { +export const PaginationSection: React.FC = ({ streamFieldPath, currentStreamIndex }) => { + const { formatMessage } = useIntl(); const [field, , helpers] = useField(streamFieldPath("paginator")); const [pageSizeField] = useField(streamFieldPath("paginator.strategy.page_size")); const [, , pageSizeOptionHelpers] = useField(streamFieldPath("paginator.pageSizeOption")); @@ -52,6 +55,12 @@ export const PaginationSection: React.FC = ({ streamFiel toggledOn, onToggle: handleToggle, }} + copyConfig={{ + path: "paginator", + currentStreamIndex, + copyFromLabel: formatMessage({ id: "connectorBuilder.copyFromPaginationTitle" }), + copyToLabel: formatMessage({ id: "connectorBuilder.copyToPaginationTitle" }), + }} > = ({ streamNum }) => { +export const StreamConfigView: React.FC = ({ streamNum, hasMultipleStreams }) => { const { formatMessage } = useIntl(); const [field, , helpers] = useField("streams"); const { openConfirmationModal, closeConfirmationModal } = useConfirmationModalService(); @@ -49,7 +50,10 @@ export const StreamConfigView: React.FC = ({ streamNum }) }; return ( - + {/* Not using intl for the labels and tooltips in this component in order to keep maintainence simple */}
@@ -97,8 +101,8 @@ export const StreamConfigView: React.FC = ({ streamNum }) optional /> - - + + string; + currentStreamIndex: number; } -export const StreamSlicerSection: React.FC = ({ streamFieldPath }) => { +export const StreamSlicerSection: React.FC = ({ streamFieldPath, currentStreamIndex }) => { + const { formatMessage } = useIntl(); const [field, , helpers] = useField(streamFieldPath("streamSlicer")); const handleToggle = (newToggleValue: boolean) => { @@ -44,6 +47,12 @@ export const StreamSlicerSection: React.FC = ({ stream toggledOn, onToggle: handleToggle, }} + copyConfig={{ + path: "streamSlicer", + currentStreamIndex, + copyFromLabel: formatMessage({ id: "connectorBuilder.copyFromSlicerTitle" }), + copyToLabel: formatMessage({ id: "connectorBuilder.copyToSlicerTitle" }), + }} >