@griddo/ax 12.6.1 → 12.6.2
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 +0 -1
- package/package.json +4 -3
- package/src/GlobalStore.tsx +35 -91
- package/src/__tests__/hooks/heartbeat.test.tsx +88 -0
- package/src/components/MainWrapper/index.tsx +2 -14
- package/src/containers/ActivityLog/{thunks.ts → actions.tsx} +89 -104
- package/src/containers/ActivityLog/constants.tsx +6 -0
- package/src/containers/ActivityLog/index.tsx +3 -16
- package/src/containers/ActivityLog/interfaces.tsx +12 -0
- package/src/containers/ActivityLog/reducer.tsx +24 -0
- package/src/containers/Analytics/actions.tsx +109 -0
- package/src/containers/Analytics/constants.tsx +5 -0
- package/src/containers/Analytics/index.tsx +3 -14
- package/src/containers/Analytics/interfaces.tsx +9 -0
- package/src/containers/Analytics/reducer.tsx +28 -0
- package/src/containers/Domains/actions.tsx +58 -0
- package/src/containers/Domains/constants.tsx +5 -0
- package/src/containers/Domains/index.tsx +3 -12
- package/src/containers/Domains/interfaces.tsx +9 -0
- package/src/containers/Domains/reducer.tsx +22 -0
- package/src/containers/Redirects/actions.tsx +190 -0
- package/src/containers/Redirects/constants.tsx +17 -1
- package/src/containers/Redirects/index.tsx +3 -14
- package/src/containers/Redirects/interfaces.tsx +23 -0
- package/src/containers/Redirects/reducer.tsx +35 -0
- package/src/containers/Settings/Languages/actions.tsx +88 -0
- package/src/containers/Settings/Languages/constants.tsx +6 -0
- package/src/containers/Settings/Languages/index.tsx +3 -15
- package/src/containers/Settings/Languages/reducer.tsx +31 -0
- package/src/containers/Settings/Social/actions.tsx +63 -0
- package/src/containers/Settings/Social/constants.tsx +3 -0
- package/src/containers/Settings/Social/index.tsx +3 -11
- package/src/containers/Settings/Social/interfaces.tsx +9 -0
- package/src/containers/Settings/Social/reducer.tsx +18 -0
- package/src/containers/Sites/actions.tsx +2 -6
- package/src/hooks/heartbeat.ts +32 -0
- package/src/hooks/index.tsx +2 -0
- package/src/index.tsx +6 -7
- package/src/modules/ActivityLog/ItemLog/EventItem/index.tsx +27 -13
- package/src/modules/ActivityLog/ItemLogUser/UserItem/EventItem/index.tsx +27 -13
- package/src/modules/ActivityLog/index.tsx +51 -22
- package/src/modules/Analytics/index.tsx +42 -33
- package/src/modules/App/Routing/index.tsx +4 -5
- package/src/modules/FileDrive/index.tsx +1 -1
- package/src/modules/GlobalEditor/index.tsx +6 -8
- package/src/modules/GlobalSettings/Robots/index.tsx +47 -24
- package/src/modules/MediaGallery/index.tsx +1 -1
- package/src/modules/PageEditor/index.tsx +5 -9
- package/src/modules/Redirects/RedirectItem/index.tsx +22 -9
- package/src/modules/Redirects/RedirectPanel/index.tsx +14 -13
- package/src/modules/Redirects/index.tsx +69 -26
- package/src/modules/Settings/ContentTypes/DataPacks/index.tsx +3 -4
- package/src/modules/Settings/Languages/LanguagePanel/Form/ConnectedField/index.tsx +18 -11
- package/src/modules/Settings/Languages/LanguagePanel/Form/index.tsx +9 -7
- package/src/modules/Settings/Languages/LanguagePanel/index.tsx +44 -38
- package/src/modules/Settings/Languages/Table/Header/index.tsx +19 -12
- package/src/modules/Settings/Languages/Table/Item/index.tsx +36 -21
- package/src/modules/Settings/Languages/Table/index.tsx +14 -6
- package/src/modules/Settings/Languages/index.tsx +30 -18
- package/src/modules/Settings/SeoAnalyticsSettings/Analytics/index.tsx +52 -42
- package/src/modules/Settings/Social/index.tsx +12 -17
- package/src/modules/StructuredData/StructuredDataList/index.tsx +2 -3
- package/src/types/index.tsx +6 -5
- package/tsconfig.paths.json +0 -1
- package/src/__tests__/modules/ActivityLog/ActivityLog.uc.test.tsx +0 -132
- package/src/__tests__/modules/Analytics/Analytics.uc.test.tsx +0 -168
- package/src/__tests__/modules/GlobalSettings/Robots/Robots.test.tsx +0 -110
- package/src/__tests__/modules/GlobalSettings/Robots/Robots.uc.test.tsx +0 -125
- package/src/__tests__/modules/Redirects/Redirects.uc.test.tsx +0 -164
- package/src/__tests__/modules/Settings/Languages/Languages.uc.test.tsx +0 -213
- package/src/__tests__/modules/Settings/Social/Social.uc.test.tsx +0 -171
- package/src/__tests__/store/activityLog.test.ts +0 -301
- package/src/__tests__/store/analytics.test.ts +0 -134
- package/src/__tests__/store/domains.test.ts +0 -107
- package/src/__tests__/store/languages.test.ts +0 -129
- package/src/__tests__/store/persist.test.ts +0 -47
- package/src/__tests__/store/redirects.test.ts +0 -313
- package/src/__tests__/store/social.test.ts +0 -165
- package/src/containers/ActivityLog/slice.ts +0 -45
- package/src/containers/Analytics/slice.ts +0 -48
- package/src/containers/Analytics/thunks.ts +0 -103
- package/src/containers/Domains/slice.ts +0 -37
- package/src/containers/Domains/thunks.ts +0 -60
- package/src/containers/Redirects/slice.ts +0 -60
- package/src/containers/Redirects/thunks.ts +0 -196
- package/src/containers/Settings/Languages/slice.ts +0 -48
- package/src/containers/Settings/Languages/thunks.ts +0 -83
- package/src/containers/Settings/Social/slice.ts +0 -40
- package/src/containers/Settings/Social/thunks.ts +0 -68
- package/src/store/hooks.ts +0 -37
- package/src/store/index.ts +0 -2
|
@@ -87,7 +87,6 @@ const griddoAxAliases = {
|
|
|
87
87
|
"@ax/routes": path.resolve(__dirname, "../../src/routes"),
|
|
88
88
|
"@ax/types": path.resolve(__dirname, "../../src/types"),
|
|
89
89
|
"@ax/schemas": path.resolve(__dirname, "../../src/schemas"),
|
|
90
|
-
"@ax/store": path.resolve(__dirname, "../../src/store"),
|
|
91
90
|
"@ax/locales": path.resolve(__dirname, "../../src/locales"),
|
|
92
91
|
"@ax/constants": path.resolve(__dirname, "../../src/constants"),
|
|
93
92
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@griddo/ax",
|
|
3
3
|
"description": "Griddo Author Experience",
|
|
4
|
-
"version": "12.6.
|
|
4
|
+
"version": "12.6.2",
|
|
5
5
|
"authors": [
|
|
6
6
|
"Álvaro Sánchez' <alvaro.sanches@secuoyas.com>",
|
|
7
7
|
"Diego M. Béjar <diego.bejar@secuoyas.com>",
|
|
@@ -46,7 +46,6 @@
|
|
|
46
46
|
"@dnd-kit/sortable": "10.0.0",
|
|
47
47
|
"@griddo/api-types": "^1.0.12",
|
|
48
48
|
"@pmmmwh/react-refresh-webpack-plugin": "0.5.16",
|
|
49
|
-
"@reduxjs/toolkit": "2.9.0",
|
|
50
49
|
"@styled-system/prop-types": "5.1.5",
|
|
51
50
|
"@styled-system/theme-get": "5.1.2",
|
|
52
51
|
"@svgr/webpack": "8.1.0",
|
|
@@ -78,6 +77,7 @@
|
|
|
78
77
|
"babel-preset-react-app": "10.1.0",
|
|
79
78
|
"case-sensitive-paths-webpack-plugin": "2.4.0",
|
|
80
79
|
"compress.js": "1.2.2",
|
|
80
|
+
"connected-react-router": "6.9.3",
|
|
81
81
|
"css-loader": "6.11.0",
|
|
82
82
|
"css-minimizer-webpack-plugin": "5.0.1",
|
|
83
83
|
"date-fns": "2.30.0",
|
|
@@ -126,6 +126,7 @@
|
|
|
126
126
|
"react-textarea-autosize": "8.5.9",
|
|
127
127
|
"react-timezone-map-select": "1.2.25",
|
|
128
128
|
"redux": "4.2.1",
|
|
129
|
+
"redux-devtools-extension": "2.13.9",
|
|
129
130
|
"redux-persist": "6.0.0",
|
|
130
131
|
"reflect-metadata": "0.1.13",
|
|
131
132
|
"resolve": "1.22.10",
|
|
@@ -200,5 +201,5 @@
|
|
|
200
201
|
"publishConfig": {
|
|
201
202
|
"access": "public"
|
|
202
203
|
},
|
|
203
|
-
"gitHead": "
|
|
204
|
+
"gitHead": "1e6a3ad08eb17a4c57f80fffb65193a82a577969"
|
|
204
205
|
}
|
package/src/GlobalStore.tsx
CHANGED
|
@@ -1,78 +1,35 @@
|
|
|
1
|
-
import type
|
|
2
|
-
|
|
3
|
-
import { configureStore } from "@reduxjs/toolkit";
|
|
4
|
-
import { type Action, combineReducers, type Reducer } from "redux";
|
|
5
|
-
import {
|
|
6
|
-
FLUSH,
|
|
7
|
-
PAUSE,
|
|
8
|
-
PERSIST,
|
|
9
|
-
type PersistConfig,
|
|
10
|
-
type PersistedState,
|
|
11
|
-
type Persistor,
|
|
12
|
-
PURGE,
|
|
13
|
-
persistReducer,
|
|
14
|
-
persistStore,
|
|
15
|
-
REGISTER,
|
|
16
|
-
REHYDRATE,
|
|
17
|
-
} from "redux-persist";
|
|
1
|
+
import { createStore, type Reducer, combineReducers, applyMiddleware, type Action, type StoreEnhancer } from "redux";
|
|
2
|
+
import { persistReducer, persistStore, type PersistConfig, type Persistor } from "redux-persist";
|
|
18
3
|
import storage from "redux-persist/lib/storage"; // defaults to localStorage for web
|
|
4
|
+
import { connectRouter, routerMiddleware } from "connected-react-router";
|
|
5
|
+
import { composeWithDevTools } from "redux-devtools-extension";
|
|
6
|
+
import thunk from "redux-thunk";
|
|
19
7
|
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
8
|
+
import { appReducer, appInitialState } from "./containers/App/reducer";
|
|
9
|
+
import { sitesReducer, sitesInitialState } from "./containers/Sites/reducer";
|
|
10
|
+
import { pageEditorReducer, pageEditorInitialState } from "./containers/PageEditor/reducer";
|
|
11
|
+
import { menuReducer, menuInitialState } from "./containers/Navigation/Menu/reducer";
|
|
12
|
+
import { navigationReducer, navigationInitialState } from "./containers/Navigation/Defaults/reducer";
|
|
13
|
+
import { structuredDataReducer, structuredDataInitialState } from "./containers/StructuredData/reducer";
|
|
14
|
+
import { languagesReducer, languagesInitialState } from "./containers/Settings/Languages/reducer";
|
|
15
|
+
import { dataPacksReducer, dataPacksInitialState } from "./containers/Settings/DataPacks/reducer";
|
|
16
|
+
import { socialReducer, socialInitialState } from "./containers/Settings/Social/reducer";
|
|
17
|
+
import { usersReducer, usersInitialState } from "./containers/Users/reducer";
|
|
18
|
+
import { galleryReducer, galleryInitialState } from "./containers/Gallery/reducer";
|
|
19
|
+
import { domainsReducer, domainsInitialState } from "./containers/Domains/reducer";
|
|
20
|
+
import { redirectsReducer, redirectsInitialState } from "./containers/Redirects/reducer";
|
|
21
|
+
import { analyticsReducer, analyticsInitialState } from "./containers/Analytics/reducer";
|
|
22
|
+
import { integrationsReducer, integrationsInitialState } from "./containers/Integrations/reducer";
|
|
23
|
+
import { fileDriveReducer, fileDriveInitialState } from "./containers/FileDrive/reducer";
|
|
24
|
+
import { formsReducer, formsInitialState } from "./containers/Forms/reducer";
|
|
25
|
+
import { activityLogReducer, activityLogInitialState } from "./containers/ActivityLog/reducer";
|
|
22
26
|
import { LOGOUT } from "./containers/App/constants";
|
|
23
|
-
import { appInitialState, appReducer } from "./containers/App/reducer";
|
|
24
|
-
import { domainsInitialState, domainsReducer } from "./containers/Domains/slice";
|
|
25
|
-
import { fileDriveInitialState, fileDriveReducer } from "./containers/FileDrive/reducer";
|
|
26
|
-
import { formsInitialState, formsReducer } from "./containers/Forms/reducer";
|
|
27
|
-
import { galleryInitialState, galleryReducer } from "./containers/Gallery/reducer";
|
|
28
|
-
import { integrationsInitialState, integrationsReducer } from "./containers/Integrations/reducer";
|
|
29
|
-
import { navigationInitialState, navigationReducer } from "./containers/Navigation/Defaults/reducer";
|
|
30
|
-
import { menuInitialState, menuReducer } from "./containers/Navigation/Menu/reducer";
|
|
31
|
-
import { pageEditorInitialState, pageEditorReducer } from "./containers/PageEditor/reducer";
|
|
32
|
-
import { redirectsInitialState, redirectsReducer } from "./containers/Redirects/slice";
|
|
33
|
-
import { dataPacksInitialState, dataPacksReducer } from "./containers/Settings/DataPacks/reducer";
|
|
34
|
-
import { languagesInitialState, languagesReducer } from "./containers/Settings/Languages/slice";
|
|
35
|
-
import { socialInitialState, socialReducer } from "./containers/Settings/Social/slice";
|
|
36
|
-
import { sitesInitialState, sitesReducer } from "./containers/Sites/reducer";
|
|
37
|
-
import { structuredDataInitialState, structuredDataReducer } from "./containers/StructuredData/reducer";
|
|
38
|
-
import { usersInitialState, usersReducer } from "./containers/Users/reducer";
|
|
39
|
-
|
|
40
|
-
// Every area migrated to RTK leaves redux-persist (docs/adr/0002): its state is a
|
|
41
|
-
// copy of the server's that the screen reloads on mount, so persisting it only
|
|
42
|
-
// meant starting up with stale data. `app` stays in — it carries the session.
|
|
43
|
-
//
|
|
44
|
-
// The test is that EVERY consumer reloads it, not just its settings screen, so
|
|
45
|
-
// `social` and `analytics` stay in for now: six page-editor screens read `state.social`
|
|
46
|
-
// and `AnalyticsField` reads `state.analytics`, and none of them fetches — the persist
|
|
47
|
-
// was their only loader on a hard refresh. They leave in waves 2-4 with those consumers
|
|
48
|
-
// (docs/RTK-MIGRATION.md §5).
|
|
49
|
-
const blacklist = ["menu", "app", "languages", "domains", "redirects", "activityLog"];
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* redux-persist applies the blacklist only when WRITING: `createPersistoid` filters the
|
|
53
|
-
* keys it stages, but `getStoredState` returns every key the previous releases wrote and
|
|
54
|
-
* `autoMergeLevel1` hard-sets them all (`newState[key] = inboundState[key]`). So adding
|
|
55
|
-
* an area to the blacklist does not stop it from being rehydrated once — it only stops
|
|
56
|
-
* the next write.
|
|
57
|
-
*
|
|
58
|
-
* Without this, the first boot after the deploy restores the areas that just left the
|
|
59
|
-
* persist from a stale blob, which is exactly what ADR 0002 removes: state from another
|
|
60
|
-
* site, in a screen that then PUTs it back. Deleting the keys from the inbound state is
|
|
61
|
-
* enough (the first write afterwards replaces the whole blob), and it needs no `version`
|
|
62
|
-
* bump — `persistReducer` calls `migrate` on every rehydrate.
|
|
63
|
-
*/
|
|
64
|
-
export const dropBlacklistedAreas = async (state: PersistedState): Promise<PersistedState> => {
|
|
65
|
-
if (!state) return state;
|
|
66
27
|
|
|
67
|
-
|
|
68
|
-
for (const area of blacklist) {
|
|
69
|
-
delete kept[area];
|
|
70
|
-
}
|
|
71
|
-
return kept as PersistedState;
|
|
72
|
-
};
|
|
28
|
+
import type { IRootState } from "@ax/types";
|
|
73
29
|
|
|
30
|
+
const blacklist = ["router", "menu", "app", "languages"];
|
|
74
31
|
export class GlobalStore {
|
|
75
|
-
private persistConfig: PersistConfig<any> = { key: "root", storage, blacklist
|
|
32
|
+
private persistConfig: PersistConfig<any> = { key: "root", storage, blacklist };
|
|
76
33
|
|
|
77
34
|
private authPersistConfig = {
|
|
78
35
|
key: "app",
|
|
@@ -80,8 +37,11 @@ export class GlobalStore {
|
|
|
80
37
|
blacklist: ["error", "isLoggingIn", "isRehydrated", "isSaving", "loadingCount", "user"],
|
|
81
38
|
};
|
|
82
39
|
|
|
83
|
-
public configureStore() {
|
|
40
|
+
public configureStore(history: History) {
|
|
41
|
+
let middleware: StoreEnhancer = applyMiddleware(thunk, routerMiddleware(history));
|
|
42
|
+
|
|
84
43
|
const globalReducer: Reducer<any> = combineReducers({
|
|
44
|
+
router: connectRouter(history),
|
|
85
45
|
app: persistReducer(this.authPersistConfig, appReducer as Reducer<any, Action<any>>),
|
|
86
46
|
sites: sitesReducer as Reducer<any, Action<any>>,
|
|
87
47
|
pageEditor: pageEditorReducer as Reducer<any, Action<any>>,
|
|
@@ -105,6 +65,7 @@ export class GlobalStore {
|
|
|
105
65
|
const rootReducer = (state: IRootState | undefined, action: any) => {
|
|
106
66
|
if (action.type === LOGOUT) {
|
|
107
67
|
state = {
|
|
68
|
+
router: state?.router,
|
|
108
69
|
app: state?.app || appInitialState,
|
|
109
70
|
sites: sitesInitialState,
|
|
110
71
|
pageEditor: pageEditorInitialState,
|
|
@@ -129,27 +90,10 @@ export class GlobalStore {
|
|
|
129
90
|
return globalReducer(state, action);
|
|
130
91
|
};
|
|
131
92
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
middleware: (getDefaultMiddleware) =>
|
|
137
|
-
getDefaultMiddleware({
|
|
138
|
-
// redux-persist dispatches these with non-serializable payloads by design.
|
|
139
|
-
// Kept ON otherwise: it only logs, and its warnings are the worklist for
|
|
140
|
-
// the offenders documented in docs/RTK-MIGRATION.md §5 (5 Date instances in
|
|
141
|
-
// state, app.error.btnAction holding a function).
|
|
142
|
-
serializableCheck: {
|
|
143
|
-
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
|
|
144
|
-
},
|
|
145
|
-
// OFF on purpose. The three editors (PageEditor, Forms, Navigation/Defaults)
|
|
146
|
-
// mutate live store references and re-dispatch the regenerated tree
|
|
147
|
-
// (docs/RTK-MIGRATION.md §2.1), so this would log on every edit — and it
|
|
148
|
-
// deep-scans the whole tree per action, which is costly on a state this big.
|
|
149
|
-
// Re-enable per area as each editor is sanitised (waves 2-4).
|
|
150
|
-
immutableCheck: false,
|
|
151
|
-
}),
|
|
152
|
-
});
|
|
93
|
+
if (process.env.NODE_ENV !== "production") {
|
|
94
|
+
middleware = composeWithDevTools(middleware);
|
|
95
|
+
}
|
|
96
|
+
const store = createStore(persistReducer(this.persistConfig, rootReducer), middleware);
|
|
153
97
|
const persistor: Persistor = persistStore(store);
|
|
154
98
|
return { store, persistor };
|
|
155
99
|
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import "@testing-library/jest-dom";
|
|
2
|
+
|
|
3
|
+
import { useEditingHeartbeat } from "@ax/hooks";
|
|
4
|
+
|
|
5
|
+
import { renderHook } from "@testing-library/react";
|
|
6
|
+
|
|
7
|
+
const HEARTBEAT = 30000;
|
|
8
|
+
|
|
9
|
+
const ORIGINAL = 213;
|
|
10
|
+
const TRANSLATION = 214;
|
|
11
|
+
|
|
12
|
+
describe("useEditingHeartbeat hook", () => {
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
vi.useFakeTimers();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
vi.useRealTimers();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const renderHeartbeat = (pageID: number | null, sendPagePing: (id: number) => void) =>
|
|
22
|
+
renderHook(({ pageID }) => useEditingHeartbeat(pageID, sendPagePing), { initialProps: { pageID } });
|
|
23
|
+
|
|
24
|
+
it("locks the page on mount, without waiting for the first interval", () => {
|
|
25
|
+
const sendPagePing = vi.fn();
|
|
26
|
+
|
|
27
|
+
renderHeartbeat(ORIGINAL, sendPagePing);
|
|
28
|
+
|
|
29
|
+
expect(sendPagePing).toHaveBeenCalledTimes(1);
|
|
30
|
+
expect(sendPagePing).toHaveBeenCalledWith(ORIGINAL);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("renews the lock every 30 seconds", () => {
|
|
34
|
+
const sendPagePing = vi.fn();
|
|
35
|
+
|
|
36
|
+
renderHeartbeat(ORIGINAL, sendPagePing);
|
|
37
|
+
vi.advanceTimersByTime(HEARTBEAT * 2);
|
|
38
|
+
|
|
39
|
+
// t+0 (mount) + t+30s + t+60s
|
|
40
|
+
expect(sendPagePing).toHaveBeenCalledTimes(3);
|
|
41
|
+
expect(sendPagePing.mock.calls.every(([id]) => id === ORIGINAL)).toBe(true);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// sc-118275: creating a translation from inside the editor changes pageID in the store without
|
|
45
|
+
// remounting. The heartbeat used to be pinned to the id it captured on mount, so it kept
|
|
46
|
+
// renewing the lock of the original page and left the translation unlocked.
|
|
47
|
+
it("follows the page when its id changes under an editor that never remounts", () => {
|
|
48
|
+
const sendPagePing = vi.fn();
|
|
49
|
+
|
|
50
|
+
const { rerender } = renderHeartbeat(ORIGINAL, sendPagePing);
|
|
51
|
+
vi.advanceTimersByTime(HEARTBEAT);
|
|
52
|
+
sendPagePing.mockClear();
|
|
53
|
+
|
|
54
|
+
rerender({ pageID: TRANSLATION });
|
|
55
|
+
|
|
56
|
+
// No 30 second gap: the page actually being edited is locked as soon as it exists.
|
|
57
|
+
expect(sendPagePing).toHaveBeenCalledTimes(1);
|
|
58
|
+
expect(sendPagePing).toHaveBeenCalledWith(TRANSLATION);
|
|
59
|
+
|
|
60
|
+
vi.advanceTimersByTime(HEARTBEAT * 2);
|
|
61
|
+
|
|
62
|
+
expect(sendPagePing).toHaveBeenCalledTimes(3);
|
|
63
|
+
// The original is left alone so its lock expires; renewing it would block the rest of the
|
|
64
|
+
// team on a page nobody is touching.
|
|
65
|
+
expect(sendPagePing).not.toHaveBeenCalledWith(ORIGINAL);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("does not ping while there is no page yet", () => {
|
|
69
|
+
const sendPagePing = vi.fn();
|
|
70
|
+
|
|
71
|
+
renderHeartbeat(null, sendPagePing);
|
|
72
|
+
vi.advanceTimersByTime(HEARTBEAT * 2);
|
|
73
|
+
|
|
74
|
+
expect(sendPagePing).not.toHaveBeenCalled();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("stops renewing the lock once the editor is closed", () => {
|
|
78
|
+
const sendPagePing = vi.fn();
|
|
79
|
+
|
|
80
|
+
const { unmount } = renderHeartbeat(ORIGINAL, sendPagePing);
|
|
81
|
+
unmount();
|
|
82
|
+
sendPagePing.mockClear();
|
|
83
|
+
|
|
84
|
+
vi.advanceTimersByTime(HEARTBEAT * 2);
|
|
85
|
+
|
|
86
|
+
expect(sendPagePing).not.toHaveBeenCalled();
|
|
87
|
+
});
|
|
88
|
+
});
|
|
@@ -6,12 +6,12 @@ import AppBar, { type IAppBarProps } from "./AppBar";
|
|
|
6
6
|
import * as S from "./style";
|
|
7
7
|
|
|
8
8
|
const MainWrapper = (props: IWrapperProps): JSX.Element => {
|
|
9
|
-
const { children, fixedAppBar, fullWidth, hasAnimation
|
|
9
|
+
const { children, fixedAppBar, fullWidth, hasAnimation } = props;
|
|
10
10
|
|
|
11
11
|
const { isOnline } = useNetworkStatus();
|
|
12
12
|
|
|
13
13
|
return (
|
|
14
|
-
<S.Wrapper fixedAppBar={fixedAppBar} data-testid="main-wrapper"
|
|
14
|
+
<S.Wrapper fixedAppBar={fixedAppBar} data-testid="main-wrapper">
|
|
15
15
|
{hasAnimation && <S.BackgroundAnimation />}
|
|
16
16
|
<AppBar {...props} />
|
|
17
17
|
<S.Main fullWidth={fullWidth} data-testid="main-component" id="main-component">
|
|
@@ -30,18 +30,6 @@ const MainWrapper = (props: IWrapperProps): JSX.Element => {
|
|
|
30
30
|
export interface IWrapperProps extends IAppBarProps {
|
|
31
31
|
children?: any[] | JSX.Element;
|
|
32
32
|
fullWidth?: boolean;
|
|
33
|
-
/**
|
|
34
|
-
* Stable per-screen identifier for e2e, emitted as `data-screen`. Separate from
|
|
35
|
-
* `data-testid="main-wrapper"`, which is the same on all 32 screens and is already
|
|
36
|
-
* consumed by tests here and in griddo-qa's `identifiers.ts`.
|
|
37
|
-
*
|
|
38
|
-
* It exists because nothing else identifies a screen: `title` is display copy and
|
|
39
|
-
* is not even unique — Robots and Redirects are both "SEO Settings", and the global
|
|
40
|
-
* and the site Analytics screens are both "Analytics Settings". The e2e specs had
|
|
41
|
-
* to assert on `location.pathname` with exact equality, and `/settings/analytics`
|
|
42
|
-
* is a substring of `/sites/settings/analytics`.
|
|
43
|
-
*/
|
|
44
|
-
screen?: string;
|
|
45
33
|
}
|
|
46
34
|
|
|
47
35
|
export default MainWrapper;
|
|
@@ -1,15 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
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";
|
|
1
|
+
import type { Dispatch } from "redux";
|
|
2
|
+
import { format } from "date-fns";
|
|
13
3
|
|
|
14
4
|
import type {
|
|
15
5
|
LogActivityExportRequest,
|
|
@@ -20,106 +10,99 @@ import type {
|
|
|
20
10
|
LogContentTypeDTO,
|
|
21
11
|
PaginationResponse,
|
|
22
12
|
} from "@griddo/api-types";
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import
|
|
13
|
+
import { logs } from "@ax/api";
|
|
14
|
+
import { appActions } from "@ax/containers/App";
|
|
15
|
+
import { usersActions } from "@ax/containers/Users";
|
|
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
|
+
}
|
|
26
35
|
|
|
27
|
-
|
|
36
|
+
function getActivityLog(params: LogActivityPaginationRequest): (dispatch: Dispatch) => Promise<void> {
|
|
37
|
+
return async (dispatch) => {
|
|
38
|
+
try {
|
|
39
|
+
const callback = async () => logs.getLogActivityTimelineByDay(params);
|
|
28
40
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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);
|
|
41
|
+
const responseActions = {
|
|
42
|
+
handleSuccess: (response: PaginationResponse<LogActivityGroupedDayDTO>) => {
|
|
43
|
+
dispatch(setActivityLog(response));
|
|
44
|
+
},
|
|
45
|
+
handleError: (response: any) => appActions.handleError(response)(dispatch),
|
|
46
|
+
};
|
|
46
47
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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 }>();
|
|
48
|
+
await handleRequest(callback, responseActions, [appActions.setIsLoading])(dispatch);
|
|
49
|
+
} catch (e) {
|
|
50
|
+
console.log(e);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
}
|
|
58
54
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
);
|
|
55
|
+
function getActivityLogByUser(params: LogActivityPaginationRequest): (dispatch: Dispatch) => Promise<void> {
|
|
56
|
+
return async (dispatch) => {
|
|
57
|
+
try {
|
|
58
|
+
const callback = async () => logs.getLogActivityTimelineByUser(params);
|
|
71
59
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
);
|
|
60
|
+
const responseActions = {
|
|
61
|
+
handleSuccess: (response: PaginationResponse<LogActivityGroupedUserDTO<LogActivityGroupedUserDTO[]>>) => {
|
|
62
|
+
dispatch(setActivityLog(response));
|
|
63
|
+
},
|
|
64
|
+
handleError: (response: any) => appActions.handleError(response)(dispatch),
|
|
65
|
+
};
|
|
85
66
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
};
|
|
67
|
+
await handleRequest(callback, responseActions, [appActions.setIsLoading])(dispatch);
|
|
68
|
+
} catch (e) {
|
|
69
|
+
console.log(e);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
}
|
|
99
73
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
);
|
|
74
|
+
function downloadActivityLog(data: LogActivityExportRequest): (dispatch: Dispatch) => Promise<void> {
|
|
75
|
+
return async (dispatch) => {
|
|
76
|
+
try {
|
|
77
|
+
const responseActions = {
|
|
78
|
+
handleSuccess: (response: any) => {
|
|
79
|
+
const dateString = format(new Date(), "yyyy-MM-dd");
|
|
80
|
+
const url = window.URL.createObjectURL(new Blob([response]));
|
|
81
|
+
const a = document.createElement("a");
|
|
82
|
+
a.href = url;
|
|
83
|
+
a.download = `activity-log-${dateString}.${data.format[0]}`;
|
|
84
|
+
document.body.appendChild(a);
|
|
85
|
+
a.click();
|
|
86
|
+
window.URL.revokeObjectURL(url);
|
|
87
|
+
},
|
|
88
|
+
handleError: (response: any) => appActions.handleError(response)(dispatch),
|
|
89
|
+
};
|
|
113
90
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
91
|
+
const callback = async () => logs.logExport(data);
|
|
92
|
+
|
|
93
|
+
await handleRequest(callback, responseActions, [])(dispatch);
|
|
94
|
+
} catch (e) {
|
|
95
|
+
console.log(e);
|
|
96
|
+
}
|
|
97
|
+
};
|
|
118
98
|
}
|
|
119
99
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
100
|
+
function goToLogContent(
|
|
101
|
+
contentType: LogContentTypeDTO | null,
|
|
102
|
+
content: LogContentDTO | null,
|
|
103
|
+
site: number | null,
|
|
104
|
+
): (dispatch: Dispatch, getState: () => IRootState) => void {
|
|
105
|
+
return async (dispatch, getState) => {
|
|
123
106
|
const {
|
|
124
107
|
app: { lang, globalLangs },
|
|
125
108
|
} = getState();
|
|
@@ -274,5 +257,7 @@ export const goToLogContent = createAppAsyncThunk(
|
|
|
274
257
|
break;
|
|
275
258
|
}
|
|
276
259
|
}
|
|
277
|
-
}
|
|
278
|
-
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export { getActivityLog, getActivityLogByUser, setListMode, downloadActivityLog, goToLogContent };
|
|
@@ -1,17 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import * as activityLogActions from "./actions";
|
|
2
|
+
import { activityLogReducer } from "./reducer";
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
// either — only the thunks write the page.
|
|
6
|
-
const activityLogActions = {
|
|
7
|
-
getActivityLog,
|
|
8
|
-
getActivityLogByUser,
|
|
9
|
-
setListMode,
|
|
10
|
-
downloadActivityLog,
|
|
11
|
-
goToLogContent,
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
export { activityLogActions };
|
|
15
|
-
|
|
16
|
-
export type { IActivityLogState } from "./slice";
|
|
17
|
-
export { activityLogInitialState, activityLogReducer } from "./slice";
|
|
4
|
+
export { activityLogActions, activityLogReducer };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ILogItemsByDay, ILogItemsByUser } from "@ax/types";
|
|
2
|
+
import type { SET_ACTIVITY_LOG, SET_LIST_MODE } from "./constants";
|
|
3
|
+
|
|
4
|
+
export interface ISetActivityLog {
|
|
5
|
+
type: typeof SET_ACTIVITY_LOG;
|
|
6
|
+
payload: { activityLog: ILogItemsByDay | ILogItemsByUser };
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface ISetListMode {
|
|
10
|
+
type: typeof SET_LIST_MODE;
|
|
11
|
+
payload: { listMode: string };
|
|
12
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ILogItemsByDay, ILogItemsByUser } from "@ax/types";
|
|
2
|
+
import { SET_ACTIVITY_LOG, SET_LIST_MODE } from "./constants";
|
|
3
|
+
|
|
4
|
+
export interface IActivityLogState {
|
|
5
|
+
activityLog: ILogItemsByDay | ILogItemsByUser;
|
|
6
|
+
listMode: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const initialState: IActivityLogState = {
|
|
10
|
+
activityLog: { totalItems: 0, items: [] },
|
|
11
|
+
listMode: "default",
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export function reducer(state = initialState, action: any): IActivityLogState {
|
|
15
|
+
switch (action.type) {
|
|
16
|
+
case SET_ACTIVITY_LOG:
|
|
17
|
+
case SET_LIST_MODE:
|
|
18
|
+
return { ...state, ...action.payload };
|
|
19
|
+
default:
|
|
20
|
+
return state;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export { initialState as activityLogInitialState, reducer as activityLogReducer };
|