src/app/configuration.js (155 lines of code) (raw):
import '@jetbrains/ring-ui/components/form/form.scss';
import React from 'react';
import PropTypes from 'prop-types';
import Select from '@jetbrains/ring-ui/components/select/select';
import List from '@jetbrains/ring-ui/components/list/list';
import Link from '@jetbrains/ring-ui/components/link/link';
import LoaderInline from '@jetbrains/ring-ui/components/loader-inline/loader-inline';
import {i18n} from 'hub-dashboard-addons/dist/localization';
import ServiceSelect from '@jetbrains/hub-widget-ui/dist/service-select';
import HttpErrorHandler from '@jetbrains/hub-widget-ui/dist/http-error-handler';
import ConfigurationForm from '@jetbrains/hub-widget-ui/dist/configuration-form';
import ServiceResources from '@jetbrains/hub-widget-ui/dist/service-resources';
import {loadAgiles} from './resources';
import {
areSprintsEnabled,
isCurrentSprint
} from './agile-board-model';
export default class Configuration extends React.Component {
static toSelectItem = it => it && {
key: it.id,
label: it.name,
description: it.homeUrl,
model: it
};
static getCurrentSprintSelectOption = currentSprint => ({
key: 'current-sprint',
label: i18n('Always display current sprint'),
description: currentSprint ? currentSprint.name : ''
});
static propTypes = {
agile: PropTypes.object,
sprint: PropTypes.object,
currentSprintMode: PropTypes.bool,
onSubmit: PropTypes.func,
onCancel: PropTypes.func,
dashboardApi: PropTypes.object,
youTrackId: PropTypes.string
};
constructor(props) {
super(props);
const selectedYouTrack = {
id: props.youTrackId
};
this.state = {
selectedAgile: props.agile,
selectedSprint: props.sprint,
currentSprintMode: props.currentSprintMode,
agiles: [],
selectedYouTrack,
youTracks: [selectedYouTrack]
};
}
componentDidMount() {
this.loadYouTrackList();
}
async loadYouTrackList() {
const {selectedYouTrack} = this.state;
const youTracks = await ServiceResources.getYouTrackServices(
this.props.dashboardApi, selectedYouTrack && selectedYouTrack.id
);
const selectedYouTrackWithAllFields = youTracks.
filter(yt => yt.id === selectedYouTrack.id)[0];
this.setState({
youTracks, selectedYouTrack: selectedYouTrackWithAllFields
}, async () => await this.onAfterYouTrackChanged());
}
async onAfterYouTrackChanged() {
this.setState({isLoading: true});
try {
await this.loadAgiles();
} catch (err) {
this.setState({
isLoading: false,
errorMessage: HttpErrorHandler.getMessage(err)
});
return;
}
this.setState({isLoading: false});
}
async loadAgiles() {
const {selectedAgile, selectedSprint} = this.state;
this.setState({agiles: [], selectedAgile: null, selectedSprint: null});
const agiles = await loadAgiles(this.fetchYouTrack);
const hasRememberedAgileInNewAgilesList = (agiles || []).
some(agile => selectedAgile && selectedAgile.id === agile.id);
this.setState({agiles});
if (hasRememberedAgileInNewAgilesList) {
this.setState({
selectedAgile,
selectedSprint
});
} else if (agiles.length) {
this.changeAgile(agiles[0]);
}
}
changeYouTrack = selectedYouTrack => {
this.setState({
selectedYouTrack,
errorMessage: ''
}, () => this.onAfterYouTrackChanged());
};
submitForm = async () => {
const {
selectedAgile, selectedSprint, selectedYouTrack, currentSprintMode
} = this.state;
await this.props.onSubmit({
agile: selectedAgile,
sprint: selectedSprint,
youTrack: selectedYouTrack,
currentSprintMode
});
};
fetchYouTrack = async (url, params) => {
const {dashboardApi} = this.props;
const {selectedYouTrack} = this.state;
return await dashboardApi.fetch(selectedYouTrack.id, url, params);
};
changeAgile = selected => {
const selectedAgile = selected.model || selected;
const sprints = selectedAgile && selectedAgile.sprints || [];
if (sprints.length) {
const hasCurrentSprint = selectedAgile.currentSprint ||
sprints.some(isCurrentSprint);
this.changeSprint(
hasCurrentSprint
? Configuration.getCurrentSprintSelectOption()
: sprints[0]
);
}
this.setState({selectedAgile});
};
changeSprint = selected => {
if (selected.key === 'current-sprint') {
this.setState({
selectedSprint: null,
currentSprintMode: true
});
} else {
this.setState({
selectedSprint: selected.model || selected,
currentSprintMode: false
});
}
};
renderNoBoardsMessage() {
const {selectedYouTrack} = this.state;
const homeUrl = (selectedYouTrack || {}).homeUrl || '';
const normalizedHomeUrl = homeUrl.charAt(homeUrl.length - 1) === '/'
? homeUrl
: `${homeUrl}/`;
return (
<div className="ring-form__group">
<span>{i18n('No boards found.')}</span>
<Link
href={`${normalizedHomeUrl}agiles/create`}
>
{i18n('Create board')}
</Link>
</div>
);
}
renderCannotLoadBoardsMessage() {
return (
<div className="ring-form__group">
<span>{i18n('Failed to load agile boards from YouTrack.')}</span>
</div>
);
}
renderBoardsSelectors() {
const {
selectedAgile,
selectedSprint,
currentSprintMode,
agiles
} = this.state;
const getSprintsOptions = () => {
const sprints = (selectedAgile.sprints || []);
const sprintsOptions = sprints.map(Configuration.toSelectItem);
const currentSprint = (selectedAgile && selectedAgile.currentSprint) ||
sprints.filter(isCurrentSprint)[0];
if (currentSprint) {
sprintsOptions.unshift({
rgItemType: List.ListProps.Type.SEPARATOR
});
sprintsOptions.unshift(
Configuration.getCurrentSprintSelectOption(currentSprint)
);
}
return sprintsOptions;
};
return (
<div>
<div className="ring-form__group">
<Select
size={Select.Size.FULL}
data={agiles.map(Configuration.toSelectItem)}
selected={Configuration.toSelectItem(selectedAgile)}
onSelect={this.changeAgile}
filter
label={i18n('Select board')}
/>
</div>
{
areSprintsEnabled(selectedAgile) &&
(
<div className="ring-form__group">
<Select
size={Select.Size.FULL}
data={getSprintsOptions()}
selected={
currentSprintMode
? Configuration.getCurrentSprintSelectOption()
: Configuration.toSelectItem(selectedSprint)
}
onSelect={this.changeSprint}
filter
label={i18n('Select sprint')}
/>
</div>
)
}
</div>
);
}
renderFormBody() {
const {
isLoading,
errorMessage,
agiles
} = this.state;
if ((agiles || []).length > 0) {
return this.renderBoardsSelectors();
}
if (isLoading) {
return (<LoaderInline/>);
}
if (errorMessage) {
return this.renderCannotLoadBoardsMessage();
}
return this.renderNoBoardsMessage();
}
render() {
const {
selectedAgile,
youTracks,
selectedYouTrack
} = this.state;
return (
<ConfigurationForm
className="ring-form"
warning={this.state.errorMessage}
isInvalid={!!this.state.errorMessage || !selectedAgile}
isLoading={this.state.isLoading}
onSave={this.submitForm}
onCancel={this.props.onCancel}
>
{
(youTracks || []).length > 1 &&
(
<ServiceSelect
className="ring-form__group"
serviceList={youTracks}
selectedService={selectedYouTrack}
onServiceSelect={this.changeYouTrack}
placeholder={i18n('Select YouTrack Server')}
/>
)
}
{this.renderFormBody()}
</ConfigurationForm>
);
}
}