packages/issue-dashboard-widgets/widgets/youtrack-activities-widget/app/activities-widget.js (262 lines of code) (raw):
import React from 'react';
import PropTypes from 'prop-types';
import ConfigurableWidget from '@jetbrains/hub-widget-ui/dist/configurable-widget';
import ServiceResources from '@jetbrains/hub-widget-ui/dist/service-resources';
import {observer} from 'mobx-react';
import {i18n} from 'hub-dashboard-addons/dist/localization';
import ActivitiesEditForm from './activities-edit-form';
import ActivitiesContent from './activities-content';
import {
loadActivities,
loadActivitiesPage,
loadConfigL10n,
loadMeProfile
} from './resources';
import DateTime from './date-time';
import filter from './activities-filter';
const MILLIS_IN_SEC = 1000;
@observer
class ActivitiesWidget extends React.Component {
static getDefaultYouTrackService =
async (dashboardApi, predefinedYouTrackId) => {
try {
// TODO: pass min-required version here
return await ServiceResources.getYouTrackService(
dashboardApi, predefinedYouTrackId
);
} catch (err) {
return null;
}
};
static propTypes = {
dashboardApi: PropTypes.object,
configWrapper: PropTypes.object,
registerWidgetApi: PropTypes.func,
editable: PropTypes.bool
};
constructor(props) {
super(props);
const {registerWidgetApi} = props;
this.state = {
isConfiguring: false,
isLoading: false,
loadingError: {}
};
registerWidgetApi({
onConfigure: () => this.setState({
isConfiguring: true,
isLoading: false,
loadingError: {}
}),
onRefresh: () => this.tryLoadNewActivities()
});
}
componentDidMount() {
this.initialize(this.props.dashboardApi);
filter.dashboardApi = this.props.dashboardApi;
}
initialize = async dashboardApi => {
await this.props.configWrapper.init();
if (this.props.configWrapper.isNewConfig()) {
await this.initializeNewWidget(dashboardApi);
} else {
await this.initializeExistingWidget(dashboardApi);
}
};
createUserDateFormats = async () => {
try {
const profile = await loadMeProfile(this.fetchYouTrack);
const dateFormats = (profile && profile.dateFieldFormat) || {};
return {
datePattern: DateTime.toFechaFormat(dateFormats.datePattern),
dateTimePattern: DateTime.toFechaFormat(dateFormats.pattern)
};
} catch (e) {
return null;
}
};
createDefaultQuery = async () => {
try {
const config = await loadConfigL10n(this.fetchYouTrack);
const l10nQueries = config.l10n.predefinedQueries;
return `${l10nQueries.by}: ${l10nQueries.me}`;
} catch (e) {
return '';
}
};
async initializeNewWidget(dashboardApi) {
const youTrackService = await ActivitiesWidget.getDefaultYouTrackService(
dashboardApi, filter.youTrackId
);
if (youTrackService && youTrackService.id) {
await this.setNewService(youTrackService);
this.setState({isConfiguring: true});
}
}
async initializeExistingWidget(dashboardApi) {
await filter.restore(this.props);
const youTrackService = await ActivitiesWidget.getDefaultYouTrackService(
dashboardApi, filter.youTrackId
);
if (youTrackService && youTrackService.id) {
filter.youTrackId = youTrackService.id;
filter.youTrackUrl = youTrackService.homeUrl;
filter.userFormats = await this.createUserDateFormats();
await filter.sync(this.props);
this.setState({isConfiguring: false});
await this.reload();
}
}
setNewService = async selectedYouTrack => {
filter.youTrackId = selectedYouTrack.id;
filter.youTrackUrl = selectedYouTrack.homeUrl;
filter.query = await this.createDefaultQuery();
filter.userFormats = await this.createUserDateFormats();
};
fetchYouTrack = async (url, params) => {
const {dashboardApi} = this.props;
return await dashboardApi.fetch(filter.youTrackId, url, params);
};
// eslint-disable-next-line complexity
tryLoadNewActivities = async () => {
if (document.hidden) {
return;
}
try {
const lastTimestamp = (this.state || {}).timestamp;
const timestamp = lastTimestamp || filter.recentPeriodStart;
const incActivities = await loadActivities(
this.fetchYouTrack,
{
author: filter.author,
query: filter.query,
start: timestamp && (timestamp + 1),
categoriesIds: filter.categoriesIds
}
);
incActivities.forEach(activity => {
activity.new = true;
});
const newest = incActivities[0];
const oldActivities = this.state.activities || [];
const newActivities = incActivities.concat(oldActivities);
this.setState({
activities: newActivities,
timestamp: newest && newest.timestamp || timestamp
});
} catch (error) {
this.updateError({
incrementalUpdate: {
title: i18n('Could not load new activities'),
message: error.message
}
});
}
};
loadActivitiesPage = async loadMore => {
const {cursor} = this.state;
const page = await loadActivitiesPage(
this.fetchYouTrack,
{
cursor: loadMore && cursor,
author: filter.author,
query: filter.query,
categoriesIds: filter.categoriesIds,
start: filter.recentPeriodStart
}
);
const newTimestamp = this.updatedTimestamp(loadMore, page);
const newActivities = this.updatedActivities(loadMore, page);
this.setState({
activities: newActivities,
timestamp: newTimestamp,
cursor: page.beforeCursor,
hasMore: page.hasBefore
});
};
updatedActivities(loadMore, page) {
const oldActivities = loadMore ? (this.state.activities || []) : [];
return oldActivities.slice().concat(page.activities);
}
updatedTimestamp(loadMore, page) {
const newest = page.activities[0];
const oldTimestamp = loadMore ? this.state.timestamp : null;
return newest && newest.timestamp || oldTimestamp;
}
reload = async () => {
try {
this.setState({isLoading: true});
await this.loadActivitiesPage(false);
} catch (error) {
this.setState({loadingError: {initialLoad: error.message}});
} finally {
this.setState({isLoading: false});
}
};
updateError = mergeError => {
this.setState({loadingError: mergeError});
};
loadMore = async () => {
await this.loadActivitiesPage(true);
};
editConfiguration = () => {
this.setState({isConfiguring: true});
};
submitConfiguration = async () => {
await filter.sync(this.props);
this.setState({isConfiguring: false});
await this.reload();
};
cancelConfiguration = async () => {
if (this.props.configWrapper.isNewConfig()) {
await this.props.dashboardApi.removeWidget();
} else {
await filter.restore(this.props);
this.setState({isConfiguring: false});
await this.props.dashboardApi.exitConfigMode();
}
};
getWidgetTitle = () => {
const title = filter.title;
if (title) {
return title;
} else {
const query = filter.query;
return query && query.length
? i18n('Issue Activity Feed \u2014 ') + query
: i18n('Issue Activity Feed');
}
};
renderConfiguration = () => (
<ActivitiesEditForm
submitConfig={this.submitConfiguration}
cancelConfig={this.cancelConfiguration}
onServiceChange={this.setNewService}
dashboardApi={this.props.dashboardApi}
/>
);
renderContent = () => (
<ActivitiesContent
activities={this.state.activities}
isLoading={this.state.isLoading}
loadingError={this.state.loadingError}
onUpdateError={this.updateError}
hasMore={this.state.hasMore}
onLoadMore={this.loadMore}
editable={this.props.editable}
tickPeriod={filter.refreshPeriod * MILLIS_IN_SEC}
onTick={this.tryLoadNewActivities}
onEdit={this.editConfiguration}
/>
);
render() {
const {
isConfiguring,
isLoading
} = this.state;
return (
<ConfigurableWidget
isConfiguring={isConfiguring}
dashboardApi={this.props.dashboardApi}
widgetTitle={this.getWidgetTitle()}
widgetLoader={isLoading}
Configuration={this.renderConfiguration}
Content={this.renderContent}
/>
);
}
}
export default ActivitiesWidget;