packages/issue-dashboard-widgets/widgets/distribution-reports/app/distribution-report-form.js (389 lines of code) (raw):

import React from 'react'; import PropTypes from 'prop-types'; import Input, {Size as InputSize} from '@jetbrains/ring-ui/components/input/input'; import { InfoIcon, CompareIcon, EyeIcon, PencilIcon } from '@jetbrains/ring-ui/components/icon'; import {i18n} from 'hub-dashboard-addons/dist/localization'; import FilterFieldsSelector from '../../../../lib/reporting-components/filter-fields-selector/filter-fields-selector'; import BackendTypes from '../../../../lib/reporting-components/backend-types/backend-types'; import SharingSetting from '../../../../lib/reporting-components/sharing-setting/sharing-setting'; import {loadUsers, loadVisibilityUserGroups} from '../../../../lib/reporting-components/resources/resources'; import StandardFormGroup from '../../../../lib/reporting-components/report-form-controls/standard-form-group'; import ReportIssuesFilter from '../../../../lib/reporting-components/report-form-controls/report-issues-filter'; import ReportProjects from '../../../../lib/reporting-components/report-form-controls/report-projects'; import { loadReportsFilterFields, loadReportsAggregationFilterFields } from './resources'; import { isTypeWithEditableXAxis } from './distribution-report-types'; import DistributionReportAxises from './distribution-report-axises'; class DistributionReportForm extends React.Component { static isNewReport = report => !report.id; static canShowSecondaryAxisOption = report => DistributionReportForm.isNewReport(report) || report.$type === BackendTypes.get().MatrixReport; static convertOneFieldReportToTwoFieldsReportIfNeeded = report => { if (report.$type === BackendTypes.get().MatrixReport) { return report; } report.$type = BackendTypes.get().MatrixReport; report.yaxis = {field: report.xaxis.field}; report.xaxis.$type = undefined; report.ysortOrder = report.xsortOrder; report.presentation = 'DEFAULT'; return report; }; static convertTwoFieldsReportToOneFieldReportIfNeeded = report => { if (report.$type !== BackendTypes.get().MatrixReport) { return report; } report.$type = BackendTypes.get().FlatDistributionReport; report.xaxis = {field: (report.yaxis || {}).field}; report.yaxis = undefined; report.ysortOrder = undefined; report.presentation = 'DEFAULT'; return report; }; static checkReportValidity = report => !!report && !!report.name; static toProjectTag = project => ({ key: project.id, label: project.name, description: project.shortName, model: project }); static propTypes = { report: PropTypes.object, onValidStateChange: PropTypes.func, onReportSettingsChange: PropTypes.func, disabled: PropTypes.bool, currentUser: PropTypes.object, fetchYouTrack: PropTypes.func, fetchHub: PropTypes.func }; constructor(props) { super(props); this.state = { report: props.report, disabled: props.disabled, fetchYouTrack: props.fetchYouTrack, fetchHub: props.fetchHub, currentUser: props.currentUser, userGroups: [], users: [] }; this.props.onValidStateChange( DistributionReportForm.checkReportValidity(props.report) ); } // eslint-disable-next-line camelcase UNSAFE_componentWillReceiveProps(props) { this.setState({ disabled: props.disabled, currentUser: props.currentUser, fetchYouTrack: props.fetchYouTrack, fetchHub: props.fetchHub }); if ((props.report || {}).id !== (this.state.report || {}).id) { this.updateReport(props.report); } } changeReportName = evt => this.getReportEditOperationHandler('name')(evt.target.value); getSharingSettingsOptions = async (query = '') => { const {report, currentUser, fetchYouTrack} = this.state; const groups = await loadVisibilityUserGroups(fetchYouTrack, {query}); const projectId = ((report || {}).projects || []). map(project => project.id); const users = await loadUsers(fetchYouTrack, { query, permissionId: 'JetBrains.YouTrack.READ_REPORT', projectId }); return {groups, users, currentUser}; }; changeSharingSettings = (settingName, options) => { const {report} = this.state; report[settingName] = { permittedUsers: (options || []). filter(option => option.$type === 'User'), permittedGroups: (options || []). filter(option => option.$type === 'UserGroup') }; this.onReportEditOperation(report); }; getReportEditOperationHandler = propertyName => value => { const {report} = this.state; report[propertyName] = value; this.onReportEditOperation(report); }; changeMainFilterField = selected => { const {report} = this.state; const mainAxis = DistributionReportAxises.getMainAxis(report); mainAxis.field = selected; this.onReportEditOperation(report); }; changeSplittingBarsFilterField = selected => { const {report} = this.state; if (selected) { DistributionReportForm. convertOneFieldReportToTwoFieldsReportIfNeeded(report); DistributionReportAxises.getSecondaryAxis(report).field = selected; } else { DistributionReportForm. convertTwoFieldsReportToOneFieldReportIfNeeded(report); } this.onReportEditOperation(report); }; changeAggregationPolicy = selected => { const {report} = this.state; if (selected) { report.aggregationPolicy = report.aggregationPolicy || {}; report.aggregationPolicy.field = selected; } else { report.aggregationPolicy = null; } this.onReportEditOperation(report); }; changeAxisPlaces = () => { const {report} = this.state; if (!report.xaxis || !report.yaxis) { return; } const xaxisFieldBuff = report.xaxis.field; report.xaxis.field = report.yaxis.field; report.yaxis.field = xaxisFieldBuff; this.onReportEditOperation(report); }; updateReport(report) { this.setState({report}); const reportIsValid = DistributionReportForm.checkReportValidity(report); this.props.onValidStateChange(reportIsValid); return reportIsValid; } onReportEditOperation(report) { if (this.updateReport(report)) { this.props.onReportSettingsChange(report); } } renderIssueDistributionFieldsEditableSelectors() { const {report, fetchYouTrack} = this.state; const filterFieldsSource = async projects => await loadReportsFilterFields(fetchYouTrack, projects); return ( <div> <span className="distribution-reports-widget__filter-field-selector"> { report.yaxis && ( <span className="distribution-reports-widget__axis-label"> {'↓'} </span> )} <FilterFieldsSelector selectedField={ DistributionReportAxises.getMainAxis(report).field } projects={report.projects} onChange={this.changeMainFilterField} filterFieldsSource={filterFieldsSource} canBeEmpty={false} /> </span> { report.yaxis && ( <CompareIcon className="distribution-reports-widget__icon distribution-reports-widget__icon_btn distribution-reports-widget__transpose-icon" onClick={this.changeAxisPlaces} color={CompareIcon.Color.GRAY} size={CompareIcon.Size.Size16} /> )} { DistributionReportForm.canShowSecondaryAxisOption(report) && ( <span className="distribution-reports-widget__filter-field-selector"> <span className="distribution-reports-widget__axis-label"> {report.yaxis ? '→' : ''} </span> <FilterFieldsSelector selectedField={ report.yaxis ? DistributionReportAxises.getSecondaryAxis(report).field : undefined } projects={report.projects} onChange={this.changeSplittingBarsFilterField} filterFieldsSource={filterFieldsSource} canBeEmpty={DistributionReportForm.isNewReport(report)} /> </span> )} </div> ); } renderIssueDistributionFieldsReadonlyLabels() { const {report} = this.state; const labels = [ DistributionReportAxises.getMainAxisPresentation(report), report.yaxis && DistributionReportAxises.getSecondaryAxisPresentation(report) ].filter(it => !!it).join(` ${i18n('and')} `); return ( <div className="distribution-reports-widget__filter-fields"> {labels} </div> ); } renderIssueDistributionFieldsBlock() { const { report, disabled } = this.state; return ( <StandardFormGroup label={i18n('Show distribution by')}> { (!disabled && isTypeWithEditableXAxis(report)) ? this.renderIssueDistributionFieldsEditableSelectors() : this.renderIssueDistributionFieldsReadonlyLabels() } </StandardFormGroup> ); } renderAggregationPolicyBlock() { const {report, disabled, fetchYouTrack} = this.state; const aggregationFilterFieldsSource = async projects => await loadReportsAggregationFilterFields(fetchYouTrack, projects); return ( <StandardFormGroup label={i18n('Show totals for {{aggregationPolicy}}', {aggregationPolicy: ''})} > <FilterFieldsSelector selectedField={(report.aggregationPolicy || {}).field} projects={report.projects} onChange={this.changeAggregationPolicy} filterFieldsSource={aggregationFilterFieldsSource} canBeEmpty placeholder={i18n('Issues')} disabled={disabled} /> </StandardFormGroup> ); } renderSharingSettingBlock(settingName, label, title, IconElement) { const { disabled, report, currentUser } = this.state; const sharingSetting = report && report[settingName] || {}; const implicitSelected = [report && report.owner || currentUser].filter( user => !!user ); return ( <StandardFormGroup label={label}> <IconElement className="distribution-reports-widget__icon distribution-reports-widget__label" color={InfoIcon.Color.GRAY} size={InfoIcon.Size.Size14} /> {title}&nbsp; <SharingSetting getOptions={this.getSharingSettingsOptions} value={sharingSetting} onChange={this.getReportEditOperationHandler(settingName)} disabled={disabled} implicitSelected={implicitSelected} /> </StandardFormGroup> ); } renderVisibleToBlock() { return this.renderSharingSettingBlock( 'readSharingSettings', i18n('Sharing settings'), i18n('Can view and use'), EyeIcon ); } renderUpdateableByBlock() { return this.renderSharingSettingBlock( 'updateSharingSettings', '', i18n('Can edit'), PencilIcon ); } renderProjectsSelectorBlock() { const { report, disabled, fetchYouTrack, fetchHub } = this.state; return ( <StandardFormGroup label={i18n('Projects')} > <ReportProjects projects={report.projects} reportId={(report || {}).id} disabled={disabled} fetchYouTrack={fetchYouTrack} fetchHub={fetchHub} onChange={this.getReportEditOperationHandler('projects')} /> </StandardFormGroup> ); } renderFilterIssuesBlock() { const { report, fetchYouTrack, disabled } = this.state; return ( <StandardFormGroup label={i18n('Filter issues')}> <ReportIssuesFilter disabled={disabled} query={report.query} fetchYouTrack={fetchYouTrack} onChange={this.getReportEditOperationHandler('query')} /> </StandardFormGroup> ); } render() { const { report, disabled } = this.state; return ( <div className="ring-form"> { !disabled && ( <StandardFormGroup label={'Name'} inputCompensationHack > <Input size={InputSize.FULL} value={report.name} placeholder={i18n('Report name')} onChange={this.changeReportName} compact /> </StandardFormGroup> )} {this.renderProjectsSelectorBlock()} {this.renderFilterIssuesBlock()} {this.renderIssueDistributionFieldsBlock()} {this.renderAggregationPolicyBlock()} {this.renderVisibleToBlock()} {this.renderUpdateableByBlock()} </div> ); } } export default DistributionReportForm;