Skip to content

Commit bd3bcea

Browse files
authored
show deprecated note in setting piped page (#6088)
* show depreacted note in setting piped page Signed-off-by: kypham <hongkyhvnh@gmail.com> * add ignore button to dialog detail breaking changes Signed-off-by: kypham <hongkyhvnh@gmail.com> * add react markdown to dialog warning breaking changes Signed-off-by: kypham <hongkyhvnh@gmail.com> --------- Signed-off-by: kypham <hongkyhvnh@gmail.com>
1 parent 8021d3a commit bd3bcea

10 files changed

Lines changed: 283 additions & 2 deletions

File tree

web/jest.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ module.exports = {
1414
"^pipecd/(.*)$": "<rootDir>/../$1",
1515
"^~/(.*)$": "<rootDir>/src/$1",
1616
"^~~/(.*)$": "<rootDir>/$1",
17+
"\\.(css|less|scss|sass)$": "<rootDir>/test-utils/styleMock.ts",
1718
},
1819
moduleDirectories: ["node_modules", "__fixtures__"],
1920
coveragePathIgnorePatterns: [

web/jest.config.local.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ module.exports = {
1414
"^pipecd/(.*)$": "<rootDir>/../$1",
1515
"^~/(.*)$": "<rootDir>/src/$1",
1616
"^~~/(.*)$": "<rootDir>/$1",
17+
"\\.(css|less|scss|sass)$": "<rootDir>/test-utils/styleMock.ts",
1718
},
1819
moduleDirectories: ["<rootDir>/node_modules", "__fixtures__"],
1920
coveragePathIgnorePatterns: ["/node_modules/", ".test.ts", ".d.ts"],

web/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
"@typescript-eslint/parser": "^6",
3636
"babel-loader": "^9.1.0",
3737
"copy-webpack-plugin": "^11.0.0",
38+
"css-loader": "^7.1.2",
3839
"eslint": "^8",
3940
"eslint-config-prettier": "^10.1.5",
4041
"eslint-plugin-jest": "^27.6.3",
@@ -52,6 +53,7 @@
5253
"process": "^0.11.10",
5354
"react-test-renderer": "^17.0.2",
5455
"redux-mock-store": "^1.5.4",
56+
"style-loader": "^4.0.0",
5557
"ts-jest": "^29.1.0",
5658
"ts-loader": "^9.1.0",
5759
"typescript": "^5.8.3",
@@ -78,6 +80,7 @@
7880
"dotenv": "^8.6.0",
7981
"echarts": "^5.6.0",
8082
"formik": "^2.2.9",
83+
"github-markdown-css": "^5.8.1",
8184
"google-protobuf": "^3.21.4",
8285
"grpc-web": "^1.5.0",
8386
"history": "^4.10.1",
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { screen, render, waitFor } from "~~/test-utils";
2+
import BreakingChangeNotes from "./index"; // adjust import if needed
3+
import userEvent from "@testing-library/user-event";
4+
5+
describe("BreakingChange component", () => {
6+
it("renders empty if no breaking changes", async () => {
7+
render(<BreakingChangeNotes notes={""} />);
8+
9+
expect(
10+
screen.queryByText(/breaking change notes/i)
11+
).not.toBeInTheDocument();
12+
});
13+
14+
it("renders breaking changes note after fetching", async () => {
15+
render(<BreakingChangeNotes notes={"warning notes"} />);
16+
17+
expect(screen.getByText(/warning notes/)).toBeInTheDocument();
18+
expect(
19+
screen.getByRole("button", { name: /view detail/i })
20+
).toBeInTheDocument();
21+
22+
userEvent.click(screen.getByRole("button", { name: /view detail/i }));
23+
expect(screen.getByText(/Breaking Changes/i)).toBeInTheDocument();
24+
expect(screen.getByRole("button", { name: /close/i })).toBeInTheDocument();
25+
expect(screen.getByRole("button", { name: /ignore/i })).toBeInTheDocument();
26+
});
27+
28+
it("close dialog when close button is clicked", async () => {
29+
render(<BreakingChangeNotes notes={"warning notes"} />);
30+
31+
await userEvent.click(screen.getByRole("button", { name: /view detail/i }));
32+
await userEvent.click(screen.getByRole("button", { name: /close/i }));
33+
34+
// Wait for the dialog to be removed from the DOM
35+
await waitFor(() => {
36+
expect(screen.queryByText(/Breaking Changes/i)).not.toBeInTheDocument();
37+
});
38+
});
39+
40+
it("close dialog when ignore button is clicked", async () => {
41+
render(<BreakingChangeNotes notes={"warning notes"} />);
42+
43+
await userEvent.click(screen.getByRole("button", { name: /view detail/i }));
44+
await userEvent.click(screen.getByRole("button", { name: /ignore/i }));
45+
46+
// Wait for the dialog to be removed from the DOM
47+
await waitFor(() => {
48+
expect(screen.queryByText(/Breaking Changes/i)).not.toBeInTheDocument();
49+
});
50+
});
51+
});
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import {
2+
Alert,
3+
Button,
4+
Dialog,
5+
DialogActions,
6+
DialogContent,
7+
DialogTitle,
8+
Typography,
9+
} from "@mui/material";
10+
import { FC, useState } from "react";
11+
import { IGNORE_BREAKING_CHANGE_NOTES_PIPEDS } from "~/constants/localstorage";
12+
import ReactMarkdown from "react-markdown";
13+
import "github-markdown-css/github-markdown.css";
14+
15+
type Props = {
16+
notes?: string | null;
17+
};
18+
19+
const getVersionsIgnoredWarning = (): string[] => {
20+
try {
21+
const rawIgnoredNotes =
22+
localStorage.getItem(IGNORE_BREAKING_CHANGE_NOTES_PIPEDS) || "[]";
23+
return JSON.parse(rawIgnoredNotes) as string[];
24+
} catch {
25+
return [];
26+
}
27+
};
28+
29+
const shouldIgnoredBreakingChangeNotes = (): boolean => {
30+
const version = process.env.PIPECD_VERSION;
31+
if (!version) return false;
32+
33+
try {
34+
const ignoredNotes = getVersionsIgnoredWarning();
35+
return ignoredNotes.includes(version);
36+
} catch {
37+
return false;
38+
}
39+
};
40+
41+
const BreakingChangeNotes: FC<Props> = ({ notes }) => {
42+
const [showDialog, setShowDialog] = useState(false);
43+
const [showNotes, setShowNotes] = useState(
44+
!shouldIgnoredBreakingChangeNotes()
45+
);
46+
47+
const onIgnoreWarning = (): void => {
48+
setShowDialog(false);
49+
const pipedVersion = process.env.PIPECD_VERSION;
50+
if (!pipedVersion) return;
51+
52+
try {
53+
const ignoredVersions = JSON.parse(
54+
localStorage.getItem(IGNORE_BREAKING_CHANGE_NOTES_PIPEDS) || "[]"
55+
);
56+
57+
if (!ignoredVersions.includes(pipedVersion)) {
58+
ignoredVersions.push(pipedVersion);
59+
}
60+
61+
localStorage.setItem(
62+
IGNORE_BREAKING_CHANGE_NOTES_PIPEDS,
63+
JSON.stringify(ignoredVersions)
64+
);
65+
} finally {
66+
setShowNotes(false);
67+
}
68+
};
69+
70+
if (!notes || !showNotes) {
71+
return null;
72+
}
73+
return (
74+
<>
75+
<Alert
76+
severity="warning"
77+
sx={{ alignItems: "center" }}
78+
action={
79+
<Button onClick={() => setShowDialog(true)}>View details</Button>
80+
}
81+
>
82+
<Typography
83+
sx={{
84+
overflow: "hidden",
85+
textOverflow: "ellipsis",
86+
display: "-webkit-box",
87+
WebkitLineClamp: "2",
88+
WebkitBoxOrient: "vertical",
89+
whiteSpace: "pre-wrap",
90+
}}
91+
>
92+
{notes}
93+
</Typography>
94+
</Alert>
95+
96+
<Dialog
97+
open={showDialog}
98+
onClose={() => setShowDialog(false)}
99+
maxWidth="lg"
100+
>
101+
<DialogTitle>Breaking Changes</DialogTitle>
102+
<DialogContent
103+
sx={{
104+
maxHeight: "60vh",
105+
overflowX: "hidden",
106+
overflowY: "auto",
107+
}}
108+
>
109+
<div className="markdown-body">
110+
<ReactMarkdown linkTarget="_blank">{notes}</ReactMarkdown>
111+
</div>
112+
</DialogContent>
113+
<DialogActions>
114+
<Button onClick={() => onIgnoreWarning()}>Ignore</Button>
115+
<Button onClick={() => setShowDialog(false)}>Close</Button>
116+
</DialogActions>
117+
</Dialog>
118+
</>
119+
);
120+
};
121+
122+
export default BreakingChangeNotes;

web/src/components/settings-page/piped/index.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import { useAddNewPipedKey } from "~/queries/pipeds/use-add-new-piped-key";
4747
import { useDisablePiped } from "~/queries/pipeds/use-disable-piped";
4848
import { useEnablePiped } from "~/queries/pipeds/use-enable-piped";
4949
import { useRestartPiped } from "~/queries/pipeds/use-restart-piped";
50+
import BreakingChangeNotes from "./components/breaking-change";
5051

5152
const OLD_KEY_ALERT_MESSAGE =
5253
"The old key is still there.\nDo not forget to delete it once you update your Piped to use this new key.";
@@ -170,6 +171,7 @@ export const SettingsPipedPage: FC = memo(function SettingsPipedPage() {
170171

171172
return (
172173
<>
174+
<BreakingChangeNotes notes={breakingChangesNote} />
173175
<Toolbar variant="dense">
174176
<Button
175177
color="primary"

web/src/constants/localstorage.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,5 @@ export const REDIRECT_PATH_KEY = "redirect_path";
22
export const BANNER_VERSION_KEY = "banner_version";
33
export const USER_PROJECTS = "projects";
44
export const LOGGING_IN_PROJECT = "login_project";
5+
export const IGNORE_BREAKING_CHANGE_NOTES_PIPEDS =
6+
"breaking_change_notes_ignored_pipeds";

web/test-utils/styleMock.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = {};

web/webpack.common.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ module.exports = (env) => {
3434
filename: "assets/[name].[hash:8][ext]",
3535
},
3636
},
37+
{
38+
test: /\.css$/i,
39+
use: ["style-loader", "css-loader"],
40+
},
3741
],
3842
},
3943
resolve: {

0 commit comments

Comments
 (0)