Expand my Community achievements bar.

SOLVED

How do I send Params to my runtime action?

Avatar

Level 4

I have created a Adobe IO runtime project based on firefly template. I have added Adobe Analytics API in my console.adobe.io. While creating the project thru AIO, it has created a sample action which fetches collections from adobe analytics.

 

Problem 1: When I open this app using localhost:9080, I get this error

 

in ForwardRef($f01f0d1066865f7027d77d32f5de6fe$var$Picker) (created by App)
    in ModalProvider (created by ForwardRef($bc3300334f45fd1ec62a173e70ad86$var$Provider))
    in I18nProvider (created by ForwardRef($bc3300334f45fd1ec62a173e70ad86$var$Provider))
    in ForwardRef($bc3300334f45fd1ec62a173e70ad86$var$Provider) (created by ForwardRef($d618213cd07bac427c632ce5c90d7$var$Form))
    in form (created by ForwardRef($d618213cd07bac427c632ce5c90d7$var$Form))
    in ForwardRef($d618213cd07bac427c632ce5c90d7$var$Form) (created by App)
    in div (created by ForwardRef($f9fee2c1db386bde8f28b36a814d1eb2$var$Flex))
    in ForwardRef($f9fee2c1db386bde8f28b36a814d1eb2$var$Flex) (created by App)
    in div (created by ForwardRef($f9fee2c1db386bde8f28b36a814d1eb2$var$Flex))
    in ForwardRef($f9fee2c1db386bde8f28b36a814d1eb2$var$Flex) (created by App)
    in div (created by ForwardRef(ProviderWrapper))
    in ForwardRef(ProviderWrapper) (created by ForwardRef($bc3300334f45fd1ec62a173e70ad86$var$Provider))
    in ModalProvider (created by ForwardRef($bc3300334f45fd1ec62a173e70ad86$var$Provider))
    in I18nProvider (created by ForwardRef($bc3300334f45fd1ec62a173e70ad86$var$Provider))
    in ForwardRef($bc3300334f45fd1ec62a173e70ad86$var$Provider) (created by App)
    in ErrorBoundary (created by App)
    in App
Cannot destructure property 'scale' of '(0 , _provider.useProvider)(...)' as it is null.

 

Problem 2: When I call the action from CURL I get this error 

 

400: missing parameter(s) 'companyId'

 

 

I understand that I need to send company Id. But what I am not understanding is how to send it. Here is what i tried so far:

1. sent it as a header - 400: missing parameter(s) 'companyId'

2. sent it in json body - Request defines parameters that are not allowed (e.g., reserved properties)

3. sent it as query param.-Request defines parameters that are not allowed (e.g., reserved properties)

 

None of these have worked so far. I do not see any documentation on how to send this parameter. Please help.

 

EDIT: here is the app.js. I havent made any changes to these files.

 

/*
* <license header>
*/

// react imports
import React from 'react'
import PropTypes from 'prop-types'
import ErrorBoundary from 'react-error-boundary'

// react spectrum components
import { Provider } from '@react-spectrum/provider'
import { theme } from '@react-spectrum/theme-default'
import { Button } from '@react-spectrum/button'
import { TextField } from '@react-spectrum/textfield'
import { Link } from '@react-spectrum/link'
import { Picker, Item } from '@react-spectrum/picker'
import { Form } from '@react-spectrum/form'
import { Flex } from '@react-spectrum/layout'
import { ProgressCircle } from '@react-spectrum/progress'
import { Heading, Text } from '@react-spectrum/text';

// local imports
import './App.css'
import { actionWebInvoke } from './utils'
import actions from './config.json'

/* Here is your entry point React Component, this class has access to the Adobe Experience Cloud Shell runtime object */

export default class App extends React.Component {
  constructor (props) {
    super(props)

    // error handler on UI rendering failure
    this.onError = (e, componentStack) => {}

    // component to show if UI fails rendering
    this.fallbackComponent = ({ componentStack, error }) => (
      <React.Fragment>
        <h1 style={{ textAlign: 'center', marginTop: '20px' }}>Something went wrong :(</h1>
        <pre>{ componentStack + '\n' + error.message }</pre>
      </React.Fragment>
    )

    this.state = {
      actionSelected: null,
      actionResponse: null,
      actionResponseError: null,
      actionHeaders: null,
      actionHeadersValid: null,
      actionParams: null,
      actionParamsValid: null,
      actionInvokeInProgress: false
    }

    console.log('runtime object:', this.props.runtime)
    console.log('ims object:', this.props.ims)

    // use exc runtime event handlers
    // respond to configuration change events (e.g. user switches org)
    this.props.runtime.on('configuration', ({ imsOrg, imsToken, locale }) => {
      console.log('configuration change', { imsOrg, imsToken, locale })
    })
    // respond to history change events
    this.props.runtime.on('history', ({ type, path }) => {
      console.log('history change', { type, path })
    })
  }

  static get propTypes () {
    return {
      runtime: PropTypes.any,
      ims: PropTypes.any
    }
  }

  // parses a JSON input and adds it to the state
  async setJSONInput (input, stateJSON, stateValid) {
    let content
    let validStr = null
    if (input) {
      try {
        content = JSON.parse(input)
        validStr = 'valid'
      } catch (e) {
        content = null
        validStr = 'invalid'
      }
    }
    this.setState({ [stateJSON]: content, [stateValid]: validStr })
  }

  // invokes a the selected backend actions with input headers and params
  async invokeAction () {
    this.setState({ actionInvokeInProgress: true })
    const action = this.state.actionSelected
    const headers = this.state.actionHeaders || {}
    const params = this.state.actionParams || {}

    // all headers to lowercase
    Object.keys(headers).forEach(h => {
      const lowercase = h.toLowerCase()
      if (lowercase !== h) {
        headers[lowercase] = headers[h]
        headers[h] = undefined
        delete headers[h]
      }
    })
    // set the authorization header and org from the ims props object
    if (this.props.ims.token && !headers.authorization) {
      headers.authorization = 'Bearer ' + this.props.ims.token
    }
    if (this.props.ims.org && !headers['x-gw-ims-org-id']) {
      headers['x-gw-ims-org-id'] = this.props.ims.org
    }
    try {
      // invoke backend action
      const actionResponse = await actionWebInvoke(action, headers, params)
      // store the response
      this.setState({ actionResponse, actionResponseError: null, actionInvokeInProgress: false })
      console.log(`Response from ${action}:`, actionResponse)
    } catch (e) {
      // log and store any error message
      console.error(e)
      this.setState({ actionResponse: null, actionResponseError: e.message, actionInvokeInProgress: false })
    }
  }

  render () {
    return (
      // ErrorBoundary wraps child components to handle eventual rendering errors
      <ErrorBoundary onError={ this.onError } FallbackComponent={ this.fallbackComponent } >
      <Provider UNSAFE_className='provider' theme={ theme }>
        <Flex UNSAFE_className='main'>
          <Heading UNSAFE_className='main-title'>Welcome to OnkarAnalyticsIO!</Heading>

          <Flex UNSAFE_className='main-actions'>
            <h3 className='actions-title'>Run your application backend actions</h3>
            { Object.keys(actions).length > 0 &&
              <Form UNSAFE_className='actions-form' necessityIndicator='label'>
              <Picker
                placeholder='select an action'
                aria-label='select an action'
                items={ Object.keys(actions).map(k => ({ name: k })) }
                itemKey='name'
                onSelectionChange={ name => this.setState({ actionSelected: name, actionResponseError: null, actionResponse: null }) }>
                { item => <Item key={item.name}>{ item.name }</Item> }
              </Picker>
              <TextField
                label='headers'
                placeholder='{ "key": "value" }'
                validationState={ this.state.actionHeadersValid }
                onChange={ input => this.setJSONInput(input, 'actionHeaders', 'actionHeadersValid' ) }/>
              <TextField
                label='params'
                placeholder='{ "key": "value" }'
                validationState={ this.state.actionParamsValid }
                onChange={ input => this.setJSONInput(input, 'actionParams', 'actionParamsValid' ) }/>
              <Flex UNSAFE_className='actions-invoke'>
                <Button
                  UNSAFE_className='actions-invoke-button'
                  variant='primary'
                  onPress={ this.invokeAction.bind(this) }
                  isDisabled={ !this.state.actionSelected }>
                  Invoke
                </Button>
                <ProgressCircle
                  UNSAFE_className='actions-invoke-progress'
                  aria-label='loading'
                  isIndeterminate
                  isHidden={ !this.state.actionInvokeInProgress }/>
                </Flex>
              </Form>
            }
            { Object.keys(actions).length === 0 &&
              <Text>You have no actions !</Text>
            }
            { this.state.actionResponseError &&
              <Text UNSAFE_className='actions-invoke-error'>
                Failure! See the error in your browser console.
              </Text>
            }
            { !this.state.actionError && this.state.actionResponse &&
              <Text UNSAFE_className='actions-invoke-success'>
                Success! See the response content in your browser console.
              </Text>
            }
          </Flex>

          <Flex UNSAFE_className='main-doc'>
            <h3 className='doc-title'>Useful documentation for your app</h3>
            <Link UNSAFE_className='doc-item'>
              <a href='https://github.com/AdobeDocs/project-firefly/blob/master/README.md#project-firefly-developer-guide' target='_blank'>
                Firefly Apps
              </a>
            </Link>
            <Link UNSAFE_className='doc-item'>
              <a href='https://github.com/adobe/aio-sdk#adobeaio-sdk' target='_blank'>
                Adobe I/O SDK
              </a>
            </Link>
            <Link UNSAFE_className='doc-item'>
              <a href='https://adobedocs.github.io/adobeio-runtime/' target='_blank'>
                Adobe I/O Runtime
              </a>
            </Link>
            <Link UNSAFE_className='doc-item'>
              <a href='https://react-spectrum.adobe.com/react-spectrum/index.html' target='_blank'>
                React Spectrum
              </a>
            </Link>
          </Flex>

        </Flex>
      </Provider>
      </ErrorBoundary>
    )
  }
}

 

Topics

Topics help categorize Community content and increase your ability to discover relevant content.

1 Accepted Solution

Avatar

Correct answer by
Employee

Problem 1: please try giving all the `@react-spectrum` dependencies version "3.0.1", then `npm install` and re-run your app. This is the tracking issue for the record: https://github.com/adobe/aio-cli/issues/201.

Problem 2: if you selected "Adobe Analytics" from the template with `aio app init`, your .env file would have a variable called `ANALYTICS_COMPANY_ID` commented out. Please set your company ID there, uncomment (by removing the hash "#"), and then re-run the app. The env var is picked up by the app according to the mapping in manifest.yml.

 

View solution in original post

10 Replies

Avatar

Employee

Hi there - To troubleshoot problem 1, could you please share your code in web-src/src/App.js ? Did you modify anything in the web-src/ folder?

 

Avatar

Correct answer by
Employee

Problem 1: please try giving all the `@react-spectrum` dependencies version "3.0.1", then `npm install` and re-run your app. This is the tracking issue for the record: https://github.com/adobe/aio-cli/issues/201.

Problem 2: if you selected "Adobe Analytics" from the template with `aio app init`, your .env file would have a variable called `ANALYTICS_COMPANY_ID` commented out. Please set your company ID there, uncomment (by removing the hash "#"), and then re-run the app. The env var is picked up by the app according to the mapping in manifest.yml.

 

Avatar

Level 4

thanks duypnguyen. I have updated the question with app.js code.

Avatar

Employee

@prashantonkar - could you please try upgrading all @react-spectrum dependencies to "3.0.1"? It did work for me. We will work on a fix to update the versions in the template

Avatar

Level 4
@duypnguyen Now that this API is working, how do I call this API from an external application? I mean, if i have to call this api from an AEM application, should i configure IMS configuration ? I see only 4 options in the IMS configuration and I/O runtime is not one of them. Should i write my code manually to generate bearer token to call this API?

Avatar

Employee

If you leave the default settings from the template, meaning "require-adobe-auth" is set to true, the Runtime action expects the bearer token in the authorization header. You could find more details here: https://github.com/AdobeDocs/project-firefly/blob/master/guides/security_overview.md.

The IMS token is generated for the API service you work with (Analytics). The Runtime action only validates that it belongs to the right org and is valid to access the service. Hence in AEM particularly, you would use the IMS config for Analytics (if available), or add it to your AEM instance.

Avatar

Level 4
In AEM IMS configuration I don't find Adobe Analytics too. So did some digging in and created an Adobe Granite Access Token Provider and configured the client id, JWT Claims. I am not understanding how to link this to the keystore file that is present on Adobe IO Console. I think I am missing something. Is there a document which has step-by-step details?

Avatar

Employee

You can read more about authentication for Adobe I/O API services here: https://www.adobe.io/authentication.html.

As mentioned in the earlier comment, the authentication check in a Firefly app is described here: https://github.com/AdobeDocs/project-firefly/blob/master/guides/security_overview.md.

You may want to reach our to AEM Customer Care / Forum about IMS Access Token Provider and how to configure it using the credentials returned by Adobe I/O Console.

I assume you selected JWT token for the Analytics API service. In brief, the access token is generated on the AEM side using the private key you set for it (either generated by AEM or uploaded by you). When it makes request to your Firefly service / app, the token should be attached to the headers, which is then validated by the Firefly app to assure that your request is authenticated to access the integrated service. Once validated, the app will retrieve data from Analytics API, compute your logics and returns the results back to AEM.

Does it answer your question?

Avatar

Level 4

thanks duypnguyen. I am able to get the token by reading the documentation you shared. Also this link helped me too in generating a certificate and accessing it in AEM: https://docs.adobe.com/content/help/en/experience-manager-learn/foundation/authentication/set-up-pub... and by using AccessTokenProvider.