diff options
author | George Goldberg <george@gberg.me> | 2017-08-01 13:11:29 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-08-01 13:11:29 +0100 |
commit | 00e8b6621a7ddf13222144d4acf80421204a23cc (patch) | |
tree | 0e6b967eeb813d99c0f07cdcedc3638d76d61144 /webapp/components/admin_console/elasticsearch_status/status.jsx | |
parent | 25a2013890c7e07b4621fa9b18342e7f35363049 (diff) | |
download | chat-00e8b6621a7ddf13222144d4acf80421204a23cc.tar.gz chat-00e8b6621a7ddf13222144d4acf80421204a23cc.tar.bz2 chat-00e8b6621a7ddf13222144d4acf80421204a23cc.zip |
PLT-6595 (Client): Elasticsearch indexing system console UI (#6991)
* PLT-6595: System Console for Elasticsearch Job Management.
* Fixing UI issues
* Fixing colors
* ESLint Fixes.
* Update test snapshots.
* Fixing cancel button
* Fix review comments.
* Config capitalisation.
* Review fixes.
Diffstat (limited to 'webapp/components/admin_console/elasticsearch_status/status.jsx')
-rw-r--r-- | webapp/components/admin_console/elasticsearch_status/status.jsx | 361 |
1 files changed, 361 insertions, 0 deletions
diff --git a/webapp/components/admin_console/elasticsearch_status/status.jsx b/webapp/components/admin_console/elasticsearch_status/status.jsx new file mode 100644 index 000000000..0a32d39c8 --- /dev/null +++ b/webapp/components/admin_console/elasticsearch_status/status.jsx @@ -0,0 +1,361 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import React from 'react'; +import PropTypes from 'prop-types'; +import {FormattedMessage} from 'react-intl'; + +import {createJob, cancelJob} from 'actions/job_actions.jsx'; +import {JobTypes, JobStatuses} from 'utils/constants.jsx'; +import RequestButton from '../request_button/request_button.jsx'; + +export default class Status extends React.PureComponent { + static propTypes = { + + /** + * Array of jobs + */ + jobs: PropTypes.arrayOf(PropTypes.object).isRequired, + + /** + * Whether Elasticsearch is properly configured. + */ + isConfigured: PropTypes.bool.isRequired, + + actions: PropTypes.shape({ + + /** + * Function to fetch jobs + */ + getJobsByType: PropTypes.func.isRequired + }).isRequired + }; + + constructor(props) { + super(props); + + this.interval = null; + + this.state = { + loading: true, + cancelInProgress: false + }; + } + + componentWillMount() { + // reload the cluster status every 15 seconds + this.interval = setInterval(this.reload, 15000); + } + + componentDidMount() { + this.props.actions.getJobsByType(JobTypes.ELASTICSEARCH_POST_INDEXING).then( + () => this.setState({loading: false}) + ); + } + + componentWillUnmount() { + if (this.interval) { + clearInterval(this.interval); + } + } + + reload = () => { + this.props.actions.getJobsByType(JobTypes.ELASTICSEARCH_POST_INDEXING).then( + () => { + this.setState({ + loading: false, + cancelInProgress: false + }); + } + ); + }; + + createIndexJob = (success, error) => { + const job = { + type: JobTypes.ELASTICSEARCH_POST_INDEXING + }; + + createJob( + job, + () => { + this.reload(); + success(); + }, + error + ); + }; + + cancelIndexJob = (e) => { + e.preventDefault(); + + const chosenJob = this.getChosenJob(); + if (!chosenJob) { + return; + } + + this.setState({ + cancelInProgress: true + }); + + cancelJob( + chosenJob.id, + () => { + this.reload(); + }, + () => { + this.reload(); + } + ); + }; + + getChosenJob = () => { + let chosenJob = null; + + if (this.props.jobs.length > 0) { + for (let i = 0; i < this.props.jobs.length; i++) { + const job = this.props.jobs[i]; + if (job.status === JobStatuses.CANCEL_REQUESTED || job.status === JobStatuses.IN_PROGRESS) { + chosenJob = job; + } else { + break; + } + } + + if (!chosenJob) { + for (let i = 0; i < this.props.jobs.length; i++) { + const job = this.props.jobs[i]; + if (job.status !== JobStatuses.PENDING && chosenJob) { + continue; + } else { + chosenJob = job; + break; + } + } + } + } + + return chosenJob; + }; + + render() { + const chosenJob = this.getChosenJob(); + + let indexButtonDisabled = !this.props.isConfigured; + let buttonText = ( + <FormattedMessage + id='admin.elasticsearch.indexButton.ready' + defaultMessage='Build Index' + /> + ); + let cancelButton = null; + let indexButtonHelp = ( + <FormattedMessage + id='admin.elasticsearch.indexHelpText.buildIndex' + defaultMessage='All posts in the database will be indexed from oldest to newest. Elasticsearch is available during indexing but search results may be incomplete until the indexing job is complete.' + /> + ); + + if (this.state.loading) { + indexButtonDisabled = true; + } else if (chosenJob) { + if (chosenJob.status === JobStatuses.PENDING || chosenJob.status === JobStatuses.IN_PROGRESS || chosenJob.status === JobStatuses.CANCEL_REQUESTED) { + indexButtonDisabled = true; + buttonText = ( + <span> + <span className='fa fa-refresh icon--rotate'/> + <FormattedMessage + id='admin.elasticsearch.indexButton.inProgress' + defaultMessage='Indexing in progress' + /> + </span> + ); + } + + if (chosenJob.status === JobStatuses.PENDING || chosenJob.status === JobStatuses.IN_PROGRESS || chosenJob.status === JobStatuses.CANCEL_REQUESTED) { + indexButtonHelp = ( + <FormattedMessage + id='admin.elasticsearch.indexHelpText.cancelIndexing' + defaultMessage='Cancelling stops the indexing job and removes it from the queue. Posts that have already been indexed will not be deleted.' + /> + ); + } + + if (!this.state.cancelInProgress && (chosenJob.status === JobStatuses.PENDING || chosenJob.status === JobStatuses.IN_PROGRESS)) { + cancelButton = ( + <a + href='#' + className='btn btn-link' + onClick={this.cancelIndexJob} + > + <FormattedMessage + id='admin.elasticsearchStatus.cancelButton' + defaultMessage='Cancel' + /> + </a> + ); + } + } + + const indexButton = ( + <RequestButton + requestAction={this.createIndexJob} + helpText={indexButtonHelp} + buttonText={buttonText} + disabled={indexButtonDisabled} + showSuccessMessage={false} + errorMessage={{ + id: 'admin.elasticsearch.bulkIndexButton.error', + defaultMessage: 'Failed to schedule Bulk Index Job: {error}' + }} + alternativeActionElement={cancelButton} + label={( + <FormattedMessage + id='admin.elasticsearchStatus.bulkIndexLabel' + defaultMessage='Bulk Indexing:' + /> + )} + /> + ); + + let status = null; + let statusHelp = null; + let statusClass = null; + if (!this.props.isConfigured) { + status = ( + <FormattedMessage + id='admin.elasticsearchStatus.statusIndexingDisabled' + defaultMessage='Indexing disabled.' + /> + ); + } else if (this.state.loading) { + status = ( + <FormattedMessage + id='admin.elasticsearchStatus.statusLoading' + defaultMessage='Loading...' + /> + ); + statusClass = 'status-icon-unknown'; + } else if (chosenJob) { + if (chosenJob.status === JobStatuses.PENDING) { + status = ( + <FormattedMessage + id='admin.elasticsearchStatus.statusPending' + defaultMessage='Job pending.' + /> + ); + statusHelp = ( + <FormattedMessage + id='admin.elasticsearchStatus.statusPending.help' + defaultMessage='Elasticsearch index job is queued on the job server. If Elasticsearch is enabled, search results may be incomplete until the job is finished.' + /> + ); + statusClass = 'status-icon-warning'; + } else if (chosenJob.status === JobStatuses.IN_PROGRESS) { + status = ( + <FormattedMessage + id='admin.elasticsearchStatus.statusInProgress' + defaultMessage='Job in progress. {percent}% complete.' + values={{ + percent: chosenJob.progress + }} + /> + ); + statusHelp = ( + <FormattedMessage + id='admin.elasticsearchStatus.statusInProgress.help' + defaultMessage='Indexing is in progress on the job server. If Elasticsearch is enabled, search results may be incomplete until the job is finished.' + /> + ); + statusClass = 'status-icon-warning'; + } else if (chosenJob.status === JobStatuses.SUCCESS) { + status = ( + <FormattedMessage + id='admin.elasticsearchStatus.statusSuccess' + defaultMessage='Indexing complete.' + /> + ); + statusHelp = ( + <FormattedMessage + id='admin.elasticsearchStatus.statusSuccess.help' + defaultMessage='Indexing is complete and new posts are being automatically indexed.' + /> + ); + statusClass = 'status-icon-success'; + } else if (chosenJob.status === JobStatuses.ERROR) { + status = ( + <FormattedMessage + id='admin.elasticsearchStatus.statusError' + defaultMessage='Indexing error.' + /> + ); + statusHelp = ( + <FormattedMessage + id='admin.elasticsearchStatus.statusError.help' + defaultMessage='Mattermost encountered an error building the Elasticsearch index: {error}' + values={{ + error: chosenJob.data ? (chosenJob.data.error || '') : '' + }} + /> + ); + statusClass = 'status-icon-error'; + } else if (chosenJob.status === JobStatuses.CANCEL_REQUESTED) { + status = ( + <FormattedMessage + id='admin.elasticsearchStatus.statusRequestCancel' + defaultMessage='Canceling Job...' + /> + ); + statusClass = 'status-icon-warning'; + } else if (chosenJob.status === JobStatuses.CANCELED) { + status = ( + <FormattedMessage + id='admin.elasticsearchStatus.statusCancelled' + defaultMessage='Indexing job cancelled.' + /> + ); + statusClass = 'status-icon-error'; + } + } else { + status = ( + <FormattedMessage + id='admin.elasticsearchStatus.statusNoJobs' + defaultMessage='No indexing jobs queued.' + /> + ); + statusClass = 'status-icon-unknown'; + } + + if (statusHelp !== null) { + statusHelp = ( + <div className='col-sm-offset-4 col-sm-8'> + <div className='help-text'> + {statusHelp} + </div> + </div> + ); + } + + statusClass = 'fa fa-circle margin--right ' + statusClass; + + return ( + <div> + {indexButton} + <div className='form-group'> + <div className='col-sm-offset-4 col-sm-8'> + <div className='help-text no-margin'> + <FormattedMessage + id='admin.elasticsearchStatus.status' + defaultMessage='Status: ' + /> + <i + className={statusClass} + /> + {status} + </div> + </div> + {statusHelp} + </div> + </div> + ); + } +} |