Skip to content

Commit

Permalink
#[UIC-1854] jira actions configurable from deployment and chart creat…
Browse files Browse the repository at this point in the history
…ion screen (#234)

* fix(jira-ticket):  Jira actions

* fix(explore on token expiry):  redirect to welcome screen

UIC-1956 #time 3h debug and redirect

* feat(jira-ticket-interface):  Jira actions payload from slice with correct interface

UIC-1994 #time 1h Jira actions payload from slice with correct interface

* feat(jira-ticket-interface):  default formatted text
UIC-1994 #time 1h default formatted text

* feat(jira-ticket-interface):  docker variable + anonymous request support
UIC-1994 #time 1h docker variable + anonymous request support

* feat(jira-ticket-interface):  error handling and validation check around not configured ticket system
UIC-1994 #time 2h  error handling and validation check around not configured ticket system

* feat(jira-ticket-interface):  removed warning

* feat(jira-ticket-interface):  added disabled erase in text

UIC-1994: #time 1h
  • Loading branch information
Arpit authored and bipinsoniguavus committed Sep 23, 2019
1 parent 50c4e7f commit 544dc2e
Show file tree
Hide file tree
Showing 12 changed files with 136 additions and 10 deletions.
5 changes: 5 additions & 0 deletions docker/superset_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,11 @@ class CeleryConfig(object):
KNOX_SSO_ORIGINALURL = get_env_variable('KNOX_SSO_ORIGINALURL')
if not KNOX_SSO_ORIGINALURL:
KNOX_SSO_ORIGINALURL = 'originalUrl'

TICKET_GENERATION_SYSTEM_NAME = get_env_variable('TICKET_GENERATION_SYSTEM_NAME','JIRA')
TICKET_GENERATION_SYSTEM_ENDPOINT = get_env_variable('TICKET_GENERATION_SYSTEM_ENDPOINT','')
TICKET_GENERATION_SYSTEM_API_KEY = get_env_variable('TICKET_GENERATION_SYSTEM_API_KEY','')
TICKET_GENERATION_SYSTEM_USER = get_env_variable('TICKET_GENERATION_SYSTEM_USER','')

# expose deployement var for WTF_CSRF_EXEMPT_LIST
WTF_CSRF_EXEMPT_STR = get_env_variable('WTF_CSRF_EXEMPT_STR').strip()
Expand Down
5 changes: 3 additions & 2 deletions superset/assets/src/chart/chartReducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -188,10 +188,11 @@ export default function chartReducer(charts = {}, action) {
};
},
[actions.REST_ACTION_FAILED](state) {
const errorMessage = action.queryResponse ? action.queryResponse.error : ''

let restAction = { ...state.restAction,
status: "error",
error: !! action.queryResponse ? t('Network error.')
: (action.queryResponse.error || action.queryResponse.errors)
error: errorMessage
}

return {
Expand Down
6 changes: 5 additions & 1 deletion superset/assets/src/dashboard/components/SliceHeader.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ const propTypes = {
sliceSubHeader: PropTypes.string,
supersetCanExplore: PropTypes.bool,
sliceCanEdit: PropTypes.bool,
conf: PropTypes.object
};

const defaultProps = {
Expand All @@ -69,6 +70,7 @@ const defaultProps = {
supersetCanExplore: false,
canExportCSV: true,
sliceCanEdit: false,
conf: {},
};

const annoationsLoading = t('Annotation layers are still loading.');
Expand Down Expand Up @@ -98,6 +100,7 @@ class SliceHeader extends React.PureComponent {
updateSliceName,
annotationQuery,
annotationError,
conf
} = this.props;

return (
Expand Down Expand Up @@ -148,7 +151,7 @@ class SliceHeader extends React.PureComponent {
<TooltipWrapper
label="annoation-errors"
placement="top"
tooltip={`${currentRestAction.action.label} has failed.`}
tooltip={ !currentRestAction.error ? `${currentRestAction.action.label} has failed.` : currentRestAction.error}
>
<i className="fa fa-exclamation-circle danger" />
</TooltipWrapper>
Expand All @@ -168,6 +171,7 @@ class SliceHeader extends React.PureComponent {
canExportCSV={canExportCSV}
supersetCanExplore={supersetCanExplore}
sliceCanEdit={sliceCanEdit}
conf={conf}
/>
)}
</div>
Expand Down
28 changes: 24 additions & 4 deletions superset/assets/src/dashboard/components/SliceHeaderControls.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,10 @@ class SliceHeaderControls extends React.PureComponent {
this.props.slice.slice_id,
);
this.restActions = this.props.slice.form_data.rest_actions || [];
this.addNavigateToDashboardAction(this.restActions)
const navigate_action = this.getNavigateToDashboardAction();
const raise_ticket_action = this.getRaiseTicketAction();
if(navigate_action) this.restActions.push(navigate_action);
if(raise_ticket_action) this.restActions.push(raise_ticket_action);

this.renderRestActions = this.renderRestActions.bind(this);
this.executeRestAction = this.props.executeRestAction.bind(this);
Expand All @@ -91,15 +94,32 @@ class SliceHeaderControls extends React.PureComponent {
};
}

addNavigateToDashboardAction(restActions) {
getNavigateToDashboardAction() {
if (this.props.slice.form_data.navigate_to_dashboards && this.props.slice.form_data.navigate_to_dash_link_name) {
let navigateToDashURL = APPLICATION_PREFIX + "/superset/dashboard/" + this.props.slice.form_data.navigate_to_dashboards + "/";
let navigateToDashAction = {
"label": this.props.slice.form_data.navigate_to_dash_link_name,
"url": navigateToDashURL,
"method": "GET"
};
restActions.push(navigateToDashAction);
return navigateToDashAction;
}
}

getRaiseTicketAction() {
let raise_ticket_action
if(this.props.conf['TICKET_GENERATION_SYSTEM_ENDPOINT'] != '') {
const raise_ticket_payload = this.props.slice.form_data.raise_ticket_action_payload
if (this.props.slice.form_data.raise_ticket_action) {
raise_ticket_action = {
"label": "Raise Ticket",
"url": "TICKET_GENERATION_SYSTEM_ENDPOINT",
"method": "POST",
"data": JSON.parse(raise_ticket_payload),
"success_message": this.props.slice.form_data.raise_ticket_action_message,
};
}
return raise_ticket_action
}
}

Expand Down Expand Up @@ -159,7 +179,7 @@ class SliceHeaderControls extends React.PureComponent {


render() {
const { slice, isCached, cachedDttm, updatedDttm } = this.props;
const { slice, isCached, cachedDttm, updatedDttm, conf } = this.props;
const cachedWhen = moment.utc(cachedDttm).fromNow();
const updatedWhen = updatedDttm ? moment.utc(updatedDttm).fromNow() : '';
const refreshTooltip = isCached
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,7 @@ class Chart extends React.Component {
sliceSubHeader={sliceSubHeader}
supersetCanExplore={supersetCanExplore}
sliceCanEdit={sliceCanEdit}
conf = {this.props.dashboardInfo.common.conf}
/>
)}

Expand Down
5 changes: 5 additions & 0 deletions superset/assets/src/explore/controlPanels/sections.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,11 @@ export const actions = {
controlSetRows: [
[<h1 className="section-header">{t('Navigate to Dashboard')}</h1>],
['navigate_to_dash_link_name','navigate_to_dashboards'],
[<h1 className="section-header">{t('Raise Ticket')}</h1>],
['raise_ticket_action'],
['raise_ticket_action_payload'],
['raise_ticket_action_message'],

],
};

Expand Down
51 changes: 50 additions & 1 deletion superset/assets/src/explore/controls.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ import * as v from './validators';
import { defaultViewport } from '../modules/geo';
import ColumnOption from '../components/ColumnOption';
import OptionDescription from '../components/OptionDescription';
import * as Jira from './jira-payload-format.json'

const categoricalSchemeRegistry = getCategoricalSchemeRegistry();
const sequentialSchemeRegistry = getSequentialSchemeRegistry();
Expand Down Expand Up @@ -243,6 +244,25 @@ function jsFunctionControl(label, description, extraDescr = null, height = 100,
};
}

function jsonDataControl(label, description,defaultText = '',mapStateToProps=null, extraDescr = null, height = 100) {
return {
type: 'TextAreaControl',
language: 'json',
label,
description,
height,
default: defaultText,
aboveEditorSection: (
<div>
<p>{description}</p>
{extraDescr}
</div>
),
mapStateToProps: mapStateToProps,
};

}

export const controls = {

metrics,
Expand Down Expand Up @@ -2219,10 +2239,39 @@ export const controls = {
},
rest_actions: {
"type": "HiddenControl",
"label": "Rest Actions",
"label": t("Rest Actions"),
"hidden": true,
"description": "rest actions"
},
raise_ticket_action: {
type: 'CheckboxControl',
label: t('Enable Raise Ticket'),
description: t('Add an option to raise tickets from chart.'),
default: false,
mapStateToProps: state => ({
disabled: state.common.conf.TICKET_GENERATION_SYSTEM_ENDPOINT == '',
label: state.common.conf.TICKET_GENERATION_SYSTEM_ENDPOINT == '' ? t('Enable Raise Ticket (Not Configured)') : t('Enable Raise Ticket')
})
},

raise_ticket_action_payload: jsonDataControl(
t('Payload Format'),
t('Raise Ticket payload format with placeholder'),
JSON.stringify(Jira.default, null, '\t'),
state => ({
hidden: state.common.conf.TICKET_GENERATION_SYSTEM_ENDPOINT == '',
})
),
raise_ticket_action_message: {
type: 'TextControl',
label: t('Success Message Format'),
default: t('Ticket %key% created successfully'),
description: t('The message to be shown on successful ticket generation'),
mapStateToProps: state => ({
hidden: state.common.conf.TICKET_GENERATION_SYSTEM_ENDPOINT == '',
})
},

url_params: {
type: 'HiddenControl',
label: t('URL Parameters'),
Expand Down
12 changes: 12 additions & 0 deletions superset/assets/src/explore/jira-payload-format.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"fields": {
"project": {
"key": "MD"
},
"summary": "[%dashboard_title%/%chart_title%: %time%] Issue raised using RVF",
"description": "*An issue has been raised by following dashboard* \n {quote} Dashboard Name: *%dashboard_title%* \n Chart Name: *%chart_title%* \n The dashboard access link is available [here|%dashboard_url%] \n RVF User: %username%{quote}\n *The chart has following selection at the time of incident*\n {quote}Chart Selection: %chart_selection%.{quote}\n {code:sql| title=Chart Query}%chart_query%{code}" ,
"issuetype": {
"name": "Task"
}
}
}
1 change: 1 addition & 0 deletions superset/assets/src/explore/visTypes.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export const sections = {
controlSetRows: [
['publish_columns'],
['rest_actions']
['raise_ticket_action']
],
},
colorScheme: {
Expand Down
5 changes: 5 additions & 0 deletions superset/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,11 @@

APPLICATION_PREFIX = ''

TICKET_GENERATION_SYSTEM_NAME = 'JIRA'
#TICKET_GENERATION_SYSTEM_ENDPOINT = 'https://guavus-jira.atlassian.net/rest/api/2/issue/'
TICKET_GENERATION_SYSTEM_ENDPOINT = ''
TICKET_GENERATION_SYSTEM_API_KEY = 'vOzAgEbwDDQEhqb2idle73FF'
TICKET_GENERATION_SYSTEM_USER ='[email protected]'
# Use this flag to enable/disable walkme
WALKME_ENABLED = True

Expand Down
1 change: 1 addition & 0 deletions superset/views/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
'DEFAULT_SQLLAB_LIMIT',
'SQL_MAX_ROW',
'SUPERSET_WEBSERVER_DOMAINS',
'TICKET_GENERATION_SYSTEM_ENDPOINT'
)


Expand Down
26 changes: 24 additions & 2 deletions superset/views/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import time
import traceback
import requests
import base64
from urllib import parse

from flask import (
Expand Down Expand Up @@ -819,13 +820,34 @@ class Superset(BaseSupersetView):

@expose('/execute_rest_action', methods=['POST'])
def execute_rest_action(self):
action = json.loads(request.form.get('action'))
action = self.build_rest_request(request.form.get('action'))
url = action['url']
values = action['data']
headers = action['headers']
if url is '':
return json_error_response(action['label']+" is not configured")
resposne = requests.post(url, json=values, headers=headers)
return json_success(resposne.text, resposne.status_code)


def build_rest_request(self,rawaction):
action = json.loads(rawaction)
if action['url'] == 'TICKET_GENERATION_SYSTEM_ENDPOINT' :
action = self.build_ticket_generation_request(action)
return action

def build_ticket_generation_request(self,action):
action['url'] = config['TICKET_GENERATION_SYSTEM_ENDPOINT']
user = config['TICKET_GENERATION_SYSTEM_USER']
api_key = config['TICKET_GENERATION_SYSTEM_API_KEY']
action['headers'] = {
"Content-Type": "application/json",
}
if api_key is not '' and user is not '':
api_token = '%s:%s' % (user,api_key)
base64string = base64.standard_b64encode(api_token.encode('utf-8'))
authorization_header_value = 'Basic %s' % base64string.decode('utf-8')
action['headers']['Authorization'] = authorization_header_value
return action

@expose('/add_to_dashboard', methods=['POST'])
def addtodashboard(self):
Expand Down

0 comments on commit 544dc2e

Please sign in to comment.