@griddo/ax 12.6.0 → 12.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config/griddo-config/index.js +1 -0
- package/package.json +3 -4
- package/src/GlobalStore.tsx +91 -35
- package/src/__tests__/modules/ActivityLog/ActivityLog.uc.test.tsx +132 -0
- package/src/__tests__/modules/Analytics/Analytics.uc.test.tsx +168 -0
- package/src/__tests__/modules/GlobalSettings/Robots/Robots.test.tsx +110 -0
- package/src/__tests__/modules/GlobalSettings/Robots/Robots.uc.test.tsx +125 -0
- package/src/__tests__/modules/Redirects/Redirects.uc.test.tsx +164 -0
- package/src/__tests__/modules/Settings/Languages/Languages.uc.test.tsx +213 -0
- package/src/__tests__/modules/Settings/Social/Social.uc.test.tsx +171 -0
- package/src/__tests__/store/activityLog.test.ts +301 -0
- package/src/__tests__/store/analytics.test.ts +134 -0
- package/src/__tests__/store/domains.test.ts +107 -0
- package/src/__tests__/store/languages.test.ts +129 -0
- package/src/__tests__/store/persist.test.ts +47 -0
- package/src/__tests__/store/redirects.test.ts +313 -0
- package/src/__tests__/store/social.test.ts +165 -0
- package/src/components/MainWrapper/index.tsx +14 -2
- package/src/containers/ActivityLog/index.tsx +16 -3
- package/src/containers/ActivityLog/slice.ts +45 -0
- package/src/containers/ActivityLog/{actions.tsx → thunks.ts} +104 -89
- package/src/containers/Analytics/index.tsx +14 -3
- package/src/containers/Analytics/slice.ts +48 -0
- package/src/containers/Analytics/thunks.ts +103 -0
- package/src/containers/Domains/index.tsx +12 -3
- package/src/containers/Domains/slice.ts +37 -0
- package/src/containers/Domains/thunks.ts +60 -0
- package/src/containers/Redirects/constants.tsx +1 -17
- package/src/containers/Redirects/index.tsx +14 -3
- package/src/containers/Redirects/slice.ts +60 -0
- package/src/containers/Redirects/thunks.ts +196 -0
- package/src/containers/Settings/Languages/index.tsx +15 -3
- package/src/containers/Settings/Languages/slice.ts +48 -0
- package/src/containers/Settings/Languages/thunks.ts +83 -0
- package/src/containers/Settings/Social/index.tsx +11 -3
- package/src/containers/Settings/Social/slice.ts +40 -0
- package/src/containers/Settings/Social/thunks.ts +68 -0
- package/src/containers/Sites/actions.tsx +6 -2
- package/src/index.tsx +7 -6
- package/src/modules/ActivityLog/ItemLog/EventItem/index.tsx +13 -27
- package/src/modules/ActivityLog/ItemLogUser/UserItem/EventItem/index.tsx +13 -27
- package/src/modules/ActivityLog/index.tsx +22 -51
- package/src/modules/Analytics/index.tsx +33 -42
- package/src/modules/App/Routing/index.tsx +5 -4
- package/src/modules/FileDrive/index.tsx +1 -1
- package/src/modules/GlobalSettings/Robots/index.tsx +24 -47
- package/src/modules/MediaGallery/index.tsx +1 -1
- package/src/modules/Redirects/RedirectItem/index.tsx +9 -22
- package/src/modules/Redirects/RedirectPanel/index.tsx +13 -14
- package/src/modules/Redirects/index.tsx +26 -69
- package/src/modules/Settings/ContentTypes/DataPacks/index.tsx +4 -3
- package/src/modules/Settings/Languages/LanguagePanel/Form/ConnectedField/index.tsx +11 -18
- package/src/modules/Settings/Languages/LanguagePanel/Form/index.tsx +7 -9
- package/src/modules/Settings/Languages/LanguagePanel/index.tsx +38 -44
- package/src/modules/Settings/Languages/Table/Header/index.tsx +12 -19
- package/src/modules/Settings/Languages/Table/Item/index.tsx +21 -36
- package/src/modules/Settings/Languages/Table/index.tsx +6 -14
- package/src/modules/Settings/Languages/index.tsx +18 -30
- package/src/modules/Settings/SeoAnalyticsSettings/Analytics/index.tsx +42 -52
- package/src/modules/Settings/Social/index.tsx +17 -12
- package/src/modules/StructuredData/StructuredDataList/index.tsx +3 -2
- package/src/store/hooks.ts +37 -0
- package/src/store/index.ts +2 -0
- package/src/types/index.tsx +5 -6
- package/tsconfig.paths.json +1 -0
- package/src/containers/ActivityLog/constants.tsx +0 -6
- package/src/containers/ActivityLog/interfaces.tsx +0 -12
- package/src/containers/ActivityLog/reducer.tsx +0 -24
- package/src/containers/Analytics/actions.tsx +0 -109
- package/src/containers/Analytics/constants.tsx +0 -5
- package/src/containers/Analytics/interfaces.tsx +0 -9
- package/src/containers/Analytics/reducer.tsx +0 -28
- package/src/containers/Domains/actions.tsx +0 -58
- package/src/containers/Domains/constants.tsx +0 -5
- package/src/containers/Domains/interfaces.tsx +0 -9
- package/src/containers/Domains/reducer.tsx +0 -22
- package/src/containers/Redirects/actions.tsx +0 -190
- package/src/containers/Redirects/interfaces.tsx +0 -23
- package/src/containers/Redirects/reducer.tsx +0 -35
- package/src/containers/Settings/Languages/actions.tsx +0 -88
- package/src/containers/Settings/Languages/constants.tsx +0 -6
- package/src/containers/Settings/Languages/reducer.tsx +0 -31
- package/src/containers/Settings/Social/actions.tsx +0 -63
- package/src/containers/Settings/Social/constants.tsx +0 -3
- package/src/containers/Settings/Social/interfaces.tsx +0 -9
- package/src/containers/Settings/Social/reducer.tsx +0 -18
|
@@ -1,5 +1,15 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import { logs } from "@ax/api";
|
|
2
|
+
import { appActions } from "@ax/containers/App";
|
|
3
|
+
import { formsActions } from "@ax/containers/Forms";
|
|
4
|
+
import { integrationsActions } from "@ax/containers/Integrations";
|
|
5
|
+
import { navigationActions } from "@ax/containers/Navigation";
|
|
6
|
+
import { pageEditorActions } from "@ax/containers/PageEditor";
|
|
7
|
+
import { sitesActions } from "@ax/containers/Sites";
|
|
8
|
+
import { structuredDataActions } from "@ax/containers/StructuredData";
|
|
9
|
+
import { usersActions } from "@ax/containers/Users";
|
|
10
|
+
import { handleRequest } from "@ax/helpers";
|
|
11
|
+
import type { AppDispatch } from "@ax/store";
|
|
12
|
+
import type { IRootState } from "@ax/types";
|
|
3
13
|
|
|
4
14
|
import type {
|
|
5
15
|
LogActivityExportRequest,
|
|
@@ -10,99 +20,106 @@ import type {
|
|
|
10
20
|
LogContentTypeDTO,
|
|
11
21
|
PaginationResponse,
|
|
12
22
|
} from "@griddo/api-types";
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import { pageEditorActions } from "@ax/containers/PageEditor";
|
|
17
|
-
import { sitesActions } from "@ax/containers/Sites";
|
|
18
|
-
import { integrationsActions } from "@ax/containers/Integrations";
|
|
19
|
-
import { formsActions } from "@ax/containers/Forms";
|
|
20
|
-
import { navigationActions } from "@ax/containers/Navigation";
|
|
21
|
-
import { structuredDataActions } from "@ax/containers/StructuredData";
|
|
22
|
-
import { handleRequest } from "@ax/helpers";
|
|
23
|
-
import type { ILogItemsByDay, ILogItemsByUser, IRootState } from "@ax/types";
|
|
24
|
-
|
|
25
|
-
import { SET_ACTIVITY_LOG, SET_LIST_MODE } from "./constants";
|
|
26
|
-
import type { ISetActivityLog, ISetListMode } from "./interfaces";
|
|
27
|
-
|
|
28
|
-
function setActivityLog(activityLog: ILogItemsByDay | ILogItemsByUser): ISetActivityLog {
|
|
29
|
-
return { type: SET_ACTIVITY_LOG, payload: { activityLog } };
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function setListMode(listMode: string): ISetListMode {
|
|
33
|
-
return { type: SET_LIST_MODE, payload: { listMode } };
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function getActivityLog(params: LogActivityPaginationRequest): (dispatch: Dispatch) => Promise<void> {
|
|
37
|
-
return async (dispatch) => {
|
|
38
|
-
try {
|
|
39
|
-
const callback = async () => logs.getLogActivityTimelineByDay(params);
|
|
23
|
+
import { createAsyncThunk } from "@reduxjs/toolkit";
|
|
24
|
+
import { format } from "date-fns";
|
|
25
|
+
import type { Dispatch } from "redux";
|
|
40
26
|
|
|
41
|
-
|
|
42
|
-
handleSuccess: (response: PaginationResponse<LogActivityGroupedDayDTO>) => {
|
|
43
|
-
dispatch(setActivityLog(response));
|
|
44
|
-
},
|
|
45
|
-
handleError: (response: any) => appActions.handleError(response)(dispatch),
|
|
46
|
-
};
|
|
27
|
+
import { setActivityLog } from "./slice";
|
|
47
28
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Read and export side of UC-AX-view-activity-log, plus the deep-link dispatcher, as
|
|
31
|
+
* createAsyncThunk.
|
|
32
|
+
*
|
|
33
|
+
* They keep calling `handleRequest` inside for the same reason the Languages pilot does —
|
|
34
|
+
* see docs/RTK-MIGRATION.md §5.
|
|
35
|
+
*
|
|
36
|
+
* `goToLogContent` is the last of the wave's mines and the one that does not get cleaned up
|
|
37
|
+
* here: it is a 160-line navigation switch that reaches into SEVEN areas that have not been
|
|
38
|
+
* migrated yet, invoking their thunks by hand as `thunk(args)(dispatch, getState)`. That still
|
|
39
|
+
* works while those areas are legacy, and it is exactly what stops working when they are not —
|
|
40
|
+
* a createAsyncThunk expects the `(dispatch, getState, extra)` triple. **When `sites` migrates
|
|
41
|
+
* in wave 3, the twelve `sitesActions.getSite(site)(dispatch, getState)` calls below break.**
|
|
42
|
+
* Its body is transplanted verbatim on purpose — diffed line by line against the legacy one —
|
|
43
|
+
* and the single-argument rule is the only edit it took.
|
|
44
|
+
*/
|
|
45
|
+
const onError = (response: unknown, dispatch: Dispatch) => appActions.handleError(response)(dispatch);
|
|
54
46
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
47
|
+
/**
|
|
48
|
+
* The store's real dispatch and state, declared once.
|
|
49
|
+
*
|
|
50
|
+
* `AppDispatch` is a redux 4 `AnyAction` thunk dispatch (`src/store/hooks.ts`), so the
|
|
51
|
+
* nineteen legacy action creators and the seven legacy thunks that `goToLogContent` still
|
|
52
|
+
* reaches for keep type-checking, and `getState()` comes back as `IRootState` instead of
|
|
53
|
+
* `unknown`. Without this RTK types `dispatch` against redux 5's `UnknownAction`, which the
|
|
54
|
+
* hand-written action interfaces are not assignable to — the §8.3 friction, reaching
|
|
55
|
+
* production code here for the first time rather than just test files.
|
|
56
|
+
*/
|
|
57
|
+
const createAppAsyncThunk = createAsyncThunk.withTypes<{ state: IRootState; dispatch: AppDispatch }>();
|
|
59
58
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
59
|
+
export const getActivityLog = createAsyncThunk(
|
|
60
|
+
"activityLog/getActivityLog",
|
|
61
|
+
async (params: LogActivityPaginationRequest, { dispatch }) =>
|
|
62
|
+
handleRequest(
|
|
63
|
+
async () => logs.getLogActivityTimelineByDay(params),
|
|
64
|
+
{
|
|
65
|
+
handleSuccess: (response: PaginationResponse<LogActivityGroupedDayDTO>) => dispatch(setActivityLog(response)),
|
|
66
|
+
handleError: (response: unknown) => onError(response, dispatch),
|
|
67
|
+
},
|
|
68
|
+
[appActions.setIsLoading],
|
|
69
|
+
)(dispatch),
|
|
70
|
+
);
|
|
66
71
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
72
|
+
export const getActivityLogByUser = createAsyncThunk(
|
|
73
|
+
"activityLog/getActivityLogByUser",
|
|
74
|
+
async (params: LogActivityPaginationRequest, { dispatch }) =>
|
|
75
|
+
handleRequest(
|
|
76
|
+
async () => logs.getLogActivityTimelineByUser(params),
|
|
77
|
+
{
|
|
78
|
+
handleSuccess: (response: PaginationResponse<LogActivityGroupedUserDTO<LogActivityGroupedUserDTO[]>>) =>
|
|
79
|
+
dispatch(setActivityLog(response)),
|
|
80
|
+
handleError: (response: unknown) => onError(response, dispatch),
|
|
81
|
+
},
|
|
82
|
+
[appActions.setIsLoading],
|
|
83
|
+
)(dispatch),
|
|
84
|
+
);
|
|
73
85
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
},
|
|
88
|
-
handleError: (response: any) => appActions.handleError(response)(dispatch),
|
|
89
|
-
};
|
|
86
|
+
// The export comes back as a blob and is handed to the browser through a throwaway
|
|
87
|
+
// `<a download>`. Kept verbatim from the legacy thunk, side effects included: it is the only
|
|
88
|
+
// place in the area that touches the DOM.
|
|
89
|
+
const saveExport = (payload: BlobPart, extension: string) => {
|
|
90
|
+
const dateString = format(new Date(), "yyyy-MM-dd");
|
|
91
|
+
const url = window.URL.createObjectURL(new Blob([payload]));
|
|
92
|
+
const a = document.createElement("a");
|
|
93
|
+
a.href = url;
|
|
94
|
+
a.download = `activity-log-${dateString}.${extension}`;
|
|
95
|
+
document.body.appendChild(a);
|
|
96
|
+
a.click();
|
|
97
|
+
window.URL.revokeObjectURL(url);
|
|
98
|
+
};
|
|
90
99
|
|
|
91
|
-
|
|
100
|
+
export const downloadActivityLog = createAsyncThunk(
|
|
101
|
+
"activityLog/downloadActivityLog",
|
|
102
|
+
// No loading flag, as before: the export runs without the screen's spinner.
|
|
103
|
+
async (data: LogActivityExportRequest, { dispatch }) =>
|
|
104
|
+
handleRequest(
|
|
105
|
+
async () => logs.logExport(data),
|
|
106
|
+
{
|
|
107
|
+
handleSuccess: (response: BlobPart) => saveExport(response, data.format[0]),
|
|
108
|
+
handleError: (response: unknown) => onError(response, dispatch),
|
|
109
|
+
},
|
|
110
|
+
[],
|
|
111
|
+
)(dispatch),
|
|
112
|
+
);
|
|
92
113
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
};
|
|
114
|
+
interface IGoToLogContentArgs {
|
|
115
|
+
contentType: LogContentTypeDTO | null;
|
|
116
|
+
content: LogContentDTO | null;
|
|
117
|
+
site: number | null;
|
|
98
118
|
}
|
|
99
119
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
content:
|
|
103
|
-
site: number | null,
|
|
104
|
-
): (dispatch: Dispatch, getState: () => IRootState) => void {
|
|
105
|
-
return async (dispatch, getState) => {
|
|
120
|
+
export const goToLogContent = createAppAsyncThunk(
|
|
121
|
+
"activityLog/goToLogContent",
|
|
122
|
+
async ({ contentType, content, site }: IGoToLogContentArgs, { dispatch, getState }) => {
|
|
106
123
|
const {
|
|
107
124
|
app: { lang, globalLangs },
|
|
108
125
|
} = getState();
|
|
@@ -257,7 +274,5 @@ function goToLogContent(
|
|
|
257
274
|
break;
|
|
258
275
|
}
|
|
259
276
|
}
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
export { getActivityLog, getActivityLogByUser, setListMode, downloadActivityLog, goToLogContent };
|
|
277
|
+
},
|
|
278
|
+
);
|
|
@@ -1,4 +1,15 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import { setAnalytics } from "./slice";
|
|
2
|
+
import { deleteScriptCode, getAnalytics, updateAnalytics, updateScriptCode } from "./thunks";
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
const analyticsActions = {
|
|
5
|
+
setAnalytics,
|
|
6
|
+
getAnalytics,
|
|
7
|
+
updateAnalytics,
|
|
8
|
+
updateScriptCode,
|
|
9
|
+
deleteScriptCode,
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export { analyticsActions };
|
|
13
|
+
|
|
14
|
+
export type { IAnalyticsState } from "./slice";
|
|
15
|
+
export { analyticsInitialState, analyticsReducer } from "./slice";
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { IAnalytics, IDimension, IDimensionsGroup } from "@ax/types";
|
|
2
|
+
|
|
3
|
+
import { createSlice, type PayloadAction } from "@reduxjs/toolkit";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The `analytics` area holds the tracking script and the Data Layer definition
|
|
7
|
+
* (dimensions and their groups) of whatever scope is loaded — global or one site
|
|
8
|
+
* (UC-AX-configure-seo-analytics). There is a single slot: loading a site
|
|
9
|
+
* overwrites what global left, which is why the two settings screens reload on mount.
|
|
10
|
+
*
|
|
11
|
+
* STAYS in redux-persist, against the rule of ADR 0002: `AnalyticsField` (the page
|
|
12
|
+
* editor's SEO/analytics field) reads `groups` from here and never fetches, so the
|
|
13
|
+
* persist is what feeds it after a hard refresh. It decides which of two different
|
|
14
|
+
* fields to render, so an empty `groups` is not a flash but the wrong editor. Leaves
|
|
15
|
+
* when PageEditor migrates (wave 4).
|
|
16
|
+
*/
|
|
17
|
+
export interface IAnalyticsState {
|
|
18
|
+
scriptCode: string;
|
|
19
|
+
siteScriptCodeExists: boolean;
|
|
20
|
+
dimensions: IDimension[];
|
|
21
|
+
groups: IDimensionsGroup[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const initialState: IAnalyticsState = {
|
|
25
|
+
scriptCode: "",
|
|
26
|
+
siteScriptCodeExists: false,
|
|
27
|
+
dimensions: [],
|
|
28
|
+
groups: [],
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const analyticsSlice = createSlice({
|
|
32
|
+
name: "analytics",
|
|
33
|
+
initialState,
|
|
34
|
+
reducers: {
|
|
35
|
+
// MERGES, it does not replace: the legacy reducer spread the payload onto the
|
|
36
|
+
// state and `Sites/actions.tsx` dispatches this with whatever the site's
|
|
37
|
+
// metrics endpoint returned, which need not carry every field. Typed as a
|
|
38
|
+
// partial to say so out loud.
|
|
39
|
+
setAnalytics(state, action: PayloadAction<Partial<IAnalytics>>) {
|
|
40
|
+
Object.assign(state, action.payload);
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
export const { setAnalytics } = analyticsSlice.actions;
|
|
46
|
+
|
|
47
|
+
export { initialState as analyticsInitialState };
|
|
48
|
+
export const analyticsReducer = analyticsSlice.reducer;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { analytics } from "@ax/api";
|
|
2
|
+
import { appActions } from "@ax/containers/App";
|
|
3
|
+
import { handleRequest } from "@ax/helpers";
|
|
4
|
+
import type { IAnalytics } from "@ax/types";
|
|
5
|
+
|
|
6
|
+
import { createAsyncThunk } from "@reduxjs/toolkit";
|
|
7
|
+
import type { Dispatch } from "redux";
|
|
8
|
+
|
|
9
|
+
import { setAnalytics } from "./slice";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Read and write side of UC-AX-configure-seo-analytics, as createAsyncThunk. They
|
|
13
|
+
* keep calling `handleRequest` inside — see the Languages pilot and §5 of
|
|
14
|
+
* docs/RTK-MIGRATION.md.
|
|
15
|
+
*
|
|
16
|
+
* `siteId` is `null` for the global scope, which the API spells `"global"`. The
|
|
17
|
+
* legacy signatures made the argument optional; createAsyncThunk takes exactly one
|
|
18
|
+
* argument, so the scope is now always explicit at the call site. That is the whole
|
|
19
|
+
* of the churn this migration pushes onto the callers.
|
|
20
|
+
*/
|
|
21
|
+
const onError = (response: unknown, dispatch: Dispatch, isMultiple = false, message?: string) =>
|
|
22
|
+
appActions.handleError(response, isMultiple, message)(dispatch);
|
|
23
|
+
|
|
24
|
+
const scopeOf = (siteId: number | null) => siteId ?? "global";
|
|
25
|
+
|
|
26
|
+
export const getAnalytics = createAsyncThunk("analytics/getAnalytics", async (siteId: number | null, { dispatch }) =>
|
|
27
|
+
handleRequest(
|
|
28
|
+
async () => analytics.getAnalytics(scopeOf(siteId)),
|
|
29
|
+
{
|
|
30
|
+
handleSuccess: (data: Partial<IAnalytics>) => dispatch(setAnalytics(data)),
|
|
31
|
+
handleError: (response: unknown) => onError(response, dispatch),
|
|
32
|
+
},
|
|
33
|
+
[],
|
|
34
|
+
)(dispatch),
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
interface IUpdateAnalyticsArgs {
|
|
38
|
+
analytics: IAnalytics;
|
|
39
|
+
siteId: number | null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const updateAnalytics = createAsyncThunk(
|
|
43
|
+
"analytics/updateAnalytics",
|
|
44
|
+
async ({ analytics: state, siteId }: IUpdateAnalyticsArgs, { dispatch }) => {
|
|
45
|
+
const { scriptCode, dimensions, groups } = state;
|
|
46
|
+
const scope = scopeOf(siteId);
|
|
47
|
+
|
|
48
|
+
return handleRequest(
|
|
49
|
+
// The three writes go out in PARALLEL and `handleRequest` treats the array
|
|
50
|
+
// of responses as a whole: any one of them failing fails the save.
|
|
51
|
+
async () =>
|
|
52
|
+
Promise.all([
|
|
53
|
+
analytics.updateScriptCode(scope, { scriptCode }),
|
|
54
|
+
analytics.createDimensions(scope, { dimensions }),
|
|
55
|
+
analytics.createDimensionsGroups(scope, { groups }),
|
|
56
|
+
]),
|
|
57
|
+
{
|
|
58
|
+
// Not awaited, as the legacy version did not await it either: the button
|
|
59
|
+
// stops saying "Saving" before the reload lands.
|
|
60
|
+
handleSuccess: () => dispatch(getAnalytics(siteId)),
|
|
61
|
+
handleError: (response: { data: { message?: string | string[] } }) => {
|
|
62
|
+
const { message } = response.data;
|
|
63
|
+
const isMultiple = Array.isArray(message) && message.length > 1;
|
|
64
|
+
const msg = isMultiple ? `The action failed due to ${message.length} errors.` : undefined;
|
|
65
|
+
onError(response, dispatch, isMultiple, msg);
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
[appActions.setIsSaving],
|
|
69
|
+
)(dispatch);
|
|
70
|
+
},
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
interface IUpdateScriptCodeArgs {
|
|
74
|
+
scriptCode: string;
|
|
75
|
+
siteId: number | null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export const updateScriptCode = createAsyncThunk(
|
|
79
|
+
"analytics/updateScriptCode",
|
|
80
|
+
async ({ scriptCode, siteId }: IUpdateScriptCodeArgs, { dispatch }) =>
|
|
81
|
+
handleRequest(
|
|
82
|
+
async () => analytics.updateScriptCode(scopeOf(siteId), { scriptCode }),
|
|
83
|
+
{
|
|
84
|
+
handleSuccess: () => dispatch(getAnalytics(siteId)),
|
|
85
|
+
handleError: (response: unknown) => onError(response, dispatch),
|
|
86
|
+
},
|
|
87
|
+
[appActions.setIsSaving],
|
|
88
|
+
)(dispatch),
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
export const deleteScriptCode = createAsyncThunk(
|
|
92
|
+
"analytics/deleteScriptCode",
|
|
93
|
+
// No loading flag, as before: resetting to the global defaults shows no spinner.
|
|
94
|
+
async (siteId: number | null, { dispatch }) =>
|
|
95
|
+
handleRequest(
|
|
96
|
+
async () => analytics.deleteScriptCode(scopeOf(siteId)),
|
|
97
|
+
{
|
|
98
|
+
handleSuccess: () => dispatch(getAnalytics(siteId)),
|
|
99
|
+
handleError: (response: unknown) => onError(response, dispatch),
|
|
100
|
+
},
|
|
101
|
+
[],
|
|
102
|
+
)(dispatch),
|
|
103
|
+
);
|
|
@@ -1,4 +1,13 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import { setDomainsRobots } from "./slice";
|
|
2
|
+
import { getDomainsRobots, updateDomainsRobots } from "./thunks";
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
const domainsActions = {
|
|
5
|
+
setDomainsRobots,
|
|
6
|
+
getDomainsRobots,
|
|
7
|
+
updateDomainsRobots,
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export { domainsActions };
|
|
11
|
+
|
|
12
|
+
export type { IDomainsState } from "./slice";
|
|
13
|
+
export { domainsInitialState, domainsReducer } from "./slice";
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { IDomainRobot } from "@ax/types";
|
|
2
|
+
|
|
3
|
+
import { createSlice, type PayloadAction } from "@reduxjs/toolkit";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The `domains` area holds the robots.txt of every domain (UC-AX-configure-seo-analytics,
|
|
7
|
+
* `/settings/robots`). Despite the name it is not a domain manager: the list of
|
|
8
|
+
* domains a site has is read through `sites`, and this slice only carries what the
|
|
9
|
+
* robots.txt editor renders.
|
|
10
|
+
*
|
|
11
|
+
* Out of redux-persist since this migration (ADR 0002): the screen calls
|
|
12
|
+
* `getDomainsRobots()` on mount, so the persisted copy was never read.
|
|
13
|
+
*/
|
|
14
|
+
export interface IDomainsState {
|
|
15
|
+
robots: IDomainRobot[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const initialState: IDomainsState = {
|
|
19
|
+
robots: [],
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const domainsSlice = createSlice({
|
|
23
|
+
name: "domains",
|
|
24
|
+
initialState,
|
|
25
|
+
reducers: {
|
|
26
|
+
// The legacy action wrapped the list in `{ robots }` to spread it onto the
|
|
27
|
+
// state; as a reducer the payload is just the list.
|
|
28
|
+
setDomainsRobots(state, action: PayloadAction<IDomainRobot[]>) {
|
|
29
|
+
state.robots = action.payload;
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
export const { setDomainsRobots } = domainsSlice.actions;
|
|
35
|
+
|
|
36
|
+
export { initialState as domainsInitialState };
|
|
37
|
+
export const domainsReducer = domainsSlice.reducer;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { domains } from "@ax/api";
|
|
2
|
+
import { appActions } from "@ax/containers/App";
|
|
3
|
+
import { handleRequest } from "@ax/helpers";
|
|
4
|
+
import type { IDomainRobot } from "@ax/types";
|
|
5
|
+
|
|
6
|
+
import { createAsyncThunk } from "@reduxjs/toolkit";
|
|
7
|
+
import type { Dispatch } from "redux";
|
|
8
|
+
|
|
9
|
+
import { setDomainsRobots } from "./slice";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Read and write side of the robots.txt editor, as createAsyncThunk.
|
|
13
|
+
*
|
|
14
|
+
* They keep calling `handleRequest` inside for the same reason the Languages
|
|
15
|
+
* pilot does: that helper owns the loading-flag and error-channel conventions of
|
|
16
|
+
* the whole codebase (152 call-sites) and replacing it is a cross-cutting job,
|
|
17
|
+
* not this area's — see docs/RTK-MIGRATION.md §5.
|
|
18
|
+
*
|
|
19
|
+
* What does change: the legacy versions wrapped everything in a `try/catch` whose
|
|
20
|
+
* body was `console.log(e)`, swallowing failures. createAsyncThunk turns a throw
|
|
21
|
+
* into a `rejected` action instead, so the failure is observable.
|
|
22
|
+
*/
|
|
23
|
+
// Called through the namespace rather than destructured at module scope: a
|
|
24
|
+
// destructured reference is frozen at import time and cannot be spied on, which
|
|
25
|
+
// would make the reload-after-save behaviour untestable.
|
|
26
|
+
const onError = (response: unknown, dispatch: Dispatch, isMultiple = false, message?: string) =>
|
|
27
|
+
appActions.handleError(response, isMultiple, message)(dispatch);
|
|
28
|
+
|
|
29
|
+
export const getDomainsRobots = createAsyncThunk<boolean, void>(
|
|
30
|
+
"domains/getDomainsRobots",
|
|
31
|
+
async (_arg, { dispatch }) =>
|
|
32
|
+
handleRequest(
|
|
33
|
+
async () => domains.getDomainsRobots(),
|
|
34
|
+
{
|
|
35
|
+
handleSuccess: (robots: IDomainRobot[]) => dispatch(setDomainsRobots(robots)),
|
|
36
|
+
handleError: (response: unknown) => onError(response, dispatch),
|
|
37
|
+
},
|
|
38
|
+
[appActions.setIsLoading],
|
|
39
|
+
)(dispatch),
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
export const updateDomainsRobots = createAsyncThunk(
|
|
43
|
+
"domains/updateDomainsRobots",
|
|
44
|
+
async (robots: IDomainRobot[], { dispatch }) =>
|
|
45
|
+
handleRequest(
|
|
46
|
+
async () => domains.updateDomainsRobots(robots),
|
|
47
|
+
{
|
|
48
|
+
// Reloading from the server is what makes `computedContent` (the
|
|
49
|
+
// inherited rules, read-only) reflect the content just saved.
|
|
50
|
+
handleSuccess: () => dispatch(getDomainsRobots()),
|
|
51
|
+
handleError: (response: { data: { message?: string | string[] } }) => {
|
|
52
|
+
const { message } = response.data;
|
|
53
|
+
const isMultiple = Array.isArray(message) && message.length > 1;
|
|
54
|
+
const msg = isMultiple ? `The action failed due to ${message.length} errors.` : undefined;
|
|
55
|
+
onError(response, dispatch, isMultiple, msg);
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
[appActions.setIsSaving],
|
|
59
|
+
)(dispatch),
|
|
60
|
+
);
|
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
const NAME = "redirects";
|
|
2
|
-
|
|
3
|
-
const SET_REDIRECTS = `${NAME}/SET_REDIRECTS`;
|
|
4
|
-
const SET_TOTAL_ITEMS = `${NAME}/SET_TOTAL_ITEMS`;
|
|
5
|
-
const SET_TOTAL_IMPORTS = `${NAME}/SET_TOTAL_IMPORTS`;
|
|
6
|
-
const SET_IMPORTS = `${NAME}/SET_IMPORTS`;
|
|
7
|
-
|
|
8
1
|
const ITEMS_PER_PAGE = 50;
|
|
9
2
|
|
|
10
3
|
const DEFAULT_PARAMS = {
|
|
@@ -17,13 +10,4 @@ const ERROR_CODE = {
|
|
|
17
10
|
isBeingUsed: 409,
|
|
18
11
|
};
|
|
19
12
|
|
|
20
|
-
export {
|
|
21
|
-
NAME,
|
|
22
|
-
SET_REDIRECTS,
|
|
23
|
-
SET_TOTAL_ITEMS,
|
|
24
|
-
ERROR_CODE,
|
|
25
|
-
DEFAULT_PARAMS,
|
|
26
|
-
ITEMS_PER_PAGE,
|
|
27
|
-
SET_IMPORTS,
|
|
28
|
-
SET_TOTAL_IMPORTS,
|
|
29
|
-
};
|
|
13
|
+
export { ERROR_CODE, DEFAULT_PARAMS, ITEMS_PER_PAGE };
|
|
@@ -1,4 +1,15 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { redirectsReducer } from "./reducer";
|
|
1
|
+
import { addRedirect, deleteRedirect, getRedirects, importRedirects, updateRedirect } from "./thunks";
|
|
3
2
|
|
|
4
|
-
|
|
3
|
+
const redirectsActions = {
|
|
4
|
+
getRedirects,
|
|
5
|
+
addRedirect,
|
|
6
|
+
updateRedirect,
|
|
7
|
+
deleteRedirect,
|
|
8
|
+
importRedirects,
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export { redirectsActions };
|
|
12
|
+
|
|
13
|
+
export type { IRedirectsState } from "./slice";
|
|
14
|
+
export { redirectsInitialState, redirectsReducer } from "./slice";
|
|
15
|
+
export type { IAddRedirectResult, IRedirectsQuery } from "./thunks";
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { IRedirect } from "@ax/types";
|
|
2
|
+
|
|
3
|
+
import { createSlice, type PayloadAction } from "@reduxjs/toolkit";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The `redirects` area holds ONE page of the redirect catalogue plus the result of the
|
|
7
|
+
* last CSV import check (UC-AX-manage-redirects). Paging, searching and filtering live in
|
|
8
|
+
* the view, so the list is whatever page was asked for last and `totalItems` is what the
|
|
9
|
+
* pager counts with.
|
|
10
|
+
*
|
|
11
|
+
* The four setters stay internal to the area: nothing outside dispatches them — the legacy
|
|
12
|
+
* `actions.tsx` did not export them either — so the barrel keeps them private and the
|
|
13
|
+
* public surface is just the five thunks.
|
|
14
|
+
*
|
|
15
|
+
* Out of redux-persist since this migration (ADR 0002).
|
|
16
|
+
*/
|
|
17
|
+
export interface IRedirectsState {
|
|
18
|
+
redirects: IRedirect[];
|
|
19
|
+
totalItems: number;
|
|
20
|
+
totalImports: number;
|
|
21
|
+
imports: null | {
|
|
22
|
+
error: IRedirect[];
|
|
23
|
+
existing: IRedirect[];
|
|
24
|
+
ok: IRedirect[];
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const initialState: IRedirectsState = {
|
|
29
|
+
redirects: [],
|
|
30
|
+
totalItems: 0,
|
|
31
|
+
totalImports: 0,
|
|
32
|
+
imports: null,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const redirectsSlice = createSlice({
|
|
36
|
+
name: "redirects",
|
|
37
|
+
initialState,
|
|
38
|
+
// The legacy reducer had all four action types share one `return { ...state, ...payload }`
|
|
39
|
+
// branch, which worked because each payload carried exactly one key. One reducer per
|
|
40
|
+
// field says the same thing without the indirection.
|
|
41
|
+
reducers: {
|
|
42
|
+
setRedirects(state, action: PayloadAction<IRedirect[]>) {
|
|
43
|
+
state.redirects = action.payload;
|
|
44
|
+
},
|
|
45
|
+
setTotalItems(state, action: PayloadAction<number>) {
|
|
46
|
+
state.totalItems = action.payload;
|
|
47
|
+
},
|
|
48
|
+
setImports(state, action: PayloadAction<IRedirectsState["imports"]>) {
|
|
49
|
+
state.imports = action.payload;
|
|
50
|
+
},
|
|
51
|
+
setTotalImports(state, action: PayloadAction<number>) {
|
|
52
|
+
state.totalImports = action.payload;
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
export const { setRedirects, setTotalItems, setImports, setTotalImports } = redirectsSlice.actions;
|
|
58
|
+
|
|
59
|
+
export { initialState as redirectsInitialState };
|
|
60
|
+
export const redirectsReducer = redirectsSlice.reducer;
|