Skip to content

Commit 80f2231

Browse files
authored
Merge pull request #1580 from umami-software/dev
v1.39
2 parents 0cb14f3 + fc879bb commit 80f2231

File tree

119 files changed

+2634
-1874
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

119 files changed

+2634
-1874
lines changed

README.md

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,7 @@ mysql://username:mypassword@localhost:3306/mydb
4848
yarn build
4949
```
5050

51-
### Create database tables
52-
53-
```bash
54-
yarn update-db
55-
```
56-
57-
This will also create a login account with username **admin** and password **umami**.
51+
The build step will also create tables in your database if you ae installing for the first time. It will also create a login account with username **admin** and password **umami**.
5852

5953
### Start the application
6054

components/common/EventDataButton.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import List from 'assets/list-ul.svg';
2+
import Modal from 'components/common/Modal';
3+
import PropTypes from 'prop-types';
4+
import { useState } from 'react';
5+
import { FormattedMessage } from 'react-intl';
6+
import Button from './Button';
7+
import EventDataForm from 'components/forms/EventDataForm';
8+
import styles from './EventDataButton.module.css';
9+
10+
function EventDataButton({ websiteId }) {
11+
const [showEventData, setShowEventData] = useState(false);
12+
13+
function handleClick() {
14+
if (!showEventData) {
15+
setShowEventData(true);
16+
}
17+
}
18+
19+
function handleClose() {
20+
setShowEventData(false);
21+
}
22+
23+
return (
24+
<>
25+
<Button
26+
icon={<List />}
27+
tooltip={<FormattedMessage id="label.event-data" defaultMessage="Event" />}
28+
tooltipId="button-event"
29+
size="small"
30+
onClick={handleClick}
31+
className={styles.button}
32+
>
33+
Event Data
34+
</Button>
35+
{showEventData && (
36+
<Modal title={<FormattedMessage id="label.event-data" defaultMessage="Query Event Data" />}>
37+
<EventDataForm websiteId={websiteId} onClose={handleClose} />
38+
</Modal>
39+
)}
40+
</>
41+
);
42+
}
43+
44+
EventDataButton.propTypes = {
45+
websiteId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
46+
};
47+
48+
export default EventDataButton;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.button {
2+
width: fit-content;
3+
}

components/common/RefreshButton.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import useDateRange from 'hooks/useDateRange';
1111
function RefreshButton({ websiteId }) {
1212
const [dateRange] = useDateRange(websiteId);
1313
const [loading, setLoading] = useState(false);
14-
const selector = useCallback(state => state[`/website/${websiteId}/stats`], [websiteId]);
14+
const selector = useCallback(state => state[`/websites/${websiteId}/stats`], [websiteId]);
1515
const completed = useStore(selector);
1616

1717
function handleClick() {

components/common/UpdateNotice.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export default function UpdateNotice() {
1818
function handleViewClick() {
1919
updateCheck();
2020
setDismissed(true);
21-
location.href = releaseUrl || REPO_URL;
21+
open(releaseUrl || REPO_URL, '_blank');
2222
}
2323

2424
function handleDismissClick() {

components/forms/AccountEditForm.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@ const initialValues = {
1515
password: '',
1616
};
1717

18-
const validate = ({ user_id, username, password }) => {
18+
const validate = ({ id, username, password }) => {
1919
const errors = {};
2020

2121
if (!username) {
2222
errors.username = <FormattedMessage id="label.required" defaultMessage="Required" />;
2323
}
24-
if (!user_id && !password) {
24+
if (!id && !password) {
2525
errors.password = <FormattedMessage id="label.required" defaultMessage="Required" />;
2626
}
2727

@@ -33,7 +33,8 @@ export default function AccountEditForm({ values, onSave, onClose }) {
3333
const [message, setMessage] = useState();
3434

3535
const handleSubmit = async values => {
36-
const { ok, data } = await post('/account', values);
36+
const { id } = values;
37+
const { ok, data } = await post(id ? `/accounts/${id}` : '/accounts', values);
3738

3839
if (ok) {
3940
onSave();

components/forms/ChangePasswordForm.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import FormLayout, {
99
FormRow,
1010
} from 'components/layout/FormLayout';
1111
import useApi from 'hooks/useApi';
12+
import useUser from '../../hooks/useUser';
1213

1314
const initialValues = {
1415
current_password: '',
@@ -39,9 +40,10 @@ const validate = ({ current_password, new_password, confirm_password }) => {
3940
export default function ChangePasswordForm({ values, onSave, onClose }) {
4041
const { post } = useApi();
4142
const [message, setMessage] = useState();
43+
const { user } = useUser();
4244

4345
const handleSubmit = async values => {
44-
const { ok, data } = await post('/account/password', values);
46+
const { ok, data } = await post(`/accounts/${user.userId}/password`, values);
4547

4648
if (ok) {
4749
onSave();

components/forms/EventDataForm.js

Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
import classNames from 'classnames';
2+
import Button from 'components/common/Button';
3+
import DateFilter from 'components/common/DateFilter';
4+
import DropDown from 'components/common/DropDown';
5+
import FormLayout, {
6+
FormButtons,
7+
FormError,
8+
FormMessage,
9+
FormRow,
10+
} from 'components/layout/FormLayout';
11+
import DataTable from 'components/metrics/DataTable';
12+
import FilterTags from 'components/metrics/FilterTags';
13+
import { Field, Form, Formik } from 'formik';
14+
import useApi from 'hooks/useApi';
15+
import useDateRange from 'hooks/useDateRange';
16+
import { useState, useEffect } from 'react';
17+
import { FormattedMessage } from 'react-intl';
18+
import styles from './EventDataForm.module.css';
19+
import useTimezone from 'hooks/useTimezone';
20+
21+
export const filterOptions = [
22+
{ label: 'Count', value: 'count' },
23+
{ label: 'Average', value: 'avg' },
24+
{ label: 'Minimum', value: 'min' },
25+
{ label: 'Maxmimum', value: 'max' },
26+
{ label: 'Sum', value: 'sum' },
27+
];
28+
29+
export const dateOptions = [
30+
{ label: <FormattedMessage id="label.today" defaultMessage="Today" />, value: '1day' },
31+
{
32+
label: (
33+
<FormattedMessage id="label.last-hours" defaultMessage="Last {x} hours" values={{ x: 24 }} />
34+
),
35+
value: '24hour',
36+
},
37+
{
38+
label: <FormattedMessage id="label.yesterday" defaultMessage="Yesterday" />,
39+
value: '-1day',
40+
},
41+
{
42+
label: <FormattedMessage id="label.this-week" defaultMessage="This week" />,
43+
value: '1week',
44+
divider: true,
45+
},
46+
{
47+
label: (
48+
<FormattedMessage id="label.last-days" defaultMessage="Last {x} days" values={{ x: 7 }} />
49+
),
50+
value: '7day',
51+
},
52+
{
53+
label: <FormattedMessage id="label.this-month" defaultMessage="This month" />,
54+
value: '1month',
55+
divider: true,
56+
},
57+
{
58+
label: (
59+
<FormattedMessage id="label.last-days" defaultMessage="Last {x} days" values={{ x: 30 }} />
60+
),
61+
value: '30day',
62+
},
63+
{
64+
label: (
65+
<FormattedMessage id="label.last-days" defaultMessage="Last {x} days" values={{ x: 90 }} />
66+
),
67+
value: '90day',
68+
},
69+
{ label: <FormattedMessage id="label.this-year" defaultMessage="This year" />, value: '1year' },
70+
{
71+
label: <FormattedMessage id="label.custom-range" defaultMessage="Custom range" />,
72+
value: 'custom',
73+
divider: true,
74+
},
75+
];
76+
77+
export default function EventDataForm({ websiteId, onClose, className }) {
78+
const { post } = useApi();
79+
const [message, setMessage] = useState();
80+
const [columns, setColumns] = useState({});
81+
const [filters, setFilters] = useState({});
82+
const [data, setData] = useState([]);
83+
const [dateRange, setDateRange] = useDateRange('report');
84+
const { startDate, endDate, value } = dateRange;
85+
const [timezone] = useTimezone();
86+
const [isValid, setIsValid] = useState(false);
87+
88+
useEffect(() => {
89+
if (Object.keys(columns).length > 0) {
90+
setIsValid(true);
91+
} else {
92+
setIsValid(false);
93+
}
94+
}, [columns]);
95+
96+
const handleAddTag = (value, list, setState, resetForm) => {
97+
setState({ ...list, [`${value.field}`]: value.value });
98+
resetForm();
99+
};
100+
101+
const handleRemoveTag = (value, list, setState) => {
102+
const newList = { ...list };
103+
104+
delete newList[`${value}`];
105+
106+
setState(newList);
107+
};
108+
109+
const handleSubmit = async () => {
110+
const params = {
111+
website_id: websiteId,
112+
start_at: +startDate,
113+
end_at: +endDate,
114+
timezone,
115+
columns,
116+
filters,
117+
};
118+
119+
const { ok, data } = await post(`/websites/${websiteId}/eventdata`, params);
120+
121+
if (!ok) {
122+
setMessage(<FormattedMessage id="message.failure" defaultMessage="Something went wrong." />);
123+
setData([]);
124+
} else {
125+
setData(data);
126+
setMessage(null);
127+
}
128+
};
129+
130+
return (
131+
<>
132+
<FormMessage>{message}</FormMessage>
133+
<div className={classNames(styles.container, className)}>
134+
<div className={styles.form}>
135+
<FormLayout>
136+
<div className={styles.filters}>
137+
<FormRow>
138+
<label htmlFor="date-range">
139+
<FormattedMessage id="label.date-range" defaultMessage="Date Range" />
140+
</label>
141+
<DateFilter
142+
value={value}
143+
startDate={startDate}
144+
endDate={endDate}
145+
onChange={setDateRange}
146+
options={dateOptions}
147+
/>
148+
</FormRow>
149+
</div>
150+
<div className={styles.filters}>
151+
<Formik
152+
initialValues={{ field: '', value: '' }}
153+
onSubmit={(value, { resetForm }) =>
154+
handleAddTag(value, columns, setColumns, resetForm)
155+
}
156+
>
157+
{({ values, setFieldValue }) => (
158+
<Form>
159+
<FormRow>
160+
<label htmlFor="field">
161+
<FormattedMessage id="label.field-name" defaultMessage="Field Name" />
162+
</label>
163+
<div>
164+
<Field name="field" type="text" />
165+
<FormError name="field" />
166+
</div>
167+
</FormRow>
168+
<FormRow>
169+
<label htmlFor="value">
170+
<FormattedMessage id="label.type" defaultMessage="Type" />
171+
</label>
172+
<div>
173+
<DropDown
174+
value={values.value}
175+
onChange={value => setFieldValue('value', value)}
176+
className={styles.dropdown}
177+
name="value"
178+
options={filterOptions}
179+
/>
180+
<FormError name="value" />
181+
</div>
182+
</FormRow>
183+
<FormButtons className={styles.formButtons}>
184+
<Button
185+
variant="action"
186+
type="submit"
187+
disabled={!values.field || !values.value}
188+
>
189+
<FormattedMessage id="label.add-column" defaultMessage="Add Column" />
190+
</Button>
191+
</FormButtons>
192+
</Form>
193+
)}
194+
</Formik>
195+
<FilterTags
196+
className={styles.filterTag}
197+
params={columns}
198+
onClick={value => handleRemoveTag(value, columns, setColumns)}
199+
/>
200+
</div>
201+
<div className={styles.filters}>
202+
<Formik
203+
initialValues={{ field: '', value: '' }}
204+
onSubmit={(value, { resetForm }) =>
205+
handleAddTag(value, filters, setFilters, resetForm)
206+
}
207+
>
208+
{({ values }) => (
209+
<Form>
210+
<FormRow>
211+
<label htmlFor="field">
212+
<FormattedMessage id="label.field-name" defaultMessage="Field Name" />
213+
</label>
214+
<div>
215+
<Field name="field" type="text" />
216+
<FormError name="field" />
217+
</div>
218+
</FormRow>
219+
<FormRow>
220+
<label htmlFor="value">
221+
<FormattedMessage id="label.value" defaultMessage="Value" />
222+
</label>
223+
<div>
224+
<Field name="value" type="text" />
225+
<FormError name="value" />
226+
</div>
227+
</FormRow>
228+
<FormButtons className={styles.formButtons}>
229+
<Button
230+
variant="action"
231+
type="submit"
232+
disabled={!values.field || !values.value}
233+
>
234+
<FormattedMessage id="label.add-filter" defaultMessage="Add Filter" />
235+
</Button>
236+
</FormButtons>
237+
</Form>
238+
)}
239+
</Formik>
240+
<FilterTags
241+
className={styles.filterTag}
242+
params={filters}
243+
onClick={value => handleRemoveTag(value, filters, setFilters)}
244+
/>
245+
</div>
246+
</FormLayout>
247+
</div>
248+
<div>
249+
<DataTable className={styles.table} data={data} title="Results" showPercentage={false} />
250+
</div>
251+
</div>
252+
<FormButtons>
253+
<Button variant="action" onClick={handleSubmit} disabled={!isValid}>
254+
<FormattedMessage id="label.search" defaultMessage="Search" />
255+
</Button>
256+
<Button onClick={onClose}>
257+
<FormattedMessage id="label.cancel" defaultMessage="Cancel" />
258+
</Button>
259+
</FormButtons>
260+
</>
261+
);
262+
}

0 commit comments

Comments
 (0)