Dev Space

Customize and extend the power of Alteryx with SDKs, APIs, custom tools, and more.

HTML SDK | Challenges with Get/Set Config only

Coxta45
11 - Bolide

I've a got couple of (probably dumb) questions for the SDK experts...

 

I'm still developing a new Jira Connector and have decided to take a new approach.  I'm using Vue.js and it's integrated data store for state management (Vuex).  I'd really like to maintain a Get/SetConfiguration only, if possible, but understand if it simply can't be done.

 

Notice here, all that I'm doing is passing portion of my data store back and forth between Vuex (when tool is loaded) and the tool's XML config (for persistence).

 

app.js

Spoiler
import Vue from 'vue'
import Vuelidate from 'vuelidate'
import Vuetify from 'vuetify'
import 'vuetify/dist/vuetify.min.css'

import {store} from './store.js'
import App from './App.vue'

Vue.use(Vuetify)
Vue.use(Vuelidate)

// render app
const app = new Vue({
  el: '#app',
  store,
  render: h => h(App)
})

window.Alteryx.Gui = {

	SetConfiguration: j => {
		
		// update Vuex store with Alteryx Designer XML config, if exists
		store.state.ui = j.Configuration.Configuration ? j.Configuration.Configuration : store.state.ui
		window.Alteryx.JsEvent(JSON.stringify({Event: 'SetConfiguration'}))

	},
	GetConfiguration: () => {

		// give Vuex store to the Alteryx Designer XML config
		window.Alteryx.JsEvent(JSON.stringify({
			Event: 'GetConfiguration',
			Configuration: {
				Configuration: store.state.ui,
				Annotation: store.state.config.appTitle
			}
		}))
	}
}

So here are my questions, given the above setup.

 

  1. Without data items and/or widgets how in the world does one "hand" the config array (data store) to a macro engine?  To put it in context, the tool's UI will just be putting together a payload that I'd like to hand to a macro engine, which would then make some POST requests to the JIRA APIs and and handle the parsing.  This is a piece of cake when using the widgets, data items, etc. but I have no idea how to handle that "hand-off" with a simple Get/SetConfiguration and a traditional macro engine.  Please help!
  2. How could I (if even possible) leverage something like the SimpleString (password) encryption for an object in my Vuex data store when I'm only using a Get/SetConfiguration method? I assume that I can't do it with that setup, but would need to use Before/After loads, etc. and leverage the SDK's manager...which would feel excessive/redundant and silly in that I'd essentially be managing 2 data stores...
  3. Where are the HTML GUI SDK source files (not compiled builds) ?? This might help me solve my second question.

Per question #2 - in a nutshell, I want to replicate the following manager method within my GetConfiguration and then implement an asynchronous listener in my SetConfiguration to retrieve it later..

 

manager.addDataItem(new AlteryxDataItems.SimpleString('userPass', { password: true }) );

 

Lastly, if #2 just isn't possible without using Before/AfterLoads with the manager, what would be the best way to encrypt and store a basic auth password string, so that Joe:JoesPassword isn't just stored as only Base64 encoded (Sm9lOkpvZXNQYXNzd29yZA==) right in the tool's XML config (yeah, I know..terrible). 

 

Lastly, anyone willing to help implement an alternative auth method for a new Jira Connector?  I've explored OAuth methods tirelessly, but it requires JIRA admins to configure connected apps within each of their JIRA instances and then users would have to provide the appropriate callback URIs etc., yada yada - doesn't really seem feasible, or perhaps I'm terribly misunderstanding JIRA's OAuth process.

 

As always, any and all help is very much appreciated.

9 REPLIES 9
RyanSw
Alteryx Alumni (Retired)

FIrst I have to ask, is your current code described in the spoiler working? It looks fairly correct to me, the only piece missing is the encryption you described.

 

Is that your current state? The whole thing works, but doesn't encrypt as you would like? Or are you having a larger fundamental failing of persisting the data altogether?

 

If it's just a failure of the encryption, the solution is to call the encryption JsEvent just as you are calling the GetConfiguration JsEvent - but when you call it, give it a callback which will *then* fire the GetConfiguration JsEvent to complete the storage with the freshly given encrypted string.

 

I can scratch up a quick example if this is the scenario you're in, but if you're fundamentally failing to persist data given the described code you have there, then I'm curious what is being persisted if anything?

 

To your other point about whether or not the data item system is available and useful without the widgets, absolutely you can use it like that! You don't necessarily have to rely on it for managing any of your overarching data store, in BeforeLoad you are handed the whole JSON object from the persisted XML, you could pull that into your Vue use there, and then implement your own GetConfiguration which uses a SimpleString data item which is a password - utilizes the toJson function on it to have it do the asynchronous encryption for you (as well as asynchronous decryption during load if you please it), and just utilize it for managing the encryption/decryption for you as such.

 

Any way you go about it though, encryption/decryption are going to be asynchronous calls due to the nature of the JsEvent calls, but fromJson and toJson take a resolve and reject function both, so you can utilize such to get at the results of such encryption/decryption before firing the GetConfiguration JsEvent.

 

I know this is a bit of a lot of information, let me know if it makes sense and what you might have more questions about!

Coxta45
11 - Bolide

Yes everything works flawlessy right now and I have no trouble with any state management or persisting.  Like you said, I'm just looking to encrypt/decrypt a single piece of my state on it's way to the persisted XML configuration.  I understand the JSEvent is a promise based asynchronous action, I'm just not sure what it's exact syntax is, as I've only used the data items and manager to store passwords.

 

Here's a peek at store.js - only focused on encrypting/decrypting state.ui.auth on it's travels between the XML config and the Vuex store.

 

Spoiler
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export const store = new Vuex.Store({
	state: {
		config: {
			appTitle: 'Jira Input',
			appVersion: 'v0.0.0',
			latestUrl: 'https://api.github.com/repos/alteryx-vue/jira-input/releases/latest',
			updateAvail: false,
			updatePrompt: false,
			moreInfo: false,
			updateVersion: 'v?',
			updateUrl: 'https://github.com/alteryx-vue/installers/raw/master/jira-input.yxi',
			icon: 'icon.png',
			pages: [
			  { id: 0, name: 'connection', icon: 'power_settings_new' },
			  { id: 1, name: 'projects', icon: 'folder_open' },
			  { id: 2, name: 'filters', icon: 'filter_list' },
			  { id: 3, name: 'fields', icon: 'check_box' }
			],
			afterConnect: 'projects'
		},
		ui: {
			projects: [],
			selections: [],
			filterMethod: 'basic',
			page: 'connection',
			url: 'https://alteryx-vue.atlassian.net',
			lastUrl: '',
			auth: '',
			lastAuth: '',
			connected: 0,
			connects: 0,
			connectError: 0,
			apiError: '',
			stopCheck: false
		}
	},
	mutations: {
	  dismissUpdate (state) {
	  	state.config.updatePrompt = false
	  },
	  updateMoreInfo (state, v) {
	  	state.config.moreInfo = v
	  },
	  updatePage (state, v) {
	  	state.ui.page = v
	  },
	  updateProjects (state, v) {
	  	state.ui.projects = v
	  },
	  updateSelections (state, v) {
	  	state.ui.selections = v
	  },
	  updateUrl (state, v) {
	    state.ui.url = v
	  },
	  updateLastUrl (state, v) {
	    state.ui.lastUrl = state.ui.url
	  },
	  updateUsername (state, v) {
	    state.ui.username = v
	  },
	  updatePassword (state, v) {
	    state.ui.password = v
	  },
	  updateAuth (state, v) {
	    state.ui.auth = v
	  },
	  updateLastAuth (state) {
	  	state.ui.lastAuth = state.ui.auth
	  },
	  updateConnected (state, v) {
	    state.ui.connected = v
	  },
	  updateConnectError (state, v) {
	  	state.ui.connectError = v
	  },
	  updateConnects (state) {
	  	state.ui.connects ++
	  },
	  updateApiError (state, v) {
	  	state.ui.apiError = v
	  },
	}
})

And then here's my main.js where I'm calling Get and SetConfigurations.  I've added some comments where ideally the async events would happen.

 

 

Spoiler
import Vue from 'vue'
import Vuelidate from 'vuelidate'
import Vuetify from 'vuetify'
import 'vuetify/dist/vuetify.min.css'

import axios from 'axios'
import {store} from './store.js'
import App from './App.vue'

Vue.use(Vuetify)
Vue.use(Vuelidate)

// GET latest release info for update check
axios.get(store.state.config.latestUrl)
.then(response => { 
					const avail = response.data.name
					const current = store.state.config.appVersion
					store.state.config.updateAvail = (avail.length > 0 && avail !== current) ? true : false
					store.state.config.updatePrompt = store.state.config.updateAvail
					store.state.config.updateVersion = avail
				  }
)
.catch(response => { /* release call failed, just proceed */ })

// render app
const app = new Vue({
  el: '#app',
  store,
  render: h => h(App)
})

window.Alteryx.Gui = {

	SetConfiguration: j => {

		// update Vuex store with Alteryx Designer XML config, if exists
		store.state.ui = j.Configuration.Configuration ? j.Configuration.Configuration : store.state.ui
		window.Alteryx.JsEvent(JSON.stringify({Event: 'SetConfiguration'}))

// ** ADDED COMMENTS **
// inform the UI that we're still decrypting the password? (assuming an added a state.ui.decrypting to our store)
// (store.state.ui.decrypting = true)

// call decryptor async event? (dont know syntax here, help please!)
// window.Alteryx.JsEvent( decrypt j.Configuration.Configuration.auth here ).then( update store.state.ui.auth with the decrypted value AND inform store.state.ui.decrypting = false)
// hopefully this helps?? }, GetConfiguration: () => { // give Vuex store to the Alteryx Designer XML config

// ** But first I need to encrypt store.state.ui.auth...
// call encryption JSEvent and THEN do the following...
window.Alteryx.JsEvent(JSON.stringify({ Event: 'GetConfiguration', Configuration: { Configuration: store.state.ui, Annotation: store.state.config.appTitle } })) } }

 

 

I went ahead and pushed the project to GitHub:

https://github.com/alteryx-vue/jira-input

 

The NYT Connector in that same GitHub organization uses an identical state management approach with a simple Get/SetConfiguration and has working releases.

 

Feel free to submit a pull request, if you're feeling ambitious.  Also, would love some contributors and/or organization members if anyone reading this is interested!  Just shoot me a message and I'll send an invite.

RyanSw
Alteryx Alumni (Retired)

That's great to hear!

 

To encrypt or decrypt manually using JsEvent, the call format is:

 

{ Event: 'Encrypt', text: 'stringToEncrypt', callback: 'JavaScriptFunctionNameToExecute' }

 

Given the callback is a little strange, we have created a helper function you can call as such:

window.Alteryx.Gui.Utils.Functions.postJsEventWithCallback('Encrypt', {text: 'stringToEncrypt'}, function(decryptedString) {})

For decryption, it's exactly the same, just the event is 'Decrypt', you can make any JsEvent calls with that helper function, but it's especially useful for JsEvent's that have callbacks such as the encrypt/decrypt.

 

FromJson and ToJson on a SimpleString will take a resolve function which it will call with the encrypted / decrypted data if password is true too, so you can use that directly; the resolve function is the first parameter of the from and to json functions.

 

Hope this helps!

pavloko
7 - Meteor

Rayan, thanks for this explanation.

 

If I have SetConfiguration and BeforeLoad defined, BeforeLoad doesn't run.
If I have GetConfiguration and BeforeGetConfiguration defined, BeforeGetConfiguration doesn't run.

However, in the docs it says it's ok to use them together. Is it expected?

I want to have custom UI elements and use ayx DropDown widget together. If I only define SetConfiguration and GetConfiguration the DropDown widget doesn't populate and I don't have access to the manager because I have to override Gui object. Thus, I have to use hybrid approach: 

  window.Alteryx.Gui.BeforeLoad = (manager, _, json) => {
      this.state = json.Configuration
  }

  window.Alteryx.Gui.AfterLoad = manager => {
      // window.Alteryx.Gui.Manager is not available
      // and I can use it to set/remove listeners, get and set values of widgets
this.isAlteryxInitialized = true; } window.Alteryx.Gui.GetConfiguration = () => {
window.Alteryx.JsEvent(JSON.stringify({ Event: 'GetConfiguration', Configuration: { Configuration: this.state, } })) }

// sometime AfterLoad runs
if (this.isAlteryxInitialized) {
let projectDataItem = window.Alteryx.Gui.Manager.getDataItem('project')
// setValue of dataItem from the XML config because beforeLoad is already executed and this.state
// has all the values form XML
projectDataItem.setValue(this.state.project)
// I can keep the state of widgets synchronized with the state of my app
projectDataItem.registerPropertyListener('value', event => {
this.state['project'] = event.value;
})
}
pavloko
7 - Meteor

@RyanSw thanks for explanations.

 

I'd like to have custom UI widgets as well as use Alteryx's ayx provided widgets.

However, if I only define SetConfiguration and GetConfiguration methods like:

window.Alteryx.Gui = {
    SetConfiguration: () => {},
    GetConfiguration: () => {}
}

The ayx items don't get initialized and don't display on the page.

 

 

If I try to define both SetConfiguration and BeforeLoad, BeforeLoad doesn't execute.

I resorted to a hybrid approach:

    window.Alteryx.Gui.BeforeLoad = (manager, _, { Configuration }) => {
      if (Configuration && Configuration['apiToken'] !== '') {
        window.Alteryx.Gui.Utils.Functions.postJsEventWithCallback('Decrypt', { text: Configuration['apiToken'] }, (decryptedToken) => {
window.state = Configuration
window.state['apiToken'] = decryptedToken }) } } window.Alteryx.Gui.AfterLoad = manager => { window.isAlteryxInitialized = true } window.Alteryx.Gui.GetConfiguration = () => { if (window.state.apiToken) { window.Alteryx.Gui.Utils.Functions.postJsEventWithCallback('Encrypt', { text: this.state.apiToken }, function (encryptedToken) { window.vm.$data.state['apiToken'] = encryptedToken window.Alteryx.JsEvent(JSON.stringify({ Event: 'GetConfiguration', Configuration: { Configuration: window.state, } })) }) } }

// somewhere when AfterLoad runs
if (window.isAlteryxInitialized) {
// Now because BeforeLoad already assigned XML values to window.state
// I can use it to set value of the ayx widgets and register listeners to
// then update window.state
let projectDataItem = window.Alteryx.Gui.manager.getDataItem('project')
projectDataItem.setOptionList(projectList)
projectDataItem.setValue(window.state.project]
projectDataItem.registerPropertyListener('value', event => {
window.state.project = event.value
})
}

Is this ok to use this approach?

RuchikaMangla
8 - Asteroid

Hi @pavloko @Coxta45 @RyanSw 

 

I am trying to create a custom tool using Html JavaScript. My use is I need to add a macro on to the canvas on button click. But I don't know how would I refer an alteryx canvas. Where we generally drag and drop other tools to create a workflow. So, could you please help me out in this if you have an idea of this. Any help would be appreciable. Thanks!!

TashaA
Alteryx Alumni (Retired)

Hey @RuchikaMangla , 

 

We don't really provide SDKs for interacting with the canvas the way that a user would. 

 

As far as macros go, you can create an HTML GUI front-end for a macro that captures the configuration information from the user that you would like to use within the macro itself. 

 

Best, 

Tasha

RuchikaMangla
8 - Asteroid

thanks, @TashaA 

 

This is what I was also thinking! 🙂

tchard
7 - Meteor

@pavloko:  Did you ever find a solution to this?

I'm in similar position of wanting to use SetConfiguration and GetConfiguration, but they don't seem to work with the other life-cycle methods.  Not sure why.