diff --git a/pkg/app/web/src/components/application-detail.test.tsx b/pkg/app/web/src/components/application-detail.test.tsx
index bdce21de8f..5bf4fe0e28 100644
--- a/pkg/app/web/src/components/application-detail.test.tsx
+++ b/pkg/app/web/src/components/application-detail.test.tsx
@@ -2,10 +2,11 @@ import { DeepPartial } from "@reduxjs/toolkit";
import userEvent from "@testing-library/user-event";
import React from "react";
import { MemoryRouter } from "react-router";
-import { createStore, render, screen } from "../../test-utils";
+import { createStore, render, screen, waitFor } from "../../test-utils";
import { server } from "../mocks/server";
import { AppState } from "../modules";
import { syncApplication } from "../modules/applications";
+import { SyncStrategy } from "../modules/deployments";
import { dummyApplication } from "../__fixtures__/dummy-application";
import { dummyApplicationLiveState } from "../__fixtures__/dummy-application-live-state";
import { dummyEnv } from "../__fixtures__/dummy-environment";
@@ -71,27 +72,68 @@ describe("ApplicationDetail", () => {
expect(screen.getByText(dummyApplication.name)).toBeInTheDocument();
expect(screen.getByText("Healthy")).toBeInTheDocument();
expect(screen.getByText("Synced")).toBeInTheDocument();
- expect(screen.getByRole("button", { name: /sync/i })).toBeInTheDocument();
+ expect(screen.getByRole("button", { name: /sync$/i })).toBeInTheDocument();
});
- it("dispatch sync action if click sync button", async () => {
- const store = createStore(baseState);
- render(
-
-
- ,
- {
- store,
- }
- );
+ describe("sync", () => {
+ it("dispatch sync action if click sync button", async () => {
+ const store = createStore(baseState);
+ render(
+
+
+ ,
+ {
+ store,
+ }
+ );
- userEvent.click(screen.getByRole("button", { name: /sync/i }));
+ userEvent.click(screen.getByRole("button", { name: /sync$/i }));
- expect(store.getActions()).toMatchObject([
- {
- type: syncApplication.pending.type,
- meta: { arg: { applicationId: dummyApplication.id } },
- },
- ]);
+ await waitFor(() =>
+ expect(store.getActions()).toMatchObject([
+ {
+ type: syncApplication.pending.type,
+ meta: {
+ arg: {
+ applicationId: dummyApplication.id,
+ syncStrategy: SyncStrategy.AUTO,
+ },
+ },
+ },
+ ])
+ );
+ });
+
+ it("dispatch sync action with selected sync strategy if changed strategy and click the sync button", async () => {
+ const store = createStore(baseState);
+ render(
+
+
+ ,
+ {
+ store,
+ }
+ );
+
+ userEvent.click(
+ screen.getByRole("button", { name: /select sync strategy/i })
+ );
+ userEvent.click(screen.getByRole("menuitem", { name: /pipeline sync/i }));
+ userEvent.click(screen.getByRole("button", { name: /pipeline sync/i }));
+
+ await waitFor(() =>
+ expect(store.getActions()).toMatchObject([
+ {
+ type: syncApplication.pending.type,
+ meta: {
+ arg: {
+ applicationId: dummyApplication.id,
+ syncStrategy: SyncStrategy.PIPELINE,
+ },
+ },
+ },
+ ])
+ );
+ });
});
});
diff --git a/pkg/app/web/src/components/application-detail.tsx b/pkg/app/web/src/components/application-detail.tsx
index c71c076907..f67504f863 100644
--- a/pkg/app/web/src/components/application-detail.tsx
+++ b/pkg/app/web/src/components/application-detail.tsx
@@ -1,7 +1,6 @@
import {
Box,
Button,
- CircularProgress,
Link,
makeStyles,
Paper,
@@ -10,14 +9,16 @@ import {
import SyncIcon from "@material-ui/icons/Cached";
import OpenInNewIcon from "@material-ui/icons/OpenInNew";
import Skeleton from "@material-ui/lab/Skeleton/Skeleton";
+import { SerializedError } from "@reduxjs/toolkit";
import dayjs from "dayjs";
import React, { FC, memo } from "react";
import { useDispatch, useSelector } from "react-redux";
import { Link as RouterLink } from "react-router-dom";
-import { PAGE_PATH_DEPLOYMENTS } from "../constants/path";
import { APPLICATION_KIND_TEXT } from "../constants/application-kind";
import { APPLICATION_SYNC_STATUS_TEXT } from "../constants/application-sync-status-text";
import { APPLICATION_HEALTH_STATUS_TEXT } from "../constants/health-status-text";
+import { PAGE_PATH_DEPLOYMENTS } from "../constants/path";
+import { UI_TEXT_REFRESH } from "../constants/ui-text";
import { AppState } from "../modules";
import {
Application,
@@ -30,6 +31,7 @@ import {
ApplicationLiveState,
selectById as selectLiveStateById,
} from "../modules/applications-live-state";
+import { SyncStrategy } from "../modules/deployments";
import {
Environment,
selectById as selectEnvById,
@@ -37,10 +39,9 @@ import {
import { Piped, selectById as selectPipeById } from "../modules/pipeds";
import { DetailTableRow } from "./detail-table-row";
import { ApplicationHealthStatusIcon } from "./health-status-icon";
+import { SplitButton } from "./split-button";
import { SyncStateReason } from "./sync-state-reason";
import { SyncStatusIcon } from "./sync-status-icon";
-import { SerializedError } from "@reduxjs/toolkit";
-import { UI_TEXT_REFRESH } from "../constants/ui-text";
const useStyles = makeStyles((theme) => ({
root: {
@@ -138,6 +139,13 @@ const MostRecentlySuccessfulDeployment: FC<{
);
};
+const syncOptions = ["Sync", "Quick Sync", "Pipeline Sync"];
+const syncStrategyByIndex: SyncStrategy[] = [
+ SyncStrategy.AUTO,
+ SyncStrategy.QUICK_SYNC,
+ SyncStrategy.PIPELINE,
+];
+
export const ApplicationDetail: FC = memo(function ApplicationDetail({
applicationId,
}) {
@@ -167,9 +175,14 @@ export const ApplicationDetail: FC = memo(function ApplicationDetail({
const isSyncing = useIsSyncingApplication(app?.id);
- const handleSync = (): void => {
+ const handleSync = (index: number): void => {
if (app) {
- dispatch(syncApplication({ applicationId: app.id }));
+ dispatch(
+ syncApplication({
+ applicationId: app.id,
+ syncStrategy: syncStrategyByIndex[index],
+ })
+ );
}
};
@@ -288,18 +301,14 @@ export const ApplicationDetail: FC = memo(function ApplicationDetail({
- }
- >
- SYNC
- {isSyncing && (
-
- )}
-
+ />
);
diff --git a/pkg/app/web/src/components/deployment-detail.tsx b/pkg/app/web/src/components/deployment-detail.tsx
index 6c8dd1a0ec..fe26636893 100644
--- a/pkg/app/web/src/components/deployment-detail.tsx
+++ b/pkg/app/web/src/components/deployment-detail.tsx
@@ -221,6 +221,7 @@ export const DeploymentDetail: FC = memo(function DeploymentDetail({
{
dispatch(
cancelDeployment({
diff --git a/pkg/app/web/src/components/split-button.stories.tsx b/pkg/app/web/src/components/split-button.stories.tsx
index 15416c8267..dd3bc6f582 100644
--- a/pkg/app/web/src/components/split-button.stories.tsx
+++ b/pkg/app/web/src/components/split-button.stories.tsx
@@ -10,6 +10,7 @@ export default {
export const overview: React.FC = () => (
}
options={["Cancel", "Cancel Without Rollback"]}
onClick={action("onClick")}
@@ -19,6 +20,7 @@ export const overview: React.FC = () => (
export const loading: React.FC = () => (
}
options={["Cancel", "Cancel Without Rollback"]}
onClick={action("onClick")}
diff --git a/pkg/app/web/src/components/split-button.test.tsx b/pkg/app/web/src/components/split-button.test.tsx
index 81cf773af7..8d74b214a8 100644
--- a/pkg/app/web/src/components/split-button.test.tsx
+++ b/pkg/app/web/src/components/split-button.test.tsx
@@ -7,6 +7,7 @@ it("calls onClick handler with option's index if clicked", () => {
const onClick = jest.fn();
render(
{
expect(onClick).toHaveBeenCalledWith(0);
act(() => {
- userEvent.click(
- screen.getByRole("button", { name: "select merge strategy" })
- );
+ userEvent.click(screen.getByRole("button", { name: "select option" }));
});
userEvent.click(screen.getByRole("menuitem", { name: "option2" }));
userEvent.click(screen.getByRole("button", { name: "option2" }));
diff --git a/pkg/app/web/src/components/split-button.tsx b/pkg/app/web/src/components/split-button.tsx
index 33485cba90..46b067f1f4 100644
--- a/pkg/app/web/src/components/split-button.tsx
+++ b/pkg/app/web/src/components/split-button.tsx
@@ -1,6 +1,7 @@
import {
Button,
ButtonGroup,
+ PropTypes,
CircularProgress,
ClickAwayListener,
Grow,
@@ -26,9 +27,11 @@ const useStyles = makeStyles((theme) => ({
interface Props {
options: string[];
+ label: string;
onClick: (index: number) => void;
startIcon?: React.ReactNode;
loading: boolean;
+ color?: PropTypes.Color;
className?: string;
}
@@ -38,6 +41,8 @@ export const SplitButton: FC = ({
loading,
startIcon,
className,
+ color,
+ label,
}) => {
const classes = useStyles();
const anchorRef = useRef(null);
@@ -48,7 +53,7 @@ export const SplitButton: FC = ({
@@ -66,7 +71,7 @@ export const SplitButton: FC = ({
size="small"
aria-controls={openCancelMenu ? "split-button-menu" : undefined}
aria-expanded={openCancelMenu ? "true" : undefined}
- aria-label="select merge strategy"
+ aria-label={label}
aria-haspopup="menu"
onClick={() => setOpenCancelMenu(!openCancelMenu)}
>
diff --git a/pkg/app/web/src/modules/applications.ts b/pkg/app/web/src/modules/applications.ts
index 72df1d0869..d24007d31d 100644
--- a/pkg/app/web/src/modules/applications.ts
+++ b/pkg/app/web/src/modules/applications.ts
@@ -13,7 +13,7 @@ import {
ApplicationGitRepository,
ApplicationKind,
} from "pipe/pkg/app/web/model/common_pb";
-import { SyncStrategy } from "pipe/pkg/app/web/model/deployment_pb";
+import { SyncStrategy } from "./deployments";
import { fetchCommand, CommandStatus, CommandModel } from "./commands";
import { AppState } from ".";
@@ -51,12 +51,9 @@ export const fetchApplication = createAsyncThunk<
export const syncApplication = createAsyncThunk<
void,
- { applicationId: string }
->("applications/sync", async ({ applicationId }, thunkAPI) => {
- const { commandId } = await applicationsAPI.syncApplication({
- applicationId: applicationId,
- syncStrategy: SyncStrategy.AUTO,
- });
+ { applicationId: string; syncStrategy: SyncStrategy }
+>("applications/sync", async (values, thunkAPI) => {
+ const { commandId } = await applicationsAPI.syncApplication(values);
await thunkAPI.dispatch(fetchCommand(commandId));
});
diff --git a/pkg/app/web/src/modules/deployments.ts b/pkg/app/web/src/modules/deployments.ts
index 7c5b9d2059..11f0894716 100644
--- a/pkg/app/web/src/modules/deployments.ts
+++ b/pkg/app/web/src/modules/deployments.ts
@@ -233,4 +233,5 @@ export const deploymentsSlice = createSlice({
export {
DeploymentStatus,
StageStatus,
+ SyncStrategy,
} from "pipe/pkg/app/web/model/deployment_pb";