Skip to content

Commit 4ac7b8b

Browse files
Removing ViewManagementStore, replacing with API funcs. (#23625)
* Removing `ViewManagementStore`, replacing with API funcs. * Adding license header. * Fixing linter hints.
1 parent dce2dfe commit 4ac7b8b

19 files changed

+124
-333
lines changed

graylog2-web-interface/src/components/users/UserDetails/SettingsSection.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ import { IfPermitted, ReadOnlyFormGroup } from 'components/common';
2424
import type User from 'logic/users/User';
2525
import SectionComponent from 'components/common/Section/SectionComponent';
2626
import { StreamsActions } from 'stores/streams/StreamsStore';
27-
import { ViewManagementActions } from 'views/stores/ViewManagementStore';
2827
import useIsGlobalTimeoutEnabled from 'hooks/useIsGlobalTimeoutEnabled';
28+
import { getView } from 'views/api/views';
2929

3030
type Props = {
3131
user: User;
@@ -60,7 +60,7 @@ const StartpageValue = ({ type, id }: { type: string | null | undefined; id: str
6060
if (type === 'stream') {
6161
StreamsActions.get(id).then(({ title: streamTitle }) => setTitle(streamTitle));
6262
} else {
63-
ViewManagementActions.get(id).then(({ title: viewTitle }) => setTitle(viewTitle));
63+
getView(id).then(({ title: viewTitle }) => setTitle(viewTitle));
6464
}
6565
}, [id, type]);
6666

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright (C) 2020 Graylog, Inc.
3+
*
4+
* This program is free software: you can redistribute it and/or modify
5+
* it under the terms of the Server Side Public License, version 1,
6+
* as published by MongoDB, Inc.
7+
*
8+
* This program is distributed in the hope that it will be useful,
9+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
* Server Side Public License for more details.
12+
*
13+
* You should have received a copy of the Server Side Public License
14+
* along with this program. If not, see
15+
* <http://www.mongodb.com/licensing/server-side-public-license>.
16+
*/
17+
import type View from 'views/logic/views/View';
18+
import type { ViewJson } from 'views/logic/views/View';
19+
import fetch from 'logic/rest/FetchProvider';
20+
import { qualifyUrl } from 'util/URLUtils';
21+
import type { EntitySharePayload } from 'actions/permissions/EntityShareActions';
22+
import { CurrentUserStore } from 'stores/users/CurrentUserStore';
23+
import UserNotification from 'util/UserNotification';
24+
25+
const viewsUrl = qualifyUrl('/views');
26+
const viewsIdUrl = (id) => qualifyUrl(`/views/${id}`);
27+
28+
export const getView = (viewId: string): Promise<ViewJson> => fetch('GET', viewsIdUrl(viewId));
29+
export const deleteView = (view: View): Promise<void> =>
30+
fetch('DELETE', viewsIdUrl(view.id)).catch((error) => {
31+
UserNotification.error(`Deleting view ${view.title} failed with status: ${error}`, 'Could not delete view');
32+
});
33+
export const createView = (view: View, entityShare?: EntitySharePayload, clonedFrom?: string): Promise<View> => {
34+
const url = clonedFrom ? viewsIdUrl(clonedFrom) : viewsUrl;
35+
const promise = fetch('POST', url, JSON.stringify({ entity: view.toJSON(), share_request: entityShare }));
36+
37+
return promise.then((response) => {
38+
CurrentUserStore.reload();
39+
40+
return response;
41+
});
42+
};
43+
44+
export const updateView = (view: View, entityShare?: EntitySharePayload): Promise<View> =>
45+
fetch('PUT', viewsIdUrl(view.id), JSON.stringify({ entity: view.toJSON(), share_request: entityShare }));

graylog2-web-interface/src/views/components/AdaptableQueryTabs.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ import CopyToDashboardForm from 'views/components/widgets/CopyToDashboardForm';
3131
import View from 'views/logic/views/View';
3232
import type { SearchJson } from 'views/logic/search/Search';
3333
import Search from 'views/logic/search/Search';
34-
import { ViewManagementActions } from 'views/stores/ViewManagementStore';
3534
import CopyPageToDashboard from 'views/logic/views/CopyPageToDashboard';
3635
import { loadAsDashboard, loadDashboard } from 'views/logic/views/Actions';
3736
import createSearch from 'views/logic/slices/createSearch';
@@ -47,6 +46,7 @@ import { TELEMETRY_EVENT_TYPE } from 'logic/telemetry/Constants';
4746
import useCurrentQueryId from 'views/logic/queries/useCurrentQueryId';
4847
import useView from 'views/hooks/useView';
4948
import useIsNew from 'views/hooks/useIsNew';
49+
import { updateView, getView } from 'views/api/views';
5050

5151
import type { QueryTabsProps } from './QueryTabs';
5252

@@ -229,7 +229,7 @@ const adjustTabsVisibility = (
229229
const _updateDashboardWithNewSearch = (dashboard: View, newSearch: Search) => {
230230
const newDashboard = dashboard.toBuilder().search(newSearch).build();
231231

232-
return ViewManagementActions.update(newDashboard);
232+
return updateView(newDashboard);
233233
};
234234

235235
const addPageToDashboard =
@@ -255,7 +255,7 @@ const _onCopyToDashboard =
255255
const view = selectView(getState());
256256
const queryId = selectActiveQuery(getState());
257257

258-
const dashboardJson = await ViewManagementActions.get(selectedDashboardId);
258+
const dashboardJson = await getView(selectedDashboardId);
259259
const targetDashboard = View.fromJSON(dashboardJson);
260260

261261
return fetchSearch(dashboardJson.search_id).then(addPageToDashboard(targetDashboard, view, queryId));

graylog2-web-interface/src/views/components/DashboardActionsMenu.test.tsx

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,14 @@ import type { LayoutState } from 'views/components/contexts/SearchPageLayoutCont
2525
import Search from 'views/logic/search/Search';
2626
import View from 'views/logic/views/View';
2727
import { SAVE_COPY, BLANK } from 'views/components/contexts/SearchPageLayoutContext';
28-
import { ViewManagementActions } from 'views/stores/ViewManagementStore';
2928
import useSaveViewFormControls from 'views/hooks/useSaveViewFormControls';
3029
import useCurrentUser from 'hooks/useCurrentUser';
3130
import TestStoreProvider from 'views/test/TestStoreProvider';
3231
import useViewsPlugin from 'views/test/testViewsPlugin';
3332
import OnSaveViewAction from 'views/logic/views/OnSaveViewAction';
3433
import HotkeysProvider from 'contexts/HotkeysProvider';
3534
import SearchPageLayoutProvider from 'views/components/contexts/SearchPageLayoutProvider';
35+
import { createView } from 'views/api/views';
3636

3737
import DashboardActionsMenu from './DashboardActionsMenu';
3838

@@ -41,10 +41,8 @@ jest.mock('views/hooks/useSaveViewFormControls');
4141
jest.mock('hooks/useCurrentUser');
4242
jest.mock('logic/generateObjectId', () => jest.fn(() => 'new-dashboard-id'));
4343

44-
jest.mock('views/stores/ViewManagementStore', () => ({
45-
ViewManagementActions: {
46-
create: jest.fn((v) => Promise.resolve(v)).mockName('create'),
47-
},
44+
jest.mock('views/api/views', () => ({
45+
createView: jest.fn((v) => Promise.resolve(v)).mockName('create'),
4846
}));
4947

5048
jest.mock('stores/permissions/EntityShareStore', () => ({
@@ -122,7 +120,7 @@ describe('DashboardActionsMenu', () => {
122120

123121
const updatedDashboard = mockView.toBuilder().id('new-dashboard-id').build();
124122

125-
await waitFor(() => expect(ViewManagementActions.create).toHaveBeenCalledWith(updatedDashboard, null, undefined));
123+
await waitFor(() => expect(createView).toHaveBeenCalledWith(updatedDashboard, null, undefined));
126124
});
127125

128126
it('should extend a dashboard with plugin data on duplication', async () => {
@@ -146,7 +144,7 @@ describe('DashboardActionsMenu', () => {
146144
.summary('This dashboard has been extended by a plugin')
147145
.build();
148146

149-
await waitFor(() => expect(ViewManagementActions.create).toHaveBeenCalledWith(updatedDashboard, null, 'view-id'));
147+
await waitFor(() => expect(createView).toHaveBeenCalledWith(updatedDashboard, null, 'view-id'));
150148
});
151149

152150
it('should open edit dashboard meta information modal', async () => {

graylog2-web-interface/src/views/components/dashboard/DashboardsOverview/DashboardActions.test.tsx

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import { PluginStore } from 'graylog-web-plugin/plugin';
2222
import Immutable from 'immutable';
2323

2424
import { asMock } from 'helpers/mocking';
25-
import { ViewManagementActions } from 'views/stores/ViewManagementStore';
2625
import OriginalDashboardActions from 'views/components/dashboard/DashboardsOverview/DashboardActions';
2726
import { simpleView } from 'views/test/ViewFixtures';
2827
import useCurrentUser from 'hooks/useCurrentUser';
@@ -31,14 +30,13 @@ import useSelectedEntities from 'components/common/EntityDataTable/hooks/useSele
3130
import type { ContextValue } from 'components/common/PaginatedEntityTable/TableFetchContext';
3231
import TableFetchContext from 'components/common/PaginatedEntityTable/TableFetchContext';
3332
import useWindowConfirmMock from 'helpers/mocking/useWindowConfirmMock';
33+
import { deleteView } from 'views/api/views';
3434

3535
jest.mock('hooks/useCurrentUser');
3636
jest.mock('components/common/EntityDataTable/hooks/useSelectedEntities');
3737

38-
jest.mock('views/stores/ViewManagementStore', () => ({
39-
ViewManagementActions: {
40-
delete: jest.fn(() => Promise.resolve()),
41-
},
38+
jest.mock('views/api/views', () => ({
39+
deleteView: jest.fn(() => Promise.resolve()),
4240
}));
4341

4442
const mockSearchParams = {
@@ -100,7 +98,7 @@ describe('DashboardActions', () => {
10098

10199
await waitFor(() => expect(window.confirm).toHaveBeenCalledWith('Are you sure you want to delete "Foo"?'));
102100

103-
expect(ViewManagementActions.delete).not.toHaveBeenCalledWith(expect.objectContaining({ id: 'foo' }));
101+
expect(deleteView).not.toHaveBeenCalledWith(expect.objectContaining({ id: 'foo' }));
104102
});
105103

106104
it('deletes dashboard when user confirms deletion', async () => {
@@ -112,7 +110,7 @@ describe('DashboardActions', () => {
112110

113111
await waitFor(() => expect(window.confirm).toHaveBeenCalledWith('Are you sure you want to delete "Foo"?'));
114112

115-
expect(ViewManagementActions.delete).toHaveBeenCalledWith(expect.objectContaining({ id: 'foo' }));
113+
expect(deleteView).toHaveBeenCalledWith(expect.objectContaining({ id: 'foo' }));
116114
});
117115

118116
it('does not display more actions dropdown when user has no permissions for deletion and there are no pluggable actions', async () => {
@@ -140,8 +138,8 @@ describe('DashboardActions', () => {
140138

141139
beforeEach(() => {
142140
PluginStore.register(plugin);
143-
asMock(ViewManagementActions.delete).mockClear();
144-
asMock(ViewManagementActions.delete).mockImplementation((view) => Promise.resolve(view));
141+
asMock(deleteView).mockClear();
142+
asMock(deleteView).mockImplementation(() => Promise.resolve());
145143
});
146144

147145
afterEach(() => {
@@ -153,9 +151,7 @@ describe('DashboardActions', () => {
153151

154152
await clickDashboardAction('Delete');
155153

156-
await waitFor(() =>
157-
expect(ViewManagementActions.delete).toHaveBeenCalledWith(expect.objectContaining({ id: 'foo' })),
158-
);
154+
await waitFor(() => expect(deleteView).toHaveBeenCalledWith(expect.objectContaining({ id: 'foo' })));
159155

160156
expect(deletingDashboard).toHaveBeenCalledWith(simpleDashboard);
161157
});
@@ -169,9 +165,7 @@ describe('DashboardActions', () => {
169165

170166
await clickDashboardAction('Delete');
171167

172-
await waitFor(() =>
173-
expect(ViewManagementActions.delete).toHaveBeenCalledWith(expect.objectContaining({ id: 'foo' })),
174-
);
168+
await waitFor(() => expect(deleteView).toHaveBeenCalledWith(expect.objectContaining({ id: 'foo' })));
175169

176170
expect(contextValue.refetch).toHaveBeenCalled();
177171
});
@@ -185,7 +179,7 @@ describe('DashboardActions', () => {
185179

186180
await waitFor(() => expect(deletingDashboard).toHaveBeenCalledWith(simpleDashboard));
187181

188-
expect(ViewManagementActions.delete).not.toHaveBeenCalledWith(expect.objectContaining({ id: 'foo' }));
182+
expect(deleteView).not.toHaveBeenCalledWith(expect.objectContaining({ id: 'foo' }));
189183
});
190184

191185
it('resorts to default behavior when hook returns `null`', async () => {
@@ -200,7 +194,7 @@ describe('DashboardActions', () => {
200194

201195
expect(window.confirm).toHaveBeenCalledWith('Are you sure you want to delete "Foo"?');
202196

203-
expect(ViewManagementActions.delete).toHaveBeenCalledWith(expect.objectContaining({ id: 'foo' }));
197+
expect(deleteView).toHaveBeenCalledWith(expect.objectContaining({ id: 'foo' }));
204198
});
205199

206200
it('resorts to default behavior when hook throws error', async () => {
@@ -228,7 +222,7 @@ describe('DashboardActions', () => {
228222

229223
expect(window.confirm).toHaveBeenCalledWith('Are you sure you want to delete "Foo"?');
230224

231-
expect(ViewManagementActions.delete).toHaveBeenCalledWith(expect.objectContaining({ id: 'foo' }));
225+
expect(deleteView).toHaveBeenCalledWith(expect.objectContaining({ id: 'foo' }));
232226
});
233227
});
234228
});

graylog2-web-interface/src/views/components/dashboard/DashboardsOverview/DashboardActions.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import type View from 'views/logic/views/View';
2424
import EntityShareModal from 'components/permissions/EntityShareModal';
2525
import ViewTypeLabel from 'views/components/ViewTypeLabel';
2626
import iterateConfirmationHooks from 'views/hooks/IterateConfirmationHooks';
27-
import { ViewManagementActions } from 'views/stores/ViewManagementStore';
2827
import usePaginationQueryParameter from 'hooks/usePaginationQueryParameter';
2928
import usePluginEntities from 'hooks/usePluginEntities';
3029
import useSelectedEntities from 'components/common/EntityDataTable/hooks/useSelectedEntities';
@@ -33,6 +32,7 @@ import { isAnyPermitted } from 'util/PermissionsMixin';
3332
import useCurrentUser from 'hooks/useCurrentUser';
3433
import { MoreActions } from 'components/common/EntityDataTable';
3534
import { useTableFetchContext } from 'components/common/PaginatedEntityTable';
35+
import { deleteView } from 'views/api/views';
3636

3737
const defaultDashboardDeletionHook = async (view: View) =>
3838
// eslint-disable-next-line no-alert
@@ -100,7 +100,7 @@ const DashboardDeleteAction = ({
100100
);
101101

102102
if (result) {
103-
ViewManagementActions.delete(dashboard)
103+
deleteView(dashboard)
104104
.then(() => {
105105
UserNotification.success(`Deleting dashboard "${dashboard.title}" was successful!`, 'Success!');
106106
deselectEntity(dashboard.id);

graylog2-web-interface/src/views/components/dashboard/DashboardsOverview/DashboardsOverview.test.tsx

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,8 @@ jest.mock('routing/Routes', () => ({ pluginRoute: () => () => '/route' }));
3030
jest.mock('components/common/PaginatedEntityTable/useFetchEntities');
3131
jest.mock('components/common/EntityDataTable/hooks/useUserLayoutPreferences');
3232

33-
jest.mock('views/stores/ViewManagementStore', () => ({
34-
ViewManagementActions: {
35-
delete: jest.fn(),
36-
update: {
37-
completed: {
38-
listen: () => jest.fn(),
39-
},
40-
},
41-
},
33+
jest.mock('views/api/views', () => ({
34+
deleteView: jest.fn(),
4235
}));
4336

4437
jest.mock('routing/QueryParams', () => ({

graylog2-web-interface/src/views/components/searchbar/saved-search/SearchActionsMenu.test.tsx

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ import View from 'views/logic/views/View';
2626
import Search from 'views/logic/search/Search';
2727
import type { ViewLoaderContextType } from 'views/logic/ViewLoaderContext';
2828
import ViewLoaderContext from 'views/logic/ViewLoaderContext';
29-
import { ViewManagementActions } from 'views/stores/ViewManagementStore';
3029
import type { NewViewLoaderContextType } from 'views/logic/NewViewLoaderContext';
3130
import NewViewLoaderContext from 'views/logic/NewViewLoaderContext';
3231
import * as ViewsPermissions from 'views/Permissions';
@@ -44,6 +43,7 @@ import HotkeysProvider from 'contexts/HotkeysProvider';
4443
import TestFieldTypesContextProvider from 'views/components/contexts/TestFieldTypesContextProvider';
4544
import { createEntityShareState } from 'fixtures/entityShareState';
4645
import { EntityShareStore } from 'stores/permissions/EntityShareStore';
46+
import { createView } from 'views/api/views';
4747

4848
import SearchActionsMenu from './SearchActionsMenu';
4949

@@ -64,10 +64,8 @@ jest.mock('stores/permissions/EntityShareStore', () => ({
6464
},
6565
}));
6666

67-
jest.mock('views/stores/ViewManagementStore', () => ({
68-
ViewManagementActions: {
69-
create: jest.fn((v) => Promise.resolve(v)).mockName('create'),
70-
},
67+
jest.mock('views/api/views', () => ({
68+
createView: jest.fn((v) => Promise.resolve(v)).mockName('create'),
7169
}));
7270

7371
jest.mock('views/hooks/useView');
@@ -84,7 +82,7 @@ jest.mock('views/logic/slices/viewSlice', () => {
8482
});
8583

8684
describe('SearchActionsMenu', () => {
87-
const createView = (id: string = undefined) =>
85+
const _createView = (id: string = undefined) =>
8886
View.builder()
8987
.id(id)
9088
.title('title')
@@ -94,7 +92,7 @@ describe('SearchActionsMenu', () => {
9492
.owner('owningUser')
9593
.build();
9694

97-
const defaultView = createView();
95+
const defaultView = _createView();
9896

9997
type SimpleSearchActionsMenuProps = {
10098
loadNewView?: NewViewLoaderContextType;
@@ -190,7 +188,7 @@ describe('SearchActionsMenu', () => {
190188
.build(),
191189
);
192190

193-
asMock(useView).mockReturnValue(createView('some-id'));
191+
asMock(useView).mockReturnValue(_createView('some-id'));
194192
render(<SimpleSearchActionsMenu />);
195193
userEvent.click(await screen.findByRole('button', { name: /open search actions/i }));
196194
const exportMenuItem = await screen.findByText('Edit metadata');
@@ -241,7 +239,7 @@ describe('SearchActionsMenu', () => {
241239

242240
const updatedView = defaultView.toBuilder().title('title and further title').id('new-search-id').build();
243241

244-
await waitFor(() => expect(ViewManagementActions.create).toHaveBeenCalledWith(updatedView, null, 'some-id-1'));
242+
await waitFor(() => expect(createView).toHaveBeenCalledWith(updatedView, null, 'some-id-1'));
245243
});
246244

247245
it('should extend a saved search with plugin data on duplication', async () => {
@@ -271,13 +269,13 @@ describe('SearchActionsMenu', () => {
271269
.id('new-search-id')
272270
.build();
273271

274-
await waitFor(() => expect(ViewManagementActions.create).toHaveBeenCalledWith(updatedView, null, 'some-id-1'));
272+
await waitFor(() => expect(createView).toHaveBeenCalledWith(updatedView, null, 'some-id-1'));
275273

276274
expect(screen.queryByText('Pluggable component!')).not.toBeInTheDocument();
277275
});
278276

279277
it('should save search when pressing related keyboard shortcut', async () => {
280-
asMock(useView).mockReturnValue(createView('some-id'));
278+
asMock(useView).mockReturnValue(_createView('some-id'));
281279
render(<SimpleSearchActionsMenu />);
282280
userEvent.keyboard('{Meta>}s{/Meta}');
283281

0 commit comments

Comments
 (0)