diff --git a/package-lock.json b/package-lock.json index de43e6a..c11df36 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "advanced_wdc_step_by_step", - "version": "1.4.0", + "version": "1.5.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 9d61767..479daf0 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "Javier Valderrama", "name": "advanced_wdc_step_by_step", "description": "Spotify Web Data Connector for Tableau", - "version": "1.4.0", + "version": "1.5.0", "dependencies": { "jquery": "~3.1.0", "q": "~1.5.1" diff --git a/src/modules/Requestor.js b/src/modules/Requestor.js index dc2e406..f05c022 100644 --- a/src/modules/Requestor.js +++ b/src/modules/Requestor.js @@ -1,6 +1,10 @@ import ADVANCED_SCHEMA from '../schemas/advancedSchemas'; +import ErrorHelper from './ErrorHelper'; import SpotifyWebApi from 'spotify-web-api-node'; +import Q from 'q'; +import _ from 'lodash'; +import TERMS from './termsDictionary'; export const DEFAULT_TIME_RANGE = 'short_term'; export const DEFAULT_OFFSET = 0; @@ -29,6 +33,83 @@ class Requestor { this.apiLib.setAccessToken(authentication.getAccessToken()); } + /** + * + * @param {Object} $0 + * @param {String} $0.name + * @param {String} $0.message + * @param {Number} $0.statusCode + * @returns {Object} + */ + statusCodeInterceptor ({ name, message, statusCode } = {}) { + + let handledErrors = { + '400': { + action: null, + customMessage: TERMS.STATUS_CODES['400'] + }, + '401': { + /** + * we could implement a REAUTH action with this error + */ + action: 'REAUTH', + customMessage: TERMS.STATUS_CODES['401'] + }, + '403': { + action: null, + customMessage: TERMS.STATUS_CODES['403'] + }, + '404': { + action: null, + customMessage: TERMS.STATUS_CODES['404'] + }, + '429': { + /** + * we could implement a RETRY action with this error + * @see https://developer.spotify.com/documentation/web-api/#rate-limiting + */ + action: 'RETRY', + customMessage: TERMS.STATUS_CODES['429'] + }, + '500': { + action: null, + customMessage: TERMS.STATUS_CODES['500'] + }, + '502': { + action: null, + customMessage: TERMS.STATUS_CODES['502'] + }, + '503': { + action: null, + customMessage: TERMS.STATUS_CODES['503'] + } + }; + + let defaultError = { + customMessage: `${name}: ${message} (${statusCode})`, + action: null + }; + + return _.get(handledErrors, statusCode, defaultError); + } + + /** + * + * @param {Object} reason + * @returns {Object} Promise/A+ a Rejected Promise + */ + responseErrorCapturing (reason = {}) { + + let capturedError = this.statusCodeInterceptor(reason); + + if (capturedError.action) { + ErrorHelper.createError('Requestor.responseErrorCapturing ->', `We could take an action for this error ${reason.statusCode}`).log(); + } + + return Q.reject(capturedError); + + } + /** * * @param {Object} $0 @@ -40,9 +121,9 @@ class Requestor { * * @returns {Object} Promise/A+ */ - getTopArtists ({ timeRange = DEFAULT_TIME_RANGE, offset = DEFAULT_OFFSET, limit = DEFAULT_LIMIT } = {}) { // eslint-disable-line no-unused-vars + getTopArtists ({ timeRange = DEFAULT_TIME_RANGE, offset = DEFAULT_OFFSET, limit = DEFAULT_LIMIT } = {}) { - return this.apiLib.getMyTopArtists({ time_range: timeRange, limit, offset }); + return this.apiLib.getMyTopArtists({ time_range: timeRange, limit, offset }).catch(this.responseErrorCapturing.bind(this)); } @@ -57,9 +138,9 @@ class Requestor { * * @returns {Object} Promise/A+ */ - getTopTracks ({ timeRange = DEFAULT_TIME_RANGE, offset = DEFAULT_OFFSET, limit = DEFAULT_LIMIT } = {}) { // eslint-disable-line no-unused-vars + getTopTracks ({ timeRange = DEFAULT_TIME_RANGE, offset = DEFAULT_OFFSET, limit = DEFAULT_LIMIT } = {}) { - return this.apiLib.getMyTopTracks({ time_range: timeRange, limit, offset }); + return this.apiLib.getMyTopTracks({ time_range: timeRange, limit, offset }).catch(this.responseErrorCapturing.bind(this)); } @@ -74,9 +155,9 @@ class Requestor { * * @returns {Object} Promise/A+ */ - getAlbums ({ market, offset = DEFAULT_OFFSET, limit = DEFAULT_LIMIT } = {}) { // eslint-disable-line no-unused-vars + getAlbums ({ market, offset = DEFAULT_OFFSET, limit = DEFAULT_LIMIT } = {}) { - return this.apiLib.getMySavedAlbums({ market, limit, offset }); + return this.apiLib.getMySavedAlbums({ market, limit, offset }).catch(this.responseErrorCapturing.bind(this)); } @@ -91,9 +172,9 @@ class Requestor { * * @returns {Object} Promise/A+ */ - getTracks ({ market, offset = DEFAULT_OFFSET, limit = DEFAULT_LIMIT } = {}) { // eslint-disable-line no-unused-vars + getTracks ({ market, offset = DEFAULT_OFFSET, limit = DEFAULT_LIMIT } = {}) { - return this.apiLib.getMySavedTracks({ market, limit, offset }); + return this.apiLib.getMySavedTracks({ market, limit, offset }).catch(this.responseErrorCapturing.bind(this)); } @@ -111,7 +192,7 @@ class Requestor { */ getTracksFeatures ({ ids = [] } = {}) { - return this.apiLib.getAudioFeaturesForTracks(ids); + return this.apiLib.getAudioFeaturesForTracks(ids).catch(this.responseErrorCapturing.bind(this)); } @@ -129,7 +210,7 @@ class Requestor { */ getArtists ({ ids = [] } = {}) { - return this.apiLib.getArtists(ids); + return this.apiLib.getArtists(ids).catch(this.responseErrorCapturing.bind(this)); } diff --git a/src/modules/SpotifyConnector.js b/src/modules/SpotifyConnector.js index 8af13c8..7de9895 100644 --- a/src/modules/SpotifyConnector.js +++ b/src/modules/SpotifyConnector.js @@ -6,6 +6,7 @@ import UI from './UI'; import TERMS from './termsDictionary'; import Requestor from './Requestor'; import Schema from './Schema'; +import ErrorHelper from './ErrorHelper'; import TopArtists from './dataviews/TopArtists'; import TopTracks from './dataviews/TopTracks'; @@ -215,7 +216,7 @@ class SpotifyConnector extends Connector { */ // error for developers to be logged - TableauShim.log(`Connector.schema -> ${reason} `); + ErrorHelper.createError('Connector.schema ->', JSON.stringify(reason)).log(); // error to the user TableauShim.abortWithError(TERMS.ERROR.DEFAULT_ERROR); @@ -310,10 +311,10 @@ class SpotifyConnector extends Connector { */ // error for developers to be logged - TableauShim.log(`Connector.data -> ${reason} `); + ErrorHelper.createError('Connector.data ->', JSON.stringify(reason)).log(); // error to the user - TableauShim.abortWithError(TERMS.ERROR.DEFAULT_ERROR); + TableauShim.abortWithError(reason.customMessage || TERMS.ERROR.DEFAULT_ERROR); }); } @@ -373,7 +374,7 @@ class SpotifyConnector extends Connector { */ // error for developers to be logged - TableauShim.log(`Connector.getDataViewInstance -> ${tableId} not found on Data View Classes`); + ErrorHelper.createError('Connector.getDataViewInstance ->', `${tableId} not found on Data View Classes`).log(); // error to the user throw new Error(TERMS.ERROR.DEFAULT_ERROR); diff --git a/src/modules/termsDictionary.js b/src/modules/termsDictionary.js index 03c0575..f28d56c 100644 --- a/src/modules/termsDictionary.js +++ b/src/modules/termsDictionary.js @@ -11,6 +11,19 @@ const TERMS_DICTIONARY = { 'SHORT_TERM': 'Last 4 weeks aprox. of data.', 'MEDIUM_TERM': 'Last 6 months aprox. of data.', 'LONG_TERM': 'Several years of data.' + }, + /** + * @see https://developer.spotify.com/documentation/web-api/#response-status-codes + */ + 'STATUS_CODES': { + '400': 'Bad Request - The request could not be understood by the server due to malformed syntax. The message body will contain more information; see Response Schema https://developer.spotify.com/documentation/web-api/#response-schema.', + '401': 'Unauthorized - The request requires user authentication or, if the request included authorization credentials, authorization has been refused for those credentials.', + '403': 'Forbidden - The server understood the request, but is refusing to fulfill it.', + '404': 'Not Found - The requested resource could not be found. This error can be due to a temporary or permanent condition.', + '429': 'Too Many Requests - Rate limiting has been applied. see https://developer.spotify.com/documentation/web-api/#rate-limiting', + '500': 'Internal Server Error. You should never receive this error because our clever coders catch them all … but if you are unlucky enough to get one, please report it to us through a comment at the bottom of this page.', + '502': 'Bad Gateway - The server was acting as a gateway or proxy and received an invalid response from the upstream server.', + '503': 'Service Unavailable - The server is currently unable to handle the request due to a temporary condition which will be alleviated after some delay. You can choose to resend the request again.' } };