From 0b5c1be462000b94ae070735da593914068d87cc Mon Sep 17 00:00:00 2001 From: futa-ikeda Date: Tue, 26 Nov 2024 16:02:56 -0500 Subject: [PATCH 1/7] WIP --- .../-components/object-list/component-test.ts | 5 +++++ .../-components/object-list/component.ts | 11 ++++++++++ .../-components/object-list/styles.scss | 9 +++++++++ .../-components/object-list/template.hbs | 20 +++++++++++-------- 4 files changed, 37 insertions(+), 8 deletions(-) diff --git a/app/institutions/dashboard/-components/object-list/component-test.ts b/app/institutions/dashboard/-components/object-list/component-test.ts index 4c1ae753cb..cac3e09344 100644 --- a/app/institutions/dashboard/-components/object-list/component-test.ts +++ b/app/institutions/dashboard/-components/object-list/component-test.ts @@ -81,6 +81,11 @@ module('Integration | institutions | dashboard | -components | object-list', hoo // The table data is not blatantly incorrect assert.dom('[data-test-object-table-body-row]').exists({ count: 10 }, 'There are 10 rows'); + + // Download buttons are present + await click('[data-test-download-dropdown]'); + assert.dom('[data-test-download-csv-link]').exists('CSV download link exists'); + assert.dom('[data-test-download-tsv-link]').exists('TSV download link exists'); }); test('the table supports filtering', async function(assert) { diff --git a/app/institutions/dashboard/-components/object-list/component.ts b/app/institutions/dashboard/-components/object-list/component.ts index 0333bfb59a..54c076e086 100644 --- a/app/institutions/dashboard/-components/object-list/component.ts +++ b/app/institutions/dashboard/-components/object-list/component.ts @@ -73,6 +73,17 @@ export default class InstitutionalObjectList extends Component - - + From e45967c714b9558fcfad9ed2a6a83bf46d4bdb22 Mon Sep 17 00:00:00 2001 From: futa-ikeda Date: Tue, 26 Nov 2024 16:18:55 -0500 Subject: [PATCH 2/7] WIP --- app/institutions/dashboard/-components/object-list/template.hbs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/institutions/dashboard/-components/object-list/template.hbs b/app/institutions/dashboard/-components/object-list/template.hbs index 34e242a208..7ba89500ee 100644 --- a/app/institutions/dashboard/-components/object-list/template.hbs +++ b/app/institutions/dashboard/-components/object-list/template.hbs @@ -98,6 +98,7 @@ as |list|> @@ -106,6 +107,7 @@ as |list|> From 7360a105636d3fb08ee8ce5c7d9d9132055cf7ac Mon Sep 17 00:00:00 2001 From: Futa Ikeda Date: Wed, 27 Nov 2024 11:02:07 -0500 Subject: [PATCH 3/7] More WIP --- .../-components/object-list/component.ts | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/app/institutions/dashboard/-components/object-list/component.ts b/app/institutions/dashboard/-components/object-list/component.ts index 54c076e086..a087e1fdfc 100644 --- a/app/institutions/dashboard/-components/object-list/component.ts +++ b/app/institutions/dashboard/-components/object-list/component.ts @@ -74,14 +74,23 @@ export default class InstitutionalObjectList extends Component Date: Wed, 27 Nov 2024 14:46:30 -0500 Subject: [PATCH 4/7] Rest of initial work --- .../-components/object-list/component-test.ts | 1 + .../-components/object-list/component.ts | 21 ++++++++++++++++--- .../-components/object-list/styles.scss | 5 ++++- .../-components/object-list/template.hbs | 12 +++++++++++ 4 files changed, 35 insertions(+), 4 deletions(-) diff --git a/app/institutions/dashboard/-components/object-list/component-test.ts b/app/institutions/dashboard/-components/object-list/component-test.ts index cac3e09344..8c1fc4aacd 100644 --- a/app/institutions/dashboard/-components/object-list/component-test.ts +++ b/app/institutions/dashboard/-components/object-list/component-test.ts @@ -86,6 +86,7 @@ module('Integration | institutions | dashboard | -components | object-list', hoo await click('[data-test-download-dropdown]'); assert.dom('[data-test-download-csv-link]').exists('CSV download link exists'); assert.dom('[data-test-download-tsv-link]').exists('TSV download link exists'); + assert.dom('[data-test-download-json-link]').exists('JSON download link exists'); }); test('the table supports filtering', async function(assert) { diff --git a/app/institutions/dashboard/-components/object-list/component.ts b/app/institutions/dashboard/-components/object-list/component.ts index a087e1fdfc..983742d88d 100644 --- a/app/institutions/dashboard/-components/object-list/component.ts +++ b/app/institutions/dashboard/-components/object-list/component.ts @@ -2,6 +2,7 @@ import { action } from '@ember/object'; import Component from '@glimmer/component'; import { tracked } from '@glimmer/tracking'; +import IndexCardSearchAdapter from 'ember-osf-web/adapters/index-card-search'; import InstitutionModel from 'ember-osf-web/models/institution'; import { SuggestedFilterOperators } from 'ember-osf-web/models/related-property-path'; import SearchResultModel from 'ember-osf-web/models/search-result'; @@ -74,10 +75,24 @@ export default class InstitutionalObjectList extends Component { + // cardSearchFilter is an object, so we need to iterate over its keys + if (key === 'cardSearchFilter') { + Object.entries(value).forEach(([filterKey, filterValue]) => { + const cardSearchFilterKey = `cardSearchFilter[${filterKey}]`; + searchUrl.searchParams.set(cardSearchFilterKey, filterValue.toString()); + }); + } else { + searchUrl.searchParams.set(key, value.toString()); + } + }); + searchUrl.searchParams.set('page[size]', '10000'); return searchUrl.toString(); } diff --git a/app/institutions/dashboard/-components/object-list/styles.scss b/app/institutions/dashboard/-components/object-list/styles.scss index 18588d765d..0677767f4f 100644 --- a/app/institutions/dashboard/-components/object-list/styles.scss +++ b/app/institutions/dashboard/-components/object-list/styles.scss @@ -134,6 +134,9 @@ flex-direction: column; align-items: flex-start; border: 1px solid $color-border-gray; - padding: 8px; width: max-content; } + +.download-link { + padding: 4px 8px; +} diff --git a/app/institutions/dashboard/-components/object-list/template.hbs b/app/institutions/dashboard/-components/object-list/template.hbs index 7ba89500ee..31e3eb604f 100644 --- a/app/institutions/dashboard/-components/object-list/template.hbs +++ b/app/institutions/dashboard/-components/object-list/template.hbs @@ -98,6 +98,7 @@ as |list|> {{t 'institutions.dashboard.format_labels.tsv'}} + + {{t 'institutions.dashboard.format_labels.json'}} + From 637f9c6cfa247f62069e3d30551ea013f59872e6 Mon Sep 17 00:00:00 2001 From: futa-ikeda Date: Wed, 27 Nov 2024 15:07:00 -0500 Subject: [PATCH 5/7] Account for boolean filters --- .../dashboard/-components/object-list/component.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/institutions/dashboard/-components/object-list/component.ts b/app/institutions/dashboard/-components/object-list/component.ts index 983742d88d..95f01b1d7f 100644 --- a/app/institutions/dashboard/-components/object-list/component.ts +++ b/app/institutions/dashboard/-components/object-list/component.ts @@ -86,6 +86,16 @@ export default class InstitutionalObjectList extends Component { const cardSearchFilterKey = `cardSearchFilter[${filterKey}]`; + // check if filterValue is an object, for boolean filters + if (typeof filterValue === 'object' && !Array.isArray(filterValue)) { + Object.entries(filterValue).forEach(([nestedFilterKey, nestedFilterValue]) => { + searchUrl.searchParams.append( + `${cardSearchFilterKey}[${nestedFilterKey}]`, + (nestedFilterValue as boolean).toString(), + ); + }); + return; + } searchUrl.searchParams.set(cardSearchFilterKey, filterValue.toString()); }); } else { From c6cba7bb7ae19e891156817734847b89b80d6fd7 Mon Sep 17 00:00:00 2001 From: futa-ikeda Date: Mon, 2 Dec 2024 10:13:31 -0500 Subject: [PATCH 6/7] Use self link for downloads --- .../-components/object-list/component.ts | 53 ++++++------------- .../-components/object-list/template.hbs | 6 +-- app/models/index-card-search.ts | 2 + app/serializers/index-card-search.ts | 14 +++++ .../index-card-searcher/component.ts | 3 ++ .../index-card-searcher/template.hbs | 1 + mirage/views/search.ts | 6 ++- 7 files changed, 44 insertions(+), 41 deletions(-) diff --git a/app/institutions/dashboard/-components/object-list/component.ts b/app/institutions/dashboard/-components/object-list/component.ts index 95f01b1d7f..9fc3740ab5 100644 --- a/app/institutions/dashboard/-components/object-list/component.ts +++ b/app/institutions/dashboard/-components/object-list/component.ts @@ -2,7 +2,7 @@ import { action } from '@ember/object'; import Component from '@glimmer/component'; import { tracked } from '@glimmer/tracking'; -import IndexCardSearchAdapter from 'ember-osf-web/adapters/index-card-search'; +import IndexCardSearchModel from 'ember-osf-web/models/index-card-search'; import InstitutionModel from 'ember-osf-web/models/institution'; import { SuggestedFilterOperators } from 'ember-osf-web/models/related-property-path'; import SearchResultModel from 'ember-osf-web/models/search-result'; @@ -74,48 +74,27 @@ export default class InstitutionalObjectList extends Component { - // cardSearchFilter is an object, so we need to iterate over its keys - if (key === 'cardSearchFilter') { - Object.entries(value).forEach(([filterKey, filterValue]) => { - const cardSearchFilterKey = `cardSearchFilter[${filterKey}]`; - // check if filterValue is an object, for boolean filters - if (typeof filterValue === 'object' && !Array.isArray(filterValue)) { - Object.entries(filterValue).forEach(([nestedFilterKey, nestedFilterValue]) => { - searchUrl.searchParams.append( - `${cardSearchFilterKey}[${nestedFilterKey}]`, - (nestedFilterValue as boolean).toString(), - ); - }); - return; - } - searchUrl.searchParams.set(cardSearchFilterKey, filterValue.toString()); - }); - } else { - searchUrl.searchParams.set(key, value.toString()); - } - }); - searchUrl.searchParams.set('page[size]', '10000'); - return searchUrl.toString(); + downloadUrl(cardSearch: IndexCardSearchModel, format: string, extension: string) { + if (!cardSearch.links.self) { + return ''; + } + const cardSearchUrl = new URL((cardSearch.links.self as string)); + cardSearchUrl.searchParams.set('page[size]', '10000'); + cardSearchUrl.searchParams.set('acceptMediatype', format); + cardSearchUrl.searchParams.set('withFileName', `${this.args.objectType}-search-results.${extension}`); + return cardSearchUrl.toString(); } - get downloadCsvUrl() { - return this.downloadUrl('text/csv'); + downloadCsvUrl(cardSearch: IndexCardSearchModel) { + return this.downloadUrl(cardSearch, 'text/csv', 'csv'); } - get downloadTsvUrl() { - return this.downloadUrl('text/tab-separated-values'); + downloadTsvUrl(cardSearch: IndexCardSearchModel) { + return this.downloadUrl(cardSearch, 'text/tab-separated-values', 'tsv'); } - get downloadJsonUrl() { - return this.downloadUrl('application/json'); + downloadJsonUrl(cardSearch: IndexCardSearchModel) { + return this.downloadUrl(cardSearch, 'application/json', 'json'); } @action diff --git a/app/institutions/dashboard/-components/object-list/template.hbs b/app/institutions/dashboard/-components/object-list/template.hbs index 31e3eb604f..77d1281f38 100644 --- a/app/institutions/dashboard/-components/object-list/template.hbs +++ b/app/institutions/dashboard/-components/object-list/template.hbs @@ -100,7 +100,7 @@ as |list|> data-analytics-name='Download CSV' local-class='download-link' {{on 'click' dd.close}} - @href={{this.downloadCsvUrl}} + @href={{call (fn (action this.downloadCsvUrl list.latestIndexCardSearch))}} @target='_blank' > {{t 'institutions.dashboard.format_labels.csv'}} @@ -110,7 +110,7 @@ as |list|> data-analytics-name='Download TSV' local-class='download-link' {{on 'click' dd.close}} - @href={{this.downloadTsvUrl}} + @href={{call (fn (action this.downloadTsvUrl list.latestIndexCardSearch))}} @target='_blank' > {{t 'institutions.dashboard.format_labels.tsv'}} @@ -120,7 +120,7 @@ as |list|> data-analytics-name='Download JSON' local-class='download-link' {{on 'click' dd.close}} - @href={{this.downloadJsonUrl}} + @href={{call (fn (action this.downloadJsonUrl list.latestIndexCardSearch))}} @target='_blank' > {{t 'institutions.dashboard.format_labels.json'}} diff --git a/app/models/index-card-search.ts b/app/models/index-card-search.ts index 103cf169dd..fc5250db51 100644 --- a/app/models/index-card-search.ts +++ b/app/models/index-card-search.ts @@ -1,4 +1,5 @@ import Model, { AsyncHasMany, attr, hasMany } from '@ember-data/model'; +import {Links} from 'jsonapi-typescript'; import RelatedPropertyPathModel from './related-property-path'; import SearchResultModel from './search-result'; @@ -15,6 +16,7 @@ export default class IndexCardSearchModel extends Model { @attr('string') cardSearchText!: string; @attr('array') cardSearchFilters!: SearchFilter[]; @attr('string') totalResultCount!: number | typeof ShareMoreThanTenThousand; + @attr('object') links!: Links; @hasMany('search-result', { inverse: null }) searchResultPage!: AsyncHasMany & SearchResultModel[]; diff --git a/app/serializers/index-card-search.ts b/app/serializers/index-card-search.ts index 5b86f0c560..49a0e7514a 100644 --- a/app/serializers/index-card-search.ts +++ b/app/serializers/index-card-search.ts @@ -1,6 +1,20 @@ +import * as JSONAPI from 'jsonapi-typescript'; + import ShareSerializer from './share-serializer'; export default class IndexCardSearchSerializer extends ShareSerializer { + // Taken from osf-serializer.ts + _mergeLinks(resourceHash: JSONAPI.ResourceObject): Partial { + const links = { ...(resourceHash.links || {}) }; + return { + attributes: { ...resourceHash.attributes, links: (links as any) }, + }; + } + + extractAttributes(modelClass: any, resourceHash: JSONAPI.ResourceObject) { + const attributeHash = this._mergeLinks(resourceHash); + return super.extractAttributes(modelClass, attributeHash); + } } declare module 'ember-data/types/registries/serializer' { diff --git a/lib/osf-components/addon/components/index-card-searcher/component.ts b/lib/osf-components/addon/components/index-card-searcher/component.ts index c0b36fc667..c2e69d870b 100644 --- a/lib/osf-components/addon/components/index-card-searcher/component.ts +++ b/lib/osf-components/addon/components/index-card-searcher/component.ts @@ -9,6 +9,7 @@ import Toast from 'ember-toastr/services/toast'; import SearchResultModel from 'ember-osf-web/models/search-result'; import { taskFor } from 'ember-concurrency-ts'; import RelatedPropertyPathModel, { SuggestedFilterOperators } from 'ember-osf-web/models/related-property-path'; +import IndexCardSearchModel from 'ember-osf-web/models/index-card-search'; interface IndexCardSearcherArgs { queryOptions: Record; @@ -27,6 +28,7 @@ export default class IndexCardSearcher extends Component @tracked relatedProperties?: RelatedPropertyPathModel[] = []; @tracked booleanFilters?: RelatedPropertyPathModel[] = []; + @tracked latestIndexCardSearch?: IndexCardSearchModel; @tracked firstPageCursor?: string; @tracked nextPageCursor?: string; @tracked prevPageCursor?: string; @@ -64,6 +66,7 @@ export default class IndexCardSearcher extends Component (property: RelatedPropertyPathModel) => property.suggestedFilterOperator !== SuggestedFilterOperators.IsPresent, // AnyOf or AtDate ); + this.latestIndexCardSearch = searchResult; this.firstPageCursor = searchResult.firstPageCursor; this.nextPageCursor = searchResult.nextPageCursor; this.prevPageCursor = searchResult.prevPageCursor; diff --git a/lib/osf-components/addon/components/index-card-searcher/template.hbs b/lib/osf-components/addon/components/index-card-searcher/template.hbs index 76d1db68bd..2b04d5b979 100644 --- a/lib/osf-components/addon/components/index-card-searcher/template.hbs +++ b/lib/osf-components/addon/components/index-card-searcher/template.hbs @@ -7,6 +7,7 @@ debouceSearchObjectsTask=this.debouceSearchObjectsTask searchObjectsTask=this.searchObjectsTask + latestIndexCardSearch=this.latestIndexCardSearch firstPageCursor=this.firstPageCursor nextPageCursor=this.nextPageCursor prevPageCursor=this.prevPageCursor diff --git a/mirage/views/search.ts b/mirage/views/search.ts index c0723c3b47..d2db1a48d8 100644 --- a/mirage/views/search.ts +++ b/mirage/views/search.ts @@ -277,10 +277,11 @@ export function cardSearch(_: Schema, request: Request) { requestedResourceTypes = Object.keys(resourceMetadataByType) as OsfmapResourceTypes[]; } + const indexCardSearchId = faker.random.uuid(); const indexCardSearch = { data: { type: 'index-card-search', - id: faker.random.uuid(), + id:indexCardSearchId, attributes: { cardSearchText: 'hello', cardSearchFilter: [ @@ -325,6 +326,9 @@ export function cardSearch(_: Schema, request: Request) { }, searchResultPage: {}, }, + links: { + self: `https://share.osf.io/api/v2/index-card-search/${indexCardSearchId}`, + }, }, included: [ // Related properties From e52e7a01bc3112dd5918df3fc833fcc735522a37 Mon Sep 17 00:00:00 2001 From: futa-ikeda Date: Mon, 2 Dec 2024 11:51:40 -0500 Subject: [PATCH 7/7] Remove file extension from withFileName param --- .../dashboard/-components/object-list/component.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/institutions/dashboard/-components/object-list/component.ts b/app/institutions/dashboard/-components/object-list/component.ts index 9fc3740ab5..ee3f4dce8b 100644 --- a/app/institutions/dashboard/-components/object-list/component.ts +++ b/app/institutions/dashboard/-components/object-list/component.ts @@ -74,27 +74,27 @@ export default class InstitutionalObjectList extends Component