API Docs for: 0.25.0
Show:

File: addon/components/discover-page/component.js

  1. import Ember from 'ember';
  2. import layout from './template';
  3.  
  4. import config from 'ember-get-config';
  5. import { task } from 'ember-concurrency';
  6.  
  7. import Analytics from '../../mixins/analytics';
  8. import hostAppName from '../../mixins/host-app-name';
  9.  
  10. /**
  11. * @module ember-osf
  12. * @submodule components
  13. */
  14.  
  15. /**
  16. * Discover-page component. Builds a search interface utilizing SHARE.
  17. * See retraction-watch, registries, and preprints discover pages for working examples.
  18. *
  19. * Majority adapted from Ember-SHARE https://github.com/CenterForOpenScience/ember-share, with additions from PREPRINTS
  20. * and REGISTRIES discover pages. Original Ember-SHARE facets and PREPRINTS/REGISTRIES facets behave differently at this time.
  21. * You can build a discover-page that uses Ember-SHARE type facets -OR- PREPRINTS/REGISTRIES type facets. Would not recommend
  22. * mixing until code is combined.
  23. *
  24. * How to Use:
  25. * Pass in custom text like searchPlaceholder. The facets property will enable you to customize the filters
  26. * on the left-hand side of the discover page. Sort options are the sort dropdown options. Each query parameter must be passed in individually,
  27. * so they are reflected in the URL. Logo and custom colors must be placed in the consuming application's stylesheet. Individual components
  28. * can additionally be overridden in your application.
  29. *
  30. * Sample usage:
  31. * ```handlebars
  32. *{{discover-page
  33. * consumingService=consumingService
  34. * searchPlaceholder=searchPlaceholder
  35. * detailRoute=detailRoute
  36. * discoverHeader=discoverHeader
  37. * themeProvider=themeProvider
  38. *
  39. * sortOptions=sortOptions
  40. * filterReplace=filterReplace
  41. * whiteListedProviders=whiteListedProviders
  42. * fetchedProviders=externalProviders
  43. *
  44. * facets=facets
  45. * results=results
  46. * numberOfResults=numberOfResults
  47. * aggregations=aggregations
  48. * queryParamsState=queryParamsState
  49. *
  50. * showActiveFilters=showActiveFilters
  51. * loading=fetchData.isRunning
  52. *
  53. * clearFilters=(action 'clearFilters')
  54. * search=(action 'search')
  55. *
  56. * size=size
  57. * page=page
  58. * q=q
  59. * sort=sort
  60. * }}
  61. * {{!-- plus any query params (e.g. provider=provider) --}}
  62. * ```
  63. * @class discover-page
  64. */
  65.  
  66. const MAX_SOURCES = 500;
  67.  
  68. export default Ember.Component.extend(Analytics, hostAppName, {
  69. layout,
  70. currentUser: Ember.inject.service('current-user'),
  71. theme: Ember.inject.service(),
  72. i18n: Ember.inject.service(),
  73.  
  74. classNames: ['discover-page'],
  75.  
  76. // ************************************************************
  77. // PROPERTIES
  78. // ************************************************************
  79.  
  80. /**
  81. * Size query parameter. If "size" is one of your query params, it must be passed to the component so it can be updated.
  82. * @property {Integer} size
  83. * @default 10
  84. */
  85. size: 10,
  86.  
  87. /**
  88. * Sort query parameter. If "sort" is one of your query params, it must be passed to the component so it can be updated.
  89. * @property {String} sort
  90. * @default ''
  91. */
  92. sort: '',
  93.  
  94. /**
  95. * Page query parameter. If "page" is one of your query params, it must be passed to the component so it can be updated.
  96. * @property {Integer} page
  97. * @default 1
  98. */
  99. page: 1,
  100.  
  101. /**
  102. * q query parameter. If "q" is one of your query params, it must be passed to the component so it can be updated.
  103. * @property {String} q
  104. * @default ''
  105. */
  106. q: '',
  107.  
  108. /**
  109. * Name of detail route for consuming application, like "content" or "detail". Override if search result title should link to detail route.
  110. * @property {String} detailRoute
  111. */
  112. detailRoute: null,
  113.  
  114. /**
  115. * Text header for top of discover page.
  116. * @property {String} discoverHeader
  117. */
  118. discoverHeader: null,
  119.  
  120.  
  121. /**
  122. * For PREPRINTS ONLY. Pass in the providers fetched in preprints app so they can be used in the provider carousel
  123. * @property {Object} fetchedProviders
  124. */
  125. fetchedProviders: null,
  126.  
  127. /**
  128. * For PREPRINTS and REGISTRIES. A mapping of filter names for front-end display. Ex. {OSF: 'OSF Preprints'}.
  129. * @property {Object} filterReplace
  130. */
  131. filterReplace: {},
  132.  
  133. /**
  134. * For PREPRINTS and REGISTRIES. Displays activeFilters box above search facets.
  135. * @property {boolean} showActiveFilters
  136. */
  137. showActiveFilters: false,
  138.  
  139. showLuceneHelp: false,
  140.  
  141. numberOfResults: 0,
  142. numberOfSources: 0,
  143.  
  144. results: Ember.ArrayProxy.create({ content: [] }), // Results from SHARE query
  145.  
  146. /**
  147. * Sort dropdown options - Array of dictionaries. Each dictionary should have display and sortBy keys.
  148. * @property {Array} sortOptions
  149. * @default [{
  150. display: 'Relevance',
  151. sortBy: ''
  152. }]
  153. */
  154. sortOptions: [{
  155. display: 'Relevance',
  156. sortBy: ''
  157. }, {
  158. display: 'Date Updated (Desc)',
  159. sortBy: '-date_updated'
  160. }, {
  161. display: 'Date Updated (Asc)',
  162. sortBy: 'date_updated'
  163. }, {
  164. display: 'Ingest Date (Asc)',
  165. sortBy: 'date_created'
  166. }, {
  167. display: 'Ingest Date (Desc)',
  168. sortBy: '-date_created'
  169. }],
  170.  
  171. /**
  172. * themeProvider
  173. * @property {Object} Preprint provider loaded from theme service. Should be passed from consuming service so it is loaded before SHARE is queried.
  174. * @default ''
  175. */
  176. themeProvider: null,
  177.  
  178.  
  179. // ************************************************************
  180. // COMPUTED PROPERTIES
  181. // ************************************************************
  182.  
  183. domainRedirectProviders: Ember.computed(function() {
  184. let providerDomains = [];
  185. let providers = this.get('fetchedProviders');
  186. for (let providerVal of providers) {
  187. if (providerVal.get('domain') && providerVal.get('domainRedirectEnabled') === true) {
  188. providerDomains.push(providerVal.get('domain'));
  189. }
  190. }
  191. return providerDomains;
  192. }),
  193.  
  194. clampedPages: Ember.computed('totalPages', 'size', function() {
  195. // requesting over 10000 will error due to elastic limitations
  196. // https://www.elastic.co/guide/en/elasticsearch/guide/current/pagination.html
  197. let maxPages = Math.ceil(10000 / this.get('size'));
  198. let totalPages = this.get('totalPages');
  199.  
  200. return totalPages < maxPages ? totalPages : maxPages;
  201. }),
  202.  
  203. totalPages: Ember.computed('numberOfResults', 'size', function() {
  204. // Total pages of search results
  205. return Math.ceil(this.get('numberOfResults') / this.get('size'));
  206. }),
  207.  
  208. actions: {
  209. clearFilters() {
  210. this.get('metrics').trackEvent({
  211. category: 'button',
  212. action: 'click',
  213. label: 'Discover - Clear Filters',
  214. });
  215.  
  216. this.get('clearFilters')();
  217. },
  218.  
  219. search() {
  220. this.get('metrics').trackEvent({
  221. category: 'button',
  222. action: 'click',
  223. label: 'Discover - Search',
  224. extra: this.get('q'),
  225. });
  226.  
  227. this.get('search')();
  228. },
  229.  
  230. selectSortOption(option) {
  231. // Runs when sort option changed in dropdown
  232. this.get('metrics').trackEvent({
  233. category: 'dropdown',
  234. action: 'select',
  235. label: `Sort by: ${option || 'relevance'}`
  236. });
  237.  
  238. this.set('sort', option);
  239. },
  240.  
  241. selectPage(pageNumber) {
  242. // When paginating, sets page and scrolls to top of results.
  243. this.set('page', pageNumber);
  244. if (scroll) {
  245. this.scrollToResults();
  246. }
  247. },
  248.  
  249. toggleShowLuceneHelp() {
  250. this.toggleProperty('showLuceneHelp');
  251. },
  252.  
  253. updateFilters(filterType, item) {
  254. item = typeof item === 'object' ? item.text : item;
  255. const currentState = this.get(`queryParamsState.${filterType}.value`).slice(0);
  256. const hasItem = currentState.includes(item);
  257.  
  258. if (hasItem) {
  259. this.set(filterType, currentState.filter(x => !item.includes(x)));
  260. } else {
  261. currentState.pushObject(item)
  262. this.set(filterType, currentState);
  263. }
  264.  
  265. this.get('metrics').trackEvent({
  266. category: 'filter',
  267. action: hasItem ? 'remove' : 'add',
  268. label: `Discover - ${filterType} ${item}`
  269. });
  270. },
  271.  
  272. updateParams(key, value) {
  273. this.set(key, value);
  274. },
  275. },
  276.  
  277. // ************************************************************
  278. // Discover-page METHODS and HOOKS
  279. // ************************************************************
  280.  
  281. scrollToResults() {
  282. // Scrolls to top of search results
  283. Ember.$('html, body').scrollTop(this.$('.results-top').position().top);
  284. },
  285.  
  286. isTypeFacet(obj) {
  287. return obj.key === 'type';
  288. },
  289.  
  290. getTypes: task(function* () {
  291. const response = yield Ember.$.ajax({
  292. url: `${config.OSF.shareApiUrl}/schema/creativework/hierarchy/`,
  293. crossDomain: true,
  294. type: 'GET',
  295. contentType: 'application/vnd.api+json',
  296. });
  297. if (response.data) {
  298. const types = response.data.CreativeWork ? response.data.CreativeWork.children : {};
  299. this.get('facets').find(this.isTypeFacet).data = this.transformTypes(types);
  300. }
  301. }),
  302.  
  303. transformTypes(types) {
  304. const tmpTypes = types;
  305. if (typeof (tmpTypes) !== 'object') {
  306. return tmpTypes;
  307. }
  308.  
  309. for (const key of Object.keys(tmpTypes)) {
  310. const lowKey = key.replace(/([A-Z])/g, ' $1').trim().toLowerCase();
  311. tmpTypes[lowKey] = this.transformTypes(tmpTypes[key]);
  312. if (key !== lowKey) {
  313. delete tmpTypes[key];
  314. }
  315. }
  316.  
  317. return tmpTypes;
  318. },
  319.  
  320. getCounts: task(function* () {
  321. const searchUrl = `${config.OSF.shareSearchUrl}?preference=${this.get('currentUser.sessionKey')}`;
  322. const queryBody = JSON.stringify({
  323. size: 0,
  324. aggregations: {
  325. sources: {
  326. cardinality: {
  327. field: 'sources',
  328. precision_threshold: MAX_SOURCES,
  329. },
  330. },
  331. },
  332. });
  333. const response = yield Ember.$.ajax({
  334. url: searchUrl,
  335. crossDomain: true,
  336. type: 'POST',
  337. contentType: 'application/json',
  338. data: queryBody,
  339. });
  340. this.setProperties({
  341. numberOfEvents: response.hits.total,
  342. numberOfSources: response.aggregations.sources.value,
  343. });
  344. }),
  345.  
  346. init() {
  347. // Runs on initial render.
  348. this._super(...arguments);
  349. if (this.get('facets').find(this.isTypeFacet)) {
  350. this.get('getTypes').perform();
  351. }
  352. this.get('getCounts').perform();
  353. },
  354. });
  355.