diff --git a/src/actions/schedule-settings-actions.js b/src/actions/schedule-settings-actions.js
index 50dde7cbc..e688720de 100644
--- a/src/actions/schedule-settings-actions.js
+++ b/src/actions/schedule-settings-actions.js
@@ -1,4 +1,4 @@
-/**
+/* *
* Copyright 2021 OpenStack Foundation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -9,8 +9,9 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
- **/
+ * */
+import T from "i18n-react";
import {
getRequest,
createAction,
@@ -23,10 +24,9 @@ import {
showMessage,
showSuccessMessage
} from "openstack-uicore-foundation/lib/utils/actions";
-import { getAccessTokenSafely } from "../utils/methods";
+import { getAccessTokenSafely } from "../utils/methods";
import history from "../history";
-import T from "i18n-react";
export const REQUEST_ALL_SCHEDULE_SETTINGS = "REQUEST_ALL_SCHEDULE_SETTINGS";
export const RECEIVE_ALL_SCHEDULE_SETTINGS = "RECEIVE_ALL_SCHEDULE_SETTINGS";
@@ -56,12 +56,11 @@ export const FILTER_TYPES = {
};
export const seedDefaultScheduleSettings = () => async (dispatch, getState) => {
+ dispatch(startLoading());
const { currentSummitState } = getState();
const accessToken = await getAccessTokenSafely();
const { currentSummit } = currentSummitState;
- dispatch(startLoading());
-
const params = {
access_token: accessToken
};
@@ -72,20 +71,21 @@ export const seedDefaultScheduleSettings = () => async (dispatch, getState) => {
`${window.API_BASE_URL}/api/v1/summits/${currentSummit.id}/schedule-settings/seed`,
{},
authErrorHandler
- )(params)(dispatch).then(() => {
- dispatch(stopLoading());
- });
+ )(params)(dispatch)
+ .finally(() => {
+ dispatch(stopLoading());
+ })
+ .catch(() => {});
};
export const getAllScheduleSettings =
(order = "key", orderDir = 1) =>
async (dispatch, getState) => {
+ dispatch(startLoading());
const { currentSummitState } = getState();
const accessToken = await getAccessTokenSafely();
const { currentSummit } = currentSummitState;
- dispatch(startLoading());
-
const params = {
page: 1,
per_page: 100,
@@ -95,7 +95,7 @@ export const getAllScheduleSettings =
// order
if (order != null && orderDir != null) {
const orderDirSign = orderDir === 1 ? "+" : "-";
- params["order"] = `${orderDirSign}${order}`;
+ params.order = `${orderDirSign}${order}`;
}
return getRequest(
@@ -104,17 +104,19 @@ export const getAllScheduleSettings =
`${window.API_BASE_URL}/api/v1/summits/${currentSummit.id}/schedule-settings`,
authErrorHandler,
{ order, orderDir }
- )(params)(dispatch).then(() => {
- dispatch(stopLoading());
- });
+ )(params)(dispatch)
+ .finally(() => {
+ dispatch(stopLoading());
+ })
+ .catch(() => {});
};
export const getScheduleSetting =
(scheduleSettingId) => async (dispatch, getState) => {
+ dispatch(startLoading());
const { currentSummitState } = getState();
const { currentSummit } = currentSummitState;
const accessToken = await getAccessTokenSafely();
- dispatch(startLoading());
const params = {
access_token: accessToken,
@@ -127,13 +129,16 @@ export const getScheduleSetting =
`${window.API_BASE_URL}/api/v1/summits/${currentSummit.id}/schedule-settings/${scheduleSettingId}`,
authErrorHandler,
{}
- )(params)(dispatch).then(() => {
- dispatch(stopLoading());
- });
+ )(params)(dispatch)
+ .finally(() => {
+ dispatch(stopLoading());
+ })
+ .catch(() => {});
};
export const deleteScheduleSetting =
(scheduleSettingId) => async (dispatch, getState) => {
+ dispatch(startLoading());
const { currentSummitState } = getState();
const accessToken = await getAccessTokenSafely();
const { currentSummit } = currentSummitState;
@@ -148,16 +153,19 @@ export const deleteScheduleSetting =
`${window.API_BASE_URL}/api/v1/summits/${currentSummit.id}/schedule-settings/${scheduleSettingId}`,
null,
authErrorHandler
- )(params)(dispatch).then(() => {
- dispatch(stopLoading());
- });
+ )(params)(dispatch)
+ .finally(() => {
+ dispatch(stopLoading());
+ })
+ .catch(() => {});
};
-export const resetScheduleSettingsForm = () => (dispatch, getState) => {
+export const resetScheduleSettingsForm = () => (dispatch) => {
dispatch(createAction(RESET_SCHEDULE_SETTINGS_FORM)({}));
};
export const saveScheduleSettings = (entity) => async (dispatch, getState) => {
+ dispatch(startLoading());
const { currentSummitState } = getState();
const accessToken = await getAccessTokenSafely();
const { currentSummit } = currentSummitState;
@@ -168,34 +176,38 @@ export const saveScheduleSettings = (entity) => async (dispatch, getState) => {
const normalizedEntity = normalizeEntity(entity);
- dispatch(startLoading());
-
if (entity.id) {
- putRequest(
+ return putRequest(
createAction(UPDATE_SCHEDULE_SETTINGS),
createAction(SCHEDULE_SETTINGS_UPDATED),
`${window.API_BASE_URL}/api/v1/summits/${currentSummit.id}/schedule-settings/${entity.id}`,
normalizedEntity,
authErrorHandler,
entity
- )(params)(dispatch).then((payload) => {
- dispatch(showSuccessMessage(T.translate("edit_schedule_settings.saved")));
- });
- } else {
- const success_message = {
- title: T.translate("general.done"),
- html: T.translate("edit_schedule_settings.created"),
- type: "success"
- };
+ )(params)(dispatch)
+ .then(() => {
+ dispatch(
+ showSuccessMessage(T.translate("edit_schedule_settings.saved"))
+ );
+ })
+ .finally(() => dispatch(stopLoading()));
+ }
- postRequest(
- createAction(UPDATE_SCHEDULE_SETTINGS),
- createAction(SCHEDULE_SETTINGS_ADDED),
- `${window.API_BASE_URL}/api/v1/summits/${currentSummit.id}/schedule-settings/`,
- normalizedEntity,
- authErrorHandler,
- entity
- )(params)(dispatch).then((payload) => {
+ const success_message = {
+ title: T.translate("general.done"),
+ html: T.translate("edit_schedule_settings.created"),
+ type: "success"
+ };
+
+ return postRequest(
+ createAction(UPDATE_SCHEDULE_SETTINGS),
+ createAction(SCHEDULE_SETTINGS_ADDED),
+ `${window.API_BASE_URL}/api/v1/summits/${currentSummit.id}/schedule-settings/`,
+ normalizedEntity,
+ authErrorHandler,
+ entity
+ )(params)(dispatch)
+ .then((payload) => {
dispatch(
showMessage(success_message, () => {
history.push(
@@ -203,8 +215,8 @@ export const saveScheduleSettings = (entity) => async (dispatch, getState) => {
);
})
);
- });
- }
+ })
+ .finally(() => dispatch(stopLoading()));
};
const normalizeEntity = (entity) => {
@@ -217,7 +229,7 @@ const normalizeEntity = (entity) => {
is_enabled: f.is_enabled
}));
normalized.pre_filters = entity.pre_filters.map((pf) => {
- let values = pf.values;
+ let {values} = pf;
if (pf.type === FILTER_TYPES.company) {
values = values.map((v) => v.id);
}
diff --git a/src/pages/schedule_settings/__tests__/schedule-settings-list-page.test.js b/src/pages/schedule_settings/__tests__/schedule-settings-list-page.test.js
new file mode 100644
index 000000000..0e8954c92
--- /dev/null
+++ b/src/pages/schedule_settings/__tests__/schedule-settings-list-page.test.js
@@ -0,0 +1,179 @@
+import React from "react";
+import { act, screen } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import "@testing-library/jest-dom";
+import flushPromises from "flush-promises";
+import { renderWithRedux, createMockSummit } from "../../../utils/test-utils";
+import ScheduleSettingsListPage from "../schedule-settings-list-page";
+import {
+ getAllScheduleSettings,
+ deleteScheduleSetting,
+ seedDefaultScheduleSettings
+} from "../../../actions/schedule-settings-actions";
+
+jest.mock("../../../actions/schedule-settings-actions", () => ({
+ getAllScheduleSettings: jest.fn(),
+ deleteScheduleSetting: jest.fn(),
+ seedDefaultScheduleSettings: jest.fn()
+}));
+
+jest.mock("openstack-uicore-foundation/lib/components/mui/table", () => ({
+ __esModule: true,
+ default: ({ data, onEdit, onDelete, onSort, canDelete }) => (
+
+ {data.map((row) => (
+
+ {row.key}
+
+ {canDelete(row) && (
+
+ )}
+
+ ))}
+
+
+ )
+}));
+
+jest.mock("i18n-react/dist/i18n-react", () => ({
+ __esModule: true,
+ default: { translate: (key) => key }
+}));
+
+const mockHistory = { push: jest.fn() };
+
+const scheduleSettings = [
+ { id: 1, key: "default-setting", is_default: true },
+ { id: 2, key: "custom-setting", is_default: false }
+];
+
+const initialState = {
+ currentSummitState: { currentSummit: createMockSummit() },
+ scheduleSettingsListState: {
+ scheduleSettings,
+ order: "key",
+ orderDir: 1,
+ totalScheduleSettings: 2
+ }
+};
+
+describe("ScheduleSettingsListPage", () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ getAllScheduleSettings.mockReturnValue(() => Promise.resolve());
+ deleteScheduleSetting.mockReturnValue(() => Promise.resolve());
+ seedDefaultScheduleSettings.mockReturnValue(() => Promise.resolve());
+ });
+
+ it("loads schedule settings on mount using the persisted order", () => {
+ renderWithRedux(, {
+ initialState
+ });
+
+ expect(getAllScheduleSettings).toHaveBeenCalledWith("key", 1);
+ });
+
+ it("navigates to the edit route", async () => {
+ renderWithRedux(, {
+ initialState
+ });
+
+ await userEvent.click(screen.getByRole("button", { name: "edit-2" }));
+
+ expect(mockHistory.push).toHaveBeenCalledWith(
+ `/app/summits/${createMockSummit().id}/schedule-settings/2`
+ );
+ });
+
+ it("navigates to the add-setting route", async () => {
+ renderWithRedux(, {
+ initialState
+ });
+
+ await userEvent.click(
+ screen.getByRole("button", {
+ name: "schedule_settings_list.add_schedule_settings"
+ })
+ );
+
+ expect(mockHistory.push).toHaveBeenCalledWith(
+ `/app/summits/${createMockSummit().id}/schedule-settings/new`
+ );
+ });
+
+ it("hides the delete action for the default schedule setting", () => {
+ renderWithRedux(, {
+ initialState
+ });
+
+ expect(
+ screen.queryByRole("button", { name: "delete-1" })
+ ).not.toBeInTheDocument();
+ expect(
+ screen.getByRole("button", { name: "delete-2" })
+ ).toBeInTheDocument();
+ });
+
+ it("deletes a non-default schedule setting by id", async () => {
+ renderWithRedux(, {
+ initialState
+ });
+
+ await act(async () => {
+ await userEvent.click(screen.getByRole("button", { name: "delete-2" }));
+ await flushPromises();
+ });
+
+ expect(deleteScheduleSetting).toHaveBeenCalledWith(2);
+ });
+
+ it("keeps sorting wired to getAllScheduleSettings", async () => {
+ renderWithRedux(, {
+ initialState
+ });
+
+ await userEvent.click(screen.getByRole("button", { name: "sort-col" }));
+
+ expect(getAllScheduleSettings).toHaveBeenCalledWith("key", -1);
+ });
+
+ it("seeds default schedule settings", async () => {
+ renderWithRedux(, {
+ initialState
+ });
+
+ await act(async () => {
+ await userEvent.click(
+ screen.getByRole("button", {
+ name: "schedule_settings_list.seed_defaults"
+ })
+ );
+ await flushPromises();
+ });
+
+ expect(seedDefaultScheduleSettings).toHaveBeenCalled();
+ });
+
+ it("shows the empty state when there are no schedule settings", () => {
+ renderWithRedux(, {
+ initialState: {
+ ...initialState,
+ scheduleSettingsListState: {
+ ...initialState.scheduleSettingsListState,
+ scheduleSettings: [],
+ totalScheduleSettings: 0
+ }
+ }
+ });
+
+ expect(
+ screen.getByText("schedule_settings_list.no_schedule_settings")
+ ).toBeInTheDocument();
+ });
+});
diff --git a/src/pages/schedule_settings/schedule-settings-list-page.js b/src/pages/schedule_settings/schedule-settings-list-page.js
index 8b26554e0..e8f2819d1 100644
--- a/src/pages/schedule_settings/schedule-settings-list-page.js
+++ b/src/pages/schedule_settings/schedule-settings-list-page.js
@@ -9,155 +9,120 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
- **/
+ * */
-import React from "react";
+import React, { useEffect } from "react";
import { connect } from "react-redux";
import T from "i18n-react/dist/i18n-react";
-import Swal from "sweetalert2";
-import Table from "openstack-uicore-foundation/lib/components/table";
+import Button from "@mui/material/Button";
+import AddIcon from "@mui/icons-material/Add";
+import MuiTable from "openstack-uicore-foundation/lib/components/mui/table";
+import GridToolbar from "../../components/mui/grid-toolbar";
import {
getAllScheduleSettings,
deleteScheduleSetting,
seedDefaultScheduleSettings
} from "../../actions/schedule-settings-actions";
-class ScheduleSettingsListPage extends React.Component {
- componentDidMount() {
- const { currentSummit } = this.props;
+const ScheduleSettingsListPage = ({
+ currentSummit,
+ scheduleSettings,
+ order,
+ orderDir,
+ totalScheduleSettings,
+ history,
+ getAllScheduleSettings,
+ deleteScheduleSetting,
+ seedDefaultScheduleSettings
+}) => {
+ useEffect(() => {
if (currentSummit) {
- this.props.getAllScheduleSettings();
+ getAllScheduleSettings(order, orderDir);
}
- }
+ }, [currentSummit]);
- handleEdit = (schedule_settings_id) => {
- const { currentSummit, history } = this.props;
+ const handleEdit = (row) => {
history.push(
- `/app/summits/${currentSummit.id}/schedule-settings/${schedule_settings_id}`
+ `/app/summits/${currentSummit.id}/schedule-settings/${row.id}`
);
};
- handleDelete = (scheduleSettingId) => {
- const { deleteScheduleSetting, scheduleSettings } = this.props;
- let scheduleSetting = scheduleSettings.find(
- (t) => t.id === scheduleSettingId
- );
-
- Swal.fire({
- title: T.translate("general.are_you_sure"),
- text:
- T.translate("schedule_settings_list.remove_warning") +
- " " +
- scheduleSetting.key,
- type: "warning",
- showCancelButton: true,
- confirmButtonColor: "#DD6B55",
- confirmButtonText: T.translate("general.yes_delete")
- }).then(function (result) {
- if (result.value) {
- deleteScheduleSetting(scheduleSettingId);
- }
- });
- };
-
- handleSort = (index, key, dir, func) => {
- this.props.getAllScheduleSettings(key, dir);
- };
-
- isNotDefault = (id) => {
- const { scheduleSettings } = this.props;
- let scheduleSetting = scheduleSettings.find((e) => e.id === id);
-
- return !scheduleSetting.is_default;
+ const handleSort = (key, dir) => {
+ getAllScheduleSettings(key, dir);
};
- handleNewScheduleSetting = (ev) => {
- const { currentSummit, history } = this.props;
+ const handleNewScheduleSetting = (ev) => {
+ ev.preventDefault();
history.push(`/app/summits/${currentSummit.id}/schedule-settings/new`);
};
- handleSeedDefaults = () => {
- this.props.seedDefaultScheduleSettings();
+ const handleSeedDefaults = () => {
+ seedDefaultScheduleSettings();
};
- render() {
- const {
- currentSummit,
- scheduleSettings,
- order,
- orderDir,
- totalScheduleSettings
- } = this.props;
-
- const columns = [
- { columnKey: "key", value: T.translate("edit_schedule_settings.key") },
- {
- columnKey: "is_enabled_str",
- value: T.translate("edit_schedule_settings.enabled")
- },
- {
- columnKey: "is_my_schedule_str",
- value: T.translate("edit_schedule_settings.is_my_schedule")
- },
- {
- columnKey: "is_access_level_str",
- value: T.translate("edit_schedule_settings.access_levels_only")
- }
- ];
-
- const table_options = {
- sortCol: order,
- sortDir: orderDir,
- actions: {
- edit: { onClick: this.handleEdit },
- delete: { onClick: this.handleDelete, display: this.isNotDefault }
- }
- };
-
- if (!currentSummit.id) return null;
-
- return (
-
-
- {" "}
- {T.translate("schedule_settings_list.schedule_settings")} (
- {totalScheduleSettings})
-
-
-
-
-
-
-
-
- {scheduleSettings.length === 0 && (
-
- {T.translate("schedule_settings_list.no_schedule_settings")}
-
- )}
-
- {scheduleSettings.length > 0 && (
-
- )}
-
- );
- }
-}
+ const columns = [
+ { columnKey: "key", header: T.translate("edit_schedule_settings.key") },
+ {
+ columnKey: "is_enabled_str",
+ header: T.translate("edit_schedule_settings.enabled")
+ },
+ {
+ columnKey: "is_my_schedule_str",
+ header: T.translate("edit_schedule_settings.is_my_schedule")
+ },
+ {
+ columnKey: "is_access_level_str",
+ header: T.translate("edit_schedule_settings.access_levels_only")
+ }
+ ];
+
+ const tableOptions = { sortCol: order, sortDir: orderDir };
+
+ if (!currentSummit.id) return null;
+
+ return (
+
+
+ {" "}
+ {T.translate("schedule_settings_list.schedule_settings")} (
+ {totalScheduleSettings})
+
+
+ }
+ >
+ {T.translate("schedule_settings_list.add_schedule_settings")}
+
+
+
+
+ {scheduleSettings.length === 0 && (
+
{T.translate("schedule_settings_list.no_schedule_settings")}
+ )}
+
+ {scheduleSettings.length > 0 && (
+
!row.is_default}
+ getName={(row) => row.key}
+ deleteDialogBody={(name) =>
+ `${T.translate("schedule_settings_list.remove_warning")} ${name}`
+ }
+ confirmButtonColor="error"
+ />
+ )}
+
+ );
+};
const mapStateToProps = ({
currentSummitState,