From ca7110ea114fae27d86b07b16154a9b9573474bf Mon Sep 17 00:00:00 2001 From: Ignacio Bona Piedrabuena Date: Tue, 11 Sep 2018 11:11:36 -0700 Subject: [PATCH] Refractor Search to TS (#1373) --- .../app/controllers/{search.js => search.ts} | 147 +++++++++--------- wherehows-web/app/routes/search.js | 61 -------- wherehows-web/app/routes/search.ts | 37 +++++ wherehows-web/app/utils/api/search.ts | 23 +++ wherehows-web/app/utils/build-url.ts | 67 +++++--- .../{build-url-test.js => build-url-test.ts} | 18 ++- 6 files changed, 199 insertions(+), 154 deletions(-) rename wherehows-web/app/controllers/{search.js => search.ts} (66%) delete mode 100644 wherehows-web/app/routes/search.js create mode 100644 wherehows-web/app/routes/search.ts create mode 100644 wherehows-web/app/utils/api/search.ts rename wherehows-web/tests/unit/utils/{build-url-test.js => build-url-test.ts} (59%) diff --git a/wherehows-web/app/controllers/search.js b/wherehows-web/app/controllers/search.ts similarity index 66% rename from wherehows-web/app/controllers/search.js rename to wherehows-web/app/controllers/search.ts index 756bf2e6bb..5da53b7c40 100644 --- a/wherehows-web/app/controllers/search.js +++ b/wherehows-web/app/controllers/search.ts @@ -1,75 +1,9 @@ import Controller from '@ember/controller'; -import { computed, set, get } from '@ember/object'; -import { capitalize } from '@ember/string'; -import { action } from '@ember-decorators/object'; +import { set } from '@ember/object'; +import { action, computed } from '@ember-decorators/object'; // gradual refactor into es class, hence extends EmberObject instance -export default class Search extends Controller.extend({ - isMetrics: computed('model.category', function() { - var model = this.get('model'); - if (model && model.category) { - if (model.category.toLocaleLowerCase() === 'metrics') { - return true; - } - } - return false; - }), - - previousPage: computed('model.page', function() { - var model = this.get('model'); - if (model && model.page) { - var currentPage = model.page; - if (currentPage <= 1) { - return currentPage; - } else { - return currentPage - 1; - } - } else { - return 1; - } - }), - nextPage: computed('model.page', function() { - var model = this.get('model'); - if (model && model.page) { - var currentPage = model.page; - var totalPages = model.totalPages; - if (currentPage >= totalPages) { - return totalPages; - } else { - return currentPage + 1; - } - } else { - return 1; - } - }), - first: computed('model.page', function() { - var model = this.get('model'); - if (model && model.page) { - var currentPage = model.page; - if (currentPage <= 1) { - return true; - } else { - return false; - } - } else { - return false; - } - }), - last: computed('model.page', function() { - var model = this.get('model'); - if (model && model.page) { - var currentPage = model.page; - var totalPages = model.totalPages; - if (currentPage >= totalPages) { - return true; - } else { - return false; - } - } else { - return false; - } - }) -}) { +export default class SearchController extends Controller { queryParams = ['keyword', 'category', 'source', 'page']; /** @@ -107,7 +41,80 @@ export default class Search extends Controller.extend({ * @param source */ @action - sourceDidChange(source) { + sourceDidChange(source: string) { set(this, 'source', source); } + + @computed('model.category') + get isMetrics(): boolean { + const model = this.get('model'); + if (model && model.category) { + if (model.category.toLocaleLowerCase() === 'metrics') { + return true; + } + } + return false; + } + + @computed('model.page') + get previousPage(): number { + const model = this.get('model'); + if (model && model.page) { + var currentPage = model.page; + if (currentPage <= 1) { + return currentPage; + } else { + return currentPage - 1; + } + } else { + return 1; + } + } + + @computed('model.page') + get nextPage(): number { + const model = this.get('model'); + if (model && model.page) { + const currentPage = model.page; + const totalPages = model.totalPages; + if (currentPage >= totalPages) { + return totalPages; + } else { + return currentPage + 1; + } + } else { + return 1; + } + } + + @computed('model.page') + get first(): boolean { + const model = this.get('model'); + if (model && model.page) { + const currentPage = model.page; + if (currentPage <= 1) { + return true; + } else { + return false; + } + } else { + return false; + } + } + + @computed('model.page') + get last(): boolean { + const model = this.get('model'); + if (model && model.page) { + const currentPage = model.page; + const totalPages = model.totalPages; + if (currentPage >= totalPages) { + return true; + } else { + return false; + } + } else { + return false; + } + } } diff --git a/wherehows-web/app/routes/search.js b/wherehows-web/app/routes/search.js deleted file mode 100644 index ccf2e26027..0000000000 --- a/wherehows-web/app/routes/search.js +++ /dev/null @@ -1,61 +0,0 @@ -import Route from '@ember/routing/route'; -import { isBlank } from '@ember/utils'; -import $ from 'jquery'; -import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin'; -import buildUrl from 'wherehows-web/utils/build-url'; -import createSearchEntries from 'wherehows-web/utils/datasets/create-search-entries'; - -const queryParams = ['keyword', 'category', 'page', 'source']; -// TODO: DSS-6581 Create URL retrieval module -const urlRoot = '/api/v1/search'; - -export default Route.extend(AuthenticatedRouteMixin, { - // Set `refreshModel` for each queryParam to true - // so each url state change results in a full transition - queryParams: queryParams.reduce((queryParams, param) => { - queryParams[param] = { refreshModel: true }; - return queryParams; - }, {}), - - /** - * Applies the returned results object as the route model and sets - * keyword property on the route controller - * @param {Object} controller search route controller - * @param {Object} model search results - */ - setupController(controller, model) { - const { keywords } = model; - - controller.setProperties({ - model, - keyword: keywords - }); - }, - - /** - * - * @param params - */ - model(params = {}) { - const searchUrl = queryParams.reduce((url, queryParam) => { - const queryValue = params[queryParam]; - if (!isBlank(queryValue)) { - return buildUrl(url, queryParam, queryValue); - } - - return url; - }, urlRoot); - - return Promise.resolve($.getJSON(searchUrl)).then(({ status, result }) => { - if (status === 'ok') { - const { keywords, data } = result; - - createSearchEntries(data, keywords); - - return result; - } - - return Promise.reject(new Error(`Request for ${searchUrl} failed with: ${status}`)); - }); - } -}); diff --git a/wherehows-web/app/routes/search.ts b/wherehows-web/app/routes/search.ts new file mode 100644 index 0000000000..fc601d240d --- /dev/null +++ b/wherehows-web/app/routes/search.ts @@ -0,0 +1,37 @@ +import Route from '@ember/routing/route'; +import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin'; +import createSearchEntries from 'wherehows-web/utils/datasets/create-search-entries'; +import { refreshModelQueryParams } from 'wherehows-web/utils/helpers/routes'; +import SearchController from 'wherehows-web/controllers/search'; +import { readSearch, ISearchApiParams } from 'wherehows-web/utils/api/search'; + +export default class SearchRoute extends Route.extend(AuthenticatedRouteMixin) { + // Set `refreshModel` for each queryParam to true + // so each url state change results in a full transition + queryParams = refreshModelQueryParams(['keyword', 'category', 'page', 'source']); + + /** + * Applies the returned results object as the route model and sets + * keyword property on the route controller + * @param {Object} controller search route controller + * @param {Object} model search results + */ + setupController(controller: SearchController, model: any) { + const { keywords } = model; + + controller.setProperties({ + model, + keyword: keywords + }); + } + + /** + * Makes an API call and process search entries + */ + async model(apiParams: ISearchApiParams) { + const { result } = await readSearch(apiParams); + const { keywords, data } = result; + createSearchEntries(data, keywords); + return result; + } +} diff --git a/wherehows-web/app/utils/api/search.ts b/wherehows-web/app/utils/api/search.ts new file mode 100644 index 0000000000..622f392c33 --- /dev/null +++ b/wherehows-web/app/utils/api/search.ts @@ -0,0 +1,23 @@ +import { getApiRoot, ApiStatus } from 'wherehows-web/utils/api/shared'; +import buildUrl from 'wherehows-web/utils/build-url'; +import { getJSON } from 'wherehows-web/utils/api/fetcher'; + +export interface ISearchApiParams { + keyword: string; + category: string; + source: string; + page: number; + [key: string]: any; +} + +export interface ISearchResponse { + status: ApiStatus; + result: { + keywords: string; + data: Array; + }; +} + +export const searchUrl = (params: ISearchApiParams): string => buildUrl(`${getApiRoot()}/search`, params); + +export const readSearch = (params: ISearchApiParams) => getJSON({ url: searchUrl(params) }); diff --git a/wherehows-web/app/utils/build-url.ts b/wherehows-web/app/utils/build-url.ts index ac4a3807aa..ea2c64cbbe 100644 --- a/wherehows-web/app/utils/build-url.ts +++ b/wherehows-web/app/utils/build-url.ts @@ -1,4 +1,5 @@ import { encode, decode } from 'wherehows-web/utils/encode-decode-uri-component-with-space'; +import { isBlank } from '@ember/utils'; /** * Construct a url by appending a query pair (?key=value | &key=value) to a base url and @@ -8,34 +9,56 @@ import { encode, decode } from 'wherehows-web/utils/encode-decode-uri-component- * @param {String} queryValue * @returns {string} */ -export default (baseUrl: string, queryParam: string, queryValue: string): string => { +function buildUrl(): string; +function buildUrl(baseUrl: string, mapParams: Record): string; +function buildUrl(baseUrl: string, queryKey: string, queryValue: string): string; +function buildUrl(baseUrl?: string, queryParamOrMap?: string | Record, queryValue?: string): string { if (!baseUrl) { return ''; } - if (!queryParam) { + if (!queryParamOrMap) { return baseUrl; } - // If the query string already contains the initial question mark append - // kv-pair with ampersand - const separator = String(baseUrl).includes('?') ? '&' : '?'; - // Malformed URL will cause decodeURIComponent to throw - // handle and encode queryValue in such instance - try { - // Check if queryValue is already encoded, - // otherwise encode queryValue before composing url - // e.g. if user directly enters query in location bar - if (decode(queryValue) === queryValue) { - queryValue = encode(queryValue); - } - } catch (err) { - if (err instanceof URIError) { - queryValue = encode(queryValue); - } - - throw err; + let paramMap: { [x: string]: string }; + if (typeof queryParamOrMap === 'string') { + paramMap = { + [queryParamOrMap]: queryValue || '' + }; + } else { + paramMap = queryParamOrMap; } - return `${baseUrl}${separator}${queryParam}=${queryValue}`; -}; + return Object.keys(paramMap).reduce((url, paramKey) => { + // If the query string already contains the initial question mark append + // kv-pair with ampersand + const separator = String(url).includes('?') ? '&' : '?'; + let paramValue = paramMap[paramKey]; + + if (isBlank(paramValue)) { + return url; + } + + // Malformed URL will cause decodeURIComponent to throw + // handle and encode queryValue in such instance + try { + // Check if queryValue is already encoded, + // otherwise encode queryValue before composing url + // e.g. if user directly enters query in location bar + if (decode(paramValue) === queryValue) { + paramValue = encode(paramValue); + } + } catch (err) { + if (err instanceof URIError) { + paramValue = encode(paramValue); + } + + throw err; + } + + return `${url}${separator}${paramKey}=${paramValue}`; + }, baseUrl); +} + +export default buildUrl; diff --git a/wherehows-web/tests/unit/utils/build-url-test.js b/wherehows-web/tests/unit/utils/build-url-test.ts similarity index 59% rename from wherehows-web/tests/unit/utils/build-url-test.js rename to wherehows-web/tests/unit/utils/build-url-test.ts index a7ce609d74..7bbc49d5c4 100644 --- a/wherehows-web/tests/unit/utils/build-url-test.js +++ b/wherehows-web/tests/unit/utils/build-url-test.ts @@ -8,7 +8,7 @@ module('Unit | Utility | build url', function() { let result = buildUrl(); assert.equal(result, '', 'returns an empty string when no arguments are passed'); - result = buildUrl(baseUrl, ''); + result = buildUrl(baseUrl, '', ''); assert.equal(result, baseUrl, 'returns the baseUrl when no query parameter is supplied'); result = buildUrl(baseUrl, 'search', 'text'); @@ -16,5 +16,21 @@ module('Unit | Utility | build url', function() { result = buildUrl(result, 'query', 'text2'); assert.equal(result, `${baseUrl}?search=text&query=text2`); + + result = buildUrl(baseUrl, { + keyName1: 'keyValue1', + keyName2: 2, + keyName3: true + }); + assert.equal(result, `${baseUrl}?keyName1=keyValue1&keyName2=2&keyName3=true`); + + result = buildUrl(baseUrl, { + keyName1: 0, + keyName3: undefined, + keyName4: '', + keyName5: null, + keyName2: false + }); + assert.equal(result, `${baseUrl}?keyName1=0&keyName2=false`); }); });