Skip to content

Commit 31e0713

Browse files
authored
Merge branch 'main' into feature/dev/export_json
2 parents f084476 + 79cf7ca commit 31e0713

File tree

9 files changed

+299
-118
lines changed

9 files changed

+299
-118
lines changed

client/app/components/items-list/ItemsList.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,10 +116,9 @@ export function wrap<I, P = any>(
116116
constructor(props: ItemsListWrapperProps) {
117117
super(props);
118118

119-
const stateStorage = createStateStorage();
120119
const itemsSource = createItemsSource();
120+
const stateStorage = createStateStorage();
121121
this._itemsSource = itemsSource;
122-
123122
itemsSource.setState({ ...stateStorage.getState(), validate: false });
124123
itemsSource.getCallbackContext = () => this.state;
125124

Lines changed: 143 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -1,147 +1,188 @@
11
import { toUpper } from "lodash";
2-
import React from "react";
2+
import React, { useEffect, useRef } from "react";
3+
import cx from "classnames";
34
import routeWithUserSession from "@/components/ApplicationArea/routeWithUserSession";
45
import Link from "@/components/Link";
56
import PageHeader from "@/components/PageHeader";
67
import Paginator from "@/components/Paginator";
78
import EmptyState, { EmptyStateHelpMessage } from "@/components/empty-state/EmptyState";
89
import { wrap as itemsList, ControllerType } from "@/components/items-list/ItemsList";
10+
import useItemsListExtraActions from "@/components/items-list/hooks/useItemsListExtraActions";
911
import { ResourceItemsSource } from "@/components/items-list/classes/ItemsSource";
12+
import { UrlStateStorage } from "@/components/items-list/classes/StateStorage";
1013
import { StateStorage } from "@/components/items-list/classes/StateStorage";
1114
import DynamicComponent from "@/components/DynamicComponent";
1215

16+
import * as Sidebar from "@/components/items-list/components/Sidebar";
1317
import ItemsTable, { Columns } from "@/components/items-list/components/ItemsTable";
1418

19+
import Layout from "@/components/layouts/ContentWithSidebar";
20+
1521
import Alert from "@/services/alert";
1622
import { currentUser } from "@/services/auth";
23+
import location from "@/services/location";
1724
import routes from "@/services/routes";
1825

26+
import AlertsListEmptyState from "./AlertsListEmptyState";
27+
28+
import "./alerts-list.css";
29+
1930
export const STATE_CLASS = {
2031
unknown: "label-warning",
2132
ok: "label-success",
22-
triggered: "label-danger",
33+
triggered: "label-danger"
2334
};
2435

25-
class AlertsList extends React.Component {
26-
static propTypes = {
27-
controller: ControllerType.isRequired,
28-
};
29-
30-
listColumns = [
31-
Columns.custom.sortable(
32-
(text, alert) => <i className={`fa fa-bell-${alert.options.muted ? "slash" : "o"} p-r-0`} />,
33-
{
34-
title: <i className="fa fa-bell p-r-0" />,
35-
field: "muted",
36-
width: "1%",
37-
}
36+
const listColumns = [
37+
Columns.custom.sortable(
38+
(text, alert) => <i className={`fa fa-bell-${alert.options.muted ? "slash" : "o"} p-r-0`} />,
39+
{
40+
title: <i className="fa fa-bell p-r-0" />,
41+
field: "muted",
42+
width: "1%",
43+
}
44+
),
45+
Columns.custom.sortable(
46+
(text, alert) => (
47+
<div>
48+
<Link className="table-main-title" href={"alerts/" + alert.id}>
49+
{alert.name}
50+
</Link>
51+
</div>
3852
),
39-
Columns.custom.sortable(
40-
(text, alert) => (
41-
<div>
42-
<Link className="table-main-title" href={"alerts/" + alert.id}>
43-
{alert.name}
44-
</Link>
45-
</div>
46-
),
47-
{
48-
title: "Name",
49-
field: "name",
50-
}
53+
{
54+
title: "Name",
55+
field: "name",
56+
}
57+
),
58+
Columns.custom((text, item) => item.user.name, { title: "Created By", width: "1%" }),
59+
Columns.custom.sortable(
60+
(text, alert) => (
61+
<div>
62+
<span className={`label ${STATE_CLASS[alert.state]}`}>{toUpper(alert.state)}</span>
63+
</div>
5164
),
52-
Columns.custom((text, item) => item.user.name, { title: "Created By", width: "1%" }),
53-
Columns.custom.sortable(
54-
(text, alert) => (
55-
<div>
56-
<span className={`label ${STATE_CLASS[alert.state]}`}>{toUpper(alert.state)}</span>
57-
</div>
58-
),
59-
{
60-
title: "State",
61-
field: "state",
62-
width: "1%",
63-
className: "text-nowrap",
65+
{
66+
title: "State",
67+
field: "state",
68+
width: "1%",
69+
className: "text-nowrap",
70+
}
71+
),
72+
Columns.timeAgo.sortable({ title: "Last Updated At", field: "updated_at", width: "1%" }),
73+
Columns.dateTime.sortable({ title: "Created At", field: "created_at", width: "1%" }),
74+
];
75+
76+
function AlertsListExtraActions(props) {
77+
return <DynamicComponent name="AlertsList.Actions" {...props} />;
78+
}
79+
80+
function AlertsList({ controller }) {
81+
const controllerRef = useRef();
82+
controllerRef.current = controller;
83+
84+
useEffect(() => {
85+
const unlistenLocationChanges = location.listen((unused, action) => {
86+
const searchTerm = location.search.q || "";
87+
if (action === "PUSH" && searchTerm !== controllerRef.current.searchTerm) {
88+
controllerRef.current.updateSearch(searchTerm);
6489
}
65-
),
66-
Columns.timeAgo.sortable({ title: "Last Updated At", field: "updated_at", width: "1%" }),
67-
Columns.dateTime.sortable({ title: "Created At", field: "created_at", width: "1%" }),
68-
];
69-
70-
render() {
71-
const { controller } = this.props;
72-
73-
return (
74-
<div className="page-alerts-list">
75-
<div className="container">
76-
<PageHeader
77-
title={controller.params.pageTitle}
78-
actions={
79-
currentUser.hasPermission("list_alerts") ? (
80-
<Link.Button block type="primary" href="alerts/new">
81-
<i className="fa fa-plus m-r-5" />
82-
New Alert
83-
</Link.Button>
84-
) : null
85-
}
86-
/>
87-
<div>
90+
});
91+
92+
return () => {
93+
unlistenLocationChanges();
94+
};
95+
}, []);
96+
97+
const {
98+
areExtraActionsAvailable,
99+
listColumns: tableColumns,
100+
Component: ExtraActionsComponent,
101+
selectedItems,
102+
} = useItemsListExtraActions(controller, listColumns, AlertsListExtraActions);
103+
104+
return (
105+
<div className="page-alerts-list">
106+
<div className="container">
107+
<PageHeader
108+
title={controller.params.pageTitle}
109+
actions={
110+
currentUser.hasPermission("list_alerts") ? (
111+
<Link.Button block type="primary" href="alerts/new">
112+
<i className="fa fa-plus m-r-5" />
113+
New Alert
114+
</Link.Button>
115+
) : null
116+
}
117+
/>
118+
<Layout>
119+
<Layout.Sidebar className="m-b-0">
120+
<Sidebar.SearchInput
121+
placeholder="Search Alert..."
122+
value={controller.searchTerm}
123+
onChange={controller.updateSearch}
124+
/>
125+
</Layout.Sidebar>
126+
<Layout.Content>
88127
{controller.isLoaded && controller.isEmpty ? (
89-
<DynamicComponent name="AlertsList.EmptyState">
90-
<EmptyState
91-
icon="fa fa-bell-o"
92-
illustration="alert"
93-
description="Get notified on certain events"
94-
helpMessage={<EmptyStateHelpMessage helpTriggerType="ALERTS" />}
95-
showAlertStep
96-
/>
97-
</DynamicComponent>
128+
<AlertsListEmptyState
129+
page={controller.params.currentPage}
130+
searchTerm={controller.searchTerm}
131+
/>
98132
) : (
99-
<div className="table-responsive bg-white tiled">
100-
<ItemsTable
101-
loading={!controller.isLoaded}
102-
items={controller.pageItems}
103-
columns={this.listColumns}
104-
orderByField={controller.orderByField}
105-
orderByReverse={controller.orderByReverse}
106-
toggleSorting={controller.toggleSorting}
107-
/>
108-
<Paginator
109-
showPageSizeSelect
110-
totalCount={controller.totalItemsCount}
111-
pageSize={controller.itemsPerPage}
112-
onPageSizeChange={itemsPerPage => controller.updatePagination({ itemsPerPage })}
113-
page={controller.page}
114-
onChange={page => controller.updatePagination({ page })}
115-
/>
116-
</div>
133+
<React.Fragment>
134+
<div className={cx({ "m-b-10": areExtraActionsAvailable })}>
135+
<ExtraActionsComponent selectedItems={selectedItems} />
136+
</div>
137+
<div className="bg-white tiled table-responsive">
138+
<ItemsTable
139+
items={controller.pageItems}
140+
loading={!controller.isLoaded}
141+
columns={tableColumns}
142+
orderByField={controller.orderByField}
143+
orderByReverse={controller.orderByReverse}
144+
toggleSorting={controller.toggleSorting}
145+
/>
146+
<Paginator
147+
showPageSizeSelect
148+
totalCount={controller.totalItemsCount}
149+
pageSize={controller.itemsPerPage}
150+
onPageSizeChange={itemsPerPage => controller.updatePagination({ itemsPerPage })}
151+
page={controller.page}
152+
onChange={page => controller.updatePagination({ page })}
153+
/>
154+
</div>
155+
</React.Fragment>
117156
)}
118-
</div>
119-
</div>
157+
</Layout.Content>
158+
</Layout>
120159
</div>
121-
);
122-
}
160+
</div>
161+
);
123162
}
124163

164+
AlertsList.propTypes = {
165+
controller: ControllerType.isRequired,
166+
};
167+
125168
const AlertsListPage = itemsList(
126169
AlertsList,
127170
() =>
128171
new ResourceItemsSource({
129-
isPlainList: true,
130-
getRequest() {
131-
return {};
132-
},
133-
getResource() {
134-
return Alert.query.bind(Alert);
135-
},
172+
getResource({ params: { currentPage } }) {
173+
return {
174+
all: Alert.query.bind(Alert)
175+
}[currentPage];
176+
}
136177
}),
137-
() => new StateStorage({ orderByField: "created_at", orderByReverse: true, itemsPerPage: 20 })
178+
() => new UrlStateStorage({ orderByField: "created_at", orderByReverse: true })
138179
);
139180

140181
routes.register(
141182
"Alerts.List",
142183
routeWithUserSession({
143184
path: "/alerts",
144185
title: "Alerts",
145-
render: pageProps => <AlertsListPage {...pageProps} currentPage="alerts" />,
186+
render: pageProps => <AlertsListPage {...pageProps} currentPage="all" />,
146187
})
147188
);
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import React from "react";
2+
import PropTypes from "prop-types";
3+
import Link from "@/components/Link";
4+
import BigMessage from "@/components/BigMessage";
5+
import NoTaggedObjectsFound from "@/components/NoTaggedObjectsFound";
6+
import EmptyState, { EmptyStateHelpMessage } from "@/components/empty-state/EmptyState";
7+
import DynamicComponent from "@/components/DynamicComponent";
8+
9+
export default function AlertListEmptyState({ page, searchTerm, selectedTags }) {
10+
if (searchTerm !== "") {
11+
return <BigMessage message="Sorry, we couldn't find anything." icon="fa-search" />;
12+
}
13+
if (page === 'all') {
14+
return (
15+
<DynamicComponent name="AlertsList.EmptyState">
16+
<EmptyState
17+
icon="fa fa-bell-o"
18+
illustration="alert"
19+
description="Get notified on certain events"
20+
helpMessage={<EmptyStateHelpMessage helpTriggerType="ALERTS" />}
21+
showAlertStep
22+
/>
23+
</DynamicComponent>
24+
);
25+
}
26+
}
27+
28+
AlertListEmptyState.propTypes = {
29+
page: PropTypes.string.isRequired,
30+
searchTerm: PropTypes.string.isRequired,
31+
selectedTags: PropTypes.array, // eslint-disable-line react/forbid-prop-types
32+
};
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
.search input[type="text"],
2+
.search button {
3+
height: 35px;
4+
}
5+
6+
.page-alerts-list .page-header-actions {
7+
width: 25%; /* same as sidebar */
8+
max-width: 350px; /* same as sidebar */
9+
}
10+
11+
/* same rule as for sidebar */
12+
@media (max-width: 990px) {
13+
.page-alerts-list .page-header-actions {
14+
width: auto;
15+
}
16+
}

client/app/pages/queries/hooks/useQueryFlags.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export default function useQueryFlags(query, dataSource = null) {
2222
!isEmpty(query.query) &&
2323
policy.canRun(query) &&
2424
(query.is_safe || (currentUser.hasPermission("execute_query") && !dataSource.view_only)),
25-
canFork: currentUser.hasPermission("edit_query") && !dataSource.view_only,
25+
canFork: currentUser.hasPermission("edit_query") && policy.canEdit(query) && !dataSource.view_only,
2626
canSchedule: currentUser.hasPermission("schedule_query"),
2727
}),
2828
[query, dataSource.view_only]

client/app/services/alert.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,11 @@ const transformRequest = data => {
2727
return newData;
2828
};
2929

30+
3031
const saveOrCreateUrl = data => (data.id ? `api/alerts/${data.id}` : "api/alerts");
3132

3233
const Alert = {
33-
query: () => axios.get("api/alerts"),
34+
query: (params) => axios.get("api/alerts", { params }),
3435
get: ({ id }) => axios.get(`api/alerts/${id}`).then(transformResponse),
3536
save: data => axios.post(saveOrCreateUrl(data), transformRequest(data)),
3637
delete: data => axios.delete(`api/alerts/${data.id}`),

0 commit comments

Comments
 (0)