Skip to content

Commit 51f93ad

Browse files
authored
feat(trollbot): improve navigation of troll alarms (#946)
Reviewed-by: @ben-pr-p
1 parent 8155754 commit 51f93ad

File tree

5 files changed

+163
-150
lines changed

5 files changed

+163
-150
lines changed

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@
161161
"timezonecomplete": "^5.11.0",
162162
"twilio": "^2.11.0",
163163
"tzdata": "^1.0.18",
164+
"use-query-params": "^1.2.2",
164165
"uuid": "^3.1.0",
165166
"winston": "^3.2.1",
166167
"yup": "^0.32.9",

src/client/App.tsx

+5-2
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ import { css, StyleSheet } from "aphrodite";
33
import MuiThemeProviderv0 from "material-ui/styles/MuiThemeProvider";
44
import React, { useEffect, useState } from "react";
55
import { ApolloProvider } from "react-apollo";
6-
import { BrowserRouter as Router } from "react-router-dom";
6+
import { BrowserRouter as Router, Route } from "react-router-dom";
77
import request from "superagent";
8+
import { QueryParamProvider } from "use-query-params";
89

910
import ApolloClientSingleton from "../network/apollo-client-singleton";
1011
import AppRoutes from "../routes";
@@ -41,7 +42,9 @@ const App: React.FC = () => {
4142
<div className={css(styles.root)}>
4243
<VersionNotifier />
4344
<Router>
44-
<AppRoutes />
45+
<QueryParamProvider ReactRouterRoute={Route}>
46+
<AppRoutes />
47+
</QueryParamProvider>
4548
</Router>
4649
</div>
4750
</ApolloProvider>

src/containers/AdminTrollAlarms/index.tsx

+144-147
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,14 @@ import Paper from "material-ui/Paper";
1010
import RaisedButton from "material-ui/RaisedButton";
1111
import Snackbar from "material-ui/Snackbar";
1212
import Toggle from "material-ui/Toggle";
13-
import React from "react";
13+
import React, { useState } from "react";
1414
import { RouteChildrenProps } from "react-router-dom";
15+
import {
16+
BooleanParam,
17+
NumberParam,
18+
StringParam,
19+
useQueryParam
20+
} from "use-query-params";
1521

1622
import { TrollAlarm } from "../../api/trollbot";
1723
import { dataSourceItem, DataSourceItemType } from "../../components/utils";
@@ -42,98 +48,92 @@ interface Props
4248
};
4349
}
4450

45-
interface State {
46-
tokenSearchText: string;
47-
copiedAlarmID?: string;
48-
pageSize: number;
49-
page: number;
50-
dismissed: boolean;
51-
token: string | null;
52-
selectedAlarmIds: string[];
53-
isWorking: boolean;
54-
error?: string;
55-
}
56-
57-
class AdminTrollAlarms extends React.Component<Props, State> {
58-
state: State = {
59-
// UI Widgets
60-
tokenSearchText: "",
61-
copiedAlarmID: undefined,
51+
const AdminTrollAlarms: React.FC<Props> = (props) => {
52+
// UI Widgets
53+
const [tokenSearchText, setTokenSearchText] = useState("");
54+
const [copiedAlarmID, setCopiedAlarmID] = useState<string | undefined>(
55+
undefined
56+
);
6257

63-
// Query params
64-
pageSize: 25,
65-
page: 0,
66-
dismissed: false,
67-
token: null,
58+
// Operations
59+
const [selectedAlarmIds, setSelectedAlarmIds] = useState<string[]>([]);
60+
const [isWorking, setIsWorking] = useState(false);
61+
const [error, setError] = useState<string | undefined>(undefined);
6862

69-
// Operations
70-
selectedAlarmIds: [],
71-
isWorking: false,
72-
error: undefined
73-
};
63+
// Query params
64+
const [pageSize, setPageSize] = useQueryParam("pageSize", NumberParam);
65+
const [page, setPage] = useQueryParam("page", NumberParam);
66+
const [dismissed, setDismissed] = useQueryParam("dismissed", BooleanParam);
67+
const [token, setToken] = useQueryParam("token", StringParam);
7468

75-
handleOnCancelError = () => this.setState({ error: undefined });
69+
const handleOnCancelError = () => setError(undefined);
7670

7771
// Query conditions
78-
handleFocusTokenSearch = () =>
79-
this.setState({ tokenSearchText: "", token: null });
72+
const handleFocusTokenSearch = () => {
73+
setTokenSearchText("");
74+
setToken(null);
75+
};
8076

81-
handleTokenSearchTextChange = (tokenSearchText: string) =>
82-
this.setState({ tokenSearchText });
77+
const handleTokenSearchTextChange = (newTokenSearchText: string) =>
78+
setTokenSearchText(newTokenSearchText);
8379

84-
handleTokenSelected = (
80+
const handleTokenSelected = (
8581
selection: DataSourceItemType | string,
8682
index: number
8783
) => {
88-
let token: string | null = null;
84+
let foundToken: string | null = null;
8985
if (index > -1 && typeof selection !== "string") {
90-
token = selection.value.key as string;
86+
foundToken = selection.value.key as string;
9187
} else {
92-
const { trollTokens } = this.props.trollTokens;
93-
token =
88+
const { trollTokens } = props.trollTokens;
89+
foundToken =
9490
trollTokens.find(({ token: trollToken }) => trollToken === selection)
9591
?.token ?? null;
9692
}
97-
if (token) {
98-
this.setState({ token, selectedAlarmIds: [] });
93+
if (foundToken) {
94+
setToken(foundToken);
95+
setSelectedAlarmIds([]);
9996
}
10097
};
10198

102-
handleToggleDismissed = (_event: any, dismissed: boolean) =>
103-
this.setState({ dismissed, selectedAlarmIds: [] });
99+
const handleToggleDismissed = (_event: any, newDismissed: boolean) => {
100+
setPage(0);
101+
setDismissed(newDismissed);
102+
setSelectedAlarmIds([]);
103+
};
104104

105105
// Actions
106-
handleDismissSelected = async () => {
107-
const { selectedAlarmIds } = this.state;
108-
this.setState({ isWorking: true, error: undefined });
106+
const handleDismissSelected = async () => {
107+
setIsWorking(true);
108+
setError(undefined);
109109
try {
110-
await this.props.mutations.dismissAlarms(selectedAlarmIds);
111-
this.setState({ selectedAlarmIds: [] });
110+
await props.mutations.dismissAlarms(selectedAlarmIds);
111+
setSelectedAlarmIds([]);
112112
} catch (err) {
113-
this.setState({ error: err.message });
113+
setError(err.message);
114114
} finally {
115-
this.setState({ isWorking: false });
115+
setIsWorking(false);
116116
}
117117
};
118118

119-
handleDismissMatching = async () => {
120-
const { token } = this.state;
119+
const handleDismissMatching = async () => {
121120
if (!token) return;
122121

123-
this.setState({ isWorking: true, error: undefined });
122+
setIsWorking(true);
123+
setError(undefined);
124124
try {
125-
await this.props.mutations.dismissMatchingAlarms(token);
126-
this.setState({ selectedAlarmIds: [] });
125+
await props.mutations.dismissMatchingAlarms(token);
126+
setSelectedAlarmIds([]);
127127
} catch (err) {
128-
this.setState({ error: err.message });
128+
setError(err.message);
129129
} finally {
130-
this.setState({ isWorking: false });
130+
setIsWorking(false);
131131
}
132132
};
133133

134-
handleDismissCopyAlarm = () => this.setState({ copiedAlarmID: undefined });
134+
const handleDismissCopyAlarm = () => setCopiedAlarmID(undefined);
135135

136-
handleCopyAlarm = (alarm: TrollAlarm) => {
136+
const handleCopyAlarm = (alarm: TrollAlarm) => {
137137
const clipboardContents = [
138138
`Triggered Token: ${alarm.token}`,
139139
`Campaign ID: ${alarm.contact.campaign.id}`,
@@ -147,110 +147,107 @@ class AdminTrollAlarms extends React.Component<Props, State> {
147147

148148
try {
149149
navigator.clipboard.writeText(clipboardContents);
150-
this.setState({ copiedAlarmID: alarm.id });
150+
setCopiedAlarmID(alarm.id);
151151
} catch (err) {
152-
this.setState({ error: err.message });
152+
setError(err.message);
153153
}
154154
};
155155

156156
// Table selection
157-
handleAlarmSelectionChange = (selectedAlarmIds: string[]) =>
158-
this.setState({ selectedAlarmIds });
157+
const handleAlarmSelectionChange = (newSelectedAlarmIds: string[]) =>
158+
setSelectedAlarmIds(newSelectedAlarmIds);
159159

160160
// Pagination
161-
handlePageSizeChange = (pageSize: number) => this.setState({ pageSize });
161+
const handlePageSizeChange = (newPageSize: number) =>
162+
setPageSize(newPageSize);
162163

163-
handlePageChange = (page: number) => this.setState({ page });
164+
const handlePageChange = (newPage: number) => setPage(newPage);
164165

165-
render() {
166-
const { tokenSearchText, copiedAlarmID } = this.state;
167-
const { pageSize, page, dismissed, token } = this.state;
168-
const { selectedAlarmIds, isWorking, error } = this.state;
169-
const { match } = this.props;
166+
const { match } = props;
170167

171-
const { trollTokens } = this.props.trollTokens;
172-
const dataSource = trollTokens.map(({ token: trollToken }) =>
173-
dataSourceItem(trollToken, trollToken)
174-
);
168+
const { trollTokens } = props.trollTokens;
169+
const dataSource = trollTokens.map(({ token: trollToken }) =>
170+
dataSourceItem(trollToken, trollToken)
171+
);
175172

176-
const deleteAllSuffix = token ? `"${token}"` : "Token";
177-
const isDeleteSelectedDisabled = selectedAlarmIds.length === 0 || isWorking;
173+
const deleteAllSuffix = token ? `"${token}"` : "Token";
174+
const isDeleteSelectedDisabled = selectedAlarmIds.length === 0 || isWorking;
178175

179-
const errorActions = [
180-
<FlatButton
181-
key="close"
182-
label="Close"
183-
primary
184-
onClick={this.handleOnCancelError}
185-
/>
186-
];
176+
const errorActions = [
177+
<FlatButton
178+
key="close"
179+
label="Close"
180+
primary
181+
onClick={handleOnCancelError}
182+
/>
183+
];
187184

188-
return (
189-
<div>
190-
<Paper style={styles.controlsContainer}>
191-
<AutoComplete
192-
floatingLabelText="Token"
193-
hintText="Search for a trigger token"
194-
style={{ marginRight: "10px", ...styles.controlsColumn }}
195-
fullWidth
196-
maxSearchResults={8}
197-
searchText={tokenSearchText}
198-
dataSource={dataSource}
199-
filter={AutoComplete.caseInsensitiveFilter}
200-
onFocus={this.handleFocusTokenSearch}
201-
onUpdateInput={this.handleTokenSearchTextChange}
202-
onNewRequest={this.handleTokenSelected}
203-
/>
204-
<Toggle
205-
label="Dismissed Alarms"
206-
style={{ ...styles.controlsColumn, width: "0px" }}
207-
onToggle={this.handleToggleDismissed}
208-
toggled={dismissed}
209-
/>
210-
<RaisedButton
211-
label={`Dismiss All Matching ${deleteAllSuffix}`}
212-
style={{ marginRight: "10px" }}
213-
secondary
214-
disabled={token === null}
215-
onClick={this.handleDismissMatching}
216-
/>
217-
<RaisedButton
218-
label={`Dismiss Selected (${selectedAlarmIds.length})`}
219-
secondary
220-
disabled={isDeleteSelectedDisabled}
221-
onClick={this.handleDismissSelected}
222-
/>
223-
</Paper>
224-
<br />
225-
<TrollAlarmList
226-
organizationId={match?.params.organizationId}
227-
pageSize={pageSize}
228-
page={page}
229-
dismissed={dismissed}
230-
token={token}
231-
selectedAlarmIds={selectedAlarmIds}
232-
onAlarmSelectionChange={this.handleAlarmSelectionChange}
233-
onPageSizeChange={this.handlePageSizeChange}
234-
onPageChange={this.handlePageChange}
235-
onCopyAlarm={this.handleCopyAlarm}
185+
return (
186+
<div>
187+
<Paper style={styles.controlsContainer}>
188+
<AutoComplete
189+
floatingLabelText="Token"
190+
hintText="Search for a trigger token"
191+
style={{ marginRight: "10px", ...styles.controlsColumn }}
192+
fullWidth
193+
value={token ?? undefined}
194+
maxSearchResults={8}
195+
searchText={tokenSearchText}
196+
dataSource={dataSource}
197+
filter={AutoComplete.caseInsensitiveFilter}
198+
onFocus={handleFocusTokenSearch}
199+
onUpdateInput={handleTokenSearchTextChange}
200+
onNewRequest={handleTokenSelected}
236201
/>
237-
<Dialog open={error !== undefined} onClose={this.handleOnCancelError}>
238-
<DialogTitle>Error</DialogTitle>
239-
<DialogContent>
240-
<DialogContentText>{error || ""}</DialogContentText>
241-
</DialogContent>
242-
<DialogActions>{errorActions}</DialogActions>
243-
</Dialog>
244-
<Snackbar
245-
open={copiedAlarmID !== undefined}
246-
message={`Alarm ${copiedAlarmID || ""} details copied to clipboard`}
247-
autoHideDuration={4000}
248-
onRequestClose={this.handleDismissCopyAlarm}
202+
<Toggle
203+
label="Dismissed Alarms"
204+
style={{ ...styles.controlsColumn, width: "0px" }}
205+
onToggle={handleToggleDismissed}
206+
toggled={dismissed}
249207
/>
250-
</div>
251-
);
252-
}
253-
}
208+
<RaisedButton
209+
label={`Dismiss All Matching ${deleteAllSuffix}`}
210+
style={{ marginRight: "10px" }}
211+
secondary
212+
disabled={token === null}
213+
onClick={handleDismissMatching}
214+
/>
215+
<RaisedButton
216+
label={`Dismiss Selected (${selectedAlarmIds.length})`}
217+
secondary
218+
disabled={isDeleteSelectedDisabled}
219+
onClick={handleDismissSelected}
220+
/>
221+
</Paper>
222+
<br />
223+
<TrollAlarmList
224+
organizationId={match?.params.organizationId}
225+
pageSize={pageSize ?? 25}
226+
page={page ?? 0}
227+
dismissed={dismissed ?? false}
228+
token={token ?? null}
229+
selectedAlarmIds={selectedAlarmIds}
230+
onAlarmSelectionChange={handleAlarmSelectionChange}
231+
onPageSizeChange={handlePageSizeChange}
232+
onPageChange={handlePageChange}
233+
onCopyAlarm={handleCopyAlarm}
234+
/>
235+
<Dialog open={error !== undefined} onClose={handleOnCancelError}>
236+
<DialogTitle>Error</DialogTitle>
237+
<DialogContent>
238+
<DialogContentText>{error || ""}</DialogContentText>
239+
</DialogContent>
240+
<DialogActions>{errorActions}</DialogActions>
241+
</Dialog>
242+
<Snackbar
243+
open={copiedAlarmID !== undefined}
244+
message={`Alarm ${copiedAlarmID || ""} details copied to clipboard`}
245+
autoHideDuration={4000}
246+
onRequestClose={handleDismissCopyAlarm}
247+
/>
248+
</div>
249+
);
250+
};
254251

255252
const queries: QueryMap<Props> = {
256253
trollTokens: {

0 commit comments

Comments
 (0)