diff --git a/airbyte-webapp/src/components/YamlEditor/DownloadYamlButton.tsx b/airbyte-webapp/src/components/YamlEditor/DownloadYamlButton.tsx new file mode 100644 index 000000000000..3527bfb3561f --- /dev/null +++ b/airbyte-webapp/src/components/YamlEditor/DownloadYamlButton.tsx @@ -0,0 +1,26 @@ +import { useIntl } from "react-intl"; + +import { Button } from "components/ui/Button"; + +import { downloadFile } from "utils/file"; + +interface DownloadYamlButtonProps { + yaml: string; + className?: string; +} + +export const DownloadYamlButton: React.FC = ({ yaml, className }) => { + const { formatMessage } = useIntl(); + + 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 ( + + ); +}; diff --git a/airbyte-webapp/src/components/YamlEditor/YamlEditor.module.scss b/airbyte-webapp/src/components/YamlEditor/YamlEditor.module.scss new file mode 100644 index 000000000000..6552c514205a --- /dev/null +++ b/airbyte-webapp/src/components/YamlEditor/YamlEditor.module.scss @@ -0,0 +1,34 @@ +@use "scss/colors"; + +.container { + display: flex; + flex-flow: column; + height: 100%; +} + +.control { + flex: 0 0 auto; + background-color: colors.$dark-blue; + display: flex; + padding: 10px; +} + +.editorContainer { + flex: 1 1 0; + background-image: linear-gradient(colors.$dark-blue-900, colors.$dark-blue-1000); +} + +.downloadButton { + margin-left: auto; +} + +// Export colors to be used in monaco editor +:export { + // Monaco editor requires 6-character hex values for theme colors + /* stylelint-disable-next-line color-hex-length, color-no-hex */ + tokenString: colors.$white; + tokenType: colors.$blue-300; + tokenNumber: colors.$orange-300; + tokenDelimiter: colors.$yellow-300; + tokenKeyword: colors.$green-300; +} diff --git a/airbyte-webapp/src/components/YamlEditor/YamlEditor.tsx b/airbyte-webapp/src/components/YamlEditor/YamlEditor.tsx new file mode 100644 index 000000000000..8129d65dc3c4 --- /dev/null +++ b/airbyte-webapp/src/components/YamlEditor/YamlEditor.tsx @@ -0,0 +1,67 @@ +import Editor, { Monaco } from "@monaco-editor/react"; +import { useState } from "react"; +import { useDebounce, useLocalStorage } from "react-use"; + +import { DownloadYamlButton } from "./DownloadYamlButton"; +import styles from "./YamlEditor.module.scss"; +import { template } from "./YamlTemplate"; + +export const YamlEditor: React.FC = () => { + const [locallyStoredEditorValue, setLocallyStoredEditorValue] = useLocalStorage( + "connectorBuilderEditorContent", + template + ); + const [editorValue, setEditorValue] = useState(locallyStoredEditorValue ?? ""); + useDebounce(() => setLocallyStoredEditorValue(editorValue), 500, [editorValue]); + + const handleEditorChange = (value: string | undefined) => { + setEditorValue(value ?? ""); + }; + + const setEditorTheme = (monaco: Monaco) => { + monaco.editor.defineTheme("airbyte", { + base: "vs-dark", + inherit: true, + rules: [ + { token: "string", foreground: styles.tokenString }, + { token: "type", foreground: styles.tokenType }, + { token: "number", foreground: styles.tokenNumber }, + { token: "delimiter", foreground: styles.tokenDelimiter }, + { token: "keyword", foreground: styles.tokenKeyword }, + ], + colors: { + "editor.background": "#00000000", // transparent, so that parent background is shown instead + }, + }); + + monaco.editor.setTheme("airbyte"); + }; + + return ( +
+
+ +
+
+ +
+
+ ); +}; diff --git a/airbyte-webapp/src/components/YamlEditor/YamlTemplate.ts b/airbyte-webapp/src/components/YamlEditor/YamlTemplate.ts new file mode 100644 index 000000000000..cb0080225971 --- /dev/null +++ b/airbyte-webapp/src/components/YamlEditor/YamlTemplate.ts @@ -0,0 +1,46 @@ +// TODO: replace with API call to get starting contents +export const template = `version: "0.1.0" + +definitions: + schema_loader: + type: JsonSchema + file_path: "./source/schemas/{{ options['name'] }}.json" + selector: + type: RecordSelector + extractor: + type: DpathExtractor + field_pointer: [] + requester: + type: HttpRequester + name: "{{ options['name'] }}" + http_method: "GET" + authenticator: + type: BearerAuthenticator + api_token: "{{ config['api_key'] }}" + retriever: + type: SimpleRetriever + $options: + url_base: TODO "your_api_base_url" + name: "{{ options['name'] }}" + primary_key: "{{ options['primary_key'] }}" + record_selector: + $ref: "*ref(definitions.selector)" + paginator: + type: NoPagination + +streams: + - type: DeclarativeStream + $options: + name: "customers" + primary_key: "id" + schema_loader: + $ref: "*ref(definitions.schema_loader)" + retriever: + $ref: "*ref(definitions.retriever)" + requester: + $ref: "*ref(definitions.requester)" + path: TODO "your_endpoint_path" +check: + type: CheckStream + stream_names: ["customers"] +`; diff --git a/airbyte-webapp/src/components/YamlEditor/index.tsx b/airbyte-webapp/src/components/YamlEditor/index.tsx new file mode 100644 index 000000000000..697628857580 --- /dev/null +++ b/airbyte-webapp/src/components/YamlEditor/index.tsx @@ -0,0 +1 @@ +export * from "./YamlEditor"; diff --git a/airbyte-webapp/src/locales/en.json b/airbyte-webapp/src/locales/en.json index 0de1529c47d1..f9883139c251 100644 --- a/airbyte-webapp/src/locales/en.json +++ b/airbyte-webapp/src/locales/en.json @@ -564,5 +564,7 @@ "airbyte.datatype.untyped": "Untyped", "airbyte.datatype.union": "Union", "airbyte.datatype.unknown": "Unknown", - "airbyte.datatype.boolean": "Boolean" + "airbyte.datatype.boolean": "Boolean", + + "builder.downloadYaml": "Download YAML" } diff --git a/airbyte-webapp/src/pages/connector-builder/ConnectorBuilderPage.tsx b/airbyte-webapp/src/pages/connector-builder/ConnectorBuilderPage.tsx new file mode 100644 index 000000000000..47728f1ea8cf --- /dev/null +++ b/airbyte-webapp/src/pages/connector-builder/ConnectorBuilderPage.tsx @@ -0,0 +1,5 @@ +import { YamlEditor } from "components/YamlEditor"; + +export const ConnectorBuilderPage: React.FC = () => { + return ; +}; diff --git a/airbyte-webapp/src/pages/routePaths.tsx b/airbyte-webapp/src/pages/routePaths.tsx index 76764a8c9428..5c0b0b1db20b 100644 --- a/airbyte-webapp/src/pages/routePaths.tsx +++ b/airbyte-webapp/src/pages/routePaths.tsx @@ -14,4 +14,6 @@ export enum RoutePaths { ConnectionNew = "new-connection", SourceNew = "new-source", DestinationNew = "new-destination", + + ConnectorBuilder = "connector-builder", } diff --git a/airbyte-webapp/src/pages/routes.tsx b/airbyte-webapp/src/pages/routes.tsx index bb3b9205ed84..1010703eba2c 100644 --- a/airbyte-webapp/src/pages/routes.tsx +++ b/airbyte-webapp/src/pages/routes.tsx @@ -15,6 +15,7 @@ import MainView from "views/layout/MainView"; import { WorkspaceRead } from "../core/request/AirbyteClient"; import ConnectionPage from "./ConnectionPage"; +import { ConnectorBuilderPage } from "./connector-builder/ConnectorBuilderPage"; import DestinationPage from "./DestinationPage"; import OnboardingPage from "./OnboardingPage"; import PreferencesPage from "./PreferencesPage"; @@ -43,6 +44,7 @@ const MainViewRoutes: React.FC<{ workspace: WorkspaceRead }> = ({ workspace }) = } /> } /> } /> + } /> {workspace.displaySetupWizard ? ( } /> ) : null} diff --git a/airbyte-webapp/src/scss/_colors.scss b/airbyte-webapp/src/scss/_colors.scss index 3521285415b8..07941b491ea8 100644 --- a/airbyte-webapp/src/scss/_colors.scss +++ b/airbyte-webapp/src/scss/_colors.scss @@ -1,4 +1,4 @@ -/* stylelint-disable color-no-hex */ +/* stylelint-disable color-no-hex, color-hex-length */ $blue-50: #eae9ff; $blue-100: #cbc8ff; $blue-200: #a6a4ff; @@ -22,6 +22,7 @@ $dark-blue-600: #353b7b; $dark-blue-700: #2d3270; $dark-blue-800: #262963; $dark-blue-900: #1a194d; +$dark-blue-1000: #0a0a23; $dark-blue: $dark-blue-900; $grey-30: #fcfcfd; @@ -77,8 +78,8 @@ $beige-50: #fef9f4; $beige-100: #ffebd7; $beige: $beige-50; -$black: #000; -$white: #fff; +$black: #000000; +$white: #ffffff; $yellow-50: #fdf8e1; $yellow-100: #fbecb3;