Skip to content

Commit

Permalink
Return error information on HTTP block request (#393)
Browse files Browse the repository at this point in the history
* return error info in any http response

* fix clearing error on a new request

* add documentation to http block

* add check for code status and error message
  • Loading branch information
gsambrotta authored Nov 6, 2023
1 parent b2176d5 commit e3fae90
Show file tree
Hide file tree
Showing 3 changed files with 171 additions and 41 deletions.
123 changes: 95 additions & 28 deletions cypress/e2e/http_block_page_following.ts → cypress/e2e/http_block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,101 @@ import { loadFlowCode } from '../support/helper';
// tslint:disable: quotemark
/// <reference types="Cypress" />

describe('HTTP Block Integration Tests', () => {

describe('HTTP Block Request', () => {

beforeEach(() => {
// Prevent external network request for adapter config
cy.intercept('GET', 'https://kendraio.github.io/kendraio-adapter/config.json', {
fixture: 'adapterConfig.json'
});

// Prevent external network requests for Workflow cloud
cy.intercept('GET', 'https://app.kendra.io/api/workflowCloud/listWorkflows', {
fixture: 'workflow-cloud.json'
});

// Prevent external network requests for fonts with empty CSS rule
cy.intercept('https://fonts.googleapis.com/\*\*', "\*{ }");
});

it('should return a single set of results. Without pagination', () => {
cy.intercept({
url: 'https://example.com/data'
}, {
statusCode: 200,
body: '["hippo", "giraffe"]'
});

loadFlowCode([
{ "type": "init" },
{
"type": "http",
"method": "GET",
"endpoint": "https://example.com/data"
},
{
"type": "debug",
"open": 2,
"showData": true
}
]);
cy.contains('hippo');
cy.contains('giraffe');
});

it('should return an error', () => {
cy.intercept({
url: 'https://example.com/data'
}, {
statusCode: 400,
body: { error: {
error: "Http failure 400 Bad request",
error_description: "There was a problem with your request"
}
}
});

loadFlowCode([
{ "type": "init" },
{
"type": "http",
"method": "GET",
"endpoint": "https://example.com/data",
"onError": {
"blocks": [
{
"type": "card",
"blocks": [
{
"type": "template",
"template": "Error with submission:<p>{{data.error.error}} - {{data.error.error_description}}</p>"
}
]
}
]
}

},
{
"type": "debug",
"open": 3,
"showData": true
}
]);

cy.contains('hasError:true');
cy.contains('status:400');
cy.contains('errorMessage:"Http failure response for https://example.com/data: 400 Bad Request"');
cy.get('app-template-block').contains('Error with submission')
});


});



describe('HTTP Block Follow Pagination', () => {

beforeEach(() => {
// Prevent external network request for adapter config
Expand Down Expand Up @@ -119,31 +213,6 @@ describe('HTTP Block Integration Tests', () => {
cy.contains('birds');
});

it('should return a single set of results if response is not paginated', () => {
cy.intercept({
url: 'https://example.com/data'
}, {
statusCode: 200,
body: '["hippo", "giraffe"]'
});

loadFlowCode([
{ "type": "init" },
{
"type": "http",
"method": "GET",
"endpoint": "https://example.com/data"
},
{
"type": "debug",
"open": 2,
"showData": true
}
]);
cy.contains('hippo');
cy.contains('giraffe');
});

it('should return first results only if not paginated, with proxy', () => {
cy.intercept({
url: 'https://proxy.kendra.io/',
Expand Down Expand Up @@ -191,6 +260,4 @@ describe('HTTP Block Integration Tests', () => {
// we check it does not contain a second page result:
cy.get('body').should('not.contain', 'fish');
});


});
62 changes: 58 additions & 4 deletions docs/workflow/blocks/http.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,13 @@ Supported properties
property.
- **headers** - A set of headers with header name as object key. Values are processed by JMESpath
- **endpoint** - The request endpoint. Can take multiple forms. See below.
- **onError** - Define an array of blocks to show when there is an error processing the HTTP request.


Examples
--------

For simple requests, the ``endpoint`` can just be a simple string:
**Default** For simple requests, the ``endpoint`` can just be a simple string:

.. code-block:: json
Expand All @@ -50,7 +51,8 @@ For simple requests, the ``endpoint`` can just be a simple string:
}
If the endpoint needs to be constructed from data, the endpoint can be specified as an object with a "valueGetter" attribute.
**Dynamic data** If the endpoint needs to be constructed from data, the endpoint can be specified as an object with a "valueGetter" attribute.
"valueGetter" can only get data from the context.

.. code-block:: json
Expand All @@ -62,9 +64,26 @@ If the endpoint needs to be constructed from data, the endpoint can be specified
}
}
.. code-block:: json
{
"type": "http",
"method": "get",
"endpoint": {
"protocol": "https:",
"host": "api.harvestapp.com/api/v2",
"pathname": "/reports/time/tasks",
"valueGetters": {
"query": "{ from: context.savedData.from, to: context.savedData.to }"
}
}
}
**Headers**
For advanced use cases, the payload can be constructed using a JMES Path expression.
Custom headers can also be specified using JMES Path expressions:
JMESPath expressions can be used to dynamically set header and payload values.
Caution: if the header value is a string, it must use two types of quotes: double and single quotes, like "payload": "'grant_type=client_credentials'".

.. code-block:: json
Expand All @@ -83,7 +102,7 @@ Custom headers can also be specified using JMES Path expressions:
}
}
It is possible to query a GraphQL endpoint using the HTTP block.
**GraphQL** It is possible to query a GraphQL endpoint using the HTTP block.

.. code-block:: json
Expand All @@ -100,6 +119,41 @@ It is possible to query a GraphQL endpoint using the HTTP block.
}
**onError** To debug and display an error message

.. code-block:: json
{
"type": "http",
"method": "get",
"endpoint": {
"protocol": "https:",
"host": "accounts.spotify.com",
"pathname": "/api/token"
},
"onError": {
"blocks": [
{
"type": "debug",
"open": 1,
"showData": true,
"showContext": false,
"showState": false
},
{
"type": "card",
"blocks": [
{
"type": "template",
"template": "Error with submission:<p>{{data.error.error}} - {{data.error.error_description}}</p>"
}
]
}
]
}
}
Pagination
----------

Expand Down
27 changes: 18 additions & 9 deletions src/app/blocks/http-block/http-block.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { MatLegacySnackBar as MatSnackBar } from '@angular/material/legacy-snack
import { catchError, expand, reduce, takeWhile } from 'rxjs/operators';
import { of, EMPTY } from 'rxjs';
import { mappingUtility } from '../mapping-block/mapping-util';

@Component({
selector: 'app-http-block',
templateUrl: './http-block.component.html',
Expand Down Expand Up @@ -63,6 +62,7 @@ export class HttpBlockComponent implements OnInit, OnChanges {
}
this.responseType = get(this.config, 'responseType', 'json');
this.errorBlocks = get(this.config, 'onError.blocks', []);

this.makeRequest();
}

Expand Down Expand Up @@ -144,12 +144,14 @@ export class HttpBlockComponent implements OnInit, OnChanges {
this.errorMessage = error.message;
this.errorData = error;
// TODO: need to prevent errors for triggering subsequent blocks
return of([]);
return of({error, hasError: this.hasError, errorMessage: this.errorMessage});
})
)
.subscribe(response => {
.subscribe((response: Record<string, any>) => {
this.isLoading = false;
this.hasError = false;
if(!response.hasError) this.errorBlocks = [];

this.outputResult(response);
});
}
Expand All @@ -162,12 +164,14 @@ export class HttpBlockComponent implements OnInit, OnChanges {
this.errorMessage = error.message;
this.errorData = error;
// TODO: need to prevent errors for triggering subsequent blocks
return of([]);
return of({error, hasError: this.hasError, errorMessage: this.errorMessage});
})
)
.subscribe(response => {
.subscribe((response: Record<string, any>) => {
this.isLoading = false;
this.hasError = false;
if(!response.hasError) this.errorBlocks = [];

this.outputResult(response);
});
break;
Expand All @@ -190,12 +194,14 @@ export class HttpBlockComponent implements OnInit, OnChanges {
this.errorMessage = error.message;
this.errorData = error;
// TODO: need to prevent errors for triggering subsequent blocks
return of([]);
return of({error, hasError: this.hasError, errorMessage: this.errorMessage});
})
)
.subscribe(response => {
.subscribe((response: Record<string, any>) => {
this.isLoading = false;
this.hasError = false;
if(!response.hasError) this.errorBlocks = [];

this.outputResult(response);
const notify = get(this.config, 'notify', true);
if (notify) {
Expand Down Expand Up @@ -237,12 +243,14 @@ export class HttpBlockComponent implements OnInit, OnChanges {
this.errorMessage = error.message;
this.errorData = error;
// TODO: need to prevent errors for triggering subsequent blocks
return of([]);
return of({error, hasError: this.hasError, errorMessage: this.errorMessage});
})
)
.subscribe(response => {
.subscribe((response: Record<string, any>) => {
this.isLoading = false;
this.hasError = false;
if(!response.hasError) this.errorBlocks = [];

this.outputResult(response);
const notify = get(this.config, 'notify', true);
if (notify) {
Expand Down Expand Up @@ -382,6 +390,7 @@ export class HttpBlockComponent implements OnInit, OnChanges {
const pathname = get(endpoint, 'pathname', '/');
const query = get(endpoint, 'query', []);
const reduceQuery = _q => Object.keys(_q).map(key => `${key}=${_q[key]}`, []).join('&');

return `${protocol}//${host}${pathname}?${reduceQuery(query)}`;
}
}

1 comment on commit e3fae90

@vercel
Copy link

@vercel vercel bot commented on e3fae90 Nov 6, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

kendraio-app – ./

kendraio-app-kendraio.vercel.app
kendraio-app-git-develop-kendraio.vercel.app

Please sign in to comment.