Skip to content

Commit d40d1b7

Browse files
authored
TS types for Redux state and selectors in Releases UI (#5488)
* feature: better TS types for API responses * feature: redux types and fix types in selectors and there's still more to do :'( * feature: make type more good? * chore: replace jsonClone calls with builtin structuredClone * fix: stupid type * chore: migrate selectors tests to TS * fix: make Releases.tsx mergeable * chore: comment docs for some of the types
1 parent 1d9f169 commit d40d1b7

10 files changed

Lines changed: 540 additions & 697 deletions

File tree

static/js/publisher/pages/Releases/Releases.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import Release from "./Release";
55
import { setPageTitle } from "../../utils";
66
import Loader from "../../components/Loader";
77

8+
import { ReleasesAPIResponse } from "../../types/releaseTypes";
9+
810
function Releases(): React.JSX.Element {
911
const { snapId } = useParams();
1012
const { isLoading, isFetched, data } = useQuery({
@@ -16,7 +18,7 @@ function Releases(): React.JSX.Element {
1618
throw new Error("There was a problem fetching releases data");
1719
}
1820

19-
const responseData = await response.json();
21+
const responseData = (await response.json()) as ReleasesAPIResponse;
2022

2123
if (!responseData.success) {
2224
throw new Error("There was a problem fetching releases data");
@@ -43,6 +45,7 @@ function Releases(): React.JSX.Element {
4345
flags: {
4446
isProgressiveReleaseEnabled: true,
4547
},
48+
snapName: data.snap_name,
4649
}}
4750
/>
4851
)}

static/js/publisher/pages/Releases/__tests__/helpers.test.js

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -90,31 +90,6 @@ describe("isSameVersion", () => {
9090
});
9191
});
9292

93-
describe("jsonClone", () => {
94-
it("should make a copy of a JS Object Literal", () => {
95-
const testJson = {
96-
string: "string",
97-
number: 12,
98-
boolean: true,
99-
array: ["string", 12, true],
100-
};
101-
const result = jsonClone(testJson);
102-
expect(result).toEqual(testJson);
103-
expect(result).not.toBe(testJson);
104-
});
105-
106-
it("should remove methods", () => {
107-
const testJson = {
108-
string: "string",
109-
function: function () {
110-
return "test";
111-
},
112-
};
113-
114-
expect(jsonClone(testJson)).toEqual({ string: "string" });
115-
});
116-
});
117-
11893
describe("getTrackGuardrails", () => {
11994
beforeEach(() => {
12095
fetch.mockClear();

static/js/publisher/pages/Releases/actions/channelMap.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ export function releaseRevisionSuccess(revision, channel) {
4141
};
4242
}
4343

44+
/**
45+
*
46+
* @param {string} channel Channel to close
47+
* @returns {{type: string, "payload": {channel: string}}}
48+
*/
4449
export function closeChannelSuccess(channel) {
4550
return {
4651
type: CLOSE_CHANNEL_SUCCESS,

static/js/publisher/pages/Releases/actions/releases.ts

Lines changed: 22 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -26,36 +26,19 @@ import {
2626
initReleasesData,
2727
} from "../releasesState";
2828
import { updateFailedRevisions } from "./failedRevisions";
29+
import { PendingReleaseItem, Release, ReleasesAPIResponse, DispatchFn, ReleasesReduxState } from "../../../types/releaseTypes";
2930

3031
export const UPDATE_RELEASES = "UPDATE_RELEASES";
3132

32-
interface FetchReleaseResponse {
33-
data: {
34-
release_history: {
35-
revisions: any[];
36-
releases: any[];
37-
};
38-
channel_map: any;
39-
snap_name: string;
40-
};
41-
}
42-
43-
function updateReleasesData(apiData: FetchReleaseResponse) {
33+
// returns a Redux thunk callback that unpacks the API response into the state
34+
function updateReleasesData(apiData: ReleasesAPIResponse) {
4435
const {
4536
release_history: releasesData,
4637
channel_map: channelMap,
4738
snap_name: snapName,
4839
} = apiData.data;
4940
return (
50-
dispatch: (arg0: {
51-
type: string;
52-
payload:
53-
| { releases: any }
54-
| { revisions: any }
55-
| { architectures: any[] }
56-
| { channelMap: any }
57-
| { failedRevisions: any };
58-
}) => void,
41+
dispatch: DispatchFn,
5942
) => {
6043
const revisionsList = releasesData.revisions;
6144
const releases = releasesData.releases;
@@ -75,7 +58,7 @@ function updateReleasesData(apiData: FetchReleaseResponse) {
7558
};
7659
}
7760

78-
export function handleCloseResponse(dispatch: any, json: any, channels: any) {
61+
export function handleCloseResponse(dispatch: DispatchFn, json: any, channels: any) {
7962
if (json.success) {
8063
if (json.closed_channels && json.closed_channels.length > 0) {
8164
json.closed_channels.forEach((channel: string) => {
@@ -126,7 +109,7 @@ export function getErrorMessage(error: {
126109
}
127110

128111
export function handleReleaseResponse(
129-
dispatch: any,
112+
dispatch: DispatchFn,
130113
json: any,
131114
release: any,
132115
revisions: any,
@@ -174,16 +157,12 @@ export function handleReleaseResponse(
174157
}
175158

176159
export function releaseRevisions() {
177-
const mapToRelease = (pendingRelease: {
178-
progressive: { percentage: number };
179-
revision: { revision: any };
180-
channel: any;
181-
}) => {
160+
const mapToRelease = (pendingRelease: PendingReleaseItem) => {
182161
let progressive = null;
183162

184163
if (
185-
pendingRelease.progressive &&
186-
pendingRelease.progressive.percentage < 100
164+
pendingRelease?.progressive?.percentage &&
165+
pendingRelease?.progressive?.percentage < 100
187166
) {
188167
progressive = pendingRelease.progressive;
189168
}
@@ -197,23 +176,18 @@ export function releaseRevisions() {
197176
};
198177

199178
return (
200-
dispatch: any,
201-
getState: () => {
202-
pendingReleases: any;
203-
pendingCloses: any;
204-
revisions: any;
205-
options: any;
206-
},
179+
dispatch: DispatchFn,
180+
getState: () => ReleasesReduxState,
207181
) => {
208182
const { pendingReleases, pendingCloses, revisions, options } = getState();
209183
const { snapName } = options;
210184

211185
// To dedupe releases
212186
const progressiveReleases: {
213-
id: any;
214-
revision: { revision: any };
215-
channels: any[];
216-
progressive: { percentage: number } | null;
187+
id: number;
188+
revision: PendingReleaseItem["revision"];
189+
channels: PendingReleaseItem["channel"][];
190+
progressive: PendingReleaseItem["progressive"] | null;
217191
}[] = [];
218192
const regularReleases: Array<any> = [];
219193
Object.keys(pendingReleases).forEach((revId) => {
@@ -222,7 +196,6 @@ export function releaseRevisions() {
222196

223197
if (pendingRelease.progressive) {
224198
// first move progressive releases out
225-
226199
progressiveReleases.push(mapToRelease(pendingRelease));
227200
} else {
228201
const releaseIndex = regularReleases.findIndex(
@@ -241,17 +214,17 @@ export function releaseRevisions() {
241214
const releases = progressiveReleases.concat(regularReleases);
242215

243216
const _handleReleaseResponse = (
244-
json: { success: any; channel_map_tree: any; errors?: any },
217+
json: { success: boolean; channel_map_tree: any; errors?: any },
245218
release: { id: number; revision: number; channels: string[] }[],
246219
) => {
247220
return handleReleaseResponse(dispatch, json, release, revisions);
248221
};
249222

250223
const _handleCloseResponse = (json: {
251-
success?: any;
252-
closed_channels?: any;
253-
error?: boolean | undefined;
254-
json?: string | undefined;
224+
success?: boolean;
225+
closed_channels?: string[];
226+
error?: boolean;
227+
json?: string;
255228
}) => {
256229
return handleCloseResponse(dispatch, json, pendingCloses);
257230
};
@@ -261,7 +234,7 @@ export function releaseRevisions() {
261234
.then(() => fetchCloses(_handleCloseResponse, snapName, pendingCloses))
262235
.then(() => fetchSnapReleaseStatus(snapName))
263236
.then((json) =>
264-
dispatch(updateReleasesData(json as unknown as FetchReleaseResponse)),
237+
dispatch(updateReleasesData(json)),
265238
)
266239
.catch((error) =>
267240
dispatch(
@@ -277,7 +250,7 @@ export function releaseRevisions() {
277250
};
278251
}
279252

280-
export function updateReleases(releases: { revision: number }[]) {
253+
export function updateReleases(releases: Release[]) {
281254
return {
282255
type: UPDATE_RELEASES,
283256
payload: { releases },

static/js/publisher/pages/Releases/api/releases.js

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
1+
import { ReleasesAPIResponse } from "../../../types/releaseTypes";
12
import { DEFAULT_ERROR_MESSAGE as ERROR_MESSAGE } from "../constants";
23

4+
/**
5+
*
6+
* @param {*} onComplete
7+
* @param {*} releases
8+
* @param {string} snapName
9+
* @returns
10+
*/
311
export function fetchReleases(onComplete, releases, snapName) {
4-
let queue = Promise.resolve(); // Q() in q
12+
let queue = Promise.resolve();
513

614
// handle releases as a queue
715
releases.forEach((release) => {
@@ -10,14 +18,19 @@ export function fetchReleases(onComplete, releases, snapName) {
1018
snapName,
1119
release.id,
1220
release.channels,
13-
release.progressive,
21+
release.progressive
1422
).then((json) => onComplete(json, release));
1523
}));
1624
});
1725

1826
return queue;
1927
}
2028

29+
/**
30+
*
31+
* @param {string} snapName
32+
* @returns {Promise<ReleasesAPIResponse>}
33+
*/
2134
export function fetchSnapReleaseStatus(snapName) {
2235
return fetch(`/api/${snapName}/releases`, {
2336
method: "GET",
@@ -37,6 +50,11 @@ export function fetchSnapReleaseStatus(snapName) {
3750
});
3851
}
3952

53+
/**
54+
*
55+
* @param {string} snapName
56+
* @returns
57+
*/
4058
export function fetchRelease(snapName, revision, channels, progressive) {
4159
const body = {
4260
name: snapName,
@@ -67,6 +85,13 @@ export function fetchRelease(snapName, revision, channels, progressive) {
6785
});
6886
}
6987

88+
/**
89+
*
90+
* @param {*} onComplete
91+
* @param {string} snapName
92+
* @param {string[]} channels
93+
* @returns
94+
*/
7095
export function fetchCloses(onComplete, snapName, channels) {
7196
if (channels && channels.length) {
7297
return fetchClose(snapName, channels).then((json) => {
@@ -77,6 +102,12 @@ export function fetchCloses(onComplete, snapName, channels) {
77102
}
78103
}
79104

105+
/**
106+
*
107+
* @param {string} snapName
108+
* @param {string[]} channels
109+
* @returns
110+
*/
80111
export function fetchClose(snapName, channels) {
81112
return fetch(`/${snapName}/releases/close-channel`, {
82113
method: "POST",

0 commit comments

Comments
 (0)