fix(ui) - dropdown fixes
This commit is contained in:
parent
c8ec85c98e
commit
188e504bb7
29 changed files with 44 additions and 883 deletions
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||
import { connect } from 'react-redux';
|
||||
import { tokenRE } from 'Types/integrations/bugsnagConfig';
|
||||
import { edit } from 'Duck/integrations/actions';
|
||||
import { Dropdown } from 'UI';
|
||||
import Select from 'Shared/Select';
|
||||
import { withRequest } from 'HOCs';
|
||||
|
||||
@connect(state => ({
|
||||
|
|
@ -50,7 +50,7 @@ export default class ProjectListDropdown extends React.PureComponent {
|
|||
this.fetchProjectList();
|
||||
}
|
||||
}
|
||||
onChange = (e, target) => {
|
||||
onChange = (target) => {
|
||||
if (typeof this.props.onChange === 'function') {
|
||||
this.props.onChange({ target });
|
||||
}
|
||||
|
|
@ -65,11 +65,11 @@ export default class ProjectListDropdown extends React.PureComponent {
|
|||
} = this.props;
|
||||
const options = projects.map(({ name, id }) => ({ text: name, value: id }));
|
||||
return (
|
||||
<Dropdown
|
||||
selection
|
||||
<Select
|
||||
// selection
|
||||
options={ options }
|
||||
name={ name }
|
||||
value={ value }
|
||||
value={ options.find(o => o.value === value) }
|
||||
placeholder={ placeholder }
|
||||
onChange={ this.onChange }
|
||||
loading={ loading }
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||
import { connect } from 'react-redux';
|
||||
import { ACCESS_KEY_ID_LENGTH, SECRET_ACCESS_KEY_LENGTH } from 'Types/integrations/cloudwatchConfig';
|
||||
import { edit } from 'Duck/integrations/actions';
|
||||
import { Dropdown } from 'UI';
|
||||
import Select from 'Shared/Select';
|
||||
import { withRequest } from 'HOCs';
|
||||
|
||||
@connect(state => ({
|
||||
|
|
@ -48,7 +48,7 @@ export default class LogGroupDropdown extends React.PureComponent {
|
|||
this.fetchLogGroups();
|
||||
}
|
||||
}
|
||||
onChange = (e, target) => {
|
||||
onChange = (target) => {
|
||||
if (typeof this.props.onChange === 'function') {
|
||||
this.props.onChange({ target });
|
||||
}
|
||||
|
|
@ -63,11 +63,11 @@ export default class LogGroupDropdown extends React.PureComponent {
|
|||
} = this.props;
|
||||
const options = values.map(g => ({ text: g, value: g }));
|
||||
return (
|
||||
<Dropdown
|
||||
selection
|
||||
<Select
|
||||
// selection
|
||||
options={ options }
|
||||
name={ name }
|
||||
value={ value }
|
||||
value={ options.find(o => o.value === value) }
|
||||
placeholder={ placeholder }
|
||||
onChange={ this.onChange }
|
||||
loading={ loading }
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@ import cn from 'classnames';
|
|||
import withPageTitle from 'HOCs/withPageTitle';
|
||||
import {
|
||||
Form, IconButton, SlideModal, Input, Button, Loader,
|
||||
NoContent, Popup, CopyButton, Dropdown } from 'UI';
|
||||
NoContent, Popup, CopyButton } from 'UI';
|
||||
import Select from 'Shared/Select';
|
||||
import { init, save, edit, remove as deleteMember, fetchList, generateInviteLink } from 'Duck/member';
|
||||
import { fetchList as fetchRoles } from 'Duck/roles';
|
||||
import styles from './manageUsers.module.css';
|
||||
|
|
@ -39,7 +40,7 @@ class ManageUsers extends React.PureComponent {
|
|||
state = { showModal: false, remaining: this.props.account.limits.teamMember.remaining, invited: false }
|
||||
|
||||
// writeOption = (e, { name, value }) => this.props.edit({ [ name ]: value });
|
||||
onChange = (e, { name, value }) => this.props.edit({ [ name ]: value });
|
||||
onChange = ({ name, value }) => this.props.edit({ [ name ]: value.value });
|
||||
onChangeCheckbox = ({ target: { checked, name } }) => this.props.edit({ [ name ]: checked });
|
||||
setFocus = () => this.focusElement && this.focusElement.focus();
|
||||
closeModal = () => this.setState({ showModal: false });
|
||||
|
|
@ -138,12 +139,12 @@ class ManageUsers extends React.PureComponent {
|
|||
{ isEnterprise && (
|
||||
<Form.Field>
|
||||
<label htmlFor="role">{ 'Role' }</label>
|
||||
<Dropdown
|
||||
<Select
|
||||
placeholder="Role"
|
||||
selection
|
||||
options={ roles }
|
||||
name="roleId"
|
||||
value={ member.roleId }
|
||||
value={ roles.find(r => r.value === member.roleId) }
|
||||
onChange={ this.onChange }
|
||||
/>
|
||||
</Form.Field>
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ const RoleForm = (props: Props) => {
|
|||
isSearchable
|
||||
name="permissions"
|
||||
options={ permissions }
|
||||
onChange={ ({ value }: any) => writeOption({ name: 'permissions', value }) }
|
||||
onChange={ ({ value }: any) => writeOption({ name: 'permissions', value: value.value }) }
|
||||
value={null}
|
||||
/>
|
||||
{ role.permissions.size > 0 && (
|
||||
|
|
|
|||
|
|
@ -37,9 +37,14 @@ function CustomMetricTableErrors(props: RouteComponentProps<Props>) {
|
|||
}, [errorId])
|
||||
|
||||
return (
|
||||
<NoContent show={metric.data.errors && metric.data.errors === 0}>
|
||||
<NoContent
|
||||
title="No data found"
|
||||
subtext=""
|
||||
show={metric.data.errors && metric.data.errors === 0}
|
||||
size="small"
|
||||
>
|
||||
{metric.data.errors && metric.data.errors.map((error: any, index: any) => (
|
||||
<ErrorListItem error={error} onClick={() => onErrorClick(error)} />
|
||||
<ErrorListItem key={index} error={error} onClick={() => onErrorClick(error)} />
|
||||
))}
|
||||
|
||||
{isEdit && (
|
||||
|
|
|
|||
|
|
@ -1,14 +0,0 @@
|
|||
.wrapper {
|
||||
background-color: $gray-light;
|
||||
/* border: solid thin $gray-medium; */
|
||||
border-radius: 3px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.innerWapper {
|
||||
border-radius: 3px;
|
||||
width: 70%;
|
||||
margin: 0 auto;
|
||||
background-color: white;
|
||||
min-height: 220px;
|
||||
}
|
||||
|
|
@ -1,169 +0,0 @@
|
|||
import React, { useEffect, useState, useRef } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { Loader, NoContent, SegmentSelection } from 'UI';
|
||||
import { Styles } from '../../common';
|
||||
import Period from 'Types/app/period';
|
||||
import stl from './CustomMetricWidgetPreview.module.css';
|
||||
import { remove } from 'Duck/customMetrics';
|
||||
import DateRange from 'Shared/DateRange';
|
||||
import { edit } from 'Duck/customMetrics';
|
||||
import CustomMetriLineChart from '../CustomMetriLineChart';
|
||||
import CustomMetricPercentage from '../CustomMetricPercentage';
|
||||
import CustomMetricTable from '../CustomMetricTable';
|
||||
import CustomMetricPieChart from '../CustomMetricPieChart';
|
||||
|
||||
const customParams = (rangeName: string) => {
|
||||
const params = { density: 70 }
|
||||
|
||||
// if (rangeName === LAST_24_HOURS) params.density = 70
|
||||
// if (rangeName === LAST_30_MINUTES) params.density = 70
|
||||
// if (rangeName === YESTERDAY) params.density = 70
|
||||
// if (rangeName === LAST_7_DAYS) params.density = 70
|
||||
|
||||
return params
|
||||
}
|
||||
|
||||
interface Props {
|
||||
metric: any;
|
||||
data?: any;
|
||||
onClickEdit?: (e) => void;
|
||||
remove: (id) => void;
|
||||
edit: (metric) => void;
|
||||
}
|
||||
function CustomMetricWidget(props: Props) {
|
||||
const { metric } = props;
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [data, setData] = useState<any>({ chart: [{}] })
|
||||
const [period, setPeriod] = useState(Period({ rangeName: metric.rangeName, startDate: metric.startDate, endDate: metric.endDate }));
|
||||
|
||||
const colors = Styles.customMetricColors;
|
||||
const params = customParams(period.rangeName)
|
||||
const prevMetricRef = useRef<any>();
|
||||
const isTimeSeries = metric.metricType === 'timeseries';
|
||||
const isTable = metric.metricType === 'table';
|
||||
|
||||
useEffect(() => {
|
||||
// Check for title change
|
||||
if (prevMetricRef.current && prevMetricRef.current.name !== metric.name) {
|
||||
prevMetricRef.current = metric;
|
||||
return
|
||||
};
|
||||
prevMetricRef.current = metric;
|
||||
setLoading(true);
|
||||
}, [metric])
|
||||
|
||||
const onDateChange = (changedDates) => {
|
||||
setPeriod({ ...changedDates, rangeName: changedDates.rangeValue })
|
||||
props.edit({ ...changedDates, rangeName: changedDates.rangeValue });
|
||||
}
|
||||
|
||||
const chagneViewType = (e, { name, value }) => {
|
||||
props.edit({ [ name ]: value });
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mb-10">
|
||||
<div className="flex items-center">
|
||||
<div className="mr-auto font-medium">Preview</div>
|
||||
<div className="flex items-center">
|
||||
{isTimeSeries && (
|
||||
<>
|
||||
<span className="color-gray-medium mr-4">Visualization</span>
|
||||
<SegmentSelection
|
||||
name="viewType"
|
||||
className="my-3"
|
||||
primary
|
||||
icons={true}
|
||||
onSelect={ chagneViewType }
|
||||
value={{ value: metric.viewType }}
|
||||
list={ [
|
||||
{ value: 'lineChart', name: 'Chart', icon: 'graph-up-arrow' },
|
||||
{ value: 'progress', name: 'Progress', icon: 'hash' },
|
||||
]}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{isTable && (
|
||||
<>
|
||||
<span className="mr-1 color-gray-medium">Visualization</span>
|
||||
<SegmentSelection
|
||||
name="viewType"
|
||||
className="my-3"
|
||||
primary={true}
|
||||
icons={true}
|
||||
onSelect={ chagneViewType }
|
||||
value={{ value: metric.viewType }}
|
||||
list={[
|
||||
{ value: 'table', name: 'Table', icon: 'table' },
|
||||
{ value: 'pieChart', name: 'Chart', icon: 'pie-chart-fill' },
|
||||
]}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<div className="mx-4" />
|
||||
<span className="mr-1 color-gray-medium">Time Range</span>
|
||||
<DateRange
|
||||
rangeValue={metric.rangeName}
|
||||
startDate={metric.startDate}
|
||||
endDate={metric.endDate}
|
||||
onDateChange={onDateChange}
|
||||
customRangeRight
|
||||
direction="left"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={stl.wrapper}>
|
||||
<div className={stl.innerWapper}>
|
||||
<Loader loading={ loading } size="small">
|
||||
<NoContent
|
||||
size="small"
|
||||
show={ data.length === 0 }
|
||||
>
|
||||
<div className="p-4 font-medium">
|
||||
{metric.name}
|
||||
</div>
|
||||
<div className="px-4 pb-4">
|
||||
{ isTimeSeries && (
|
||||
<>
|
||||
{ metric.viewType === 'progress' && (
|
||||
<CustomMetricPercentage
|
||||
data={data[0]}
|
||||
colors={colors}
|
||||
params={params}
|
||||
/>
|
||||
)}
|
||||
{ metric.viewType === 'lineChart' && (
|
||||
<CustomMetriLineChart
|
||||
data={data}
|
||||
// seriesMap={seriesMap}
|
||||
colors={colors}
|
||||
params={params}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{ isTable && (
|
||||
<>
|
||||
{ metric.viewType === 'table' ? (
|
||||
<CustomMetricTable metric={metric} data={data[0]} />
|
||||
) : (
|
||||
<CustomMetricPieChart
|
||||
metric={metric}
|
||||
data={data[0]}
|
||||
colors={colors}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</NoContent>
|
||||
</Loader>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(null, { remove, edit })(CustomMetricWidget);
|
||||
|
|
@ -1 +0,0 @@
|
|||
export { default } from './CustomMetricWidgetPreview';
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
import React from 'react';
|
||||
import { Dropdown } from 'semantic-ui-react';
|
||||
import { IconButton } from 'UI';
|
||||
|
||||
const sessionSortOptions = {
|
||||
// '': 'All',
|
||||
'open': 'Open',
|
||||
'closed': 'Closed',
|
||||
};
|
||||
|
||||
const sortOptions = Object.entries(sessionSortOptions)
|
||||
.map(([ value, text ]) => ({ value, text }));
|
||||
|
||||
const IssuesSortDropdown = ({ onChange, value }) => {
|
||||
// sort = (e, { value }) => {
|
||||
// const [ sort, order ] = value.split('-');
|
||||
// const sign = order === 'desc' ? -1 : 1;
|
||||
// this.props.applyFilter({ order, sort });
|
||||
|
||||
// this.props.sort(sort, sign)
|
||||
// setTimeout(() => this.props.sort(sort, sign), 3000);
|
||||
// }
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
name="issueType"
|
||||
value={ value || sortOptions[ 0 ].value}
|
||||
trigger={
|
||||
<IconButton
|
||||
// outline
|
||||
label=""
|
||||
icon="filter"
|
||||
size="medium"
|
||||
// shadow
|
||||
className="outline-none"
|
||||
/>
|
||||
}
|
||||
pointing="top right"
|
||||
options={ sortOptions }
|
||||
onChange={ onChange }
|
||||
// defaultValue={ sortOptions[ 0 ].value }
|
||||
icon={ null }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default IssuesSortDropdown;
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { getRE } from 'App/utils';
|
||||
import { Input } from 'UI';
|
||||
import IssueListItem from './IssueListItem';
|
||||
import IssuesSortDropdown from './IssuesSortDropdown';
|
||||
|
||||
class SessionIssuesPanel extends React.Component {
|
||||
state = { search: '', closed: false, issueType: 'open' }
|
||||
write = ({ target: { value, name } }) => this.setState({ [ name ]: value });
|
||||
writeOption = (e, { name, value }) => this.setState({ [ name ]: value });
|
||||
|
||||
render() {
|
||||
const { issueTypeIcons, users, activeIssue, issues = [], onIssueClick = () => null } = this.props;
|
||||
const { search, closed, issueType } = this.state;
|
||||
|
||||
let filteredIssues = issues.filter(({ closed, title }) => getRE(search, 'i').test(title))
|
||||
if (!issueType !== '') {
|
||||
filteredIssues = filteredIssues.filter(({ closed }) => closed === ( this.state.issueType === 'closed'))
|
||||
}
|
||||
// .filter(({ closed }) => closed == this.state.closed);
|
||||
|
||||
filteredIssues = filteredIssues.map(issue => {
|
||||
issue.user = users.filter(user => user.id === issue.assignee).first();
|
||||
return issue;
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="p-3 bg-white flex items-center justify-between">
|
||||
<Input
|
||||
name="search"
|
||||
className="flex-1 mr-4"
|
||||
icon="search"
|
||||
iconPosition="left"
|
||||
placeholder="Search"
|
||||
onChange={ this.write }
|
||||
/>
|
||||
<IssuesSortDropdown
|
||||
onChange={ this.writeOption }
|
||||
value={ issueType }
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
{ filteredIssues.map(issue => (
|
||||
<IssueListItem
|
||||
key={ issue.key }
|
||||
onClick={ () => onIssueClick(issue) }
|
||||
issue={ issue }
|
||||
icon={ issueTypeIcons[issue.issueType] }
|
||||
user={ issue.user }
|
||||
active={ activeIssue && activeIssue.id === issue.id }
|
||||
/>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(state => ({
|
||||
issues: state.getIn(['assignments', 'list']),
|
||||
issueTypeIcons: state.getIn(['assignments', 'issueTypeIcons']),
|
||||
users: state.getIn(['assignments', 'users']),
|
||||
}))(SessionIssuesPanel);
|
||||
|
|
@ -5,7 +5,6 @@ import IssueHeader from './IssueHeader';
|
|||
import IssueComment from './IssueComment';
|
||||
import IssueCommentForm from './IssueCommentForm';
|
||||
import IssueDetails from './IssueDetails';
|
||||
import SessionIssuesPanel from './SessionIssuesPanel';
|
||||
import IssueForm from './IssueForm';
|
||||
import IssueListItem from './IssueListItem';
|
||||
import IssueDescription from './IssueDescription';
|
||||
|
|
@ -298,13 +297,4 @@ storiesOf('Issues', module)
|
|||
<IssueForm issueTypes={ List(issueTypes) } />
|
||||
</div>
|
||||
))
|
||||
.add('SessionIssuesPanel', () => (
|
||||
<div className="bg-white">
|
||||
<SessionIssuesPanel
|
||||
issues={ issues }
|
||||
onIssueClick={ onIssueClick }
|
||||
issueTypeIcons={ issueTypeIcons }
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
|
||||
|
||||
.searchInput {
|
||||
padding: 10px 6px !important;
|
||||
|
||||
&:focus {
|
||||
border-color: $teal !important;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,11 @@
|
|||
import React from 'react'
|
||||
import { Form, Input, Icon, Button, Link, Dropdown, CircularLoader } from 'UI'
|
||||
import { Form, Input, Icon, Button, Link, CircularLoader } from 'UI'
|
||||
import { login } from 'App/routes'
|
||||
import ReCAPTCHA from 'react-google-recaptcha'
|
||||
import stl from './signup.module.css'
|
||||
import { signup } from 'Duck/user';
|
||||
import { connect } from 'react-redux'
|
||||
import Select from 'Shared/Select'
|
||||
|
||||
const LOGIN_ROUTE = login()
|
||||
const recaptchaRef = React.createRef()
|
||||
|
|
@ -48,7 +49,7 @@ export default class SignupForm extends React.Component {
|
|||
}
|
||||
|
||||
write = ({ target: { value, name } }) => this.setState({ [ name ]: value })
|
||||
writeOption = (e, { name, value }) => this.setState({ [ name ]: value });
|
||||
writeOption = ({ name, value }) => this.setState({ [ name ]: value.value });
|
||||
|
||||
onSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
|
|
@ -82,7 +83,7 @@ export default class SignupForm extends React.Component {
|
|||
{ tenants.length > 0 && (
|
||||
<Form.Field>
|
||||
<label>Existing Accounts</label>
|
||||
<Dropdown
|
||||
<Select
|
||||
className="w-full"
|
||||
placeholder="Select account"
|
||||
selection
|
||||
|
|
|
|||
|
|
@ -1,223 +0,0 @@
|
|||
import React from 'react';
|
||||
import { Form, Button, IconButton, HelpText } from 'UI';
|
||||
import FilterSeries from '../FilterSeries';
|
||||
import { connect } from 'react-redux';
|
||||
import { edit as editMetric, save, addSeries, removeSeries, remove } from 'Duck/customMetrics';
|
||||
import CustomMetricWidgetPreview from 'App/components/Dashboard/Widgets/CustomMetricsWidgets/CustomMetricWidgetPreview';
|
||||
import { confirm } from 'UI';
|
||||
import { toast } from 'react-toastify';
|
||||
import cn from 'classnames';
|
||||
import DropdownPlain from '../../DropdownPlain';
|
||||
import { metricTypes, metricOf, issueOptions } from 'App/constants/filterOptions';
|
||||
import { FilterKey } from 'Types/filter/filterType';
|
||||
interface Props {
|
||||
metric: any;
|
||||
editMetric: (metric, shouldFetch?) => void;
|
||||
save: (metric) => Promise<void>;
|
||||
loading: boolean;
|
||||
addSeries: (series?) => void;
|
||||
onClose: () => void;
|
||||
remove: (id) => Promise<void>;
|
||||
removeSeries: (seriesIndex) => void;
|
||||
}
|
||||
|
||||
function CustomMetricForm(props: Props) {
|
||||
const { metric, loading } = props;
|
||||
// const metricOfOptions = metricOf.filter(i => i.key === metric.metricType);
|
||||
const timeseriesOptions = metricOf.filter(i => i.type === 'timeseries');
|
||||
const tableOptions = metricOf.filter(i => i.type === 'table');
|
||||
const isTable = metric.metricType === 'table';
|
||||
const isTimeSeries = metric.metricType === 'timeseries';
|
||||
const _issueOptions = [{ text: 'All', value: 'all' }].concat(issueOptions);
|
||||
|
||||
|
||||
const addSeries = () => {
|
||||
props.addSeries();
|
||||
}
|
||||
|
||||
const removeSeries = (index) => {
|
||||
props.removeSeries(index);
|
||||
}
|
||||
|
||||
const write = ({ target: { value, name } }) => props.editMetric({ [ name ]: value }, false);
|
||||
const writeOption = (e, { value, name }) => {
|
||||
props.editMetric({ [ name ]: value }, false);
|
||||
|
||||
if (name === 'metricValue') {
|
||||
props.editMetric({ metricValue: [value] }, false);
|
||||
}
|
||||
|
||||
if (name === 'metricOf') {
|
||||
if (value === FilterKey.ISSUE) {
|
||||
props.editMetric({ metricValue: ['all'] }, false);
|
||||
}
|
||||
}
|
||||
|
||||
if (name === 'metricType') {
|
||||
if (value === 'timeseries') {
|
||||
props.editMetric({ metricOf: timeseriesOptions[0].value, viewType: 'lineChart' }, false);
|
||||
} else if (value === 'table') {
|
||||
props.editMetric({ metricOf: tableOptions[0].value, viewType: 'table' }, false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// const changeConditionTab = (e, { name, value }) => {
|
||||
// props.editMetric({[ 'viewType' ]: value });
|
||||
// };
|
||||
|
||||
const save = () => {
|
||||
props.save(metric).then(() => {
|
||||
toast.success(metric.exists() ? 'Updated succesfully.' : 'Created succesfully.');
|
||||
props.onClose()
|
||||
});
|
||||
}
|
||||
|
||||
const deleteHandler = async () => {
|
||||
if (await confirm({
|
||||
header: 'Custom Metric',
|
||||
confirmButton: 'Delete',
|
||||
confirmation: `Are you sure you want to delete ${metric.name}`
|
||||
})) {
|
||||
props.remove(metric.metricId).then(() => {
|
||||
toast.success('Deleted succesfully.');
|
||||
props.onClose();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Form
|
||||
className="relative"
|
||||
onSubmit={save}
|
||||
>
|
||||
<div className="p-5 pb-20" style={{ height: 'calc(100vh - 60px)', overflowY: 'auto' }}>
|
||||
<div className="form-group">
|
||||
<label className="font-medium">Metric Title</label>
|
||||
<input
|
||||
autoFocus={ true }
|
||||
className="text-lg"
|
||||
name="name"
|
||||
style={{ fontSize: '18px', padding: '10px', fontWeight: 600}}
|
||||
value={ metric.name }
|
||||
onChange={ write }
|
||||
placeholder="Metric Title"
|
||||
id="name-field"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label className="font-medium">Metric Type</label>
|
||||
<div className="flex items-center">
|
||||
<DropdownPlain
|
||||
name="metricType"
|
||||
options={metricTypes}
|
||||
value={ metric.metricType }
|
||||
onChange={ writeOption }
|
||||
/>
|
||||
|
||||
{metric.metricType === 'timeseries' && (
|
||||
<>
|
||||
<span className="mx-3">of</span>
|
||||
<DropdownPlain
|
||||
name="metricOf"
|
||||
options={timeseriesOptions}
|
||||
value={ metric.metricOf }
|
||||
onChange={ writeOption }
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{metric.metricType === 'table' && (
|
||||
<>
|
||||
<span className="mx-3">of</span>
|
||||
<DropdownPlain
|
||||
name="metricOf"
|
||||
options={tableOptions}
|
||||
value={ metric.metricOf }
|
||||
onChange={ writeOption }
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{metric.metricOf === FilterKey.ISSUE && (
|
||||
<>
|
||||
<span className="mx-3">issue type</span>
|
||||
<DropdownPlain
|
||||
name="metricValue"
|
||||
options={_issueOptions}
|
||||
value={ metric.metricValue[0] }
|
||||
onChange={ writeOption }
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{metric.metricType === 'table' && (
|
||||
<>
|
||||
<span className="mx-3">showing</span>
|
||||
<DropdownPlain
|
||||
name="metricFormat"
|
||||
options={[
|
||||
{ value: 'sessionCount', text: 'Session Count' },
|
||||
]}
|
||||
value={ metric.metricFormat }
|
||||
onChange={ writeOption }
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label className="font-medium flex items-center">
|
||||
{`${isTable ? 'Filter by' : 'Chart Series'}`}
|
||||
{!isTable && <HelpText position="top left" text="Defines a series of data for the line in chart." className="pl-3" />}
|
||||
</label>
|
||||
{metric.series && metric.series.size > 0 && metric.series.take(isTable ? 1 : metric.series.size).map((series: any, index: number) => (
|
||||
<div className="mb-2">
|
||||
<FilterSeries
|
||||
hideHeader={ isTable }
|
||||
seriesIndex={index}
|
||||
series={series}
|
||||
onRemoveSeries={() => removeSeries(index)}
|
||||
canDelete={metric.series.size > 1}
|
||||
emptyMessage={isTable ?
|
||||
'Filter data using any event or attribute. Use Add Step button below to do so.' :
|
||||
'Add user event or filter to define the series by clicking Add Step.'
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{ isTimeSeries && (
|
||||
<div className={cn("flex justify-end -my-4", {'disabled' : metric.series.size > 2})}>
|
||||
<IconButton hover type="button" onClick={addSeries} primaryText label="SERIES" icon="plus" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="my-8" />
|
||||
|
||||
<CustomMetricWidgetPreview metric={metric} />
|
||||
</div>
|
||||
|
||||
<div className="flex items-center fixed border-t w-full bottom-0 px-5 py-2 bg-white">
|
||||
<div className="mr-auto">
|
||||
<Button loading={loading} variant="primary" className="float-left mr-2" disabled={!metric.validate()}>
|
||||
{ `${metric.exists() ? 'Update' : 'Create'}` }
|
||||
</Button>
|
||||
|
||||
<Button type="button" onClick={props.onClose}>Cancel</Button>
|
||||
</div>
|
||||
<div>
|
||||
{ metric.exists() && <Button variant="text" type="button" className="ml-3" outline hover plain onClick={deleteHandler}>Delete</Button> }
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(state => ({
|
||||
metric: state.getIn(['customMetrics', 'instance']),
|
||||
loading: state.getIn(['customMetrics', 'saveRequest', 'loading']),
|
||||
}), { editMetric, save, addSeries, remove, removeSeries })(CustomMetricForm);
|
||||
|
|
@ -1 +0,0 @@
|
|||
export { default } from './CustomMetricForm';
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
import React from 'react'
|
||||
import { IconButton, SlideModal } from 'UI';
|
||||
import CustomMetricForm from '../CustomMetricForm';
|
||||
import { connect } from 'react-redux'
|
||||
import { init } from 'Duck/customMetrics';
|
||||
|
||||
interface Props {
|
||||
metric: any;
|
||||
init: (instance?, setDefault?) => void;
|
||||
}
|
||||
function CustomMetricsModal(props: Props) {
|
||||
const { metric } = props;
|
||||
return (
|
||||
<>
|
||||
<SlideModal
|
||||
title={
|
||||
<div className="flex items-center">
|
||||
<span className="mr-3">{ metric && metric.exists() ? 'Update Custom Metric' : 'Create Custom Metric' }</span>
|
||||
</div>
|
||||
}
|
||||
isDisplayed={ !!metric }
|
||||
onClose={ () => props.init(null, true)}
|
||||
content={ (!!metric) && (
|
||||
<div style={{ backgroundColor: '#f6f6f6' }}>
|
||||
<CustomMetricForm metric={metric} onClose={() => props.init(null, true)} />
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
export default connect(state => ({
|
||||
metric: state.getIn(['customMetrics', 'instance']),
|
||||
alertInstance: state.getIn(['alerts', 'instance']),
|
||||
showModal: state.getIn(['customMetrics', 'showModal']),
|
||||
}), { init })(CustomMetricsModal);
|
||||
|
|
@ -1 +0,0 @@
|
|||
export { default } from './CustomMetricsModal';
|
||||
|
|
@ -1,124 +0,0 @@
|
|||
import React from 'react';
|
||||
import { Dropdown } from 'semantic-ui-react';
|
||||
import cn from 'classnames';
|
||||
import {
|
||||
getDateRangeFromValue,
|
||||
getDateRangeLabel,
|
||||
dateRangeValues,
|
||||
getDateRangeFromTs,
|
||||
CUSTOM_RANGE,
|
||||
DATE_RANGE_VALUES,
|
||||
} from 'App/dateRange';
|
||||
import { Icon } from 'UI';
|
||||
import DateRangePopup from './DateRangePopup';
|
||||
import DateOptionLabel from './DateOptionLabel';
|
||||
import styles from './dateRangeDropdown.module.css';
|
||||
|
||||
const getDateRangeOptions = (customRange = getDateRangeFromValue(CUSTOM_RANGE)) => dateRangeValues.map(value => ({
|
||||
value,
|
||||
text: <DateOptionLabel range={ value === CUSTOM_RANGE ? customRange : getDateRangeFromValue(value) } />,
|
||||
content: getDateRangeLabel(value),
|
||||
}));
|
||||
|
||||
export default class DateRangeDropdown extends React.PureComponent {
|
||||
state = {
|
||||
showDateRangePopup: false,
|
||||
range: null,
|
||||
value: DATE_RANGE_VALUES.TODAY,
|
||||
};
|
||||
|
||||
static getDerivedStateFromProps(props) {
|
||||
const { rangeValue, startDate, endDate } = props;
|
||||
if (rangeValue) {
|
||||
const range = rangeValue === CUSTOM_RANGE
|
||||
? getDateRangeFromTs(startDate, endDate)
|
||||
: getDateRangeFromValue(rangeValue);
|
||||
return {
|
||||
value: rangeValue,
|
||||
range,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
onCancelDateRange = () => this.setState({ showDateRangePopup: false });
|
||||
|
||||
onApplyDateRange = (range, value) => {
|
||||
this.setState({
|
||||
showDateRangePopup: false,
|
||||
range,
|
||||
value,
|
||||
});
|
||||
|
||||
this.props.onChange({
|
||||
startDate: range.start.unix() * 1000,
|
||||
endDate: range.end.unix() * 1000,
|
||||
rangeValue: value,
|
||||
});
|
||||
}
|
||||
|
||||
onItemClick = (event, { value }) => {
|
||||
if (value !== CUSTOM_RANGE) {
|
||||
const range = getDateRangeFromValue(value);
|
||||
this.onApplyDateRange(range, value);
|
||||
} else {
|
||||
this.setState({ showDateRangePopup: true });
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { customRangeRight, button = false, className, direction = 'right', customHidden=false, show30Minutes=false } = this.props;
|
||||
const { showDateRangePopup, value, range } = this.state;
|
||||
|
||||
let options = getDateRangeOptions(range);
|
||||
|
||||
if (customHidden) {
|
||||
options.pop();
|
||||
}
|
||||
|
||||
if (!show30Minutes) {
|
||||
options.shift()
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={ cn(styles.dateRangeOptions, className) }>
|
||||
<Dropdown
|
||||
trigger={ button ?
|
||||
<div className={ cn(styles.dropdownTrigger, 'flex items-center')}>
|
||||
<span>{ value === CUSTOM_RANGE ? range.start.format('MMM Do YY, hh:mm A') + ' - ' + range.end.format('MMM Do YY, hh:mm A') : getDateRangeLabel(value) }</span>
|
||||
<Icon name="chevron-down" color="gray-dark" size="14" className={styles.dropdownIcon} />
|
||||
</div> : null
|
||||
}
|
||||
// selection={!button}
|
||||
name="sessionDateRange"
|
||||
direction={ direction }
|
||||
className={ button ? "" : "customDropdown" }
|
||||
// pointing="top left"
|
||||
placeholder="Select..."
|
||||
icon={ null }
|
||||
>
|
||||
<Dropdown.Menu>
|
||||
{ options.map((props, i) =>
|
||||
<Dropdown.Item
|
||||
key={i}
|
||||
{...props}
|
||||
onClick={this.onItemClick}
|
||||
active={props.value === value }
|
||||
/>
|
||||
) }
|
||||
</Dropdown.Menu>
|
||||
</Dropdown>
|
||||
{
|
||||
showDateRangePopup &&
|
||||
<div className={ cn(styles.dateRangePopup, { [styles.customRangeRight] : customRangeRight}) }>
|
||||
<DateRangePopup
|
||||
onApply={ this.onApplyDateRange }
|
||||
onCancel={ this.onCancelDateRange }
|
||||
selectedDateRange={ range }
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,69 +0,0 @@
|
|||
.button {
|
||||
padding: 0 4px;
|
||||
border-radius: 3px;
|
||||
color: $teal;
|
||||
cursor: pointer;
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
& span {
|
||||
white-space: nowrap;
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdownTrigger {
|
||||
padding: 4px;
|
||||
&:hover {
|
||||
background-color: $gray-light;
|
||||
}
|
||||
}
|
||||
.dateRangeOptions {
|
||||
position: relative;
|
||||
|
||||
display: flex !important;
|
||||
border-radius: 3px;
|
||||
color: $gray-darkest;
|
||||
font-weight: 500;
|
||||
|
||||
|
||||
& .dateRangePopup {
|
||||
top: 38px;
|
||||
bottom: 0;
|
||||
z-index: 999;
|
||||
position: absolute;
|
||||
background-color: white;
|
||||
border: solid thin $gray-light;
|
||||
border-radius: 3px;
|
||||
min-height: fit-content;
|
||||
min-width: 773px;
|
||||
box-shadow: 0px 2px 10px 0 $gray-light;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
display: flex !important;
|
||||
padding: 4px 4px;
|
||||
border-radius: 3px;
|
||||
color: $gray-darkest;
|
||||
font-weight: 500;
|
||||
&:hover {
|
||||
background-color: $gray-light;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdownTrigger {
|
||||
padding: 4px 4px;
|
||||
border-radius: 3px;
|
||||
&:hover {
|
||||
background-color: $gray-light;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdownIcon {
|
||||
margin-top: 1px;
|
||||
margin-left: 3px;
|
||||
}
|
||||
|
||||
.customRangeRight {
|
||||
right: 0 !important;
|
||||
}
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
import React from 'react';
|
||||
// import { Input, Label } from 'semantic-ui-react';
|
||||
import styles from './FilterDuration.module.css';
|
||||
import { Input } from 'UI'
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
import React from 'react';
|
||||
import { Dropdown } from 'semantic-ui-react';
|
||||
|
||||
export default props => (
|
||||
<Dropdown
|
||||
{ ...props }
|
||||
/>
|
||||
);
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
import { storiesOf } from '@storybook/react';
|
||||
import Dropdown from '.';
|
||||
|
||||
storiesOf('Dropdown', module)
|
||||
.add('Pure', () => (
|
||||
<Dropdown />
|
||||
))
|
||||
|
||||
|
|
@ -1 +0,0 @@
|
|||
export { default } from './Dropdown';
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
import React from 'react'
|
||||
import { Dropdown } from 'semantic-ui-react'
|
||||
import { Icon } from 'UI';
|
||||
import stl from './dropdownPlain.module.css'
|
||||
|
||||
const sessionSortOptions = {
|
||||
'latest': 'Newest',
|
||||
'editedAt': 'Last Modified'
|
||||
};
|
||||
const sortOptions = Object.entries(sessionSortOptions)
|
||||
.map(([ value, text ]) => ({ value, text }));
|
||||
|
||||
function DropdownPlain({ name, label, options, onChange, defaultValue, wrapperStyle = {}, disabled = false }) {
|
||||
return (
|
||||
<div className="flex items-center" style={wrapperStyle}>
|
||||
{ label && <span className="mr-2 color-gray-medium">{label}</span> }
|
||||
<Dropdown
|
||||
name={name}
|
||||
className={ stl.dropdown }
|
||||
// pointing="top right"
|
||||
options={ options }
|
||||
onChange={ onChange }
|
||||
defaultValue={ defaultValue || options[ 0 ].value }
|
||||
disabled={disabled}
|
||||
icon={ <Icon name="chevron-down" color="gray-dark" size="14" className={stl.dropdownIcon} /> }
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default DropdownPlain
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
.dropdown {
|
||||
display: flex !important;
|
||||
padding: 4px 6px;
|
||||
border-radius: 3px;
|
||||
color: $gray-darkest;
|
||||
font-weight: 500;
|
||||
&:hover {
|
||||
background-color: $gray-light;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdownTrigger {
|
||||
padding: 4px 8px;
|
||||
border-radius: 3px;
|
||||
&:hover {
|
||||
background-color: $gray-light;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdownIcon {
|
||||
margin-top: 2px;
|
||||
margin-left: 3px;
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
export { default } from './DropdownPlain';
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
import React from 'react';
|
||||
import { Popup } from 'semantic-ui-react';
|
||||
import cn from 'classnames';
|
||||
import { Icon } from 'UI';
|
||||
import { Icon, Popup } from 'UI';
|
||||
import styles from './textLabel.module.css';
|
||||
|
||||
export default function TextLabel({
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
export { default as Loader } from './Loader';
|
||||
export { default as Link } from './Link';
|
||||
export { default as Dropdown } from './Dropdown';
|
||||
// export { default as Dropdown } from './Dropdown';
|
||||
export { default as Button } from './Button';
|
||||
export { default as Label } from './Label';
|
||||
export { default as Popup } from './Popup';
|
||||
|
|
@ -40,7 +40,6 @@ export { default as ErrorFrame } from './ErrorFrame';
|
|||
export { default as ErrorDetails } from './ErrorDetails';
|
||||
export { default as LoadMoreButton } from './LoadMoreButton';
|
||||
export { default as EscapeButton } from './EscapeButton';
|
||||
export { default as DropdownPlain } from './DropdownPlain';
|
||||
export { default as TextLink } from './TextLink';
|
||||
export { default as Information } from './Information';
|
||||
export { default as QuestionMarkHint } from './QuestionMarkHint';
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { Configuration as WebpackDevServerConfiguration } from 'webpack-dev-serv
|
|||
import CopyWebpackPlugin from 'copy-webpack-plugin';
|
||||
import HtmlWebpackPlugin from "html-webpack-plugin";
|
||||
import MiniCssExtractPlugin from 'mini-css-extract-plugin';
|
||||
// import CompressionPlugin from "compression-webpack-plugin";
|
||||
import CompressionPlugin from "compression-webpack-plugin";
|
||||
const dotenv = require('dotenv').config({ path: __dirname + '/.env' })
|
||||
const isDevelopment = process.env.NODE_ENV !== 'production'
|
||||
const stylesHandler = MiniCssExtractPlugin.loader;
|
||||
|
|
@ -61,19 +61,19 @@ const config: Configuration = {
|
|||
mode: "local",
|
||||
auto: true,
|
||||
localIdentName: "[name]__[local]--[hash:base64:5]",
|
||||
},
|
||||
url: {
|
||||
filter: (url: string) => {
|
||||
// Semantic-UI-CSS has an extra semi colon in one of the URL due to which CSS loader along
|
||||
// with webpack 5 fails to generate a build.
|
||||
// Below if condition is a hack. After Semantic-UI-CSS fixes this, one can replace use clause with just
|
||||
// use: ['style-loader', 'css-loader']
|
||||
if (url.includes('charset=utf-8;;')) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
}
|
||||
}
|
||||
// url: {
|
||||
// filter: (url: string) => {
|
||||
// // Semantic-UI-CSS has an extra semi colon in one of the URL due to which CSS loader along
|
||||
// // with webpack 5 fails to generate a build.
|
||||
// // Below if condition is a hack. After Semantic-UI-CSS fixes this, one can replace use clause with just
|
||||
// // use: ['style-loader', 'css-loader']
|
||||
// if (url.includes('charset=utf-8;;')) {
|
||||
// return false;
|
||||
// }
|
||||
// return true;
|
||||
// },
|
||||
// }
|
||||
},
|
||||
},
|
||||
'postcss-loader'
|
||||
|
|
@ -106,6 +106,7 @@ const config: Configuration = {
|
|||
},
|
||||
},
|
||||
plugins: [
|
||||
new CompressionPlugin(),
|
||||
new webpack.DefinePlugin({
|
||||
// 'process.env': ENV_VARIABLES,
|
||||
'window.env': ENV_VARIABLES,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue