Skip to content

Commit

Permalink
🪟 🎉 Multi-cloud: edit connection & workspace data geographies in the …
Browse files Browse the repository at this point in the history
…UI (#18611)

* add geographies service and basic dropdown

* add feature for changing data geography

* add default data residency to workspace settings

* add data residency card to connection creation

* add data residency editing to connection settings tab
  • Loading branch information
josephkmh authored Nov 4, 2022
1 parent 8145be5 commit 4352686
Show file tree
Hide file tree
Showing 36 changed files with 486 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {

import styles from "./CreateConnectionForm.module.scss";
import { CreateConnectionNameField } from "./CreateConnectionNameField";
import { DataResidency } from "./DataResidency";
import { SchemaError } from "./SchemaError";

interface CreateConnectionProps {
Expand All @@ -39,7 +40,7 @@ interface CreateConnectionPropsInner extends Pick<CreateConnectionProps, "afterS

const CreateConnectionFormInner: React.FC<CreateConnectionPropsInner> = ({ schemaError, afterSubmitConnection }) => {
const navigate = useNavigate();

const canEditDataGeographies = useFeature(FeatureItem.AllowChangeDataGeographies);
const { mutateAsync: createConnection } = useCreateConnection();

const { clearFormChange } = useFormChangeTrackerService();
Expand Down Expand Up @@ -113,6 +114,7 @@ const CreateConnectionFormInner: React.FC<CreateConnectionPropsInner> = ({ schem
{({ values, isSubmitting, isValid, dirty }) => (
<Form>
<CreateConnectionNameField />
{canEditDataGeographies && <DataResidency />}
<ConnectionFormFields values={values} isSubmitting={isSubmitting} dirty={dirty} />
<OperationsSection
onStartEditTransformation={() => setEditingTransformation(true)}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
@use "scss/variables";

.flexRow {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: flex-start;
gap: variables.$spacing-md;
}

.leftFieldCol {
flex: 1;
max-width: 640px;
padding-right: 30px;
}

.rightFieldCol {
flex: 1;
max-width: 300px;
}
64 changes: 64 additions & 0 deletions airbyte-webapp/src/components/CreateConnection/DataResidency.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { Field, FieldProps, useFormikContext } from "formik";
import { FormattedMessage, useIntl } from "react-intl";

import { ControlLabels } from "components/LabeledControl";
import { DropDown } from "components/ui/DropDown";

import { useAvailableGeographies } from "packages/cloud/services/geographies/GeographiesService";
import { links } from "utils/links";
import { Section } from "views/Connection/ConnectionForm/components/Section";

import styles from "./DataResidency.module.scss";

interface DataResidencyProps {
name?: string;
}

export const DataResidency: React.FC<DataResidencyProps> = ({ name = "geography" }) => {
const { formatMessage } = useIntl();
const { setFieldValue } = useFormikContext();
const { geographies } = useAvailableGeographies();

return (
<Section title={formatMessage({ id: "connection.geographyTitle" })}>
<Field name={name}>
{({ field, form }: FieldProps<string>) => (
<div className={styles.flexRow}>
<div className={styles.leftFieldCol}>
<ControlLabels
nextLine
label={<FormattedMessage id="connection.geographyTitle" />}
message={
<FormattedMessage
id="connection.geographyDescription"
values={{
lnk: (node: React.ReactNode) => (
<a href={links.cloudAllowlistIPsLink} target="_blank" rel="noreferrer">
{node}
</a>
),
}}
/>
}
/>
</div>
<div className={styles.rightFieldCol}>
<DropDown
isDisabled={form.isSubmitting}
options={geographies.map((geography) => ({
label: formatMessage({
id: `connection.geography.${geography}`,
defaultMessage: geography.toUpperCase(),
}),
value: geography,
}))}
value={field.value}
onChange={(geography) => setFieldValue(name, geography.value)}
/>
</div>
</div>
)}
</Field>
</Section>
);
};
20 changes: 10 additions & 10 deletions airbyte-webapp/src/components/Label/Label.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,18 @@ interface IProps {
nextLine?: boolean;
success?: boolean;
message?: string | React.ReactNode;
additionLength?: number;
className?: string;
onClick?: (data: unknown) => void;
htmlFor?: string;
}

const Content = styled.label<{ additionLength?: number | string }>`
const Content = styled.label`
display: block;
font-weight: 500;
font-size: 14px;
line-height: 17px;
color: ${({ theme }) => theme.textColor};
padding-bottom: 5px;
width: calc(100% + ${({ additionLength }) => (additionLength === 0 || additionLength ? additionLength : 30)}px);
& a {
text-decoration: underline;
Expand All @@ -31,16 +29,18 @@ const MessageText = styled.span<Pick<IProps, "error" | "success">>`
white-space: break-spaces;
color: ${(props) =>
props.error ? props.theme.dangerColor : props.success ? props.theme.successColor : props.theme.greyColor40};
font-size: 13px;
font-size: 12px;
font-weight: 400;
a:link,
a:hover,
a:visited {
color: ${(props) => props.theme.greyColor40};
}
`;

const Label: React.FC<React.PropsWithChildren<IProps>> = (props) => (
<Content
additionLength={props.additionLength}
className={props.className}
onClick={props.onClick}
htmlFor={props.htmlFor}
>
<Content className={props.className} onClick={props.onClick} htmlFor={props.htmlFor}>
{props.children}
{props.message && (
<span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ export interface ControlLabelsProps {
success?: boolean;
nextLine?: boolean;
message?: React.ReactNode;
labelAdditionLength?: number;
label?: React.ReactNode;
infoTooltipContent?: React.ReactNode;
optional?: boolean;
Expand All @@ -27,7 +26,6 @@ const ControlLabels: React.FC<React.PropsWithChildren<ControlLabelsProps>> = (pr
error={props.error}
success={props.success}
message={props.message}
additionLength={props.labelAdditionLength}
nextLine={props.nextLine}
htmlFor={props.htmlFor}
>
Expand Down
21 changes: 3 additions & 18 deletions airbyte-webapp/src/components/LabeledInput/LabeledInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,11 @@ import React from "react";
import { ControlLabels, ControlLabelsProps } from "components/LabeledControl";
import { Input, InputProps } from "components/ui/Input";

type LabeledInputProps = Pick<ControlLabelsProps, "success" | "message" | "label" | "labelAdditionLength"> &
type LabeledInputProps = Pick<ControlLabelsProps, "success" | "message" | "label"> &
InputProps & { className?: string };

const LabeledInput: React.FC<LabeledInputProps> = ({
error,
success,
message,
label,
labelAdditionLength,
className,
...inputProps
}) => (
<ControlLabels
error={error}
success={success}
message={message}
label={label}
className={className}
labelAdditionLength={labelAdditionLength}
>
const LabeledInput: React.FC<LabeledInputProps> = ({ error, success, message, label, className, ...inputProps }) => (
<ControlLabels error={error} success={success} message={message} label={label} className={className}>
<Input {...inputProps} error={error} />
</ControlLabels>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
.wrapper {
display: flex;
align-items: center;
justify-content: space-between;
}

.dropdownWrapper {
display: flex;
flex: 1 0 310px;
}

.spinner {
width: 50px;
display: flex;
justify-content: center;
align-items: center;
}

.dropdown {
flex-grow: 1;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import React, { useState } from "react";
import { FormattedMessage, useIntl } from "react-intl";

import { ControlLabels } from "components/LabeledControl";
import { Card } from "components/ui/Card";
import { DropDown } from "components/ui/DropDown";
import { Spinner } from "components/ui/Spinner";

import { Geography } from "core/request/AirbyteClient";
import { useConnectionEditService } from "hooks/services/ConnectionEdit/ConnectionEditService";
import { useNotificationService } from "hooks/services/Notification";
import { useAvailableGeographies } from "packages/cloud/services/geographies/GeographiesService";
import { links } from "utils/links";

import styles from "./UpdateConnectionDataResidency.module.scss";

export const UpdateConnectionDataResidency: React.FC = () => {
const { connection, updateConnection, connectionUpdating } = useConnectionEditService();
const { registerNotification } = useNotificationService();
const { formatMessage } = useIntl();
const [selectedValue, setSelectedValue] = useState<Geography>();

const { geographies } = useAvailableGeographies();

const handleSubmit = async ({ value }: { value: Geography }) => {
try {
setSelectedValue(value);
await updateConnection({
connectionId: connection.connectionId,
geography: value,
});
} catch (e) {
registerNotification({
id: "connection.geographyUpdateError",
title: formatMessage({ id: "connection.geographyUpdateError" }),
isError: true,
});
}
setSelectedValue(undefined);
};

return (
<Card withPadding>
<div className={styles.wrapper}>
<div>
<ControlLabels
nextLine
label={<FormattedMessage id="connection.geographyTitle" />}
message={
<FormattedMessage
id="connection.geographyDescription"
values={{
lnk: (node: React.ReactNode) => (
<a href={links.cloudAllowlistIPsLink} target="_blank" rel="noreferrer">
{node}
</a>
),
}}
/>
}
/>
</div>
<div className={styles.dropdownWrapper}>
<div className={styles.spinner}>{connectionUpdating && <Spinner small />}</div>
<div className={styles.dropdown}>
<DropDown
isDisabled={connectionUpdating}
options={geographies.map((geography) => ({
label: formatMessage({
id: `connection.geography.${geography}`,
defaultMessage: geography.toUpperCase(),
}),
value: geography,
}))}
value={selectedValue || connection.geography}
onChange={handleSubmit}
/>
</div>
</div>
</div>
</Card>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { UpdateConnectionDataResidency } from "./UpdateConnectionDataResidency";
8 changes: 4 additions & 4 deletions airbyte-webapp/src/components/ui/Card/Card.module.scss
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
@use "../../../scss/colors";
@use "../../../scss/variables" as vars;
@use "scss/colors";
@use "scss/variables";

.container {
width: auto;
Expand All @@ -17,9 +17,9 @@
}

.title {
padding: 25px 25px 22px;
padding: variables.$spacing-xl;
color: colors.$dark-blue;
border-bottom: colors.$grey-100 vars.$border-thin solid;
border-bottom: colors.$grey-100 variables.$border-thin solid;
font-weight: 600;
letter-spacing: 0.008em;
border-top-left-radius: 10px;
Expand Down
2 changes: 1 addition & 1 deletion airbyte-webapp/src/components/ui/SideMenu/SideMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ interface SideMenuProps {
}

const Content = styled.nav`
min-width: 147px;
min-width: 155px;
`;

const Category = styled.div`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export enum PageTrackingCodes {
SETTINGS_NOTIFICATION = "Settings.Notifications",
SETTINGS_ACCESS_MANAGEMENT = "Settings.AccessManagement",
SETTINGS_METRICS = "Settings.Metrics",
SETTINGS_DATA_RESIDENCY = "Settings.DataResidency",
CREDITS = "Credits",
WORKSPACES = "Workspaces",
PREFERENCES = "Preferences",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { act, renderHook } from "@testing-library/react-hooks";
import React from "react";
import mockConnection from "test-utils/mock-data/mockConnection.json";
import mockDest from "test-utils/mock-data/mockDestinationDefinition.json";
import mockWorkspace from "test-utils/mock-data/mockWorkspace.json";
import { TestWrapper } from "test-utils/testutils";

import { WebBackendConnectionUpdate } from "core/request/AirbyteClient";
Expand All @@ -13,6 +14,10 @@ jest.mock("services/connector/DestinationDefinitionSpecificationService", () =>
useGetDestinationDefinitionSpecification: () => mockDest,
}));

jest.mock("services/workspaces/WorkspacesService", () => ({
useCurrentWorkspace: () => mockWorkspace,
}));

jest.mock("../useConnectionHook", () => ({
useGetConnection: () => mockConnection,
useWebConnectionService: () => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { act, renderHook } from "@testing-library/react-hooks";
import React from "react";
import mockConnection from "test-utils/mock-data/mockConnection.json";
import mockDest from "test-utils/mock-data/mockDestinationDefinition.json";
import mockWorkspace from "test-utils/mock-data/mockWorkspace.json";
import { TestWrapper } from "test-utils/testutils";

import { AirbyteCatalog, WebBackendConnectionRead } from "core/request/AirbyteClient";
Expand All @@ -17,6 +18,10 @@ jest.mock("services/connector/DestinationDefinitionSpecificationService", () =>
useGetDestinationDefinitionSpecification: () => mockDest,
}));

jest.mock("services/workspaces/WorkspacesService", () => ({
useCurrentWorkspace: () => mockWorkspace,
}));

describe("ConnectionFormService", () => {
const Wrapper: React.FC<Parameters<typeof ConnectionFormServiceProvider>[0]> = ({ children, ...props }) => (
<TestWrapper>
Expand Down
1 change: 1 addition & 0 deletions airbyte-webapp/src/hooks/services/Feature/types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export enum FeatureItem {
AllowUpdateConnectors = "ALLOW_UPDATE_CONNECTORS",
AllowOAuthConnector = "ALLOW_OAUTH_CONNECTOR",
AllowSync = "ALLOW_SYNC",
AllowChangeDataGeographies = "ALLOW_CHANGE_DATA_GEOGRAPHIES",
AllowSyncSubOneHourCronExpressions = "ALLOW_SYNC_SUB_ONE_HOUR_CRON_EXPRESSIONS",
}

Expand Down
Loading

0 comments on commit 4352686

Please sign in to comment.