-
Notifications
You must be signed in to change notification settings - Fork 276
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
🪟 🎉 New connection header CTAs (#11981)
Co-authored-by: Chandler Prall <[email protected]>
- Loading branch information
1 parent
7a50d2f
commit da36da8
Showing
31 changed files
with
729 additions
and
45 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
3 changes: 3 additions & 0 deletions
3
...p/src/components/connection/ConnectionHeaderControls/ConnectionHeaderControls.module.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
.switch { | ||
width: 90px; | ||
} |
107 changes: 107 additions & 0 deletions
107
...te-webapp/src/components/connection/ConnectionHeaderControls/ConnectionHeaderControls.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
import React from "react"; | ||
import { FormattedMessage } from "react-intl"; | ||
import { useNavigate } from "react-router-dom"; | ||
|
||
import { Box } from "components/ui/Box"; | ||
import { Button } from "components/ui/Button"; | ||
import { FlexContainer } from "components/ui/Flex"; | ||
import { SwitchNext } from "components/ui/SwitchNext"; | ||
import { Text } from "components/ui/Text"; | ||
import { Tooltip } from "components/ui/Tooltip"; | ||
|
||
import { ConnectionStatus } from "core/api/types/AirbyteClient"; | ||
import { useSchemaChanges } from "hooks/connection/useSchemaChanges"; | ||
import { useConnectionEditService } from "hooks/services/ConnectionEdit/ConnectionEditService"; | ||
import { useConnectionFormService } from "hooks/services/ConnectionForm/ConnectionFormService"; | ||
import { ConnectionRoutePaths } from "pages/routePaths"; | ||
|
||
import styles from "./ConnectionHeaderControls.module.scss"; | ||
import { FormattedScheduleDataMessage } from "./FormattedScheduleDataMessage"; | ||
import { useConnectionStatus } from "../ConnectionStatus/useConnectionStatus"; | ||
import { useConnectionSyncContext } from "../ConnectionSync/ConnectionSyncContext"; | ||
import { FreeHistoricalSyncIndicator } from "../EnabledControl/FreeHistoricalSyncIndicator"; | ||
|
||
export const ConnectionHeaderControls: React.FC = () => { | ||
const { mode } = useConnectionFormService(); | ||
const { connection, updateConnectionStatus, connectionUpdating } = useConnectionEditService(); | ||
const { hasBreakingSchemaChange } = useSchemaChanges(connection.schemaChange); | ||
const navigate = useNavigate(); | ||
|
||
const connectionStatus = useConnectionStatus(connection.connectionId ?? ""); | ||
const isReadOnly = mode === "readonly"; | ||
|
||
const { syncStarting, cancelStarting, cancelJob, syncConnection, connectionEnabled, resetStarting, jobResetRunning } = | ||
useConnectionSyncContext(); | ||
|
||
const onScheduleBtnClick = () => { | ||
navigate(`${ConnectionRoutePaths.Settings}`, { | ||
state: { action: "scheduleType" }, | ||
}); | ||
}; | ||
|
||
const onChangeStatus = async (checked: boolean) => | ||
await updateConnectionStatus(checked ? ConnectionStatus.active : ConnectionStatus.inactive); | ||
|
||
const isDisabled = isReadOnly || syncStarting || cancelStarting || resetStarting; | ||
const isStartSyncBtnDisabled = isDisabled || !connectionEnabled; | ||
const isCancelBtnDisabled = isDisabled || connectionUpdating; | ||
const isSwitchDisabled = isDisabled || hasBreakingSchemaChange; | ||
|
||
return ( | ||
<FlexContainer alignItems="center" gap="none"> | ||
<FreeHistoricalSyncIndicator /> | ||
<Tooltip | ||
control={ | ||
<Button icon="clockOutline" variant="clear" onClick={onScheduleBtnClick}> | ||
<FormattedScheduleDataMessage | ||
scheduleType={connection.scheduleType} | ||
scheduleData={connection.scheduleData} | ||
/> | ||
</Button> | ||
} | ||
placement="top" | ||
> | ||
<FormattedMessage id="connection.header.frequency.tooltip" /> | ||
</Tooltip> | ||
{!connectionStatus.isRunning && ( | ||
<Button | ||
onClick={syncConnection} | ||
variant="clear" | ||
data-testid="manual-sync-button" | ||
disabled={isStartSyncBtnDisabled} | ||
icon={syncStarting ? "loading" : "sync"} | ||
iconSize="sm" | ||
iconColor="primary" | ||
> | ||
<Text size="md" color="blue" bold> | ||
<FormattedMessage id="connection.startSync" /> | ||
</Text> | ||
</Button> | ||
)} | ||
{connectionStatus.isRunning && cancelJob && ( | ||
<Button | ||
onClick={cancelJob} | ||
disabled={isCancelBtnDisabled} | ||
variant="clear" | ||
icon={cancelStarting ? "loading" : "cross"} | ||
iconColor="error" | ||
> | ||
<Text size="md" color="red" bold> | ||
<FormattedMessage | ||
id={resetStarting || jobResetRunning ? "connection.cancelReset" : "connection.cancelSync"} | ||
/> | ||
</Text> | ||
</Button> | ||
)} | ||
<Box p="md"> | ||
<SwitchNext | ||
onChange={onChangeStatus} | ||
checked={connection.status === ConnectionStatus.active} | ||
loading={connectionUpdating} | ||
disabled={isSwitchDisabled} | ||
className={styles.switch} | ||
/> | ||
</Box> | ||
</FlexContainer> | ||
); | ||
}; |
58 changes: 58 additions & 0 deletions
58
.../src/components/connection/ConnectionHeaderControls/FormattedScheduleDataMessage.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import { render } from "@testing-library/react"; | ||
|
||
import { TestWrapper } from "test-utils"; | ||
|
||
import { ConnectionScheduleData, ConnectionScheduleDataBasicScheduleTimeUnit } from "core/api/types/AirbyteClient"; | ||
|
||
import { FormattedScheduleDataMessage, FormattedScheduleDataMessageProps } from "./FormattedScheduleDataMessage"; | ||
|
||
describe("FormattedScheduleDataMessage", () => { | ||
const renderComponent = (props: FormattedScheduleDataMessageProps) => { | ||
return render( | ||
<TestWrapper> | ||
<FormattedScheduleDataMessage {...props} /> | ||
</TestWrapper> | ||
); | ||
}; | ||
|
||
it("should render 'Manual' schedule type if scheduleData wasn't provided", () => { | ||
const { getByText } = renderComponent({ scheduleType: "manual" }); | ||
expect(getByText("Manual")).toBeInTheDocument(); | ||
}); | ||
|
||
it("should render '24 hours' schedule type", () => { | ||
const scheduleData = { | ||
basicSchedule: { | ||
units: 24, | ||
timeUnit: "hours" as ConnectionScheduleDataBasicScheduleTimeUnit, | ||
}, | ||
}; | ||
const { getByText } = renderComponent({ scheduleType: "basic", scheduleData }); | ||
expect(getByText("Every 24 hours")).toBeInTheDocument(); | ||
}); | ||
|
||
it("should render 'Cron' schedule type with humanized format", () => { | ||
const scheduleData = { | ||
cron: { | ||
cronExpression: "0 0 14 ? * THU" as string, | ||
cronTimeZone: "UTC", | ||
}, | ||
}; | ||
const { getByText } = renderComponent({ scheduleType: "cron", scheduleData }); | ||
expect(getByText("At 02:00 PM, only on Thursday")).toBeInTheDocument(); | ||
}); | ||
|
||
it("should NOT render anything", () => { | ||
const scheduleData = { | ||
basic: { | ||
units: 24, | ||
timeUnit: "hours" as ConnectionScheduleDataBasicScheduleTimeUnit, | ||
}, | ||
}; | ||
const { queryByText } = renderComponent({ | ||
scheduleType: "cron", | ||
scheduleData: scheduleData as unknown as ConnectionScheduleData, // for testing purposes | ||
}); | ||
expect(queryByText("24")).toBeNull(); | ||
}); | ||
}); |
45 changes: 45 additions & 0 deletions
45
...ebapp/src/components/connection/ConnectionHeaderControls/FormattedScheduleDataMessage.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import React from "react"; | ||
import { FormattedMessage } from "react-intl"; | ||
|
||
import { ConnectionScheduleData, ConnectionScheduleType } from "core/api/types/AirbyteClient"; | ||
import { humanizeCron } from "core/utils/cron"; | ||
|
||
export interface FormattedScheduleDataMessageProps { | ||
scheduleType?: ConnectionScheduleType; | ||
scheduleData?: ConnectionScheduleData; | ||
} | ||
|
||
/** | ||
* Formats schedule data based on the schedule type and schedule data. | ||
* If schedule type is "manual" returns "Manual". | ||
* If schedule type is "basic" returns "Every {units} {timeUnit}". | ||
* If schedule type is "cron" returns humanized cron expression. | ||
* @param scheduleType | ||
* @param scheduleData | ||
*/ | ||
export const FormattedScheduleDataMessage: React.FC<FormattedScheduleDataMessageProps> = ({ | ||
scheduleType, | ||
scheduleData, | ||
}: { | ||
scheduleType?: ConnectionScheduleType; | ||
scheduleData?: ConnectionScheduleData; | ||
}) => { | ||
if (scheduleType === "manual") { | ||
return <FormattedMessage id="frequency.manual" />; | ||
} | ||
|
||
if (scheduleType === "basic" && scheduleData?.basicSchedule) { | ||
return ( | ||
<FormattedMessage | ||
id={`form.every.${scheduleData.basicSchedule.timeUnit}`} | ||
values={{ value: scheduleData.basicSchedule.units }} | ||
/> | ||
); | ||
} | ||
|
||
if (scheduleType === "cron" && scheduleData?.cron) { | ||
return <>{humanizeCron(scheduleData.cron.cronExpression)}</>; | ||
} | ||
|
||
return null; | ||
}; |
1 change: 1 addition & 0 deletions
1
airbyte-webapp/src/components/connection/ConnectionHeaderControls/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { ConnectionHeaderControls } from "./ConnectionHeaderControls"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
25 changes: 25 additions & 0 deletions
25
...s/connection/CreateConnectionForm/SimplifiedConnectionCreation/InputContainer.module.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,28 @@ | ||
@use "scss/colors"; | ||
@use "scss/variables"; | ||
|
||
@keyframes highlight { | ||
0%, | ||
50% { | ||
position: relative; | ||
box-shadow: variables.$box-shadow-highlight colors.$blue-200; | ||
z-index: 1; | ||
} | ||
|
||
99% { | ||
z-index: 1; | ||
} | ||
|
||
100% { | ||
box-shadow: 0 0 0 0 transparent; | ||
z-index: 0; | ||
} | ||
} | ||
|
||
.container { | ||
width: 300px; | ||
|
||
&.highlighted { | ||
animation: highlight 2s ease-out; | ||
} | ||
} |
37 changes: 35 additions & 2 deletions
37
...omponents/connection/CreateConnectionForm/SimplifiedConnectionCreation/InputContainer.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,38 @@ | ||
import classNames from "classnames"; | ||
import { useState } from "react"; | ||
import { Location, useLocation, useNavigate } from "react-router-dom"; | ||
import { useEffectOnce } from "react-use"; | ||
|
||
import styles from "./InputContainer.module.scss"; | ||
|
||
export const InputContainer: React.FC<React.PropsWithChildren> = ({ children }) => { | ||
return <div className={styles.container}>{children}</div>; | ||
export interface LocationWithState extends Location { | ||
state: { action?: "scheduleType" }; | ||
} | ||
|
||
export const InputContainer: React.FC<React.PropsWithChildren<{ highlightAfterRedirect?: boolean }>> = ({ | ||
children, | ||
highlightAfterRedirect, | ||
}) => { | ||
const [highlighted, setHighlighted] = useState(false); | ||
const navigate = useNavigate(); | ||
const { state: locationState, pathname } = useLocation() as LocationWithState; | ||
|
||
useEffectOnce(() => { | ||
let highlightTimeout: number; | ||
|
||
if (highlightAfterRedirect && locationState?.action === "scheduleType") { | ||
setHighlighted(true); | ||
highlightTimeout = window.setTimeout(() => { | ||
setHighlighted(false); | ||
}, 1500); | ||
} | ||
// remove the redirection info from the location state | ||
navigate(pathname, { replace: true }); | ||
|
||
return () => { | ||
window.clearTimeout(highlightTimeout); | ||
}; | ||
}); | ||
|
||
return <div className={classNames(styles.container, { [styles.highlighted]: highlighted })}>{children}</div>; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.