From d55f3719070e4d8751f3d7fbfa34ba611d82306e Mon Sep 17 00:00:00 2001 From: lmossman Date: Thu, 3 Nov 2022 17:07:11 -0700 Subject: [PATCH 01/15] change splitter to be single bar, and rename props --- .../ResizablePanels.module.scss | 12 ++++--- .../ui/ResizablePanels/ResizablePanels.tsx | 36 +++++++++---------- .../ConnectorBuilderPage.tsx | 4 +-- .../ConnectorDocumentationLayout.tsx | 6 ++-- 4 files changed, 30 insertions(+), 28 deletions(-) diff --git a/airbyte-webapp/src/components/ui/ResizablePanels/ResizablePanels.module.scss b/airbyte-webapp/src/components/ui/ResizablePanels/ResizablePanels.module.scss index db855b63589a27..b72f9047d997e8 100644 --- a/airbyte-webapp/src/components/ui/ResizablePanels/ResizablePanels.module.scss +++ b/airbyte-webapp/src/components/ui/ResizablePanels/ResizablePanels.module.scss @@ -42,14 +42,18 @@ .panelGrabber { height: 100vh; - padding: variables.$spacing-sm; + + // padding: variables.$spacing-sm; + width: 10px; display: flex; } .grabberHandleIcon { - margin: auto; - height: 25px; - color: colors.$grey-100; + margin: auto 0 auto auto; + height: 30px; + width: 3px; + background-color: colors.$grey-100; + border-radius: variables.$border-radius-sm; } .splitter { diff --git a/airbyte-webapp/src/components/ui/ResizablePanels/ResizablePanels.tsx b/airbyte-webapp/src/components/ui/ResizablePanels/ResizablePanels.tsx index f43703e9e32ce1..7e2daa600b2e99 100644 --- a/airbyte-webapp/src/components/ui/ResizablePanels/ResizablePanels.tsx +++ b/airbyte-webapp/src/components/ui/ResizablePanels/ResizablePanels.tsx @@ -1,5 +1,3 @@ -import { faGripLinesVertical } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import classNames from "classnames"; import React from "react"; import { ReflexContainer, ReflexElement, ReflexSplitter } from "react-reflex"; @@ -10,9 +8,9 @@ import styles from "./ResizablePanels.module.scss"; interface ResizablePanelsProps { className?: string; - hideRightPanel?: boolean; - leftPanel: PanelProps; - rightPanel: PanelProps; + firstPanel: PanelProps; + secondPanel: PanelProps; + hideSecondPanel?: boolean; } interface PanelProps { @@ -67,39 +65,39 @@ const PanelContainer: React.FC> = ( export const ResizablePanels: React.FC = ({ className, - hideRightPanel = false, - leftPanel, - rightPanel, + firstPanel, + secondPanel, + hideSecondPanel = false, }) => { return ( - - {leftPanel.children} + + {firstPanel.children} {/* NOTE: ReflexElement will not load its contents if wrapped in an empty jsx tag along with ReflexSplitter. They must be evaluated/rendered separately. */} - {!hideRightPanel && ( + {!hideSecondPanel && (
- +
)} - {!hideRightPanel && ( + {!hideSecondPanel && ( - - {rightPanel.children} + + {secondPanel.children} )} diff --git a/airbyte-webapp/src/pages/ConnectorBuilderPage/ConnectorBuilderPage.tsx b/airbyte-webapp/src/pages/ConnectorBuilderPage/ConnectorBuilderPage.tsx index ec0e74cd4a0f2d..a6fedf6a84fffe 100644 --- a/airbyte-webapp/src/pages/ConnectorBuilderPage/ConnectorBuilderPage.tsx +++ b/airbyte-webapp/src/pages/ConnectorBuilderPage/ConnectorBuilderPage.tsx @@ -17,12 +17,12 @@ const ConnectorBuilderPageInner: React.FC = () => { return ( , className: styles.leftPanel, minWidth: 400, }} - rightPanel={{ + secondPanel={{ children: , className: styles.rightPanel, startingFlex: 0.33, diff --git a/airbyte-webapp/src/views/Connector/ConnectorDocumentationLayout/ConnectorDocumentationLayout.tsx b/airbyte-webapp/src/views/Connector/ConnectorDocumentationLayout/ConnectorDocumentationLayout.tsx index 8c081de050c49d..89807bdee86163 100644 --- a/airbyte-webapp/src/views/Connector/ConnectorDocumentationLayout/ConnectorDocumentationLayout.tsx +++ b/airbyte-webapp/src/views/Connector/ConnectorDocumentationLayout/ConnectorDocumentationLayout.tsx @@ -26,13 +26,13 @@ export const ConnectorDocumentationLayout: React.FC Date: Thu, 3 Nov 2022 18:35:22 -0700 Subject: [PATCH 02/15] add logs viewer to testing panel --- .../ResultDisplay.module.scss | 31 ++++++++++ .../StreamTestingPanel/ResultDisplay.tsx | 59 +++++++++++++++---- .../StreamTestingPanel.module.scss | 4 +- .../components/ui/ListBox/ListBox.module.scss | 1 + .../ResizablePanels.module.scss | 27 +++++++-- .../ui/ResizablePanels/ResizablePanels.tsx | 25 ++++++-- airbyte-webapp/src/locales/en.json | 3 +- .../ConnectorBuilderPage.tsx | 2 +- 8 files changed, 123 insertions(+), 29 deletions(-) diff --git a/airbyte-webapp/src/components/StreamTestingPanel/ResultDisplay.module.scss b/airbyte-webapp/src/components/StreamTestingPanel/ResultDisplay.module.scss index 585ac3abddb8fb..dff1c4fdf4dbfb 100644 --- a/airbyte-webapp/src/components/StreamTestingPanel/ResultDisplay.module.scss +++ b/airbyte-webapp/src/components/StreamTestingPanel/ResultDisplay.module.scss @@ -2,9 +2,15 @@ @use "scss/variables"; .container { + // required to hide the connector logs splitter underneath the resizable panel overlay + z-index: 0; +} + +.resultContainer { display: flex; flex-direction: column; gap: variables.$spacing-lg; + padding-bottom: variables.$spacing-md; } .sliceSelector { @@ -35,3 +41,28 @@ justify-content: center; font-size: 10px; } + +.logsContainer { + padding-top: variables.$spacing-sm; + display: flex; + flex-direction: column; +} + +.logsHeader { + background-color: colors.$grey-50; + display: flex; + align-items: center; + gap: variables.$spacing-md; + padding: variables.$spacing-sm variables.$spacing-md; +} + +.numLogsDisplay { + border-radius: variables.$border-radius-md; + background-color: colors.$red; + padding: 1px variables.$spacing-sm; + color: colors.$white; +} + +.logsDisplay { + overflow-y: auto; +} diff --git a/airbyte-webapp/src/components/StreamTestingPanel/ResultDisplay.tsx b/airbyte-webapp/src/components/StreamTestingPanel/ResultDisplay.tsx index 31fb6f68e71047..3482d3ac253367 100644 --- a/airbyte-webapp/src/components/StreamTestingPanel/ResultDisplay.tsx +++ b/airbyte-webapp/src/components/StreamTestingPanel/ResultDisplay.tsx @@ -1,7 +1,9 @@ import classNames from "classnames"; import { useState } from "react"; +import { FormattedMessage } from "react-intl"; import { Paginator } from "components/ui/Paginator"; +import { ResizablePanels } from "components/ui/ResizablePanels"; import { Text } from "components/ui/Text"; import { StreamRead } from "core/request/ConnectorBuilderClient"; @@ -18,6 +20,7 @@ interface ResultDisplayProps { export const ResultDisplay: React.FC = ({ streamRead, className }) => { const [selectedSliceIndex, setSelectedSliceIndex] = useState(0); const [selectedPage, setSelectedPage] = useState(0); + // const [flex, setFlex] = useState(0.0); const handlePageChange = (selectedPageIndex: number) => { setSelectedPage(selectedPageIndex); @@ -28,18 +31,48 @@ export const ResultDisplay: React.FC = ({ streamRead, classN const page = slice.pages[selectedPage]; return ( -
- - -
- Page: - -
-
+ + + +
+ Page: + +
+ + ), + minWidth: 120, + }} + secondPanel={{ + className: styles.logsContainer, + children: ( + <> +
+ + + + + {streamRead.logs.length} + +
+
+
{JSON.stringify(streamRead.logs, null, 2)}
+
+ + ), + minWidth: 30, + flex: 0, + }} + /> ); }; diff --git a/airbyte-webapp/src/components/StreamTestingPanel/StreamTestingPanel.module.scss b/airbyte-webapp/src/components/StreamTestingPanel/StreamTestingPanel.module.scss index 34d1abcff1499a..153080705bd176 100644 --- a/airbyte-webapp/src/components/StreamTestingPanel/StreamTestingPanel.module.scss +++ b/airbyte-webapp/src/components/StreamTestingPanel/StreamTestingPanel.module.scss @@ -1,11 +1,11 @@ @use "scss/variables"; .container { - padding: variables.$spacing-xl; + padding: variables.$spacing-xl variables.$spacing-md variables.$spacing-md variables.$spacing-md; display: flex; flex-direction: column; height: 100%; - gap: variables.$spacing-xl; + gap: variables.$spacing-lg; } .streamSelector { diff --git a/airbyte-webapp/src/components/ui/ListBox/ListBox.module.scss b/airbyte-webapp/src/components/ui/ListBox/ListBox.module.scss index 62ba6782ae7a8f..e674dc1e9173a0 100644 --- a/airbyte-webapp/src/components/ui/ListBox/ListBox.module.scss +++ b/airbyte-webapp/src/components/ui/ListBox/ListBox.module.scss @@ -22,6 +22,7 @@ right: 0; left: 0; width: 100%; + z-index: 1; } .option { diff --git a/airbyte-webapp/src/components/ui/ResizablePanels/ResizablePanels.module.scss b/airbyte-webapp/src/components/ui/ResizablePanels/ResizablePanels.module.scss index b72f9047d997e8..9c1e536caf2b0b 100644 --- a/airbyte-webapp/src/components/ui/ResizablePanels/ResizablePanels.module.scss +++ b/airbyte-webapp/src/components/ui/ResizablePanels/ResizablePanels.module.scss @@ -40,20 +40,35 @@ transform: rotate(180deg); } -.panelGrabber { +.panelGrabberVertical { height: 100vh; - - // padding: variables.$spacing-sm; width: 10px; display: flex; + flex-direction: row; +} + +.panelGrabberHorizontal { + width: 100%; + height: 10px; + display: flex; + flex-direction: column; +} + +.handleIcon { + background-color: colors.$grey-100; + border-radius: variables.$border-radius-sm; } -.grabberHandleIcon { +.handleIconVertical { margin: auto 0 auto auto; height: 30px; width: 3px; - background-color: colors.$grey-100; - border-radius: variables.$border-radius-sm; +} + +.handleIconHorizontal { + margin: 0 auto auto; + width: 30px; + height: 3px; } .splitter { diff --git a/airbyte-webapp/src/components/ui/ResizablePanels/ResizablePanels.tsx b/airbyte-webapp/src/components/ui/ResizablePanels/ResizablePanels.tsx index 7e2daa600b2e99..b63558da8c044c 100644 --- a/airbyte-webapp/src/components/ui/ResizablePanels/ResizablePanels.tsx +++ b/airbyte-webapp/src/components/ui/ResizablePanels/ResizablePanels.tsx @@ -8,6 +8,7 @@ import styles from "./ResizablePanels.module.scss"; interface ResizablePanelsProps { className?: string; + orientation?: "vertical" | "horizontal"; firstPanel: PanelProps; secondPanel: PanelProps; hideSecondPanel?: boolean; @@ -17,7 +18,7 @@ interface PanelProps { children: React.ReactNode; minWidth: number; className?: string; - startingFlex?: number; + flex?: number; overlay?: Overlay; } @@ -65,17 +66,18 @@ const PanelContainer: React.FC> = ( export const ResizablePanels: React.FC = ({ className, + orientation = "vertical", firstPanel, secondPanel, hideSecondPanel = false, }) => { return ( - + {firstPanel.children} @@ -84,8 +86,18 @@ export const ResizablePanels: React.FC = ({ {/* NOTE: ReflexElement will not load its contents if wrapped in an empty jsx tag along with ReflexSplitter. They must be evaluated/rendered separately. */} {!hideSecondPanel && ( -
-
+
+
)} @@ -94,7 +106,8 @@ export const ResizablePanels: React.FC = ({ className={styles.panelStyle} propagateDimensions minSize={secondPanel.minWidth} - flex={secondPanel.startingFlex} + flex={secondPanel.flex} + onResize={(args) => console.log(args.component.props.flex)} > {secondPanel.children} diff --git a/airbyte-webapp/src/locales/en.json b/airbyte-webapp/src/locales/en.json index ffccf21210701d..a54cb49a217489 100644 --- a/airbyte-webapp/src/locales/en.json +++ b/airbyte-webapp/src/locales/en.json @@ -590,5 +590,6 @@ "connectorBuilder.requestTab": "Request", "connectorBuilder.responseTab": "Response", "connectorBuilder.resultsPlaceholder": "Click Test to fetch data for this stream", - "connectorBuilder.sliceLabel": "Slice" + "connectorBuilder.sliceLabel": "Slice", + "connectorBuilder.connectorLogs": "Connector logs" } diff --git a/airbyte-webapp/src/pages/ConnectorBuilderPage/ConnectorBuilderPage.tsx b/airbyte-webapp/src/pages/ConnectorBuilderPage/ConnectorBuilderPage.tsx index a6fedf6a84fffe..686a3ba9147e3f 100644 --- a/airbyte-webapp/src/pages/ConnectorBuilderPage/ConnectorBuilderPage.tsx +++ b/airbyte-webapp/src/pages/ConnectorBuilderPage/ConnectorBuilderPage.tsx @@ -25,7 +25,7 @@ const ConnectorBuilderPageInner: React.FC = () => { secondPanel={{ children: , className: styles.rightPanel, - startingFlex: 0.33, + flex: 0.33, minWidth: 60, overlay: { displayThreshold: 300, From baeb9094ba9514f5bfe3ab9d5575001e23a37f5a Mon Sep 17 00:00:00 2001 From: lmossman Date: Thu, 3 Nov 2022 18:39:19 -0700 Subject: [PATCH 03/15] make log display height 100% --- .../src/components/StreamTestingPanel/ResultDisplay.module.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/airbyte-webapp/src/components/StreamTestingPanel/ResultDisplay.module.scss b/airbyte-webapp/src/components/StreamTestingPanel/ResultDisplay.module.scss index dff1c4fdf4dbfb..5e485b7a04caa8 100644 --- a/airbyte-webapp/src/components/StreamTestingPanel/ResultDisplay.module.scss +++ b/airbyte-webapp/src/components/StreamTestingPanel/ResultDisplay.module.scss @@ -65,4 +65,5 @@ .logsDisplay { overflow-y: auto; + height: 100%; } From 8a5ba15d3f9ae21722e821f537a379f053bb536a Mon Sep 17 00:00:00 2001 From: lmossman Date: Thu, 3 Nov 2022 18:41:35 -0700 Subject: [PATCH 04/15] remove comment --- .../src/components/StreamTestingPanel/ResultDisplay.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/airbyte-webapp/src/components/StreamTestingPanel/ResultDisplay.tsx b/airbyte-webapp/src/components/StreamTestingPanel/ResultDisplay.tsx index 3482d3ac253367..b8b0a216a954e8 100644 --- a/airbyte-webapp/src/components/StreamTestingPanel/ResultDisplay.tsx +++ b/airbyte-webapp/src/components/StreamTestingPanel/ResultDisplay.tsx @@ -20,7 +20,6 @@ interface ResultDisplayProps { export const ResultDisplay: React.FC = ({ streamRead, className }) => { const [selectedSliceIndex, setSelectedSliceIndex] = useState(0); const [selectedPage, setSelectedPage] = useState(0); - // const [flex, setFlex] = useState(0.0); const handlePageChange = (selectedPageIndex: number) => { setSelectedPage(selectedPageIndex); From 645c5f41a805b8e7edf8270ffeb2d06d2eeaf70b Mon Sep 17 00:00:00 2001 From: lmossman Date: Fri, 4 Nov 2022 10:56:59 -0700 Subject: [PATCH 05/15] clean up some styling, and add record count to tab title --- .../src/components/StreamTestingPanel/PageDisplay.tsx | 7 ++++++- .../StreamTestingPanel/ResultDisplay.module.scss | 2 +- .../components/StreamTestingPanel/TestControls.module.scss | 1 - .../src/components/ui/Paginator/Paginator.module.scss | 2 +- .../pages/ConnectorBuilderPage/ConnectorBuilderPage.tsx | 2 +- 5 files changed, 9 insertions(+), 5 deletions(-) diff --git a/airbyte-webapp/src/components/StreamTestingPanel/PageDisplay.tsx b/airbyte-webapp/src/components/StreamTestingPanel/PageDisplay.tsx index eba4c77e0aade2..fb8b2721d8dccb 100644 --- a/airbyte-webapp/src/components/StreamTestingPanel/PageDisplay.tsx +++ b/airbyte-webapp/src/components/StreamTestingPanel/PageDisplay.tsx @@ -25,7 +25,12 @@ interface TabData { export const PageDisplay: React.FC = ({ page, className }) => { const { formatMessage } = useIntl(); - const tabs: TabData[] = [{ title: formatMessage({ id: "connectorBuilder.recordsTab" }), content: page.records }]; + const tabs: TabData[] = [ + { + title: `${formatMessage({ id: "connectorBuilder.recordsTab" })} (${page.records.length})`, + content: page.records, + }, + ]; if (page.request) { tabs.push({ title: formatMessage({ id: "connectorBuilder.requestTab" }), content: page.request }); } diff --git a/airbyte-webapp/src/components/StreamTestingPanel/ResultDisplay.module.scss b/airbyte-webapp/src/components/StreamTestingPanel/ResultDisplay.module.scss index 5e485b7a04caa8..c073c1d90c8776 100644 --- a/airbyte-webapp/src/components/StreamTestingPanel/ResultDisplay.module.scss +++ b/airbyte-webapp/src/components/StreamTestingPanel/ResultDisplay.module.scss @@ -58,7 +58,7 @@ .numLogsDisplay { border-radius: variables.$border-radius-md; - background-color: colors.$red; + background-color: colors.$blue; padding: 1px variables.$spacing-sm; color: colors.$white; } diff --git a/airbyte-webapp/src/components/StreamTestingPanel/TestControls.module.scss b/airbyte-webapp/src/components/StreamTestingPanel/TestControls.module.scss index b5c61e3bf33996..c25cd6bcc2c348 100644 --- a/airbyte-webapp/src/components/StreamTestingPanel/TestControls.module.scss +++ b/airbyte-webapp/src/components/StreamTestingPanel/TestControls.module.scss @@ -4,7 +4,6 @@ $testIconHeight: 17px; .container { - margin: variables.$spacing-lg 0; display: flex; gap: variables.$spacing-md; height: 36px; diff --git a/airbyte-webapp/src/components/ui/Paginator/Paginator.module.scss b/airbyte-webapp/src/components/ui/Paginator/Paginator.module.scss index 623b0ce13f0430..398b206f94c0d0 100644 --- a/airbyte-webapp/src/components/ui/Paginator/Paginator.module.scss +++ b/airbyte-webapp/src/components/ui/Paginator/Paginator.module.scss @@ -8,7 +8,7 @@ $containerHeight: 23px; height: $containerHeight; width: 80%; display: flex; - gap: variables.$spacing-sm; + gap: 2px; font-size: 10px; margin: 0; } diff --git a/airbyte-webapp/src/pages/ConnectorBuilderPage/ConnectorBuilderPage.tsx b/airbyte-webapp/src/pages/ConnectorBuilderPage/ConnectorBuilderPage.tsx index 686a3ba9147e3f..e432d8c40f4f16 100644 --- a/airbyte-webapp/src/pages/ConnectorBuilderPage/ConnectorBuilderPage.tsx +++ b/airbyte-webapp/src/pages/ConnectorBuilderPage/ConnectorBuilderPage.tsx @@ -28,7 +28,7 @@ const ConnectorBuilderPageInner: React.FC = () => { flex: 0.33, minWidth: 60, overlay: { - displayThreshold: 300, + displayThreshold: 325, header: capitalize(selectedStream.name), rotation: "counter-clockwise", }, From 8283a2814a4e232de74743c03c5641c72f9fb712 Mon Sep 17 00:00:00 2001 From: lmossman Date: Fri, 4 Nov 2022 11:58:03 -0700 Subject: [PATCH 06/15] cleanup + only render paginator and slice selector when necessary --- .../StreamTestingPanel/PageDisplay.tsx | 4 ++-- .../StreamTestingPanel/ResultDisplay.tsx | 24 +++++++++++-------- .../ui/ResizablePanels/ResizablePanels.tsx | 1 - .../ConnectorBuilderRequestService.ts | 14 ++++++++++- 4 files changed, 29 insertions(+), 14 deletions(-) diff --git a/airbyte-webapp/src/components/StreamTestingPanel/PageDisplay.tsx b/airbyte-webapp/src/components/StreamTestingPanel/PageDisplay.tsx index fb8b2721d8dccb..7b48992b9fa6b5 100644 --- a/airbyte-webapp/src/components/StreamTestingPanel/PageDisplay.tsx +++ b/airbyte-webapp/src/components/StreamTestingPanel/PageDisplay.tsx @@ -43,7 +43,7 @@ export const PageDisplay: React.FC = ({ page, className }) => {tabs.map((tab) => ( - + {({ selected }) => ( {tab.title} )} @@ -52,7 +52,7 @@ export const PageDisplay: React.FC = ({ page, className }) => {tabs.map((tab) => ( - +
{JSON.stringify(tab.content, null, 2)}
))} diff --git a/airbyte-webapp/src/components/StreamTestingPanel/ResultDisplay.tsx b/airbyte-webapp/src/components/StreamTestingPanel/ResultDisplay.tsx index b8b0a216a954e8..22d827d6b2f97b 100644 --- a/airbyte-webapp/src/components/StreamTestingPanel/ResultDisplay.tsx +++ b/airbyte-webapp/src/components/StreamTestingPanel/ResultDisplay.tsx @@ -37,17 +37,21 @@ export const ResultDisplay: React.FC = ({ streamRead, classN className: styles.resultContainer, children: ( <> - + {streamRead.slices.length > 1 && ( + + )} -
- Page: - -
+ {slice.pages.length > 1 && ( +
+ Page: + +
+ )} ), minWidth: 120, diff --git a/airbyte-webapp/src/components/ui/ResizablePanels/ResizablePanels.tsx b/airbyte-webapp/src/components/ui/ResizablePanels/ResizablePanels.tsx index b63558da8c044c..7dabd2d5269653 100644 --- a/airbyte-webapp/src/components/ui/ResizablePanels/ResizablePanels.tsx +++ b/airbyte-webapp/src/components/ui/ResizablePanels/ResizablePanels.tsx @@ -107,7 +107,6 @@ export const ResizablePanels: React.FC = ({ propagateDimensions minSize={secondPanel.minWidth} flex={secondPanel.flex} - onResize={(args) => console.log(args.component.props.flex)} > {secondPanel.children} diff --git a/airbyte-webapp/src/core/domain/connectorBuilder/ConnectorBuilderRequestService.ts b/airbyte-webapp/src/core/domain/connectorBuilder/ConnectorBuilderRequestService.ts index 5f151f6433f99f..a1d81568c4acfb 100644 --- a/airbyte-webapp/src/core/domain/connectorBuilder/ConnectorBuilderRequestService.ts +++ b/airbyte-webapp/src/core/domain/connectorBuilder/ConnectorBuilderRequestService.ts @@ -1,6 +1,7 @@ import { StreamRead, StreamReadRequestBody, + StreamReadSlicesItem, StreamsListRead, StreamsListRequestBody, } from "core/request/ConnectorBuilderClient"; @@ -71,7 +72,18 @@ export class ConnectorBuilderRequestService extends AirbyteRequestService { console.log(`Connector manifest:\n${JSON.stringify(readParams.manifest)}`); console.log(`Config:\n${JSON.stringify(readParams.config)}`); return new Promise((resolve) => setTimeout(resolve, 200)).then(() => { - const slices = Array.from(Array(4).keys()).map((i) => mockSlice(readParams.stream, i + 1, 20, 20)); + let slices: StreamReadSlicesItem[] = []; + switch (readParams.stream) { + case "disputes": + slices = [mockSlice(readParams.stream, 1, 1, 45)]; + break; + case "transactions": + slices = [mockSlice(readParams.stream, 1, 13, 35)]; + break; + case "users": + slices = Array.from(Array(4).keys()).map((i) => mockSlice(readParams.stream, i + 1, 20, 25)); + break; + } return { logs: [ From 3ce55a78050ca8a7dc72c2ce21b707eb0b592a15 Mon Sep 17 00:00:00 2001 From: lmossman Date: Fri, 4 Nov 2022 14:26:33 -0700 Subject: [PATCH 07/15] move selected slice/page state into context and fix bug with state between streams --- .../StreamTestingPanel/PageDisplay.tsx | 2 +- .../StreamTestingPanel/ResultDisplay.tsx | 17 +++----- .../src/components/ui/Paginator/Paginator.tsx | 41 +++++++++--------- .../ConnectorBuilderRequestService.ts | 6 +-- .../ConnectorBuilderStateService.tsx | 42 ++++++++++++------- 5 files changed, 57 insertions(+), 51 deletions(-) diff --git a/airbyte-webapp/src/components/StreamTestingPanel/PageDisplay.tsx b/airbyte-webapp/src/components/StreamTestingPanel/PageDisplay.tsx index 7b48992b9fa6b5..c45fb85e5c926e 100644 --- a/airbyte-webapp/src/components/StreamTestingPanel/PageDisplay.tsx +++ b/airbyte-webapp/src/components/StreamTestingPanel/PageDisplay.tsx @@ -27,7 +27,7 @@ export const PageDisplay: React.FC = ({ page, className }) => const { formatMessage } = useIntl(); const tabs: TabData[] = [ { - title: `${formatMessage({ id: "connectorBuilder.recordsTab" })} (${page.records.length})`, + title: formatMessage({ id: "connectorBuilder.recordsTab" }), content: page.records, }, ]; diff --git a/airbyte-webapp/src/components/StreamTestingPanel/ResultDisplay.tsx b/airbyte-webapp/src/components/StreamTestingPanel/ResultDisplay.tsx index 22d827d6b2f97b..ae98a8b68157dc 100644 --- a/airbyte-webapp/src/components/StreamTestingPanel/ResultDisplay.tsx +++ b/airbyte-webapp/src/components/StreamTestingPanel/ResultDisplay.tsx @@ -1,5 +1,4 @@ import classNames from "classnames"; -import { useState } from "react"; import { FormattedMessage } from "react-intl"; import { Paginator } from "components/ui/Paginator"; @@ -7,6 +6,7 @@ import { ResizablePanels } from "components/ui/ResizablePanels"; import { Text } from "components/ui/Text"; import { StreamRead } from "core/request/ConnectorBuilderClient"; +import { useConnectorBuilderState } from "services/connectorBuilder/ConnectorBuilderStateService"; import { PageDisplay } from "./PageDisplay"; import styles from "./ResultDisplay.module.scss"; @@ -18,14 +18,9 @@ interface ResultDisplayProps { } export const ResultDisplay: React.FC = ({ streamRead, className }) => { - const [selectedSliceIndex, setSelectedSliceIndex] = useState(0); - const [selectedPage, setSelectedPage] = useState(0); + const { selectedSlice, selectedPage, setSelectedSlice, setSelectedPage } = useConnectorBuilderState(); - const handlePageChange = (selectedPageIndex: number) => { - setSelectedPage(selectedPageIndex); - }; - - const slice = streamRead.slices[selectedSliceIndex]; + const slice = streamRead.slices[selectedSlice]; const numPages = slice.pages.length; const page = slice.pages[selectedPage]; @@ -41,15 +36,15 @@ export const ResultDisplay: React.FC = ({ streamRead, classN )} {slice.pages.length > 1 && (
Page: - +
)} diff --git a/airbyte-webapp/src/components/ui/Paginator/Paginator.tsx b/airbyte-webapp/src/components/ui/Paginator/Paginator.tsx index 6da26d04bc4f4f..28cf85d7389c78 100644 --- a/airbyte-webapp/src/components/ui/Paginator/Paginator.tsx +++ b/airbyte-webapp/src/components/ui/Paginator/Paginator.tsx @@ -22,24 +22,23 @@ function pageRangeDisplayed(numPages: number, selectedPageIndex: number): number return 3; } -export const Paginator: React.FC = ({ className, numPages, onPageChange, selectedPage }) => { - return ( - { - onPageChange(event.selected); - }} - breakLabel="…" - nextLabel=">" - previousLabel="<" - pageRangeDisplayed={pageRangeDisplayed(numPages, selectedPage)} - marginPagesDisplayed={2} - containerClassName={classNames(className, styles.container)} - pageClassName={classNames(styles.button, styles.page)} - breakClassName={classNames(styles.button, styles.break)} - activeClassName={styles.active} - previousClassName={classNames(styles.button, styles.previous)} - nextClassName={classNames(styles.button, styles.next)} - /> - ); -}; +export const Paginator: React.FC = ({ className, numPages, onPageChange, selectedPage }) => ( + { + onPageChange(event.selected); + }} + forcePage={selectedPage} + breakLabel="…" + nextLabel=">" + previousLabel="<" + pageRangeDisplayed={pageRangeDisplayed(numPages, selectedPage)} + marginPagesDisplayed={2} + containerClassName={classNames(className, styles.container)} + pageClassName={classNames(styles.button, styles.page)} + breakClassName={classNames(styles.button, styles.break)} + activeClassName={styles.active} + previousClassName={classNames(styles.button, styles.previous)} + nextClassName={classNames(styles.button, styles.next)} + /> +); diff --git a/airbyte-webapp/src/core/domain/connectorBuilder/ConnectorBuilderRequestService.ts b/airbyte-webapp/src/core/domain/connectorBuilder/ConnectorBuilderRequestService.ts index a1d81568c4acfb..ccf59775ea0675 100644 --- a/airbyte-webapp/src/core/domain/connectorBuilder/ConnectorBuilderRequestService.ts +++ b/airbyte-webapp/src/core/domain/connectorBuilder/ConnectorBuilderRequestService.ts @@ -67,10 +67,7 @@ export class ConnectorBuilderRequestService extends AirbyteRequestService { public readStream(readParams: StreamReadRequestBody): Promise { // TODO: uncomment this and remove mock responses once there is a real API to call // return readStream(readParams, this.requestOptions); - console.log("------------"); - console.log(`Stream: ${readParams.stream}`); - console.log(`Connector manifest:\n${JSON.stringify(readParams.manifest)}`); - console.log(`Config:\n${JSON.stringify(readParams.config)}`); + return new Promise((resolve) => setTimeout(resolve, 200)).then(() => { let slices: StreamReadSlicesItem[] = []; switch (readParams.stream) { @@ -99,6 +96,7 @@ export class ConnectorBuilderRequestService extends AirbyteRequestService { // TODO: uncomment this and remove mock responses once there is a real API to call // return listStreams(listParams, this.requestOptions); console.log(`Received listStreams body: ${JSON.stringify(listParams)}`); + return new Promise((resolve) => setTimeout(resolve, 200)).then(() => { return { streams: [ diff --git a/airbyte-webapp/src/services/connectorBuilder/ConnectorBuilderStateService.tsx b/airbyte-webapp/src/services/connectorBuilder/ConnectorBuilderStateService.tsx index 97f16a991b6b52..9677ee6ae707ee 100644 --- a/airbyte-webapp/src/services/connectorBuilder/ConnectorBuilderStateService.tsx +++ b/airbyte-webapp/src/services/connectorBuilder/ConnectorBuilderStateService.tsx @@ -16,10 +16,14 @@ interface Context { jsonManifest: StreamsListRequestBodyManifest; streams: StreamsListReadStreamsItem[]; selectedStream: StreamsListReadStreamsItem; + selectedSlice: number; + selectedPage: number; configString: string; configJson: StreamReadRequestBodyConfig; setYamlManifest: (yamlValue: string) => void; setSelectedStream: (streamName: string) => void; + setSelectedSlice: (sliceIndex: number) => void; + setSelectedPage: (pageIndex: number) => void; setConfigString: (configString: string) => void; } @@ -45,7 +49,7 @@ const useYamlManifest = () => { return { yamlManifest, jsonManifest, setYamlManifest }; }; -const useStreams = () => { +const useSelected = () => { const { jsonManifest } = useYamlManifest(); const streamListRead = useListStreams({ manifest: jsonManifest }); const streams = streamListRead.streams; @@ -56,7 +60,23 @@ const useStreams = () => { url: "", }; - return { streams, selectedStream, setSelectedStream }; + const [streamToSelectedSlice, setStreamToSelectedSlice] = useState({ [selectedStreamName]: 0 }); + const setSelectedSlice = (sliceIndex: number) => { + setStreamToSelectedSlice((prev) => { + return { ...prev, [selectedStreamName]: sliceIndex }; + }); + }; + const selectedSlice = streamToSelectedSlice[selectedStreamName] ?? 0; + + const [streamToSelectedPage, setStreamToSelectedPage] = useState({ [selectedStreamName]: 0 }); + const setSelectedPage = (pageIndex: number) => { + setStreamToSelectedPage((prev) => { + return { ...prev, [selectedStreamName]: pageIndex }; + }); + }; + const selectedPage = streamToSelectedPage[selectedStreamName] ?? 0; + + return { streams, selectedStream, selectedSlice, selectedPage, setSelectedStream, setSelectedSlice, setSelectedPage }; }; const useConfig = () => { @@ -76,20 +96,14 @@ const useConfig = () => { }; export const ConnectorBuilderStateProvider: React.FC> = ({ children }) => { - const { yamlManifest, jsonManifest, setYamlManifest } = useYamlManifest(); - const { streams, selectedStream, setSelectedStream } = useStreams(); - const { configString, configJson, setConfigString } = useConfig(); + const yamlManifest = useYamlManifest(); + const selected = useSelected(); + const config = useConfig(); const ctx = { - yamlManifest, - jsonManifest, - streams, - selectedStream, - configString, - configJson, - setYamlManifest, - setSelectedStream, - setConfigString, + ...yamlManifest, + ...selected, + ...config, }; return {children}; From 20e4d02c9436ea01fe36446277bc829e69d7487a Mon Sep 17 00:00:00 2001 From: lmossman Date: Fri, 4 Nov 2022 14:34:38 -0700 Subject: [PATCH 08/15] fix tab keys --- .../StreamTestingPanel/PageDisplay.tsx | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/airbyte-webapp/src/components/StreamTestingPanel/PageDisplay.tsx b/airbyte-webapp/src/components/StreamTestingPanel/PageDisplay.tsx index c45fb85e5c926e..da8535523e297f 100644 --- a/airbyte-webapp/src/components/StreamTestingPanel/PageDisplay.tsx +++ b/airbyte-webapp/src/components/StreamTestingPanel/PageDisplay.tsx @@ -20,6 +20,7 @@ interface PageDisplayProps { interface TabData { title: string; + key: string; content: StreamReadSlicesItemPagesItemRecordsItem[] | HttpRequest | HttpResponse; } @@ -27,15 +28,20 @@ export const PageDisplay: React.FC = ({ page, className }) => const { formatMessage } = useIntl(); const tabs: TabData[] = [ { - title: formatMessage({ id: "connectorBuilder.recordsTab" }), + title: `${formatMessage({ id: "connectorBuilder.recordsTab" })} (${page.records.length})`, + key: "records", content: page.records, }, ]; if (page.request) { - tabs.push({ title: formatMessage({ id: "connectorBuilder.requestTab" }), content: page.request }); + tabs.push({ title: formatMessage({ id: "connectorBuilder.requestTab" }), key: "request", content: page.request }); } if (page.response) { - tabs.push({ title: formatMessage({ id: "connectorBuilder.responseTab" }), content: page.response }); + tabs.push({ + title: formatMessage({ id: "connectorBuilder.responseTab" }), + key: "response", + content: page.response, + }); } return ( @@ -43,7 +49,7 @@ export const PageDisplay: React.FC = ({ page, className }) => {tabs.map((tab) => ( - + {({ selected }) => ( {tab.title} )} @@ -52,7 +58,7 @@ export const PageDisplay: React.FC = ({ page, className }) => {tabs.map((tab) => ( - +
{JSON.stringify(tab.content, null, 2)}
))} From 22837e06a47a85b6f5675d82a24905cd39b4fe7b Mon Sep 17 00:00:00 2001 From: lmossman Date: Fri, 4 Nov 2022 15:21:58 -0700 Subject: [PATCH 09/15] pull LogsDisplay out into its own component to simplify ResultDisplay --- .../LogsDisplay.module.scss | 29 ++++++++ .../StreamTestingPanel/LogsDisplay.tsx | 29 ++++++++ .../ResultDisplay.module.scss | 34 +--------- .../StreamTestingPanel/ResultDisplay.tsx | 66 +++++-------------- .../StreamTestingPanel.module.scss | 5 +- .../StreamTestingPanel/StreamTestingPanel.tsx | 19 +++++- 6 files changed, 99 insertions(+), 83 deletions(-) create mode 100644 airbyte-webapp/src/components/StreamTestingPanel/LogsDisplay.module.scss create mode 100644 airbyte-webapp/src/components/StreamTestingPanel/LogsDisplay.tsx diff --git a/airbyte-webapp/src/components/StreamTestingPanel/LogsDisplay.module.scss b/airbyte-webapp/src/components/StreamTestingPanel/LogsDisplay.module.scss new file mode 100644 index 00000000000000..7c1184b828d989 --- /dev/null +++ b/airbyte-webapp/src/components/StreamTestingPanel/LogsDisplay.module.scss @@ -0,0 +1,29 @@ +@use "scss/colors"; +@use "scss/variables"; + +.container { + padding-top: variables.$spacing-sm; + display: flex; + flex-direction: column; + height: 100%; +} + +.header { + background-color: colors.$grey-50; + display: flex; + align-items: center; + gap: variables.$spacing-md; + padding: variables.$spacing-sm variables.$spacing-md; +} + +.numLogsDisplay { + border-radius: variables.$border-radius-md; + background-color: colors.$blue; + padding: 1px variables.$spacing-sm; + color: colors.$white; +} + +.logsDisplay { + overflow-y: auto; + height: 100%; +} diff --git a/airbyte-webapp/src/components/StreamTestingPanel/LogsDisplay.tsx b/airbyte-webapp/src/components/StreamTestingPanel/LogsDisplay.tsx new file mode 100644 index 00000000000000..71db6f665c4b28 --- /dev/null +++ b/airbyte-webapp/src/components/StreamTestingPanel/LogsDisplay.tsx @@ -0,0 +1,29 @@ +import { FormattedMessage } from "react-intl"; + +import { Text } from "components/ui/Text"; + +import { StreamReadLogsItem } from "core/request/ConnectorBuilderClient"; + +import styles from "./LogsDisplay.module.scss"; + +interface LogsDisplayProps { + logs: StreamReadLogsItem[]; +} + +export const LogsDisplay: React.FC = ({ logs }) => { + return ( +
+
+ + + + + {logs.length} + +
+
+
{JSON.stringify(logs, null, 2)}
+
+
+ ); +}; diff --git a/airbyte-webapp/src/components/StreamTestingPanel/ResultDisplay.module.scss b/airbyte-webapp/src/components/StreamTestingPanel/ResultDisplay.module.scss index c073c1d90c8776..cc97997c4d0567 100644 --- a/airbyte-webapp/src/components/StreamTestingPanel/ResultDisplay.module.scss +++ b/airbyte-webapp/src/components/StreamTestingPanel/ResultDisplay.module.scss @@ -2,15 +2,11 @@ @use "scss/variables"; .container { - // required to hide the connector logs splitter underneath the resizable panel overlay - z-index: 0; -} - -.resultContainer { display: flex; flex-direction: column; gap: variables.$spacing-lg; padding-bottom: variables.$spacing-md; + height: 100%; } .sliceSelector { @@ -27,7 +23,7 @@ .paginator { display: flex; - gap: variables.$spacing-sm; + gap: 2px; align-self: center; justify-self: flex-end; margin-top: auto; @@ -41,29 +37,3 @@ justify-content: center; font-size: 10px; } - -.logsContainer { - padding-top: variables.$spacing-sm; - display: flex; - flex-direction: column; -} - -.logsHeader { - background-color: colors.$grey-50; - display: flex; - align-items: center; - gap: variables.$spacing-md; - padding: variables.$spacing-sm variables.$spacing-md; -} - -.numLogsDisplay { - border-radius: variables.$border-radius-md; - background-color: colors.$blue; - padding: 1px variables.$spacing-sm; - color: colors.$white; -} - -.logsDisplay { - overflow-y: auto; - height: 100%; -} diff --git a/airbyte-webapp/src/components/StreamTestingPanel/ResultDisplay.tsx b/airbyte-webapp/src/components/StreamTestingPanel/ResultDisplay.tsx index ae98a8b68157dc..2dc041f5bde676 100644 --- a/airbyte-webapp/src/components/StreamTestingPanel/ResultDisplay.tsx +++ b/airbyte-webapp/src/components/StreamTestingPanel/ResultDisplay.tsx @@ -1,8 +1,6 @@ import classNames from "classnames"; -import { FormattedMessage } from "react-intl"; import { Paginator } from "components/ui/Paginator"; -import { ResizablePanels } from "components/ui/ResizablePanels"; import { Text } from "components/ui/Text"; import { StreamRead } from "core/request/ConnectorBuilderClient"; @@ -25,52 +23,22 @@ export const ResultDisplay: React.FC = ({ streamRead, classN const page = slice.pages[selectedPage]; return ( - - {streamRead.slices.length > 1 && ( - - )} - - {slice.pages.length > 1 && ( -
- Page: - -
- )} - - ), - minWidth: 120, - }} - secondPanel={{ - className: styles.logsContainer, - children: ( - <> -
- - - - - {streamRead.logs.length} - -
-
-
{JSON.stringify(streamRead.logs, null, 2)}
-
- - ), - minWidth: 30, - flex: 0, - }} - /> +
+ {streamRead.slices.length > 1 && ( + + )} + + {slice.pages.length > 1 && ( +
+ Page: + +
+ )} +
); }; diff --git a/airbyte-webapp/src/components/StreamTestingPanel/StreamTestingPanel.module.scss b/airbyte-webapp/src/components/StreamTestingPanel/StreamTestingPanel.module.scss index 153080705bd176..ab1f38796d737e 100644 --- a/airbyte-webapp/src/components/StreamTestingPanel/StreamTestingPanel.module.scss +++ b/airbyte-webapp/src/components/StreamTestingPanel/StreamTestingPanel.module.scss @@ -16,9 +16,12 @@ flex: 0 0 auto; } -.resultDisplay { +.resizablePanelsContainer { flex: 1; min-height: 0; + + // required to hide the connector logs splitter underneath the resizable panel overlay + z-index: 0; } .placeholder { diff --git a/airbyte-webapp/src/components/StreamTestingPanel/StreamTestingPanel.tsx b/airbyte-webapp/src/components/StreamTestingPanel/StreamTestingPanel.tsx index 2169d0cbdc92bf..fb70e6dce72386 100644 --- a/airbyte-webapp/src/components/StreamTestingPanel/StreamTestingPanel.tsx +++ b/airbyte-webapp/src/components/StreamTestingPanel/StreamTestingPanel.tsx @@ -1,8 +1,11 @@ import { useIntl } from "react-intl"; +import { ResizablePanels } from "components/ui/ResizablePanels"; + import { useReadStream } from "services/connectorBuilder/ConnectorBuilderApiService"; import { useConnectorBuilderState } from "services/connectorBuilder/ConnectorBuilderStateService"; +import { LogsDisplay } from "./LogsDisplay"; import { ResultDisplay } from "./ResultDisplay"; import { StreamSelector } from "./StreamSelector"; import styles from "./StreamTestingPanel.module.scss"; @@ -27,7 +30,21 @@ export const StreamTestingPanel: React.FC = () => { }} /> {streamReadData && streamReadData.slices.length !== 0 ? ( - + , + minWidth: 120, + }} + secondPanel={{ + className: styles.logsContainer, + children: , + minWidth: 30, + flex: 0, + }} + hideSecondPanel={streamReadData.logs.length === 0} + /> ) : (
{formatMessage({ id: "connectorBuilder.resultsPlaceholder" })}
)} From 5bf152fb626e51c636662b8af7284c1057f8a0c3 Mon Sep 17 00:00:00 2001 From: lmossman Date: Fri, 4 Nov 2022 15:23:55 -0700 Subject: [PATCH 10/15] simplify ResultDisplay prop --- .../components/StreamTestingPanel/ResultDisplay.tsx | 12 ++++++------ .../StreamTestingPanel/StreamTestingPanel.tsx | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/airbyte-webapp/src/components/StreamTestingPanel/ResultDisplay.tsx b/airbyte-webapp/src/components/StreamTestingPanel/ResultDisplay.tsx index 2dc041f5bde676..625ed8fb04936a 100644 --- a/airbyte-webapp/src/components/StreamTestingPanel/ResultDisplay.tsx +++ b/airbyte-webapp/src/components/StreamTestingPanel/ResultDisplay.tsx @@ -3,7 +3,7 @@ import classNames from "classnames"; import { Paginator } from "components/ui/Paginator"; import { Text } from "components/ui/Text"; -import { StreamRead } from "core/request/ConnectorBuilderClient"; +import { StreamReadSlicesItem } from "core/request/ConnectorBuilderClient"; import { useConnectorBuilderState } from "services/connectorBuilder/ConnectorBuilderStateService"; import { PageDisplay } from "./PageDisplay"; @@ -11,23 +11,23 @@ import styles from "./ResultDisplay.module.scss"; import { SliceSelector } from "./SliceSelector"; interface ResultDisplayProps { - streamRead: StreamRead; + slices: StreamReadSlicesItem[]; className?: string; } -export const ResultDisplay: React.FC = ({ streamRead, className }) => { +export const ResultDisplay: React.FC = ({ slices, className }) => { const { selectedSlice, selectedPage, setSelectedSlice, setSelectedPage } = useConnectorBuilderState(); - const slice = streamRead.slices[selectedSlice]; + const slice = slices[selectedSlice]; const numPages = slice.pages.length; const page = slice.pages[selectedPage]; return (
- {streamRead.slices.length > 1 && ( + {slices.length > 1 && ( diff --git a/airbyte-webapp/src/components/StreamTestingPanel/StreamTestingPanel.tsx b/airbyte-webapp/src/components/StreamTestingPanel/StreamTestingPanel.tsx index fb70e6dce72386..1335c30e053681 100644 --- a/airbyte-webapp/src/components/StreamTestingPanel/StreamTestingPanel.tsx +++ b/airbyte-webapp/src/components/StreamTestingPanel/StreamTestingPanel.tsx @@ -34,7 +34,7 @@ export const StreamTestingPanel: React.FC = () => { className={styles.resizablePanelsContainer} orientation="horizontal" firstPanel={{ - children: , + children: , minWidth: 120, }} secondPanel={{ From 96e8bf9d230b2738d51e288fd373b0051c327b57 Mon Sep 17 00:00:00 2001 From: lmossman Date: Fri, 4 Nov 2022 16:29:37 -0700 Subject: [PATCH 11/15] add yaml validation and error marking logic to yaml editor --- .../src/components/YamlEditor/YamlEditor.tsx | 60 +++++++++++++++++-- .../YamlEditor}/YamlTemplate.ts | 0 .../components/ui/CodeEditor/CodeEditor.tsx | 4 ++ .../ConnectorBuilderStateService.tsx | 29 ++------- 4 files changed, 65 insertions(+), 28 deletions(-) rename airbyte-webapp/src/{services/connectorBuilder => components/YamlEditor}/YamlTemplate.ts (100%) diff --git a/airbyte-webapp/src/components/YamlEditor/YamlEditor.tsx b/airbyte-webapp/src/components/YamlEditor/YamlEditor.tsx index 6aa53a83b9ce72..a8dc12f0de92d4 100644 --- a/airbyte-webapp/src/components/YamlEditor/YamlEditor.tsx +++ b/airbyte-webapp/src/components/YamlEditor/YamlEditor.tsx @@ -1,25 +1,77 @@ +import { useMonaco } from "@monaco-editor/react"; +import { load, YAMLException } from "js-yaml"; +import { editor } from "monaco-editor/esm/vs/editor/editor.api"; +import { useEffect, useRef, useState } from "react"; +import { useDebounce, useLocalStorage } from "react-use"; + import { CodeEditor } from "components/ui/CodeEditor"; +import { StreamsListRequestBodyManifest } from "core/request/ConnectorBuilderClient"; import { useConnectorBuilderState } from "services/connectorBuilder/ConnectorBuilderStateService"; import { DownloadYamlButton } from "./DownloadYamlButton"; import styles from "./YamlEditor.module.scss"; +import { template } from "./YamlTemplate"; export const YamlEditor: React.FC = () => { - const { yamlManifest, setYamlManifest } = useConnectorBuilderState(); + const yamlEditorRef = useRef(); + + const [locallyStoredYaml, setLocallyStoredYaml] = useLocalStorage("connectorBuilderYaml", template); + const [yamlValue, setYamlValue] = useState(locallyStoredYaml ?? template); + useDebounce(() => setLocallyStoredYaml(yamlValue), 500, [yamlValue]); + + const { setJsonManifest } = useConnectorBuilderState(); + + const monaco = useMonaco(); + + useEffect(() => { + if (monaco && yamlEditorRef.current && yamlValue) { + const errOwner = "yaml"; + console.log(yamlValue); + const yamlEditorModel = yamlEditorRef.current.getModel(); + + try { + const json = load(yamlValue) as StreamsListRequestBodyManifest; + setJsonManifest(json); + + // clear editor errors + if (yamlEditorModel) { + monaco.editor.setModelMarkers(yamlEditorModel, errOwner, []); + } + } catch (err) { + console.log(err.message); + if (err instanceof YAMLException) { + const mark = err.mark; + if (yamlEditorModel) { + monaco.editor.setModelMarkers(yamlEditorModel, errOwner, [ + { + startLineNumber: mark.line + 1, + startColumn: mark.column + 1, + endLineNumber: mark.line + 1, + endColumn: mark.column + 2, + message: err.message, + severity: monaco.MarkerSeverity.Error, + }, + ]); + } + } + } + } + }, [yamlValue, monaco, setJsonManifest]); return (
- +
setYamlManifest(value ?? "")} + onChange={(value) => setYamlValue(value ?? "")} lineNumberCharacterWidth={6} + onMount={(editor) => (yamlEditorRef.current = editor)} />
diff --git a/airbyte-webapp/src/services/connectorBuilder/YamlTemplate.ts b/airbyte-webapp/src/components/YamlEditor/YamlTemplate.ts similarity index 100% rename from airbyte-webapp/src/services/connectorBuilder/YamlTemplate.ts rename to airbyte-webapp/src/components/YamlEditor/YamlTemplate.ts diff --git a/airbyte-webapp/src/components/ui/CodeEditor/CodeEditor.tsx b/airbyte-webapp/src/components/ui/CodeEditor/CodeEditor.tsx index b5577f02466962..c1c8f1425dcee0 100644 --- a/airbyte-webapp/src/components/ui/CodeEditor/CodeEditor.tsx +++ b/airbyte-webapp/src/components/ui/CodeEditor/CodeEditor.tsx @@ -1,4 +1,5 @@ import Editor, { Monaco } from "@monaco-editor/react"; +import { editor } from "monaco-editor/esm/vs/editor/editor.api"; import React from "react"; import styles from "./CodeEditor.module.scss"; @@ -11,6 +12,7 @@ interface CodeEditorProps { onChange?: (value: string | undefined) => void; height?: string; lineNumberCharacterWidth?: number; + onMount?: (editor: editor.IStandaloneCodeEditor) => void; } export const CodeEditor: React.FC = ({ @@ -21,6 +23,7 @@ export const CodeEditor: React.FC = ({ onChange, height, lineNumberCharacterWidth, + onMount, }) => { const setAirbyteTheme = (monaco: Monaco) => { monaco.editor.defineTheme("airbyte", { @@ -44,6 +47,7 @@ export const CodeEditor: React.FC = ({ return ( void; + setJsonManifest: (jsonValue: StreamsListRequestBodyManifest) => void; setSelectedStream: (streamName: string) => void; setSelectedSlice: (sliceIndex: number) => void; setSelectedPage: (pageIndex: number) => void; @@ -29,28 +25,13 @@ interface Context { export const ConnectorBuilderStateContext = React.createContext(null); -const useYamlManifest = () => { - const [locallyStoredYaml, setLocallyStoredYaml] = useLocalStorage("connectorBuilderYaml", template); - const [yamlManifest, setYamlManifest] = useState(locallyStoredYaml ?? ""); - useDebounce(() => setLocallyStoredYaml(yamlManifest), 500, [yamlManifest]); - +const useJsonManifest = () => { const [jsonManifest, setJsonManifest] = useState({}); - useEffect(() => { - try { - const json = load(yamlManifest) as StreamsListRequestBodyManifest; - setJsonManifest(json); - } catch (err) { - if (err instanceof YAMLException) { - console.error(`Connector manifest yaml is not valid! Error: ${err}`); - } - } - }, [yamlManifest]); - - return { yamlManifest, jsonManifest, setYamlManifest }; + return { jsonManifest, setJsonManifest }; }; const useSelected = () => { - const { jsonManifest } = useYamlManifest(); + const { jsonManifest } = useJsonManifest(); const streamListRead = useListStreams({ manifest: jsonManifest }); const streams = streamListRead.streams; @@ -96,7 +77,7 @@ const useConfig = () => { }; export const ConnectorBuilderStateProvider: React.FC> = ({ children }) => { - const yamlManifest = useYamlManifest(); + const yamlManifest = useJsonManifest(); const selected = useSelected(); const config = useConfig(); From decf6d6957d9fbe19b5963ae56f56f92b51e6c61 Mon Sep 17 00:00:00 2001 From: lmossman Date: Fri, 4 Nov 2022 16:35:16 -0700 Subject: [PATCH 12/15] generify YamlEditor --- .../src/components/YamlEditor/YamlEditor.tsx | 16 +++++++++------- .../ConnectorBuilderPage.tsx | 4 ++-- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/airbyte-webapp/src/components/YamlEditor/YamlEditor.tsx b/airbyte-webapp/src/components/YamlEditor/YamlEditor.tsx index a8dc12f0de92d4..94bfc7a0249c40 100644 --- a/airbyte-webapp/src/components/YamlEditor/YamlEditor.tsx +++ b/airbyte-webapp/src/components/YamlEditor/YamlEditor.tsx @@ -7,21 +7,23 @@ import { useDebounce, useLocalStorage } from "react-use"; import { CodeEditor } from "components/ui/CodeEditor"; import { StreamsListRequestBodyManifest } from "core/request/ConnectorBuilderClient"; -import { useConnectorBuilderState } from "services/connectorBuilder/ConnectorBuilderStateService"; import { DownloadYamlButton } from "./DownloadYamlButton"; import styles from "./YamlEditor.module.scss"; import { template } from "./YamlTemplate"; -export const YamlEditor: React.FC = () => { +interface YamlEditorProps { + localStorageKey: string; + setJsonValue: (value: Record) => void; +} + +export const YamlEditor: React.FC = ({ localStorageKey, setJsonValue }) => { const yamlEditorRef = useRef(); - const [locallyStoredYaml, setLocallyStoredYaml] = useLocalStorage("connectorBuilderYaml", template); + const [locallyStoredYaml, setLocallyStoredYaml] = useLocalStorage(localStorageKey, template); const [yamlValue, setYamlValue] = useState(locallyStoredYaml ?? template); useDebounce(() => setLocallyStoredYaml(yamlValue), 500, [yamlValue]); - const { setJsonManifest } = useConnectorBuilderState(); - const monaco = useMonaco(); useEffect(() => { @@ -32,7 +34,7 @@ export const YamlEditor: React.FC = () => { try { const json = load(yamlValue) as StreamsListRequestBodyManifest; - setJsonManifest(json); + setJsonValue(json); // clear editor errors if (yamlEditorModel) { @@ -57,7 +59,7 @@ export const YamlEditor: React.FC = () => { } } } - }, [yamlValue, monaco, setJsonManifest]); + }, [yamlValue, monaco, setJsonValue]); return (
diff --git a/airbyte-webapp/src/pages/ConnectorBuilderPage/ConnectorBuilderPage.tsx b/airbyte-webapp/src/pages/ConnectorBuilderPage/ConnectorBuilderPage.tsx index e432d8c40f4f16..8c6a94bb92ab93 100644 --- a/airbyte-webapp/src/pages/ConnectorBuilderPage/ConnectorBuilderPage.tsx +++ b/airbyte-webapp/src/pages/ConnectorBuilderPage/ConnectorBuilderPage.tsx @@ -12,13 +12,13 @@ import { import styles from "./ConnectorBuilderPage.module.scss"; const ConnectorBuilderPageInner: React.FC = () => { - const { selectedStream } = useConnectorBuilderState(); + const { selectedStream, setJsonManifest } = useConnectorBuilderState(); return ( , + children: , className: styles.leftPanel, minWidth: 400, }} From f7ed7646fdfd005854ed381a391695428567d9b8 Mon Sep 17 00:00:00 2001 From: lmossman Date: Fri, 4 Nov 2022 16:38:42 -0700 Subject: [PATCH 13/15] Revert "generify YamlEditor" This reverts commit decf6d6957d9fbe19b5963ae56f56f92b51e6c61. --- .../src/components/YamlEditor/YamlEditor.tsx | 16 +++++++--------- .../ConnectorBuilderPage.tsx | 4 ++-- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/airbyte-webapp/src/components/YamlEditor/YamlEditor.tsx b/airbyte-webapp/src/components/YamlEditor/YamlEditor.tsx index 94bfc7a0249c40..a8dc12f0de92d4 100644 --- a/airbyte-webapp/src/components/YamlEditor/YamlEditor.tsx +++ b/airbyte-webapp/src/components/YamlEditor/YamlEditor.tsx @@ -7,23 +7,21 @@ import { useDebounce, useLocalStorage } from "react-use"; import { CodeEditor } from "components/ui/CodeEditor"; import { StreamsListRequestBodyManifest } from "core/request/ConnectorBuilderClient"; +import { useConnectorBuilderState } from "services/connectorBuilder/ConnectorBuilderStateService"; import { DownloadYamlButton } from "./DownloadYamlButton"; import styles from "./YamlEditor.module.scss"; import { template } from "./YamlTemplate"; -interface YamlEditorProps { - localStorageKey: string; - setJsonValue: (value: Record) => void; -} - -export const YamlEditor: React.FC = ({ localStorageKey, setJsonValue }) => { +export const YamlEditor: React.FC = () => { const yamlEditorRef = useRef(); - const [locallyStoredYaml, setLocallyStoredYaml] = useLocalStorage(localStorageKey, template); + const [locallyStoredYaml, setLocallyStoredYaml] = useLocalStorage("connectorBuilderYaml", template); const [yamlValue, setYamlValue] = useState(locallyStoredYaml ?? template); useDebounce(() => setLocallyStoredYaml(yamlValue), 500, [yamlValue]); + const { setJsonManifest } = useConnectorBuilderState(); + const monaco = useMonaco(); useEffect(() => { @@ -34,7 +32,7 @@ export const YamlEditor: React.FC = ({ localStorageKey, setJson try { const json = load(yamlValue) as StreamsListRequestBodyManifest; - setJsonValue(json); + setJsonManifest(json); // clear editor errors if (yamlEditorModel) { @@ -59,7 +57,7 @@ export const YamlEditor: React.FC = ({ localStorageKey, setJson } } } - }, [yamlValue, monaco, setJsonValue]); + }, [yamlValue, monaco, setJsonManifest]); return (
diff --git a/airbyte-webapp/src/pages/ConnectorBuilderPage/ConnectorBuilderPage.tsx b/airbyte-webapp/src/pages/ConnectorBuilderPage/ConnectorBuilderPage.tsx index 8c6a94bb92ab93..e432d8c40f4f16 100644 --- a/airbyte-webapp/src/pages/ConnectorBuilderPage/ConnectorBuilderPage.tsx +++ b/airbyte-webapp/src/pages/ConnectorBuilderPage/ConnectorBuilderPage.tsx @@ -12,13 +12,13 @@ import { import styles from "./ConnectorBuilderPage.module.scss"; const ConnectorBuilderPageInner: React.FC = () => { - const { selectedStream, setJsonManifest } = useConnectorBuilderState(); + const { selectedStream } = useConnectorBuilderState(); return ( , + children: , className: styles.leftPanel, minWidth: 400, }} From aeb3f92adb15b5d78396af68ff1adabc86d9e0af Mon Sep 17 00:00:00 2001 From: lmossman Date: Fri, 4 Nov 2022 17:24:39 -0700 Subject: [PATCH 14/15] disable download and test buttons while yaml is invalid --- .../TestControls.module.scss | 1 + .../StreamTestingPanel/TestControls.tsx | 50 +++++++++++++------ .../YamlEditor/DownloadYamlButton.tsx | 25 ++++++++-- .../src/components/YamlEditor/YamlEditor.tsx | 8 +-- airbyte-webapp/src/locales/en.json | 4 +- .../ConnectorBuilderStateService.tsx | 6 ++- 6 files changed, 69 insertions(+), 25 deletions(-) diff --git a/airbyte-webapp/src/components/StreamTestingPanel/TestControls.module.scss b/airbyte-webapp/src/components/StreamTestingPanel/TestControls.module.scss index c25cd6bcc2c348..f177f59f32a636 100644 --- a/airbyte-webapp/src/components/StreamTestingPanel/TestControls.module.scss +++ b/airbyte-webapp/src/components/StreamTestingPanel/TestControls.module.scss @@ -28,6 +28,7 @@ $testIconHeight: 17px; .testButton { margin-right: 0; flex: 0 0 100px; + width: 100px; } .testButtonText { diff --git a/airbyte-webapp/src/components/StreamTestingPanel/TestControls.tsx b/airbyte-webapp/src/components/StreamTestingPanel/TestControls.tsx index a9eae2c29c4e45..3beed42a6f35c3 100644 --- a/airbyte-webapp/src/components/StreamTestingPanel/TestControls.tsx +++ b/airbyte-webapp/src/components/StreamTestingPanel/TestControls.tsx @@ -1,9 +1,12 @@ +import { faWarning } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import classNames from "classnames"; import { FormattedMessage } from "react-intl"; import { RotateIcon } from "components/icons/RotateIcon"; import { Button } from "components/ui/Button"; import { Text } from "components/ui/Text"; +import { Tooltip } from "components/ui/Tooltip"; import { useConnectorBuilderState } from "services/connectorBuilder/ConnectorBuilderStateService"; @@ -16,28 +19,43 @@ interface TestControlsProps { } export const TestControls: React.FC = ({ onClickTest, className }) => { - const { selectedStream } = useConnectorBuilderState(); + const { selectedStream, yamlIsValid } = useConnectorBuilderState(); + + const testButton = ( +
+ ) : ( + + ) + } + > + + + + + ); return (
- {selectedStream.url} + {selectedStream.url}}>{selectedStream.url}
-
- } - > - - - - + {yamlIsValid ? ( + testButton + ) : ( + + + + )}
); }; diff --git a/airbyte-webapp/src/components/YamlEditor/DownloadYamlButton.tsx b/airbyte-webapp/src/components/YamlEditor/DownloadYamlButton.tsx index 1f8c92d010d39a..f5a49a5fc99eab 100644 --- a/airbyte-webapp/src/components/YamlEditor/DownloadYamlButton.tsx +++ b/airbyte-webapp/src/components/YamlEditor/DownloadYamlButton.tsx @@ -1,24 +1,41 @@ +import { faDownload, faWarning } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FormattedMessage } from "react-intl"; import { Button } from "components/ui/Button"; +import { Tooltip } from "components/ui/Tooltip"; import { downloadFile } from "utils/file"; interface DownloadYamlButtonProps { - yaml: string; className?: string; + yaml: string; + yamlIsValid: boolean; } -export const DownloadYamlButton: React.FC = ({ yaml, className }) => { +export const DownloadYamlButton: React.FC = ({ className, yaml, yamlIsValid }) => { const downloadYaml = () => { const file = new Blob([yaml], { type: "text/plain;charset=utf-8" }); // TODO: pull name from connector name input or generate from yaml contents downloadFile(file, "connector_builder.yaml"); }; - return ( - ); + + return yamlIsValid ? ( + downloadButton + ) : ( + + + + ); }; diff --git a/airbyte-webapp/src/components/YamlEditor/YamlEditor.tsx b/airbyte-webapp/src/components/YamlEditor/YamlEditor.tsx index a8dc12f0de92d4..b9a9851aab49d1 100644 --- a/airbyte-webapp/src/components/YamlEditor/YamlEditor.tsx +++ b/airbyte-webapp/src/components/YamlEditor/YamlEditor.tsx @@ -20,7 +20,7 @@ export const YamlEditor: React.FC = () => { const [yamlValue, setYamlValue] = useState(locallyStoredYaml ?? template); useDebounce(() => setLocallyStoredYaml(yamlValue), 500, [yamlValue]); - const { setJsonManifest } = useConnectorBuilderState(); + const { yamlIsValid, setYamlIsValid, setJsonManifest } = useConnectorBuilderState(); const monaco = useMonaco(); @@ -33,6 +33,7 @@ export const YamlEditor: React.FC = () => { try { const json = load(yamlValue) as StreamsListRequestBodyManifest; setJsonManifest(json); + setYamlIsValid(true); // clear editor errors if (yamlEditorModel) { @@ -41,6 +42,7 @@ export const YamlEditor: React.FC = () => { } catch (err) { console.log(err.message); if (err instanceof YAMLException) { + setYamlIsValid(false); const mark = err.mark; if (yamlEditorModel) { monaco.editor.setModelMarkers(yamlEditorModel, errOwner, [ @@ -57,12 +59,12 @@ export const YamlEditor: React.FC = () => { } } } - }, [yamlValue, monaco, setJsonManifest]); + }, [yamlValue, monaco, setJsonManifest, setYamlIsValid]); return (
- +
void; + setYamlIsValid: (value: boolean) => void; setSelectedStream: (streamName: string) => void; setSelectedSlice: (sliceIndex: number) => void; setSelectedPage: (pageIndex: number) => void; @@ -27,7 +29,9 @@ export const ConnectorBuilderStateContext = React.createContext( const useJsonManifest = () => { const [jsonManifest, setJsonManifest] = useState({}); - return { jsonManifest, setJsonManifest }; + const [yamlIsValid, setYamlIsValid] = useState(true); + + return { jsonManifest, yamlIsValid, setJsonManifest, setYamlIsValid }; }; const useSelected = () => { From e6db9385bdb0a2dd440f3e4a57b3f1940ebac0f0 Mon Sep 17 00:00:00 2001 From: lmossman Date: Mon, 7 Nov 2022 14:26:46 -0800 Subject: [PATCH 15/15] remove console log and add comments --- airbyte-webapp/src/components/YamlEditor/YamlEditor.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/airbyte-webapp/src/components/YamlEditor/YamlEditor.tsx b/airbyte-webapp/src/components/YamlEditor/YamlEditor.tsx index b9a9851aab49d1..fa1e01cc82c121 100644 --- a/airbyte-webapp/src/components/YamlEditor/YamlEditor.tsx +++ b/airbyte-webapp/src/components/YamlEditor/YamlEditor.tsx @@ -27,7 +27,6 @@ export const YamlEditor: React.FC = () => { useEffect(() => { if (monaco && yamlEditorRef.current && yamlValue) { const errOwner = "yaml"; - console.log(yamlValue); const yamlEditorModel = yamlEditorRef.current.getModel(); try { @@ -35,15 +34,16 @@ export const YamlEditor: React.FC = () => { setJsonManifest(json); setYamlIsValid(true); - // clear editor errors + // clear editor error markers if (yamlEditorModel) { monaco.editor.setModelMarkers(yamlEditorModel, errOwner, []); } } catch (err) { - console.log(err.message); if (err instanceof YAMLException) { setYamlIsValid(false); const mark = err.mark; + + // set editor error markers if (yamlEditorModel) { monaco.editor.setModelMarkers(yamlEditorModel, errOwner, [ {