From 65817e13c7900ea81947e40e177459cfea8acee4 Mon Sep 17 00:00:00 2001 From: Chris Date: Wed, 2 Aug 2017 01:36:54 -0700 Subject: PLT-6965 jira integration (plus plugin scaffolding) (#6918) * plugin scaffolding / jira integration * add vendored testify packages * webhook fix * don't change i18n ids * support configuration watching * add basic jira plugin configuration to admin console * fix eslint errors * fix another eslint warning * polish * undo unintentional config.json commit >:( * test fix * add jira plugin diagnostics, remove dm support, add bot tag, generate web-safe secrets * rebase, implement requested changes * requested changes * remove tests and minimize makefile change * add missing license headers * add missing comma * remove bad line from Makefile --- webapp/plugins/jira/components/settings.jsx | 251 ++++++++++++++++++++++++++++ webapp/plugins/jira/components/style.scss | 60 +++++++ 2 files changed, 311 insertions(+) create mode 100644 webapp/plugins/jira/components/settings.jsx create mode 100644 webapp/plugins/jira/components/style.scss (limited to 'webapp/plugins') diff --git a/webapp/plugins/jira/components/settings.jsx b/webapp/plugins/jira/components/settings.jsx new file mode 100644 index 000000000..5e5b5fac6 --- /dev/null +++ b/webapp/plugins/jira/components/settings.jsx @@ -0,0 +1,251 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +import React from 'react'; + +import crypto from 'crypto'; + +import Suggestion from 'components/suggestion/suggestion.jsx'; +import Provider from 'components/suggestion/provider.jsx'; +import SuggestionBox from 'components/suggestion/suggestion_box.jsx'; +import SuggestionList from 'components/suggestion/suggestion_list.jsx'; +import {autocompleteUsersInTeam} from 'actions/user_actions.jsx'; + +import AppDispatcher from 'dispatcher/app_dispatcher.jsx'; +import {Client4} from 'mattermost-redux/client'; +import {ActionTypes} from 'utils/constants.jsx'; +import * as Utils from 'utils/utils.jsx'; + +import AdminSettings from 'components/admin_console/admin_settings.jsx'; +import {FormattedMessage} from 'react-intl'; +import SettingsGroup from 'components/admin_console/settings_group.jsx'; +import BooleanSetting from 'components/admin_console/boolean_setting.jsx'; +import GeneratedSetting from 'components/admin_console/generated_setting.jsx'; +import Setting from 'components/admin_console/setting.jsx'; + +import './style.scss'; + +class UserSuggestion extends Suggestion { + render() { + const {item, isSelection} = this.props; + + let className = 'jirabots__name'; + if (isSelection) { + className += ' suggestion--selected'; + } + + const username = item.username; + let description = ''; + + if ((item.first_name || item.last_name) && item.nickname) { + description = `- ${Utils.getFullName(item)} (${item.nickname})`; + } else if (item.nickname) { + description = `- (${item.nickname})`; + } else if (item.first_name || item.last_name) { + description = `- ${Utils.getFullName(item)}`; + } + + return ( +
+
+ + + {'@' + username} + + + {' '} + {description} + +
+
+ ); + } +} + +class UserProvider extends Provider { + handlePretextChanged(suggestionId, pretext) { + const normalizedPretext = pretext.toLowerCase(); + this.startNewRequest(suggestionId, normalizedPretext); + + autocompleteUsersInTeam( + normalizedPretext, + (data) => { + if (this.shouldCancelDispatch(normalizedPretext)) { + return; + } + + const users = Object.assign([], data.users); + + AppDispatcher.handleServerAction({ + type: ActionTypes.SUGGESTION_RECEIVED_SUGGESTIONS, + id: suggestionId, + matchedPretext: normalizedPretext, + terms: users.map((user) => user.username), + items: users, + component: UserSuggestion + }); + } + ); + + return true; + } +} + +export default class JIRASettings extends AdminSettings { + constructor(props) { + super(props); + + this.getConfigFromState = this.getConfigFromState.bind(this); + this.renderSettings = this.renderSettings.bind(this); + this.handleSecretChange = this.handleSecretChange.bind(this); + this.handleEnabledChange = this.handleEnabledChange.bind(this); + this.handleUserSelected = this.handleUserSelected.bind(this); + + this.userSuggestionProviders = [new UserProvider()]; + } + + getConfigFromState(config) { + config.PluginSettings.Plugins = { + jira: { + Enabled: this.state.enabled, + Secret: this.state.secret, + UserName: this.state.userName + } + }; + + return config; + } + + getStateFromConfig(config) { + const settings = config.PluginSettings; + + const ret = { + enabled: false, + secret: '', + userName: '', + siteURL: config.ServiceSettings.SiteURL + }; + + if (typeof settings.Plugins !== 'undefined' && typeof settings.Plugins.jira !== 'undefined') { + ret.enabled = settings.Plugins.jira.Enabled || settings.Plugins.jira.enabled || false; + ret.secret = settings.Plugins.jira.Secret || settings.Plugins.jira.secret || ''; + ret.userName = settings.Plugins.jira.UserName || settings.Plugins.jira.username || ''; + } + + return ret; + } + + handleSecretChange(id, secret) { + this.handleChange(id, secret.replace('+', '-').replace('/', '_')); + } + + handleEnabledChange(enabled) { + if (enabled && this.state.secret === '') { + this.handleSecretChange('secret', crypto.randomBytes(256).toString('base64').substring(0, 32)); + } + this.handleChange('enabled', enabled); + } + + handleUserSelected(user) { + this.handleChange('userName', user.username); + } + + renderTitle() { + return Utils.localizeMessage('admin.plugins.jira', 'JIRA (Beta)'); + } + + renderSettings() { + var webhookDocsLink = ( + + + + ); + + return ( + + this.handleEnabledChange(value)} + /> + +
+ this.handleChange('userName', e.target.value)} + onItemSelected={this.handleUserSelected} + listComponent={SuggestionList} + listStyle='bottom' + providers={this.userSuggestionProviders} + disabled={!this.state.enabled} + type='input' + requiredCharacters={0} + /> +
+
+ +
+
+

+ +

+

+ ' + Utils.localizeMessage('admin.plugins.jira.secretParamPlaceholder', 'secret') + '')) + + '&team=' + + Utils.localizeMessage('admin.plugins.jira.teamParamPlaceholder', 'teamname') + + '&channel=' + + Utils.localizeMessage('admin.plugins.jira.channelParamNamePlaceholder', 'channelname') + + '' + }} + /> +

+
+
+
+ ); + } +} diff --git a/webapp/plugins/jira/components/style.scss b/webapp/plugins/jira/components/style.scss new file mode 100644 index 000000000..477328316 --- /dev/null +++ b/webapp/plugins/jira/components/style.scss @@ -0,0 +1,60 @@ +@charset 'UTF-8'; + +@import 'compass/utilities'; +@import 'compass/css3'; + +.jirabots__dropdown { + position: relative; +} + +.jirabots__dropdown::before { + position: absolute; + top: 13px; + right: 8px; + content: " "; + pointer-events: none; + + width: 0; + height: 0; + + border-left: 8px solid transparent; + border-right: 8px solid transparent; + border-top: 8px solid #e2e2e2; +} + +.jirabots__name { + @include clearfix; + cursor: pointer; + font-size: 13px; + line-height: 20px; + margin: 0; + padding: 6px 10px; + position: relative; + white-space: nowrap; + width: 100%; + z-index: 101; +} + +.jirabot__image { + @include border-radius(20px); + display: block; + font-size: 15px; + height: 16px; + line-height: 16px; + margin-right: 7px; + margin-top: 3px; + text-align: center; + width: 16px; + + .jirabot--align { + display: inline-block; + max-width: 80%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } +} + +.jirabot__fullname { + @include opacity(.5); +} -- cgit v1.2.3-1-g7c22