mirror of
https://github.com/umami-software/umami.git
synced 2026-02-14 17:45:38 +01:00
Update insights report parameters. Added contains logic.
This commit is contained in:
parent
d59477deb5
commit
5daad2726e
15 changed files with 280 additions and 190 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
.popup {
|
.popup {
|
||||||
display: flex;
|
display: flex;
|
||||||
max-width: 300px;
|
max-width: 300px;
|
||||||
max-height: 400px;
|
max-height: 210px;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -19,6 +19,6 @@
|
||||||
min-width: 180px;
|
min-width: 180px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu {
|
.text {
|
||||||
min-width: 200px;
|
min-width: 180px;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,19 @@
|
||||||
import { useState, useMemo } from 'react';
|
import { useState, useMemo } from 'react';
|
||||||
import { Form, FormRow, Item, Flexbox, Dropdown, Button } from 'react-basics';
|
import {
|
||||||
|
Form,
|
||||||
|
FormRow,
|
||||||
|
Item,
|
||||||
|
Flexbox,
|
||||||
|
Dropdown,
|
||||||
|
Button,
|
||||||
|
TextField,
|
||||||
|
Menu,
|
||||||
|
Popup,
|
||||||
|
PopupTrigger,
|
||||||
|
} from 'react-basics';
|
||||||
import { useMessages, useFilters, useFormat, useLocale } from 'components/hooks';
|
import { useMessages, useFilters, useFormat, useLocale } from 'components/hooks';
|
||||||
|
import { safeDecodeURIComponent } from 'next-basics';
|
||||||
|
import { OPERATORS } from 'lib/constants';
|
||||||
import styles from './FieldFilterForm.module.css';
|
import styles from './FieldFilterForm.module.css';
|
||||||
|
|
||||||
export interface FieldFilterFormProps {
|
export interface FieldFilterFormProps {
|
||||||
|
|
@ -22,12 +35,11 @@ export default function FieldFilterForm({
|
||||||
}: FieldFilterFormProps) {
|
}: FieldFilterFormProps) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const [filter, setFilter] = useState('eq');
|
const [filter, setFilter] = useState('eq');
|
||||||
const [value, setValue] = useState();
|
const [value, setValue] = useState('');
|
||||||
const { getFilters } = useFilters();
|
const { getFilters } = useFilters();
|
||||||
const { formatValue } = useFormat();
|
const { formatValue } = useFormat();
|
||||||
const { locale } = useLocale();
|
const { locale } = useLocale();
|
||||||
const filters = getFilters(type);
|
const filters = getFilters(type);
|
||||||
const [search, setSearch] = useState('');
|
|
||||||
|
|
||||||
const formattedValues = useMemo(() => {
|
const formattedValues = useMemo(() => {
|
||||||
const formatted = {};
|
const formatted = {};
|
||||||
|
|
@ -45,21 +57,25 @@ export default function FieldFilterForm({
|
||||||
}, [formatValue, locale, name, values]);
|
}, [formatValue, locale, name, values]);
|
||||||
|
|
||||||
const filteredValues = useMemo(() => {
|
const filteredValues = useMemo(() => {
|
||||||
return search ? values.filter(n => n.includes(search)) : values;
|
return value ? values.filter(n => n.includes(value)) : values;
|
||||||
}, [search, formattedValues]);
|
}, [value, formattedValues]);
|
||||||
|
|
||||||
const renderFilterValue = value => {
|
const renderFilterValue = value => {
|
||||||
return filters.find(f => f.value === value)?.label;
|
return filters.find(f => f.value === value)?.label;
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderValue = value => {
|
|
||||||
return formattedValues[value];
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleAdd = () => {
|
const handleAdd = () => {
|
||||||
onSelect({ name, type, filter, value });
|
onSelect({ name, type, filter, value });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleMenuSelect = value => {
|
||||||
|
setValue(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const showMenu =
|
||||||
|
[OPERATORS.equals, OPERATORS.notEquals].includes(filter as any) &&
|
||||||
|
!(filteredValues.length === 1 && filteredValues[0] === value);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form>
|
<Form>
|
||||||
<FormRow label={label} className={styles.filter}>
|
<FormRow label={label} className={styles.filter}>
|
||||||
|
|
@ -77,21 +93,24 @@ export default function FieldFilterForm({
|
||||||
}}
|
}}
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
)}
|
)}
|
||||||
<Dropdown
|
<PopupTrigger>
|
||||||
className={styles.dropdown}
|
<TextField
|
||||||
popupProps={{ className: styles.popup }}
|
className={styles.text}
|
||||||
menuProps={{ className: styles.menu }}
|
value={decodeURIComponent(value)}
|
||||||
items={filteredValues}
|
onChange={e => setValue(e.target.value)}
|
||||||
value={value}
|
/>
|
||||||
renderValue={renderValue}
|
{showMenu && (
|
||||||
onChange={(key: any) => setValue(key)}
|
<Popup className={styles.popup} alignment="end">
|
||||||
allowSearch={true}
|
{filteredValues.length > 0 && (
|
||||||
onSearch={setSearch}
|
<Menu variant="popup" onSelect={handleMenuSelect}>
|
||||||
>
|
{filteredValues.map(value => {
|
||||||
{(value: string) => {
|
return <Item key={value}>{safeDecodeURIComponent(value)}</Item>;
|
||||||
return <Item key={value}>{formattedValues[value]}</Item>;
|
})}
|
||||||
}}
|
</Menu>
|
||||||
</Dropdown>
|
)}
|
||||||
|
</Popup>
|
||||||
|
)}
|
||||||
|
</PopupTrigger>
|
||||||
</Flexbox>
|
</Flexbox>
|
||||||
<Button variant="primary" onClick={handleAdd} disabled={!filter || !value}>
|
<Button variant="primary" onClick={handleAdd} disabled={!filter || !value}>
|
||||||
{formatMessage(labels.add)}
|
{formatMessage(labels.add)}
|
||||||
|
|
|
||||||
|
|
@ -24,14 +24,14 @@ function useValues(websiteId: string, type: string) {
|
||||||
|
|
||||||
export interface FilterSelectFormProps {
|
export interface FilterSelectFormProps {
|
||||||
websiteId: string;
|
websiteId: string;
|
||||||
items: any[];
|
fields: any[];
|
||||||
onSelect?: (key: any) => void;
|
onSelect?: (key: any) => void;
|
||||||
allowFilterSelect?: boolean;
|
allowFilterSelect?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function FilterSelectForm({
|
export default function FilterSelectForm({
|
||||||
websiteId,
|
websiteId,
|
||||||
items,
|
fields,
|
||||||
onSelect,
|
onSelect,
|
||||||
allowFilterSelect,
|
allowFilterSelect,
|
||||||
}: FilterSelectFormProps) {
|
}: FilterSelectFormProps) {
|
||||||
|
|
@ -39,7 +39,7 @@ export default function FilterSelectForm({
|
||||||
const { data, isLoading } = useValues(websiteId, field?.name);
|
const { data, isLoading } = useValues(websiteId, field?.name);
|
||||||
|
|
||||||
if (!field) {
|
if (!field) {
|
||||||
return <FieldSelectForm fields={items} onSelect={setField} showType={false} />;
|
return <FieldSelectForm fields={fields} onSelect={setField} showType={false} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,4 @@
|
||||||
border: 1px solid var(--base400);
|
border: 1px solid var(--base400);
|
||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
box-shadow: 1px 1px 1px var(--base400);
|
box-shadow: 1px 1px 1px var(--base400);
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
align-self: center;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,40 +1,36 @@
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
import { Icon, TooltipPopup } from 'react-basics';
|
import { Icon } from 'react-basics';
|
||||||
import Icons from 'components/icons';
|
import Icons from 'components/icons';
|
||||||
import Empty from 'components/common/Empty';
|
import Empty from 'components/common/Empty';
|
||||||
import { useMessages } from 'components/hooks';
|
import { useMessages } from 'components/hooks';
|
||||||
import styles from './ParameterList.module.css';
|
import styles from './ParameterList.module.css';
|
||||||
|
|
||||||
export interface ParameterListProps {
|
export interface ParameterListProps {
|
||||||
items: any[];
|
children?: ReactNode;
|
||||||
children?: ReactNode | ((item: any) => ReactNode);
|
|
||||||
onRemove: (index: number, e: any) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ParameterList({ items = [], children, onRemove }: ParameterListProps) {
|
export function ParameterList({ children }: ParameterListProps) {
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.list}>
|
<div className={styles.list}>
|
||||||
{!items.length && <Empty message={formatMessage(labels.none)} />}
|
{!children && <Empty message={formatMessage(labels.none)} />}
|
||||||
{items.map((item, index) => {
|
{children}
|
||||||
return (
|
|
||||||
<div key={index} className={styles.item}>
|
|
||||||
{typeof children === 'function' ? children(item) : item}
|
|
||||||
<TooltipPopup
|
|
||||||
className={styles.icon}
|
|
||||||
label={formatMessage(labels.remove)}
|
|
||||||
position="right"
|
|
||||||
>
|
|
||||||
<Icon onClick={onRemove.bind(null, index)}>
|
|
||||||
<Icons.Close />
|
|
||||||
</Icon>
|
|
||||||
</TooltipPopup>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const Item = ({ children, onRemove }: { children?: ReactNode; onRemove?: () => void }) => {
|
||||||
|
return (
|
||||||
|
<div className={styles.item}>
|
||||||
|
{children}
|
||||||
|
<Icon onClick={onRemove}>
|
||||||
|
<Icons.Close />
|
||||||
|
</Icon>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
ParameterList.Item = Item;
|
||||||
|
|
||||||
export default ParameterList;
|
export default ParameterList;
|
||||||
|
|
|
||||||
|
|
@ -2,4 +2,5 @@
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-rows: max-content 1fr;
|
grid-template-rows: max-content 1fr;
|
||||||
grid-template-columns: max-content 1fr;
|
grid-template-columns: max-content 1fr;
|
||||||
|
margin-bottom: 60px;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
75
src/app/(main)/reports/insights/InsightsFieldParameters.tsx
Normal file
75
src/app/(main)/reports/insights/InsightsFieldParameters.tsx
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
import { useMessages } from 'components/hooks';
|
||||||
|
import Icons from 'components/icons';
|
||||||
|
import { useContext } from 'react';
|
||||||
|
import { Button, FormRow, Icon, Popup, PopupTrigger } from 'react-basics';
|
||||||
|
import FieldSelectForm from '../[reportId]/FieldSelectForm';
|
||||||
|
import ParameterList from '../[reportId]/ParameterList';
|
||||||
|
import PopupForm from '../[reportId]/PopupForm';
|
||||||
|
import { ReportContext } from '../[reportId]/Report';
|
||||||
|
|
||||||
|
export function InsightsFieldParameters() {
|
||||||
|
const { report, updateReport } = useContext(ReportContext);
|
||||||
|
const { formatMessage, labels } = useMessages();
|
||||||
|
const { parameters } = report || {};
|
||||||
|
const { fields } = parameters || {};
|
||||||
|
|
||||||
|
const fieldOptions = [
|
||||||
|
{ name: 'url', type: 'string', label: formatMessage(labels.url) },
|
||||||
|
{ name: 'title', type: 'string', label: formatMessage(labels.pageTitle) },
|
||||||
|
{ name: 'referrer', type: 'string', label: formatMessage(labels.referrer) },
|
||||||
|
{ name: 'query', type: 'string', label: formatMessage(labels.query) },
|
||||||
|
{ name: 'browser', type: 'string', label: formatMessage(labels.browser) },
|
||||||
|
{ name: 'os', type: 'string', label: formatMessage(labels.os) },
|
||||||
|
{ name: 'device', type: 'string', label: formatMessage(labels.device) },
|
||||||
|
{ name: 'country', type: 'string', label: formatMessage(labels.country) },
|
||||||
|
{ name: 'region', type: 'string', label: formatMessage(labels.region) },
|
||||||
|
{ name: 'city', type: 'string', label: formatMessage(labels.city) },
|
||||||
|
];
|
||||||
|
|
||||||
|
const handleAdd = (value: { name: any }) => {
|
||||||
|
if (!fields.find(({ name }) => name === value.name)) {
|
||||||
|
updateReport({ parameters: { fields: fields.concat(value) } });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRemove = (name: string) => {
|
||||||
|
updateReport({ parameters: { fields: fields.filter(f => f.name !== name) } });
|
||||||
|
};
|
||||||
|
|
||||||
|
const AddButton = () => {
|
||||||
|
return (
|
||||||
|
<PopupTrigger>
|
||||||
|
<Button size="sm">
|
||||||
|
<Icon>
|
||||||
|
<Icons.Plus />
|
||||||
|
</Icon>
|
||||||
|
</Button>
|
||||||
|
<Popup position="bottom" alignment="start">
|
||||||
|
<PopupForm>
|
||||||
|
<FieldSelectForm
|
||||||
|
fields={fieldOptions.filter(({ name }) => !fields.find(f => f.name === name))}
|
||||||
|
onSelect={handleAdd}
|
||||||
|
showType={false}
|
||||||
|
/>
|
||||||
|
</PopupForm>
|
||||||
|
</Popup>
|
||||||
|
</PopupTrigger>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormRow label={formatMessage(labels.fields)} action={<AddButton />}>
|
||||||
|
<ParameterList>
|
||||||
|
{fields.map(({ name }) => {
|
||||||
|
return (
|
||||||
|
<ParameterList.Item key={name} onRemove={() => handleRemove(name)}>
|
||||||
|
{fieldOptions.find(f => f.name === name)?.label}
|
||||||
|
</ParameterList.Item>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ParameterList>
|
||||||
|
</FormRow>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default InsightsFieldParameters;
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
.item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
color: var(--base800);
|
||||||
|
border: 1px solid var(--base300);
|
||||||
|
font-weight: 900;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 5px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter {
|
||||||
|
color: var(--blue900);
|
||||||
|
background-color: var(--blue100);
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 900;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 5px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value {
|
||||||
|
color: var(--base900);
|
||||||
|
background-color: var(--base100);
|
||||||
|
font-weight: 900;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 5px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
88
src/app/(main)/reports/insights/InsightsFilterParameters.tsx
Normal file
88
src/app/(main)/reports/insights/InsightsFilterParameters.tsx
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
import { useMessages, useFormat, useFilters } from 'components/hooks';
|
||||||
|
import Icons from 'components/icons';
|
||||||
|
import { useContext } from 'react';
|
||||||
|
import { Button, FormRow, Icon, Popup, PopupTrigger } from 'react-basics';
|
||||||
|
import FilterSelectForm from '../[reportId]/FilterSelectForm';
|
||||||
|
import ParameterList from '../[reportId]/ParameterList';
|
||||||
|
import PopupForm from '../[reportId]/PopupForm';
|
||||||
|
import { ReportContext } from '../[reportId]/Report';
|
||||||
|
import styles from './InsightsFilterParameters.module.css';
|
||||||
|
import { safeDecodeURIComponent } from 'next-basics';
|
||||||
|
import { OPERATORS } from 'lib/constants';
|
||||||
|
|
||||||
|
export function InsightsFilterParameters() {
|
||||||
|
const { report, updateReport } = useContext(ReportContext);
|
||||||
|
const { formatMessage, labels } = useMessages();
|
||||||
|
const { formatValue } = useFormat();
|
||||||
|
const { filterLabels } = useFilters();
|
||||||
|
const { parameters } = report || {};
|
||||||
|
const { websiteId, filters } = parameters || {};
|
||||||
|
|
||||||
|
const fieldOptions = [
|
||||||
|
{ name: 'url', type: 'string', label: formatMessage(labels.url) },
|
||||||
|
{ name: 'title', type: 'string', label: formatMessage(labels.pageTitle) },
|
||||||
|
{ name: 'referrer', type: 'string', label: formatMessage(labels.referrer) },
|
||||||
|
{ name: 'query', type: 'string', label: formatMessage(labels.query) },
|
||||||
|
{ name: 'browser', type: 'string', label: formatMessage(labels.browser) },
|
||||||
|
{ name: 'os', type: 'string', label: formatMessage(labels.os) },
|
||||||
|
{ name: 'device', type: 'string', label: formatMessage(labels.device) },
|
||||||
|
{ name: 'country', type: 'string', label: formatMessage(labels.country) },
|
||||||
|
{ name: 'region', type: 'string', label: formatMessage(labels.region) },
|
||||||
|
{ name: 'city', type: 'string', label: formatMessage(labels.city) },
|
||||||
|
];
|
||||||
|
|
||||||
|
const handleAdd = (value: { name: any }) => {
|
||||||
|
if (!filters.find(({ name }) => name === value.name)) {
|
||||||
|
updateReport({ parameters: { filters: filters.concat(value) } });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRemove = (name: string) => {
|
||||||
|
updateReport({ parameters: { filters: filters.filter(f => f.name !== name) } });
|
||||||
|
};
|
||||||
|
|
||||||
|
const AddButton = () => {
|
||||||
|
return (
|
||||||
|
<PopupTrigger>
|
||||||
|
<Button size="sm">
|
||||||
|
<Icon>
|
||||||
|
<Icons.Plus />
|
||||||
|
</Icon>
|
||||||
|
</Button>
|
||||||
|
<Popup position="bottom" alignment="start">
|
||||||
|
<PopupForm>
|
||||||
|
<FilterSelectForm
|
||||||
|
websiteId={websiteId}
|
||||||
|
fields={fieldOptions.filter(({ name }) => !filters.find(f => f.name === name))}
|
||||||
|
onSelect={handleAdd}
|
||||||
|
/>
|
||||||
|
</PopupForm>
|
||||||
|
</Popup>
|
||||||
|
</PopupTrigger>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormRow label={formatMessage(labels.filters)} action={<AddButton />}>
|
||||||
|
<ParameterList>
|
||||||
|
{filters.map(({ name, filter, value }) => {
|
||||||
|
const label = fieldOptions.find(f => f.name === name)?.label;
|
||||||
|
const isEquals = [OPERATORS.equals, OPERATORS.notEquals].includes(filter);
|
||||||
|
return (
|
||||||
|
<ParameterList.Item key={name} onRemove={() => handleRemove(name)}>
|
||||||
|
<div className={styles.item}>
|
||||||
|
<div className={styles.label}>{label}</div>
|
||||||
|
<div className={styles.filter}>{filterLabels[filter]}</div>
|
||||||
|
<div className={styles.value}>
|
||||||
|
{safeDecodeURIComponent(isEquals ? formatValue(value, name) : value)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ParameterList.Item>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ParameterList>
|
||||||
|
</FormRow>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default InsightsFilterParameters;
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
.parameter {
|
|
||||||
display: flex;
|
|
||||||
gap: 10px;
|
|
||||||
overflow: hidden;
|
|
||||||
white-space: nowrap;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.op {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.popup {
|
|
||||||
margin-top: -10px;
|
|
||||||
margin-inline-start: 30px;
|
|
||||||
}
|
|
||||||
|
|
@ -1,136 +1,29 @@
|
||||||
import { useFilters, useFormat, useMessages } from 'components/hooks';
|
import { useMessages } from 'components/hooks';
|
||||||
import Icons from 'components/icons';
|
|
||||||
import { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
import {
|
import { Form, FormButtons, SubmitButton } from 'react-basics';
|
||||||
Form,
|
|
||||||
FormButtons,
|
|
||||||
FormRow,
|
|
||||||
Icon,
|
|
||||||
Popup,
|
|
||||||
PopupTrigger,
|
|
||||||
SubmitButton,
|
|
||||||
TooltipPopup,
|
|
||||||
} from 'react-basics';
|
|
||||||
import BaseParameters from '../[reportId]/BaseParameters';
|
import BaseParameters from '../[reportId]/BaseParameters';
|
||||||
import FieldSelectForm from '../[reportId]/FieldSelectForm';
|
|
||||||
import FilterSelectForm from '../[reportId]/FilterSelectForm';
|
|
||||||
import ParameterList from '../[reportId]/ParameterList';
|
|
||||||
import PopupForm from '../[reportId]/PopupForm';
|
|
||||||
import { ReportContext } from '../[reportId]/Report';
|
import { ReportContext } from '../[reportId]/Report';
|
||||||
import styles from './InsightsParameters.module.css';
|
import InsightsFieldParameters from './InsightsFieldParameters';
|
||||||
|
import InsightsFilterParameters from './InsightsFilterParameters';
|
||||||
|
|
||||||
export function InsightsParameters() {
|
export function InsightsParameters() {
|
||||||
const { report, runReport, updateReport, isRunning } = useContext(ReportContext);
|
const { report, runReport, isRunning } = useContext(ReportContext);
|
||||||
const { formatMessage, labels } = useMessages();
|
const { formatMessage, labels } = useMessages();
|
||||||
const { formatValue } = useFormat();
|
|
||||||
const { filterLabels } = useFilters();
|
|
||||||
const { id, parameters } = report || {};
|
const { id, parameters } = report || {};
|
||||||
const { websiteId, dateRange, fields, filters } = parameters || {};
|
const { websiteId, dateRange, fields, filters } = parameters || {};
|
||||||
const { startDate, endDate } = dateRange || {};
|
const { startDate, endDate } = dateRange || {};
|
||||||
const parametersSelected = websiteId && startDate && endDate;
|
const parametersSelected = websiteId && startDate && endDate;
|
||||||
const queryEnabled = websiteId && dateRange && (fields?.length || filters?.length);
|
const queryEnabled = websiteId && dateRange && (fields?.length || filters?.length);
|
||||||
|
|
||||||
const fieldOptions = [
|
|
||||||
{ name: 'url', type: 'string', label: formatMessage(labels.url) },
|
|
||||||
{ name: 'title', type: 'string', label: formatMessage(labels.pageTitle) },
|
|
||||||
{ name: 'referrer', type: 'string', label: formatMessage(labels.referrer) },
|
|
||||||
{ name: 'query', type: 'string', label: formatMessage(labels.query) },
|
|
||||||
{ name: 'browser', type: 'string', label: formatMessage(labels.browser) },
|
|
||||||
{ name: 'os', type: 'string', label: formatMessage(labels.os) },
|
|
||||||
{ name: 'device', type: 'string', label: formatMessage(labels.device) },
|
|
||||||
{ name: 'country', type: 'string', label: formatMessage(labels.country) },
|
|
||||||
{ name: 'region', type: 'string', label: formatMessage(labels.region) },
|
|
||||||
{ name: 'city', type: 'string', label: formatMessage(labels.city) },
|
|
||||||
];
|
|
||||||
|
|
||||||
const parameterGroups = [
|
|
||||||
{ id: 'fields', label: formatMessage(labels.fields) },
|
|
||||||
{ id: 'filters', label: formatMessage(labels.filters) },
|
|
||||||
];
|
|
||||||
|
|
||||||
const parameterData = {
|
|
||||||
fields,
|
|
||||||
filters,
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmit = (values: any) => {
|
const handleSubmit = (values: any) => {
|
||||||
runReport(values);
|
runReport(values);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAdd = (id: string | number, value: { name: any }) => {
|
|
||||||
const data = parameterData[id];
|
|
||||||
|
|
||||||
if (!data.find(({ name }) => name === value.name)) {
|
|
||||||
updateReport({ parameters: { [id]: data.concat(value) } });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleRemove = (id: string, index: number) => {
|
|
||||||
const data = [...parameterData[id]];
|
|
||||||
data.splice(index, 1);
|
|
||||||
updateReport({ parameters: { [id]: data } });
|
|
||||||
};
|
|
||||||
|
|
||||||
const AddButton = ({ id, onAdd }) => {
|
|
||||||
return (
|
|
||||||
<PopupTrigger>
|
|
||||||
<TooltipPopup label={formatMessage(labels.add)} position="top">
|
|
||||||
<Icon>
|
|
||||||
<Icons.Plus />
|
|
||||||
</Icon>
|
|
||||||
</TooltipPopup>
|
|
||||||
<Popup position="bottom" alignment="start" className={styles.popup}>
|
|
||||||
<PopupForm>
|
|
||||||
{id === 'fields' && (
|
|
||||||
<FieldSelectForm
|
|
||||||
fields={fieldOptions}
|
|
||||||
onSelect={onAdd.bind(null, id)}
|
|
||||||
showType={false}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{id === 'filters' && (
|
|
||||||
<FilterSelectForm
|
|
||||||
websiteId={websiteId}
|
|
||||||
items={fieldOptions}
|
|
||||||
onSelect={onAdd.bind(null, id)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</PopupForm>
|
|
||||||
</Popup>
|
|
||||||
</PopupTrigger>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form values={parameters} onSubmit={handleSubmit}>
|
<Form values={parameters} onSubmit={handleSubmit}>
|
||||||
<BaseParameters allowWebsiteSelect={!id} />
|
<BaseParameters allowWebsiteSelect={!id} />
|
||||||
{parametersSelected &&
|
{parametersSelected && <InsightsFieldParameters />}
|
||||||
parameterGroups.map(({ id, label }) => {
|
{parametersSelected && <InsightsFilterParameters />}
|
||||||
return (
|
|
||||||
<FormRow key={label} label={label} action={<AddButton id={id} onAdd={handleAdd} />}>
|
|
||||||
<ParameterList items={parameterData[id]} onRemove={index => handleRemove(id, index)}>
|
|
||||||
{({ name, filter, value }) => {
|
|
||||||
return (
|
|
||||||
<div className={styles.parameter}>
|
|
||||||
{id === 'fields' && (
|
|
||||||
<>
|
|
||||||
<div>{fieldOptions.find(f => f.name === name)?.label}</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{id === 'filters' && (
|
|
||||||
<>
|
|
||||||
<div>{fieldOptions.find(f => f.name === name)?.label}</div>
|
|
||||||
<div className={styles.op}>{filterLabels[filter]}</div>
|
|
||||||
<div>{formatValue(value, name)}</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</ParameterList>
|
|
||||||
</FormRow>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
<FormButtons>
|
<FormButtons>
|
||||||
<SubmitButton variant="primary" disabled={!queryEnabled} isLoading={isRunning}>
|
<SubmitButton variant="primary" disabled={!queryEnabled} isLoading={isRunning}>
|
||||||
{formatMessage(labels.runQuery)}
|
{formatMessage(labels.runQuery)}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
.bar {
|
.bar {
|
||||||
font-size: 14px;
|
font-size: 12px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
color: var(--base600);
|
color: var(--base600);
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ export function useFilters() {
|
||||||
};
|
};
|
||||||
|
|
||||||
const typeFilters = {
|
const typeFilters = {
|
||||||
string: [OPERATORS.equals, OPERATORS.notEquals],
|
string: [OPERATORS.equals, OPERATORS.notEquals, OPERATORS.contains, OPERATORS.doesNotContain],
|
||||||
array: [OPERATORS.contains, OPERATORS.doesNotContain],
|
array: [OPERATORS.contains, OPERATORS.doesNotContain],
|
||||||
boolean: [OPERATORS.true, OPERATORS.false],
|
boolean: [OPERATORS.true, OPERATORS.false],
|
||||||
number: [
|
number: [
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,8 @@ function mapFilter(column: string, filter: string, name: string, type: string =
|
||||||
return `${column} != {${name}:${type}}`;
|
return `${column} != {${name}:${type}}`;
|
||||||
case OPERATORS.contains:
|
case OPERATORS.contains:
|
||||||
return `positionCaseInsensitive(${column}, {${name}:${type}}) > 0`;
|
return `positionCaseInsensitive(${column}, {${name}:${type}}) > 0`;
|
||||||
|
case OPERATORS.doesNotContain:
|
||||||
|
return `positionCaseInsensitive(${column}, {${name}:${type}}) = 0`;
|
||||||
default:
|
default:
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -100,6 +100,8 @@ function mapFilter(column: string, filter: string, name: string, type = 'varchar
|
||||||
return `${column} != {{${name}::${type}}}`;
|
return `${column} != {{${name}::${type}}}`;
|
||||||
case OPERATORS.contains:
|
case OPERATORS.contains:
|
||||||
return `${column} like {{${name}::${type}}}`;
|
return `${column} like {{${name}::${type}}}`;
|
||||||
|
case OPERATORS.doesNotContain:
|
||||||
|
return `${column} not like {{${name}::${type}}}`;
|
||||||
default:
|
default:
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue