Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
2e65d86
refactor: change activity list grid from coreui to mui data grid
ako3131 Oct 2, 2026
3cdc1d3
fix: add original pagination back to the top and bottom of grid
ako3131 Oct 2, 2026
39d075b
fix: add sticky headers to the grid
ako3131 Oct 2, 2026
ddd8705
refactor: reorder grid header buttons
ako3131 Oct 2, 2026
bc764f1
fix: move bottom pagination to under and outside of the grid to align…
ako3131 Oct 2, 2026
5a90c78
refactor: add add activity and import to grid toolbar
ako3131 Oct 2, 2026
4ef2a1d
refactor: move saved views into the grid toolbar
ako3131 Oct 2, 2026
c0db6cf
refactor: remove gap between activity list, pagination, and grid
ako3131 Oct 2, 2026
769b757
fix: increase size of import button
ako3131 Oct 2, 2026
3d1732a
feat: make selection status hideable
ako3131 Oct 2, 2026
89c7d70
fix: columns modal should show under the button not the left side of …
ako3131 Oct 2, 2026
ddc3267
fix: clicking the column button when the panel is open now closes it
ako3131 Oct 2, 2026
a18160d
fix: increase the appearance of the add button
ako3131 Oct 2, 2026
032e72e
fix: actions column should not be able to be deselected
ako3131 Oct 5, 2026
5b47122
fix: remove nonexistant placeholder
ako3131 Oct 5, 2026
33e9633
refactor: use ui core search not mui search in grid
ako3131 Oct 5, 2026
ec0c6c1
fix: match user sorted arrow displays to match previous bulk edit table
ako3131 Oct 5, 2026
7ee23fd
fix: use prexisting utilities
ako3131 Oct 5, 2026
924f6c1
fix: fix unnecessary acceptance of string in delete dialog body
ako3131 Oct 5, 2026
5359bec
refactor: make add activity a text button
ako3131 Oct 5, 2026
25297f0
fix: saved views no longer briefly shows incorrect list
ako3131 Oct 6, 2026
4ea3e8b
fix: bring back place holder text fallback
ako3131 Oct 6, 2026
88bebe2
fix: test and comment clean up
ako3131 Oct 6, 2026
bb3601c
fix: use the preexsting general name
ako3131 Oct 6, 2026
bdf606f
fix: all columns are hideable, defaults reset and optional one are re…
ako3131 Oct 6, 2026
588d318
fix: keep grid column widths stable when resizing and truncate cell text
ako3131 Oct 9, 2026
4c0ab32
fix: make added columns persist when navigating away or refresh
ako3131 Oct 19, 2026
929fd4e
fix: speaker and selection plan persist during an activity type switc…
ako3131 Oct 19, 2026
a864eee
fix: allow escape and tab keystrokes on saved views. replace deprecat…
ako3131 Oct 19, 2026
c66da5f
fix: search box collapses when it is empty and no search term applied
ako3131 Oct 19, 2026
e6ce9e7
fix: add custom search placeholder and increase size of search bar to…
ako3131 Oct 21, 2026
d533819
fix: sort columns options by alphabetical order in column panel
ako3131 Oct 21, 2026
e88cc0c
fix: stop clearing list on a saved views dropdown open
ako3131 Oct 21, 2026
d1a4472
fix: remove update view functionality since it did not align with onS…
ako3131 Oct 22, 2026
43d6955
fix: remove unneccesary comment and make saved views pop up close on …
ako3131 Oct 22, 2026
e956390
fix: use core ui dialog component for saved view pop up
ako3131 Oct 22, 2026
2a536ba
fix: move save view dialog into its own file
ako3131 Oct 22, 2026
d039761
fix: move tool bar buttons into their own files
ako3131 Oct 22, 2026
0876d6c
fix: make sure all columns are resizable when user is not editing
ako3131 Oct 22, 2026
9cd02da
fix: guard a pending submit if more submits are sent
ako3131 Oct 23, 2026
14f3a77
fix: saved views can no longer show stale results
ako3131 Oct 23, 2026
17465de
fix: a failed "apply changes" no longer resets the edits
ako3131 Oct 23, 2026
e367428
fix: move misplaced comment
ako3131 Oct 23, 2026
a33003a
fix: make edit/delete buttons larger and always keep checkbox and tit…
ako3131 Oct 24, 2026
2a3d67f
fix: selection status is a default column
ako3131 Oct 26, 2026
c11ba6d
fix: adjust activity list test
ako3131 Oct 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"@fontsource/roboto": "^5.1.1",
"@mui/icons-material": "^6.4.3",
"@mui/material": "^6.4.3",
"@mui/x-data-grid": "^7.29.13",
"@mui/x-date-pickers": "^7.26.0",
"@react-pdf/renderer": "^4.4.1",
"@sentry/react": "^8.32.0",
Expand Down
8 changes: 7 additions & 1 deletion src/actions/event-actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ export const FLAG_CHANGED = "FLAG_CHANGED";
export const REQUEST_EVENT_COMMENTS = "REQUEST_EVENT_COMMENTS";
export const RECEIVE_EVENT_COMMENTS = "RECEIVE_EVENT_COMMENTS";
export const CHANGE_SEARCH_TERM = "CHANGE_SEARCH_TERM";
export const CHANGE_EXTRA_COLUMNS = "CHANGE_EXTRA_COLUMNS";
export const SUBMISSION_PERIOD_REOPENED = "SUBMISSION_PERIOD_REOPENED";
export const SUBMISSION_PERIOD_CLOSED = "SUBMISSION_PERIOD_CLOSED";
export const SUBMISSION_REOPEN_NOTIFIED = "SUBMISSION_REOPEN_NOTIFIED";
Expand Down Expand Up @@ -385,8 +386,9 @@ export const bulkUpdateEvents = (events) => async (dispatch, getState) => {
)
);
})
.catch(() => {
.catch((err) => {
dispatch(stopLoading());
throw err;
});
};

Expand Down Expand Up @@ -1529,3 +1531,7 @@ export const queryAllCompanies = debounce(async (input, callback) => {
export const changeEventListSearchTerm = (term) => (dispatch) => {
dispatch(createAction(CHANGE_SEARCH_TERM)({ term }));
};

export const changeEventListExtraColumns = (extraColumns) => (dispatch) => {
dispatch(createAction(CHANGE_EXTRA_COLUMNS)({ extraColumns }));
};
5 changes: 3 additions & 2 deletions src/actions/filter-criteria-actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import {
} from "openstack-uicore-foundation/lib/utils/actions";
import URI from "urijs";
import Swal from "sweetalert2";
import debounce from "lodash/debounce"
import debounce from "lodash/debounce";

import { getAccessTokenSafely } from "../utils/methods";
import {
Expand Down Expand Up @@ -87,8 +87,9 @@ export const saveFilterCriteria = (filterCriteria) => async (dispatch) => {
`${window.PERSIST_FILTER_CRITERIA_API}/api/v1/filter-criterias`,
filterCriteria,
customErrorHandler
)(params)(dispatch).then(() => {
)(params)(dispatch).then((payload) => {
dispatch(stopLoading());
return payload;
});
};

Expand Down
237 changes: 237 additions & 0 deletions src/components/filters/saved-views/__tests__/saved-views.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
import React from "react";
import { render, screen, waitFor, act } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import "@testing-library/jest-dom";
import SavedViews from "..";
import showConfirmDialog from "../../../mui/showConfirmDialog";
import { queryFilterCriterias } from "../../../../actions/filter-criteria-actions";

jest.mock("i18n-react/dist/i18n-react", () => ({
__esModule: true,
default: { translate: (key) => key }
}));

jest.mock("../../../mui/showConfirmDialog", () => jest.fn());

jest.mock("../../../../actions/filter-criteria-actions", () => ({
queryFilterCriterias: jest.fn()
}));

const VIEWS = [
{ id: 1, name: "Open Activities", visibility: "Everyone", criteria: [] },
{ id: 2, name: "Recently Modified", visibility: "Me", criteria: [] }
];

const renderViews = (props = {}) =>
render(
<SavedViews
summitId={12}
context="Activities"
onChange={jest.fn()}
onSave={jest.fn(() => Promise.resolve())}
onDelete={jest.fn(() => Promise.resolve())}
// eslint-disable-next-line react/jsx-props-no-spreading
{...props}
/>
);

const openMenu = (label = "saved_views.title") =>
userEvent.click(screen.getByRole("button", { name: label }));

describe("SavedViews", () => {
beforeEach(() => {
jest.clearAllMocks();
queryFilterCriterias.mockImplementation((summitId, context, input, cb) =>
cb(VIEWS.filter((v) => v.name.includes(input)))
);
});

test("lists the show's views for this context and applies the one picked", async () => {
const onChange = jest.fn();
renderViews({ onChange });

await openMenu();

expect(queryFilterCriterias).toHaveBeenCalledWith(
12,
"Activities",
"",
expect.any(Function)
);
await userEvent.click(
screen.getByRole("menuitem", { name: /Recently Modified/ })
);

expect(onChange).toHaveBeenCalledWith(VIEWS[1]);
});

test("says there are no saved views only once they've loaded empty", async () => {
let resolveViews;
queryFilterCriterias.mockImplementation((summitId, context, input, cb) => {
resolveViews = cb;
});
renderViews();

await openMenu();
expect(
screen.queryByRole("menuitem", { name: "saved_views.no_views" })
).not.toBeInTheDocument();

act(() => resolveViews([]));
expect(
screen.getByRole("menuitem", { name: "saved_views.no_views" })
).toBeInTheDocument();
});

test("searches views by name", async () => {
renderViews();

await openMenu();
await userEvent.type(
screen.getByRole("textbox", { name: "general.search" }),
"Open"
);

expect(queryFilterCriterias).toHaveBeenLastCalledWith(
12,
"Activities",
"Open",
expect.any(Function)
);
});

test("deletes a view after confirming, without applying it", async () => {
const onChange = jest.fn();
const onDelete = jest.fn(() => Promise.resolve());
showConfirmDialog.mockResolvedValueOnce(true);
renderViews({ onChange, onDelete });

await openMenu();
await userEvent.click(
screen.getByRole("button", { name: "general.delete Open Activities" })
);

await waitFor(() => expect(onDelete).toHaveBeenCalledWith(1));
await waitFor(() =>
expect(
screen.queryByRole("menuitem", { name: /Open Activities/ })
).not.toBeInTheDocument()
);
expect(onChange).not.toHaveBeenCalled();
});

test("keeps the view when the delete is cancelled", async () => {
const onDelete = jest.fn();
showConfirmDialog.mockResolvedValueOnce(false);
renderViews({ onDelete });

await openMenu();
await userEvent.click(
screen.getByRole("button", { name: "general.delete Open Activities" })
);

await waitFor(() => expect(showConfirmDialog).toHaveBeenCalled());
expect(onDelete).not.toHaveBeenCalled();
});

test("offers clearing only while a view is active", async () => {
const onChange = jest.fn();
const { rerender } = renderViews({ onChange });

await openMenu();
expect(
screen.queryByRole("menuitem", { name: "saved_views.clear" })
).not.toBeInTheDocument();
await userEvent.keyboard("{Escape}");

rerender(
<SavedViews
summitId={12}
context="Activities"
selectedView={VIEWS[0]}
onChange={onChange}
onSave={jest.fn()}
onDelete={jest.fn()}
/>
);
await openMenu("Open Activities");
await userEvent.click(
screen.getByRole("menuitem", { name: "saved_views.clear" })
);

expect(onChange).toHaveBeenCalledWith(null);
});

test("requires a name and visibility before saving", async () => {
const onSave = jest.fn(() => Promise.resolve());
renderViews({ onSave });

await openMenu();
await userEvent.click(
screen.getByRole("menuitem", { name: "saved_views.save_current" })
);
await userEvent.click(screen.getByRole("button", { name: "general.save" }));

expect(await screen.findAllByText("validation.required")).toHaveLength(2);
expect(onSave).not.toHaveBeenCalled();
});

test("starts with an empty name and closes once the save resolves", async () => {
const onSave = jest.fn(() => Promise.resolve());
renderViews({ onSave, selectedView: VIEWS[0] });

await openMenu("Open Activities");
await userEvent.click(
screen.getByRole("menuitem", { name: "saved_views.save_current" })
);

const name = screen.getByRole("textbox", { name: "general.name" });
expect(name).toHaveValue("");
await userEvent.type(name, "Open Keynotes");
await userEvent.click(screen.getByRole("button", { name: "general.save" }));

expect(onSave).toHaveBeenCalledWith({
name: "Open Keynotes",
visibility: "Everyone"
});
await waitFor(() =>
expect(screen.queryByRole("dialog")).not.toBeInTheDocument()
);
});

test("closes the dialog when the save fails so the error isn't covered", async () => {
let rejectSave;
const onSave = jest.fn(
() =>
new Promise((resolve, reject) => {
rejectSave = reject;
})
);
renderViews({ onSave });

await openMenu();
await userEvent.click(
screen.getByRole("menuitem", { name: "saved_views.save_current" })
);
await userEvent.type(
screen.getByRole("textbox", { name: "general.name" }),
"Mine"
);
await userEvent.click(
screen.getByRole("radio", { name: "save_filter_criteria.me" })
);
await userEvent.click(screen.getByRole("button", { name: "general.save" }));

await waitFor(() =>
expect(
screen.getByRole("button", { name: "general.save" })
).toBeDisabled()
);
expect(screen.getByRole("dialog")).toBeInTheDocument();
await act(async () => rejectSave(new Error("nope")));

await waitFor(() =>
expect(screen.queryByRole("dialog")).not.toBeInTheDocument()
);
});
});
Loading
Loading