From 98c850d0d61655712bd27b930745726d29f64b5f Mon Sep 17 00:00:00 2001 From: futa-ikeda <51409893+futa-ikeda@users.noreply.github.com> Date: Tue, 10 Oct 2023 12:52:14 -0400 Subject: [PATCH] Feature/search phase 2 (#2024) * Re add institution and preprints discover page; remove registries discover page. (#1985) * Remove ember-parachute; Remove page QP (#1986) * Reword object filter dropdown label (#1979) * Fix branded page query-params (#1992) * [ENG-4660] activeFilters query param (#1990) * activeFilters query param * add activeFilters to other pages * Update app/institutions/discover/controller.ts * [ENG-4656] Special-case is-present type filters (#1994) * Special-case is-present type filters * Remove unused style * Update mirage endpoint * Show provider hero-banner on search page (#1995) * Show provider hero-banner on search page * Allow html entities from institution description * Update institution discover default query options (#1993) * Update institution discover default query options * Update link; Combine default qps * Add accessService qp if appropriate * No labels, just keys * Remove accessService qp to SHARE (#1997) * Use less restrictive filter to fetch non-boolean filters (#1999) * Return sort dropdown for registries and preprints discover (#2000) * Return sort dropdown for registries and preprints discover * Address styling issue for tablet * Address branded registries style issues (#2002) * [ENG-4648] Opt out of SHARE indexing (#1998) * Add allowIndexing attr to user model * Add opt-out panel to user-settings * Add test * Update wording * Use constructor; Match mirage default with BE default * Reroute users to general discover page for preprints/discover and preprints/osf/discover (#2004) * change query param name from cardSearchText to q (#2005) * Institutions header (#2006) * Add id back for search box * Update institution page header * Reroute for registry not found (#2007) * Increase color contrast of link in help tour (#2010) * Merge develop into feature/search phase 2 (#2009) * [ENG-4012] Un-silence deprecation manager-capabilities.modifiers-3-13 (#1889) ## Purpose Un-silence the deprecation `manager-capabilities.modifiers-3-13` and fix the resulting problems ## Summary of Changes 1. Unsilence deprecation 2. Upgrade ember-auto-import to v2 3. Upgrade ember-modifiers 4. Upgrade ember-moment and imports 5. Spread moment around all the engines 6. Refactor modifiers to work with new api for ember-modifiers 7. Remove unused modifier 8. Fix usage of side-effect from ember computed to give ember power select a placeholder in institutional dashboard ## Side Effects If I didn't fix the modifiers right, something may have broken there silently. * Fix the missing asset prefix for ember-auto-import (#1906) ## Purpose Staging2 is broken because we can't find `chunk*.js` and it looks like that's because we need to add our `assetsPrefix` to ember-auto-import to make it work. This fix seems to work locally, but it works locally without the fix, so we'll see what actually happens on staging2. ## Summary of Changes 1. Fix the missing asset prefix * [ENG-4591] Barium/upgrade ember 3.28 (#1899) ## Purpose Upgrade ember to 3.28 ## Summary of Changes 1. Match ember dependencies to `ember new` blueprint 2. Fix all the problems 3. Modify getApiErrorMessage to re-throw in the case of no API error message (some problems fixed h/t @futa-ikeda) * [ENG-4488] Remove ember-cli-clipboard (#1910) ## Purpose Ember-cli-clipboard was doing deprecated things, and upgrading it was a nightmare. The actual functionality is trivial to implement, though, so we've made our own version of its copy button. ## Summary of Changes 1. Add copy button 2. Add tests * Don't re-throw error in getApiErrorMessage (#1911) ## Purpose See if a weird bug gets fixed on staging2 by removing a slightly risky re-throw of error messages. ## Summary of Changes 1. Don't re-throw error message * [No ticket] Throw an error when registries overview page errors (#1912) ## Purpose Help to be able to debug situations where the registries overview page goes to the not-found page inappropriately. ## Summary of Changes 1. Send the error to sentry after transitioning to page not found. * Remove most non-dirty relationships (#1918) ## Purpose When upgrading to ember 3.28, we lost the ability to distinguish between dirty relationships and clean ones when PATCHing a record, so we would send all the relationships. This was a problem because many of the clean relationships in the serializer had null information, which broke the API, because it doesn't like PATCHed relationships with null information. This fixes that by removing any relationship that is just null info in the serializer. We will still sometimes PATCH relationships we don't need to, but the info should be the same and thus not cause problems for the API. ## Summary of Changes 1. Scan for null relationships in osf-serializer and remove them from the request payload. * Fix infinite scrolling loader (#1919) ## Purpose The infinite scroller stopped working because the component that manages if it's in the viewport was not compatible with the new modifiers until it was updated. ## Summary of Changes 1. Update ember-in-viewport * [No ticket] Remove clean relationships from PATCH request (#1925) ## Purpose Properly remove clean relationships from a PATCH request. ## Summary of Changes 1. Get the graph 2. Scan the graph for changes in each relationship in the serializer 3. Remove the relationship from the serializer if there are no changes 4. Types * Make relationships polymorphic (#1928) ## Purpose Make registrations load on basket/barium release ## Summary of Changes 1. Make relationships polymorphic * [ENG-4025] Deprecation: has-block-and-has-block-params (#1909) ## Purpose - Address deprecation: [has-block-and-has-block-params](https://deprecations.emberjs.com/v3.x#toc_has-block-and-has-block-params) ## Summary of Changes - Remove `ember-cli-password-strength` and use `zxcvbn` directly - Remove `ember-promise-helpers` as it doesn't seem to be used - Update liquid-fire - Update ember-radio-buttons - Remove deprecation silencer Co-authored-by: Brian J. Geiger * Remove test that accidentally merged in (#1977) * [ENG-4013] Barium/deprecation this property fallback (#1970) ## Purpose Eliminate this-property-fallback deprecation errors. Might want to review this commit-by-commit. The removal of ember-get-config added a bunch of noise. ## Summary of Changes 1. Upgrade ember-sortable 2. Fix tests that failed this deprecation 3. Upgrade: - ember-cli-showdown - tag-input - Ember-collapsible-panel - Ember-content-placeholders - Ember-radio-button 4. Remove ember-get-config 5. Move environment.d.ts 6. Fix tests * remove 'argument-less-helper-paren-less-invocation' (#1978) * Unsilence deprecation route-render-template (#1984) ## Purpose Fix deprecation route-render-template ## Summary of Changes 1. Unsilence the deprecation * Fix `ember.built-in-components.import` deprecation by upgrading ember-flatpickr (#1982) * fix deprecation by upgrading ember-flatpickr * use null as date param for ember-flatpickr * Fix `deprecated-run-loop-and-computed-dot-access` by upgrading ember-responsive and ember-intl; fix `ember.built-in-components.legacy-arguments` (#1983) * Fix `ember.built in components.legacy-attribute-arguments`; Fix `ember.built-in-components.reopen` (#1987) * Try to fix selenium test (#1988) ## Purpose Try to fix a selenium test that was broken when we re-wrote the clipboard button. ## Summary of Changes 1. Add data selector * Update ember-composable-helpers (#1980) ## Purpose There are two major parts to fixing the try-invoke deprecation. The first is updating ember-composable-helpers, and the second is removing ember parachute. This handles the first. The second will be part of Search Phase 2 once the branded registries discovery page, which is the page that uses ember-parachute, is removed. ## Summary of Changes 1. Update ember-composable-helpers * Fix broken meetings searches (#1991) ## Purpose A recent deprecation fix caused meetings searches to break. This should fix that. ## Summary of Changes 1. Add `target` string to `on` actions * Fix some build-time deprecations (#1989) ## Purpose Fix some build-time deprecations. Everything I know about except for https://deprecations.emberjs.com/v3.x/#toc_ember-global ## Summary of Changes 1. Upgrade ember power select 2. Upgrade Node 3. Remove node-sass 4. Remove ember-component-attributes 5. Fix usage of htmlSafe 6. Upgrade ember-cli-string-helpers 7. Remove unused osf-logo component 8. Update and remove various other dependencies ## Side effects Builds may be a bit faster. I'm getting between 45 and 60ish second builds, although this may only be true for M1 and M2 chips. Could be slower on Intel. Also, we'll get a bunch of extraneous notices in the buld process like `unexpectedly found " Co-authored-by: Brian J. Geiger Co-authored-by: futa-ikeda <51409893+futa-ikeda@users.noreply.github.com> * fix query param (#2011) * Search a11y (#2012) * Add/update aria attributes * Use darker color for object filter links * Institutions updates (#2013) * Remove All as option for instition discover page * Use new iris field for institution identifiers * Change institution default filters by object type * Add back accessService to SHARE request (#2014) * Add back accessService to SHARE request * Move accessService logic to share-adapter * Search: Mobile fixes (#2016) * Add global-classes for use in admin app to modify provider descriptions * Use institution banner image for all size screens * Prevent covering help text on mobile * Darken institution description links (#2015) * Result counts (#2017) * Show results counts for mobile preprint/registry discover page * Grammar for result counts * Search bug fixes (#2019) * Update config import path * Prevent defaultQPs from overwriting left panel filters * Institution discover page-title * Update search styles (#2021) * well it's a fix, sort of (#2023) * Add specificity to supposedly important rules (#2025) --------- Co-authored-by: Yuhuai Liu Co-authored-by: Brian J. Geiger Co-authored-by: Brian J. Geiger --- app/adapters/share-adapter.ts | 12 +- app/institutions/discover/controller.ts | 31 +- app/institutions/discover/template.hbs | 3 + app/models/institution.ts | 4 + app/models/preprint-provider.ts | 2 +- app/models/related-property-path.ts | 22 +- app/models/user.ts | 1 + app/preprints/discover/controller.ts | 14 +- app/preprints/discover/route.ts | 5 + app/preprints/discover/template.hbs | 3 +- app/register/controller.ts | 33 +- app/register/route.ts | 6 + app/router.ts | 9 +- app/search/controller.ts | 6 +- app/search/template.hbs | 1 + .../account/-components/opt-out/component.ts | 50 +++ .../account/-components/opt-out/template.hbs | 41 ++ app/settings/account/template.hbs | 1 + .../components/branded-navbar/component.ts | 2 +- .../components/branded-navbar/styles.scss | 6 +- .../search-page/boolean-filters/component.ts | 38 ++ .../search-page/boolean-filters/styles.scss | 50 +++ .../search-page/boolean-filters/template.hbs | 54 +++ .../addon/components/search-page/component.ts | 61 ++- .../search-page/filter-facet/component.ts | 33 +- .../search-page/filter-facet/template.hbs | 4 +- .../addon/components/search-page/styles.scss | 169 +++++++- .../addon/components/search-page/template.hbs | 82 ++-- .../search-result-card/template.hbs | 188 +++++---- .../addon/helpers/get-localized-property.ts | 40 ++ .../search-page/boolean-filters/component.js | 1 + .../search-page/boolean-filters/template.js | 1 + .../app/helpers/get-localized-property.js | 1 + .../addon/application/controller.ts | 14 +- .../addon/branded/discover/controller.ts | 41 +- .../addon/branded/discover/route.ts | 12 +- .../addon/branded/discover/styles.scss | 11 - .../addon/branded/discover/template.hbs | 1 + lib/registries/addon/branded/index/route.ts | 2 +- lib/registries/addon/branded/route.ts | 10 +- .../styles.scss | 55 --- .../template.hbs | 33 -- .../registries-discover-search/component.ts | 13 - .../registries-discover-search/styles.scss | 12 - .../registries-discover-search/template.hbs | 15 - .../x-result/component.ts | 11 - .../x-result/styles.scss | 10 - .../x-result/template.hbs | 1 - .../x-results/component.ts | 24 -- .../x-results/styles.scss | 0 .../x-results/template.hbs | 5 - .../x-sidebar/component.ts | 62 --- .../x-sidebar/styles.scss | 39 -- .../x-sidebar/template.hbs | 30 -- .../components/registries-navbar/template.hbs | 2 +- .../registries-provider-facet/component.ts | 60 --- .../registries-provider-facet/styles.scss | 35 -- .../registries-provider-facet/template.hbs | 31 -- .../component.ts | 117 ------ .../styles.scss | 54 --- .../template.hbs | 25 -- .../component.ts | 11 - .../styles.scss | 14 - .../template.hbs | 4 - .../registries-search-result/component.ts | 46 -- .../registries-search-result/styles.scss | 107 ----- .../registries-search-result/template.hbs | 84 ---- .../registries-subjects-facet/component.ts | 184 -------- .../registries-subjects-facet/template.hbs | 5 - lib/registries/addon/discover/controller.ts | 397 ------------------ lib/registries/addon/discover/route.ts | 19 - lib/registries/addon/discover/styles.scss | 97 ----- lib/registries/addon/discover/template.hbs | 107 ----- lib/registries/addon/routes.ts | 1 - mirage/factories/institution.ts | 2 + mirage/factories/user.ts | 1 + mirage/scenarios/registrations.ts | 1 + mirage/serializers/preprint-provider.ts | 2 +- mirage/views/search.ts | 6 + package.json | 1 - .../acceptance/institutions/discover-test.ts | 2 +- .../acceptance/branded/discover-test.ts | 23 +- .../account/-components/opt-out-test.ts | 44 ++ translations/en-us.yml | 17 +- yarn.lock | 7 - 85 files changed, 843 insertions(+), 2038 deletions(-) create mode 100644 app/settings/account/-components/opt-out/component.ts create mode 100644 app/settings/account/-components/opt-out/template.hbs create mode 100644 lib/osf-components/addon/components/search-page/boolean-filters/component.ts create mode 100644 lib/osf-components/addon/components/search-page/boolean-filters/styles.scss create mode 100644 lib/osf-components/addon/components/search-page/boolean-filters/template.hbs create mode 100644 lib/osf-components/addon/helpers/get-localized-property.ts create mode 100644 lib/osf-components/app/components/search-page/boolean-filters/component.js create mode 100644 lib/osf-components/app/components/search-page/boolean-filters/template.js create mode 100644 lib/osf-components/app/helpers/get-localized-property.js delete mode 100644 lib/registries/addon/branded/discover/styles.scss delete mode 100644 lib/registries/addon/components/registries-discover-results-header/styles.scss delete mode 100644 lib/registries/addon/components/registries-discover-results-header/template.hbs delete mode 100644 lib/registries/addon/components/registries-discover-search/component.ts delete mode 100644 lib/registries/addon/components/registries-discover-search/styles.scss delete mode 100644 lib/registries/addon/components/registries-discover-search/template.hbs delete mode 100644 lib/registries/addon/components/registries-discover-search/x-result/component.ts delete mode 100644 lib/registries/addon/components/registries-discover-search/x-result/styles.scss delete mode 100644 lib/registries/addon/components/registries-discover-search/x-result/template.hbs delete mode 100644 lib/registries/addon/components/registries-discover-search/x-results/component.ts delete mode 100644 lib/registries/addon/components/registries-discover-search/x-results/styles.scss delete mode 100644 lib/registries/addon/components/registries-discover-search/x-results/template.hbs delete mode 100644 lib/registries/addon/components/registries-discover-search/x-sidebar/component.ts delete mode 100644 lib/registries/addon/components/registries-discover-search/x-sidebar/styles.scss delete mode 100644 lib/registries/addon/components/registries-discover-search/x-sidebar/template.hbs delete mode 100644 lib/registries/addon/components/registries-provider-facet/component.ts delete mode 100644 lib/registries/addon/components/registries-provider-facet/styles.scss delete mode 100644 lib/registries/addon/components/registries-provider-facet/template.hbs delete mode 100644 lib/registries/addon/components/registries-registration-type-facet/component.ts delete mode 100644 lib/registries/addon/components/registries-registration-type-facet/styles.scss delete mode 100644 lib/registries/addon/components/registries-registration-type-facet/template.hbs delete mode 100644 lib/registries/addon/components/registries-search-facet-container/component.ts delete mode 100644 lib/registries/addon/components/registries-search-facet-container/styles.scss delete mode 100644 lib/registries/addon/components/registries-search-facet-container/template.hbs delete mode 100644 lib/registries/addon/components/registries-search-result/component.ts delete mode 100644 lib/registries/addon/components/registries-search-result/styles.scss delete mode 100644 lib/registries/addon/components/registries-search-result/template.hbs delete mode 100644 lib/registries/addon/components/registries-subjects-facet/component.ts delete mode 100644 lib/registries/addon/components/registries-subjects-facet/template.hbs delete mode 100644 lib/registries/addon/discover/controller.ts delete mode 100644 lib/registries/addon/discover/route.ts delete mode 100644 lib/registries/addon/discover/styles.scss delete mode 100644 lib/registries/addon/discover/template.hbs create mode 100644 tests/integration/routes/settings/account/-components/opt-out-test.ts diff --git a/app/adapters/share-adapter.ts b/app/adapters/share-adapter.ts index b8ce1c88dd6..5716e5b5b0a 100644 --- a/app/adapters/share-adapter.ts +++ b/app/adapters/share-adapter.ts @@ -1,7 +1,17 @@ import JSONAPIAdapter from '@ember-data/adapter/json-api'; -import config from 'ember-get-config'; +import config from 'ember-osf-web/config/environment'; + +const osfUrl = config.OSF.url; export default class ShareAdapter extends JSONAPIAdapter { host = config.OSF.shareBaseUrl.replace(/\/$/, ''); // Remove trailing slash to avoid // in URLs namespace = 'api/v3'; + + queryRecord(store: any, type: any, query: any) { + // check if we aren't serving locally, otherwise add accessService query param to card/value searches + if (['index-card-search', 'index-value-search'].includes(type.modelName) && !osfUrl.includes('localhost')) { + query.cardSearchFilter['accessService'] = osfUrl; + } + return super.queryRecord(store, type, query); + } } diff --git a/app/institutions/discover/controller.ts b/app/institutions/discover/controller.ts index db5eac7f340..7f52f572a19 100644 --- a/app/institutions/discover/controller.ts +++ b/app/institutions/discover/controller.ts @@ -3,29 +3,42 @@ import { inject as service } from '@ember/service'; import CurrentUser from 'ember-osf-web/services/current-user'; import { tracked } from '@glimmer/tracking'; import { action } from '@ember/object'; -import pathJoin from 'ember-osf-web/utils/path-join'; -import config from 'ember-get-config'; -import { OnSearchParams, ResourceTypeFilterValue } from 'osf-components/components/search-page/component'; +import { Filter, OnSearchParams, ResourceTypeFilterValue } from 'osf-components/components/search-page/component'; export default class InstitutionDiscoverController extends Controller { @service currentUser!: CurrentUser; - @tracked cardSearchText?: string = ''; + @tracked q?: string = ''; @tracked sort?: string = '-relevance'; - @tracked resourceType?: ResourceTypeFilterValue | null = null; + @tracked resourceType: ResourceTypeFilterValue = ResourceTypeFilterValue.Projects; + @tracked activeFilters?: Filter[] = []; - queryParams = ['cardSearchText', 'sort', 'resourceType']; + queryParams = ['q', 'sort', 'resourceType', 'activeFilters']; get defaultQueryOptions() { + const identifiers = this.model.iris.join(','); + let key = 'affiliation'; + const { resourceType } = this; + switch (resourceType) { + case ResourceTypeFilterValue.Preprints: + key = 'creator.affiliation'; + break; + case ResourceTypeFilterValue.Files: + key = 'isContainedby.affiliation'; + break; + default: + break; + } return { - publisher: pathJoin(config.OSF.url, 'institutions', this.model.id), + [key]: identifiers, }; } @action onSearch(queryOptions: OnSearchParams) { - this.cardSearchText = queryOptions.cardSearchText; + this.q = queryOptions.cardSearchText; this.sort = queryOptions.sort; - this.resourceType = queryOptions.resourceType; + this.resourceType = queryOptions.resourceType as ResourceTypeFilterValue; + this.activeFilters = queryOptions.activeFilters; } } diff --git a/app/institutions/discover/template.hbs b/app/institutions/discover/template.hbs index 7a30ee06cb5..35b22695a50 100644 --- a/app/institutions/discover/template.hbs +++ b/app/institutions/discover/template.hbs @@ -1,3 +1,5 @@ +{{page-title this.model.name}} + diff --git a/app/models/institution.ts b/app/models/institution.ts index 1afb7c4c904..9e2fa60ce7a 100644 --- a/app/models/institution.ts +++ b/app/models/institution.ts @@ -30,6 +30,10 @@ export default class InstitutionModel extends OsfModel { @attr('object') assets?: Assets; @attr('boolean', { defaultValue: false }) currentUserIsAdmin!: boolean; @attr('date') lastUpdated!: Date; + @attr('fixstring') rorIri!: string; + // identifier_domain in the admin app + @attr('fixstring') iri!: string; + @attr('fixstringarray') iris!: string[]; // TODO Might want to replace calls to `users` with `institutionUsers.user`? @hasMany('user', { inverse: 'institutions' }) diff --git a/app/models/preprint-provider.ts b/app/models/preprint-provider.ts index 1b4d7ff6285..bdf21c91f48 100644 --- a/app/models/preprint-provider.ts +++ b/app/models/preprint-provider.ts @@ -2,7 +2,7 @@ import { attr, hasMany, AsyncHasMany, belongsTo, AsyncBelongsTo } from '@ember-d import { computed } from '@ember/object'; import { alias } from '@ember/object/computed'; import { inject as service } from '@ember/service'; -import config from 'ember-get-config'; +import config from 'ember-osf-web/config/environment'; import Intl from 'ember-intl/services/intl'; import BrandModel from 'ember-osf-web/models/brand'; diff --git a/app/models/related-property-path.ts b/app/models/related-property-path.ts index 604c8e15e43..1c73cb83686 100644 --- a/app/models/related-property-path.ts +++ b/app/models/related-property-path.ts @@ -13,29 +13,21 @@ interface PropertyPath { shortFormLabel: LanguageText[]; } +export enum SuggestedFilterOperators { + AnyOf = 'any-of', + IsPresent = 'is-present', + AtDate = 'at-date' +} + export default class RelatedPropertyPathModel extends OsfModel { @attr('string') propertyPathKey!: string; @attr('number') cardSearchResultCount!: number; @attr('array') osfmapPropertyPath!: string[]; @attr('array') propertyPath!: PropertyPath[]; + @attr('string') suggestedFilterOperator!: SuggestedFilterOperators; getLocalizedString = new GetLocalizedPropertyHelper(getOwner(this)); - get shortFormLabel() { - const labelArray = []; - // propertyPath is likely an array of length 1, - // unless it is nested property(e.g. file's isContainedBy.funder, file's isContainedBy.license) - for (const property of this.propertyPath) { - const label = this.getLocalizedString.compute( - [property as unknown as Record, 'shortFormLabel'], - ); - if (label) { - labelArray.push(label); - } - } - return labelArray.join(','); - } - get displayLabel() { // propertyPath is likely an array of length 1, // unless it is nested property(e.g. file's isContainedBy.funder, file's isContainedBy.license) diff --git a/app/models/user.ts b/app/models/user.ts index 728c89fc62d..1c7e2f945f1 100644 --- a/app/models/user.ts +++ b/app/models/user.ts @@ -94,6 +94,7 @@ export default class UserModel extends OsfModel.extend(Validations) { @attr('object') social!: {}; @attr('array') employment!: Employment[]; @attr('array') education!: Education[]; + @attr('boolean', { allowNull: true }) allowIndexing?: boolean; @belongsTo('region', { async: false }) defaultRegion!: RegionModel; diff --git a/app/preprints/discover/controller.ts b/app/preprints/discover/controller.ts index dad5ea479a5..af7d5ec281a 100644 --- a/app/preprints/discover/controller.ts +++ b/app/preprints/discover/controller.ts @@ -4,20 +4,21 @@ import Controller from '@ember/controller'; import { action } from '@ember/object'; import { inject as service } from '@ember/service'; import { tracked } from '@glimmer/tracking'; -import config from 'ember-get-config'; +import config from 'ember-osf-web/config/environment'; import Theme from 'ember-osf-web/services/theme'; import pathJoin from 'ember-osf-web/utils/path-join'; -import { OnSearchParams } from 'osf-components/components/search-page/component'; +import { Filter, OnSearchParams } from 'osf-components/components/search-page/component'; export default class PreprintDiscoverController extends Controller { @service store!: Store; @service theme!: Theme; - @tracked cardSearchText?: string = ''; - @tracked sort?: string = '-relevance'; + @tracked q?: string = ''; + @tracked sort?: string = '-relevance'; + @tracked activeFilters?: Filter[] = []; - queryParams = ['cardSearchText', 'sort']; + queryParams = ['q', 'sort', 'activeFilters']; get defaultQueryOptions() { return { @@ -28,7 +29,8 @@ export default class PreprintDiscoverController extends Controller { @action onSearch(queryOptions: OnSearchParams) { - this.cardSearchText = queryOptions.cardSearchText; + this.q = queryOptions.cardSearchText; this.sort = queryOptions.sort; + this.activeFilters = queryOptions.activeFilters; } } diff --git a/app/preprints/discover/route.ts b/app/preprints/discover/route.ts index 529014833ca..09ff0225c5f 100644 --- a/app/preprints/discover/route.ts +++ b/app/preprints/discover/route.ts @@ -2,6 +2,7 @@ import Store from '@ember-data/store'; import Route from '@ember/routing/route'; import RouterService from '@ember/routing/router-service'; import { inject as service } from '@ember/service'; +import config from 'ember-osf-web/config/environment'; import Theme from 'ember-osf-web/services/theme'; @@ -21,6 +22,10 @@ export default class PreprintDiscoverRoute extends Route { async model(args: any) { try { + if (!args.provider_id || args.provider_id === config.defaultProvider) { + this.router.transitionTo('search', { queryParams: { resourceType: 'Preprint' } }); + return null; + } const provider = await this.store.findRecord('preprint-provider', args.provider_id); this.theme.providerType = 'preprint'; this.theme.id = args.provider_id; diff --git a/app/preprints/discover/template.hbs b/app/preprints/discover/template.hbs index 6a0bc4bc5cc..1f2d9544ebe 100644 --- a/app/preprints/discover/template.hbs +++ b/app/preprints/discover/template.hbs @@ -2,7 +2,7 @@
diff --git a/app/register/controller.ts b/app/register/controller.ts index 0197b449993..8a2329dc767 100644 --- a/app/register/controller.ts +++ b/app/register/controller.ts @@ -3,10 +3,10 @@ import Controller from '@ember/controller'; import { computed } from '@ember/object'; import { inject as service } from '@ember/service'; import { waitFor } from '@ember/test-waiters'; +import { tracked } from '@glimmer/tracking'; import { task } from 'ember-concurrency'; import { taskFor } from 'ember-concurrency-ts'; import config from 'ember-osf-web/config/environment'; -import QueryParams from 'ember-parachute'; import PreprintProvider from 'ember-osf-web/models/preprint-provider'; import Analytics from 'ember-osf-web/services/analytics'; @@ -14,21 +14,7 @@ import param from 'ember-osf-web/utils/param'; const { OSF: { casUrl, url: baseUrl } } = config; -interface RegisterQueryParams { - next: string; - campaign: string; -} - -export const registerQueryParams = new QueryParams({ - next: { - defaultValue: '', - }, - campaign: { - defaultValue: '', - }, -}); - -export default class Register extends Controller.extend(registerQueryParams.Mixin) { +export default class Register extends Controller.extend() { @service analytics!: Analytics; @service store!: Store; @@ -40,7 +26,11 @@ export default class Register extends Controller.extend(registerQueryParams.Mixi isOsfPreprints = false; isOsfRegistries = false; - @computed('next') + @tracked next?: string = ''; + @tracked campaign?: string = ''; + + queryParams = ['next', 'campaign']; + get orcidUrl() { return `${casUrl}/login?${param({ redirectOrcid: 'true', @@ -48,7 +38,6 @@ export default class Register extends Controller.extend(registerQueryParams.Mixi })}`; } - @computed('next') get institutionUrl() { return `${casUrl}/login?${param({ campaign: 'institution', @@ -77,10 +66,10 @@ export default class Register extends Controller.extend(registerQueryParams.Mixi } } - setup({ queryParams }: { queryParams: RegisterQueryParams }) { - if (queryParams.campaign) { - this.set('signUpCampaign', queryParams.campaign); - const matches = queryParams.campaign.match(/^(.*)-(.*)$/); + setup() { + if (this.campaign) { + this.set('signUpCampaign', this.campaign); + const matches = this.campaign.match(/^(.*)-(.*)$/); if (matches) { const [, provider, type] = matches; if (provider === 'osf') { diff --git a/app/register/route.ts b/app/register/route.ts index 0b86fa1a5e6..b77e07c5020 100644 --- a/app/register/route.ts +++ b/app/register/route.ts @@ -5,6 +5,7 @@ import Store from '@ember-data/store'; import Session from 'ember-simple-auth/services/session'; +import RegisterController from './controller'; export default class Register extends Route { @service session!: Session; @@ -21,4 +22,9 @@ export default class Register extends Route { model() { return this.store.createRecord('user-registration'); } + + setupController(controller: RegisterController, model: any, transition: Transition) { + super.setupController(controller, model, transition); + controller.setup(); + } } diff --git a/app/router.ts b/app/router.ts index 8b13c09a827..e69ef624be8 100644 --- a/app/router.ts +++ b/app/router.ts @@ -22,12 +22,13 @@ Router.map(function() { this.route('goodbye'); this.route('search'); this.route('institutions', function() { - // this.route('discover', { path: '/:institution_id' }); + this.route('discover', { path: '/:institution_id' }); this.route('dashboard', { path: '/:institution_id/dashboard' }); }); - // this.route('preprints', function() { - // this.route('discover', { path: '/:provider_id/discover' }); - // }); + this.route('preprints', function() { + this.route('discover'); + this.route('discover', { path: '/:provider_id/discover' }); + }); this.route('register'); this.route('settings', function() { this.route('profile', function() { diff --git a/app/search/controller.ts b/app/search/controller.ts index 9c0bb9a8aa5..e8ed982aa50 100644 --- a/app/search/controller.ts +++ b/app/search/controller.ts @@ -1,19 +1,21 @@ import Controller from '@ember/controller'; import { action } from '@ember/object'; import { tracked } from '@glimmer/tracking'; -import { OnSearchParams, ResourceTypeFilterValue } from 'osf-components/components/search-page/component'; +import { Filter, OnSearchParams, ResourceTypeFilterValue } from 'osf-components/components/search-page/component'; export default class SearchController extends Controller { @tracked q?: string = ''; @tracked sort?: string = '-relevance'; @tracked resourceType?: ResourceTypeFilterValue | null = null; + @tracked activeFilters?: Filter[] = []; - queryParams = ['q', 'sort', 'resourceType']; + queryParams = ['q', 'sort', 'resourceType', 'activeFilters']; @action onSearch(queryOptions: OnSearchParams) { this.q = queryOptions.cardSearchText; this.sort = queryOptions.sort; this.resourceType = queryOptions.resourceType; + this.activeFilters = queryOptions.activeFilters; } } diff --git a/app/search/template.hbs b/app/search/template.hbs index f6c7bab398a..ac29113295b 100644 --- a/app/search/template.hbs +++ b/app/search/template.hbs @@ -9,4 +9,5 @@ @sort={{this.sort}} @resourceType={{this.resourceType}} @page={{this.page}} + @activeFilters={{this.activeFilters}} /> diff --git a/app/settings/account/-components/opt-out/component.ts b/app/settings/account/-components/opt-out/component.ts new file mode 100644 index 00000000000..c24c243c574 --- /dev/null +++ b/app/settings/account/-components/opt-out/component.ts @@ -0,0 +1,50 @@ +import Component from '@glimmer/component'; +import { inject as service } from '@ember/service'; +import { tracked } from '@glimmer/tracking'; +import CurrentUserService from 'ember-osf-web/services/current-user'; +import { restartableTask } from 'ember-concurrency'; +import { waitFor } from '@ember/test-waiters'; +import config from 'ember-osf-web/config/environment'; +import IntlService from 'ember-intl/services/intl'; + +import captureException, { getApiErrorMessage } from 'ember-osf-web/utils/capture-exception'; + +const { support: { supportEmail } } = config; + +export default class OptOutComponent extends Component { + @service currentUser!: CurrentUserService; + @service intl!: IntlService; + @service toast!: Toastr; + + @tracked indexingPreference?: boolean; + + get allowIndexingIsFalse() { + // allowIndexing is null by default + return this.currentUser.user?.allowIndexing === false; + } + + constructor(owner: unknown, args: any) { + super(owner, args); + this.indexingPreference = this.currentUser.user?.allowIndexing; + } + + @restartableTask + @waitFor + async updateIndexingPreference() { + if (!this.currentUser.user) { + return; + } + try { + this.currentUser.user.allowIndexing = this.indexingPreference; + await this.currentUser.user.save(); + this.toast.success(this.intl.t('settings.account.opt-out.success')); + } catch (e) { + const errorMessage = this.intl.t( + 'settings.account.opt-out.error', + { supportEmail, htmlSafe: true }, + ); + captureException(e, { errorMessage: errorMessage.toString() }); + this.toast.error(getApiErrorMessage(e), errorMessage as string); + } + } +} diff --git a/app/settings/account/-components/opt-out/template.hbs b/app/settings/account/-components/opt-out/template.hbs new file mode 100644 index 00000000000..2cdb2d3e626 --- /dev/null +++ b/app/settings/account/-components/opt-out/template.hbs @@ -0,0 +1,41 @@ + + + +

+ {{t 'settings.account.opt-out.description' htmlSafe=true}} +

+ +
+ +
+ +
+
diff --git a/app/settings/account/template.hbs b/app/settings/account/template.hbs index 26e17808f1f..4853aedb702 100644 --- a/app/settings/account/template.hbs +++ b/app/settings/account/template.hbs @@ -3,6 +3,7 @@ + diff --git a/lib/app-components/addon/components/branded-navbar/component.ts b/lib/app-components/addon/components/branded-navbar/component.ts index 286b82d4d43..e323a64f9ae 100644 --- a/lib/app-components/addon/components/branded-navbar/component.ts +++ b/lib/app-components/addon/components/branded-navbar/component.ts @@ -3,7 +3,7 @@ import Component from '@ember/component'; import { action, computed } from '@ember/object'; import { alias } from '@ember/object/computed'; import { inject as service } from '@ember/service'; -import config from 'ember-get-config'; +import config from 'ember-osf-web/config/environment'; import Intl from 'ember-intl/services/intl'; import Media from 'ember-responsive'; import Session from 'ember-simple-auth/services/session'; diff --git a/lib/app-components/addon/components/branded-navbar/styles.scss b/lib/app-components/addon/components/branded-navbar/styles.scss index 53ae3242693..f67cc0a7433 100644 --- a/lib/app-components/addon/components/branded-navbar/styles.scss +++ b/lib/app-components/addon/components/branded-navbar/styles.scss @@ -119,14 +119,14 @@ } &.light-text { - a, + a:not(:global(.btn-top-signup)), :global(.secondary-nav-dropdown) { color: $color-text-white !important; } } &.dark-text { - a, + a:not(:global(.btn-top-signup)), :global(.secondary-nav-dropdown) { color: $color-text-black !important; } @@ -136,7 +136,7 @@ .white-background-branded-navbar.white-background-branded-navbar.white-background-branded-navbar { background-color: #fff; - a, + a:not(:global(.btn-top-signup)), :global(.secondary-nav-dropdown) { color: $color-text-black !important; } diff --git a/lib/osf-components/addon/components/search-page/boolean-filters/component.ts b/lib/osf-components/addon/components/search-page/boolean-filters/component.ts new file mode 100644 index 00000000000..3e84bf7dbd0 --- /dev/null +++ b/lib/osf-components/addon/components/search-page/boolean-filters/component.ts @@ -0,0 +1,38 @@ +import { action } from '@ember/object'; +import { inject as service } from '@ember/service'; +import Component from '@glimmer/component'; +import { tracked } from '@glimmer/tracking'; +import IntlService from 'ember-intl/services/intl'; +import RelatedPropertyPathModel from 'ember-osf-web/models/related-property-path'; + +import { Filter } from '../component'; + +interface BooleanFiltersArgs { + cardSearchText: string; + cardSearchFilter: Filter[]; + properties: RelatedPropertyPathModel[]; + toggleFilter: (filter: Filter) => void; +} + +export default class BooleanFilters extends Component { + @service intl!: IntlService; + + @tracked collapsed = true; + + get visibleLabel() { + return this.intl.t('search.boolean-filters.dropdown-label'); + } + + get hasFilterableValues() { + return this.args.properties.some(property => property.cardSearchResultCount > 0); + } + + get filterableValues() { + return this.args.properties.filterBy('cardSearchResultCount').sortBy('cardSearchResultCount').reverse(); + } + + @action + toggleFacet() { + this.collapsed = !this.collapsed; + } +} diff --git a/lib/osf-components/addon/components/search-page/boolean-filters/styles.scss b/lib/osf-components/addon/components/search-page/boolean-filters/styles.scss new file mode 100644 index 00000000000..2bf8ff08acb --- /dev/null +++ b/lib/osf-components/addon/components/search-page/boolean-filters/styles.scss @@ -0,0 +1,50 @@ +// Styles derived from component + +.facet-wrapper { + padding: 0.5rem 0; +} + +.facet-wrapper:not(:first-of-type) { + border-top: 1px solid $color-border-gray; +} + +.facet-expand-button { + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; + + &:active { + box-shadow: none; + } +} + +.facet-list { + list-style: none; + max-height: 300px; + overflow-y: auto; + margin: 0; + padding: 0.2rem; + + &.collapsed { + display: none; + } +} + +.facet-value { + display: flex; + justify-content: space-between; + margin: 10px 0; + + .facet-link { + margin: 0 5px; + max-width: 90%; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + } + + .facet-count { + flex-shrink: 0; + } +} diff --git a/lib/osf-components/addon/components/search-page/boolean-filters/template.hbs b/lib/osf-components/addon/components/search-page/boolean-filters/template.hbs new file mode 100644 index 00000000000..c8d8f25858a --- /dev/null +++ b/lib/osf-components/addon/components/search-page/boolean-filters/template.hbs @@ -0,0 +1,54 @@ +{{#if this.hasFilterableValues}} +
+ {{#let (unique-id 'boolean-filters') as |facetElementId|}} + +
    + {{#each this.filterableValues as |value|}} +
  • + + + {{value.cardSearchResultCount}} + +
  • + {{/each}} +
+ {{/let}} +
+{{/if}} diff --git a/lib/osf-components/addon/components/search-page/component.ts b/lib/osf-components/addon/components/search-page/component.ts index d5cc346332e..80491840810 100644 --- a/lib/osf-components/addon/components/search-page/component.ts +++ b/lib/osf-components/addon/components/search-page/component.ts @@ -12,13 +12,12 @@ import { action } from '@ember/object'; import Media from 'ember-responsive'; import { ShareMoreThanTenThousand } from 'ember-osf-web/models/index-card-search'; +import InstitutionModel from 'ember-osf-web/models/institution'; import SearchResultModel from 'ember-osf-web/models/search-result'; import ProviderModel from 'ember-osf-web/models/provider'; -import RelatedPropertyPathModel from 'ember-osf-web/models/related-property-path'; +import RelatedPropertyPathModel, { SuggestedFilterOperators } from 'ember-osf-web/models/related-property-path'; import uniqueId from 'ember-osf-web/utils/unique-id'; -import { booleanFilterProperties } from './filter-facet/component'; - interface ResourceTypeOption { display: string; value?: ResourceTypeFilterValue | null; @@ -39,16 +38,17 @@ interface SortOption { export interface Filter { propertyVisibleLabel: string; - propertyShortFormLabel: string; // OSFMAP shorthand label + propertyPathKey: string; // OSFMAP shorthand label value: string; label: string; + suggestedFilterOperator?: SuggestedFilterOperators; } export interface OnSearchParams { cardSearchText?: string; - page?: string; sort?: string; resourceType?: ResourceTypeFilterValue | null; + activeFilters?: Filter[]; } interface SearchArgs { @@ -62,8 +62,10 @@ interface SearchArgs { resourceType?: ResourceTypeFilterValue; defaultQueryOptions: Record; provider?: ProviderModel; + institution?: InstitutionModel; showResourceTypeFilter: boolean; page: string; + activeFilters: Filter[]; } const searchDebounceTime = 100; @@ -77,6 +79,7 @@ export default class SearchPage extends Component { @tracked cardSearchText?: string; @tracked searchResults?: SearchResultModel[]; @tracked relatedProperties?: RelatedPropertyPathModel[] = []; + @tracked booleanFilters?: RelatedPropertyPathModel[] = []; @tracked page?: string = ''; @tracked totalResultCount?: string | number; @tracked firstPageCursor?: string | null; @@ -90,6 +93,7 @@ export default class SearchPage extends Component { this.cardSearchText = this.args.cardSearchText; this.sort = this.args.sort; this.resourceType = this.args.resourceType; + this.activeFilters = A(this.args.activeFilters); taskFor(this.search).perform(); } @@ -101,6 +105,7 @@ export default class SearchPage extends Component { leftPanelObjectDropdownId = uniqueId(['left-panel-object-dropdown']); firstTopbarObjectTypeLinkId = uniqueId(['first-topbar-object-type-link']); searchInputWrapperId = uniqueId(['search-input-wrapper']); + searchBoxId = uniqueId(['search-box']); leftPanelHeaderId = uniqueId(['left-panel-header']); firstFilterId = uniqueId(['first-filter']); @@ -144,7 +149,7 @@ export default class SearchPage extends Component { } get showResultCountLeft() { - return this.totalResultCount && this.args.showResourceTypeFilter; + return this.totalResultCount && (this.args.showResourceTypeFilter || this.showSidePanelToggle); } get selectedSortOption() { @@ -152,7 +157,7 @@ export default class SearchPage extends Component { } // Resource type - resourceTypeOptions: ResourceTypeOption[] = [ + defaultResourceTypeOptions: ResourceTypeOption[] = [ { display: this.intl.t('search.resource-type.all'), value: null, @@ -179,6 +184,9 @@ export default class SearchPage extends Component { }, ]; + resourceTypeOptions = this.args.institution ? this.defaultResourceTypeOptions.slice(1) + : this.defaultResourceTypeOptions; + // Sort sortOptions: SortOption[] = [ { display: this.intl.t('search.sort.relevance'), value: '-relevance' }, @@ -198,16 +206,21 @@ export default class SearchPage extends Component { try { const cardSearchText = this.cardSearchText; const { page, sort, activeFilters, resourceType } = this; - let filterQueryObject = activeFilters.reduce((acc, filter) => { + if (this.args.onSearch) { + this.args.onSearch({cardSearchText, sort, resourceType, activeFilters}); + } + const filterQueryObject = activeFilters.reduce((acc, filter) => { // boolean filters should look like cardSearchFilter[hasDataResource][is-present] - if (booleanFilterProperties.includes(filter.propertyShortFormLabel)) { - acc[filter.propertyShortFormLabel] = {}; - acc[filter.propertyShortFormLabel][filter.value] = true; + if (filter.suggestedFilterOperator === SuggestedFilterOperators.IsPresent) { + acc[filter.propertyPathKey] = {}; + acc[filter.propertyPathKey][filter.value] = true; return acc; } + const currentValue = acc[filter.propertyPathKey]; // other filters should look like cardSearchFilter[propertyName]=IRI - const currentValue = acc[filter.propertyShortFormLabel]; - acc[filter.propertyShortFormLabel] = currentValue ? currentValue.concat(filter.value) : [filter.value]; + // or cardSearchFilter[propertyName] = IRI1, IRI2 + // Logic below is to handle the case where there are multiple filters for the same property + acc[filter.propertyPathKey] = currentValue ? currentValue.concat(filter.value) : [filter.value]; return acc; }, {} as { [key: string]: any }); let resourceTypeFilter = this.resourceType as string; @@ -216,7 +229,15 @@ export default class SearchPage extends Component { resourceTypeFilter = Object.values(ResourceTypeFilterValue).join(','); } filterQueryObject['resourceType'] = resourceTypeFilter; - filterQueryObject = { ...filterQueryObject, ...this.args.defaultQueryOptions }; + if (this.args.defaultQueryOptions) { + const { defaultQueryOptions } = this.args; + const defaultQueryOptionKeys = Object.keys(this.args.defaultQueryOptions); + defaultQueryOptionKeys.forEach(key => { + const currentValue = filterQueryObject[key]; + const defaultValue = defaultQueryOptions[key]; + filterQueryObject[key] = currentValue ? currentValue.concat(defaultValue) : [defaultValue]; + }); + } this.filterQueryObject = filterQueryObject; const searchResult = await this.store.queryRecord('index-card-search', { cardSearchText, @@ -226,16 +247,18 @@ export default class SearchPage extends Component { 'page[size]': 10, }); await searchResult.relatedProperties; - this.relatedProperties = searchResult.relatedProperties; + this.booleanFilters = searchResult.relatedProperties + .filterBy('suggestedFilterOperator', SuggestedFilterOperators.IsPresent); + this.relatedProperties = searchResult.relatedProperties.filter( + (property: RelatedPropertyPathModel) => + property.suggestedFilterOperator !== SuggestedFilterOperators.IsPresent, // AnyOf or AtDate + ); this.firstPageCursor = searchResult.firstPageCursor; this.nextPageCursor = searchResult.nextPageCursor; this.prevPageCursor = searchResult.prevPageCursor; this.searchResults = searchResult.searchResultPage.toArray(); this.totalResultCount = searchResult.totalResultCount === ShareMoreThanTenThousand ? '10,000+' : searchResult.totalResultCount; - if (this.args.onSearch) { - this.args.onSearch({cardSearchText, sort, resourceType}); - } } catch (e) { this.toast.error(e); } @@ -259,7 +282,7 @@ export default class SearchPage extends Component { @action toggleFilter(filter: Filter) { const filterIndex = this.activeFilters.findIndex( - f => f.propertyShortFormLabel === filter.propertyShortFormLabel && f.value === filter.value, + f => f.propertyPathKey === filter.propertyPathKey && f.value === filter.value, ); if (filterIndex > -1) { this.activeFilters.removeAt(filterIndex); diff --git a/lib/osf-components/addon/components/search-page/filter-facet/component.ts b/lib/osf-components/addon/components/search-page/filter-facet/component.ts index ebecb1bfb07..3367e679bc3 100644 --- a/lib/osf-components/addon/components/search-page/filter-facet/component.ts +++ b/lib/osf-components/addon/components/search-page/filter-facet/component.ts @@ -31,18 +31,6 @@ interface FilterFacetArgs { const searchDebounceTime = 500; -export const booleanFilterProperties = [ - 'hasAnalyticCodeResource', // registrations - 'hasMaterialsResource', // registrations - 'hasPapersResource', // registrations - 'hasSupplementalResource', // registrations - 'hasDataResource', // registrations and preprints - 'hasPreregisteredAnalysisPlan', // preprints - 'hasPreregisteredStudyDesign', // preprints - 'isSupplementedBy', // preprints - 'supplements', // projects -]; - export default class FilterFacet extends Component { @service store!: Store; @service intl!: IntlService; @@ -87,7 +75,7 @@ export default class FilterFacet extends Component { const card = this.selectedProperty.indexCard; const filter = { propertyVisibleLabel: property.displayLabel, - propertyShortFormLabel: property.shortFormLabel, + propertyPathKey: property.propertyPathKey, label: card.get('label'), value: card.get('resourceId'), }; @@ -118,26 +106,11 @@ export default class FilterFacet extends Component { async fetchFacetValues() { const { cardSearchText, cardSearchFilter, property } = this.args; const { page, sort, filterString } = this; - // If the property is a boolean filter (e.g. hasDataResource), we don't want to fetch IRI values - // SHARE API filters on these properties using: - // `share.osf.io/api/v3/index-card-search?cardSearchFilter[hasDataResource][is-present]` - // or cardSearchFilter[hasDataResource][is-absent] (although this one is not used in the app) - if (booleanFilterProperties.includes(property.shortFormLabel)) { - this.filterableValues = [ - { - resourceId: 'is-present', - indexCard: { - label: this.intl.t('search.filter-facet.has-resource', { resource: property.displayLabel }), - resourceId: 'is-present', - }, - }, - ]; - return; - } + const valueSearch = await this.store.queryRecord('index-value-search', { cardSearchText, cardSearchFilter, - valueSearchPropertyPath: property.shortFormLabel, + valueSearchPropertyPath: property.propertyPathKey, valueSearchText: filterString || '', 'page[cursor]': page, sort, diff --git a/lib/osf-components/addon/components/search-page/filter-facet/template.hbs b/lib/osf-components/addon/components/search-page/filter-facet/template.hbs index d7e05aabd02..45c6ffa6c49 100644 --- a/lib/osf-components/addon/components/search-page/filter-facet/template.hbs +++ b/lib/osf-components/addon/components/search-page/filter-facet/template.hbs @@ -5,7 +5,7 @@ local-class='facet-wrapper' ...attributes > - {{#let (unique-id @property.displayLabel) as |facetElementId|}} + {{#let (unique-id @property.propertyPathKey) as |facetElementId|}} + {{/if}} - {{#if (not-eq @result.resourceType 'user')}} - +

+ {{@result.displayTitle}} + {{#if @result.isWithdrawn}} + {{t 'osf-components.search-result-card.withdrawn'}} + {{/if}} + {{#if @result.orcids}} + {{#each @result.orcids as |item|}} + + + + {{/each}} + {{/if}} +

+ + {{#if @result.affiliatedEntities}} +
+ + {{#if list.item}} + {{list.item.name}} + {{else if list.remainingCount}} + {{t 'osf-components.search-result-card.remaining_count' count=list.remainingCount}} + {{/if}} + +
{{/if}} - -

- {{@result.displayTitle}} - {{#if @result.isWithdrawn}} - {{t 'osf-components.search-result-card.withdrawn'}} + {{#if @result.isPartOf}} +
+ {{t 'osf-components.search-result-card.from'}}: {{@result.isPartOfTitleAndUrl.title}} +
{{/if}} - {{#if @result.orcids}} - {{#each @result.orcids as |item|}} - - - - {{/each}} + {{#if @result.isContainedBy}} +
+ {{t 'osf-components.search-result-card.from'}}: {{@result.isContainedByTitleAndUrl.title}} +
{{/if}} -

- - {{#if @result.affiliatedEntities}} -
- - {{#if list.item}} - {{list.item.name}} - {{else if list.remainingCount}} - {{t 'osf-components.search-result-card.remaining_count' count=list.remainingCount}} +
+ {{#each @result.dateFields as |field|}} + {{#if field.date}} + {{field.label}}: {{field.date}} {{/if}} - -
- {{/if}} - {{#if @result.isPartOf}} -
- {{t 'osf-components.search-result-card.from'}}: {{@result.isPartOfTitleAndUrl.title}} -
- {{/if}} - {{#if @result.isContainedBy}} -
- {{t 'osf-components.search-result-card.from'}}: {{@result.isContainedByTitleAndUrl.title}} + {{/each}}
- {{/if}} -
- {{#each @result.dateFields as |field|}} - {{#if field.date}} - {{field.label}}: {{field.date}} - {{/if}} - {{/each}} + {{#if @result.context}} +
+ {{t 'osf-components.search-result-card.context'}}: {{@result.context}} +
+ {{/if}} + {{#if (or (eq @result.resourceType 'registration') (eq @result.resourceType 'registration_component'))}} +
+ +
+ {{/if}}
- {{#if @result.context}} -
- {{t 'osf-components.search-result-card.context'}}: {{@result.context}} -
- {{/if}} - {{#if (or (eq @result.resourceType 'registration') (eq @result.resourceType 'registration_component'))}} -
- -
- {{/if}} -
- - -
- {{component this.secondaryMetadataComponent result=@result}} -
-
+ + +
+ {{component this.secondaryMetadataComponent result=@result}} +
+
+ {{/let}} \ No newline at end of file diff --git a/lib/osf-components/addon/helpers/get-localized-property.ts b/lib/osf-components/addon/helpers/get-localized-property.ts new file mode 100644 index 00000000000..848a9f4607a --- /dev/null +++ b/lib/osf-components/addon/helpers/get-localized-property.ts @@ -0,0 +1,40 @@ +import Helper from '@ember/component/helper'; +import { inject as service } from '@ember/service'; +import IntlService from 'ember-intl/services/intl'; + +import { LanguageText } from 'ember-osf-web/models/index-card'; + +/** + * This helper is used to get a locale-appropriate string for a property from a metadata hash. + * It is used to fetch metadata fields from a index-card's resourceMetadata attribute, but can be used for any + * hash that contains an array of LangaugeText objects. + * If the property is not found, the first value in the array is returned, or if the property is found, + * but there is no locale-appropriate value, the first value in the array is returned. + * + * @example + * ```handlebars + * {{get-localized-property indexCard.resourceMetadata 'title'}} + * ``` + * where `indexCard` is an index-card model instance. + * @class get-localized-property + * @param {Object} metadataHash The metadata hash to search for the property + * @param {String} propertyName The name of the property to search for + * @return {String} The locale-appropriate string or the first value in the array or 'Not provided' message + */ +export default class GetLocalizedPropertyHelper extends Helper { + @service intl!: IntlService; + + compute([metadataHash, propertyName]: [Record, string]): string { + const locale = this.intl.locale; + const valueOptions = metadataHash?.[propertyName]; + if (!metadataHash || !valueOptions || valueOptions.length === 0) { + return this.intl.t('helpers.get-localized-property.not-provided'); + } + + const index = valueOptions.findIndex((valueOption: LanguageText) => valueOption['@language'] === locale); + if (index === -1) { + return valueOptions[0]['@value']; + } + return valueOptions[index]['@value']; + } +} diff --git a/lib/osf-components/app/components/search-page/boolean-filters/component.js b/lib/osf-components/app/components/search-page/boolean-filters/component.js new file mode 100644 index 00000000000..dedddcdffe1 --- /dev/null +++ b/lib/osf-components/app/components/search-page/boolean-filters/component.js @@ -0,0 +1 @@ +export { default } from 'osf-components/components/search-page/boolean-filters/component'; diff --git a/lib/osf-components/app/components/search-page/boolean-filters/template.js b/lib/osf-components/app/components/search-page/boolean-filters/template.js new file mode 100644 index 00000000000..c998549b2b9 --- /dev/null +++ b/lib/osf-components/app/components/search-page/boolean-filters/template.js @@ -0,0 +1 @@ +export { default } from 'osf-components/components/search-page/boolean-filters/template'; diff --git a/lib/osf-components/app/helpers/get-localized-property.js b/lib/osf-components/app/helpers/get-localized-property.js new file mode 100644 index 00000000000..42cf034a936 --- /dev/null +++ b/lib/osf-components/app/helpers/get-localized-property.js @@ -0,0 +1 @@ +export { default } from 'osf-components/helpers/get-localized-property'; diff --git a/lib/registries/addon/application/controller.ts b/lib/registries/addon/application/controller.ts index 7570785dfcc..16213c92c48 100644 --- a/lib/registries/addon/application/controller.ts +++ b/lib/registries/addon/application/controller.ts @@ -1,19 +1,9 @@ import Controller from '@ember/controller'; -import { action } from '@ember/object'; +import RouterService from '@ember/routing/router-service'; import { inject as service } from '@ember/service'; import Features from 'ember-feature-flags/services/features'; -import { OSFService } from 'osf-components/components/osf-navbar/component'; - export default class Application extends Controller { @service features!: Features; - - activeService = OSFService.REGISTRIES; - - @action - search(query: string) { - this.transitionToRoute('discover', { - queryParams: { query }, - }); - } + @service router!: RouterService; } diff --git a/lib/registries/addon/branded/discover/controller.ts b/lib/registries/addon/branded/discover/controller.ts index 863ea7c7e6d..722451d17f3 100644 --- a/lib/registries/addon/branded/discover/controller.ts +++ b/lib/registries/addon/branded/discover/controller.ts @@ -1,18 +1,35 @@ -import DiscoverController from 'registries/discover/controller'; +import Store from '@ember-data/store'; +// import EmberArray, { A } from '@ember/array'; +import Controller from '@ember/controller'; +import { action } from '@ember/object'; +import { inject as service } from '@ember/service'; +import Intl from 'ember-intl/services/intl'; +import Media from 'ember-responsive'; +import { tracked } from '@glimmer/tracking'; +import { Filter, OnSearchParams } from 'osf-components/components/search-page/component'; +import pathJoin from 'ember-osf-web/utils/path-join'; +import config from 'ember-osf-web/config/environment'; +export default class BrandedDiscover extends Controller.extend() { + @service media!: Media; + @service intl!: Intl; + @service store!: Store; -import { ShareTermsFilter } from 'registries/services/share-search'; + @tracked cardSearchText? = ''; + @tracked sort?= '-relevance'; + @tracked activeFilters?: Filter[] = []; -export default class Discover extends DiscoverController { - // this route uses the registries.discover page template where the custom branding is handled - get providerModel() { - return this.model; - } + queryParams = ['cardSearchText', 'sort', 'activeFilters']; - get additionalFilters() { - const { shareSource, name } = this.model; + get defaultQueryOptions() { + return { + publisher: pathJoin(config.OSF.url, 'registries', this.model.id), + }; + } - return [ - new ShareTermsFilter('sources', shareSource, name), - ]; + @action + onSearch(onSearchParams: OnSearchParams) { + this.cardSearchText = onSearchParams.cardSearchText; + this.sort = onSearchParams.sort; + this.activeFilters = onSearchParams.activeFilters; } } diff --git a/lib/registries/addon/branded/discover/route.ts b/lib/registries/addon/branded/discover/route.ts index 9a881cc4070..c9f253ba6ba 100644 --- a/lib/registries/addon/branded/discover/route.ts +++ b/lib/registries/addon/branded/discover/route.ts @@ -1,11 +1,12 @@ import Route from '@ember/routing/route'; +import RouterService from '@ember/routing/router-service'; +import { inject as service } from '@ember/service'; import RegistrationProviderModel from 'ember-osf-web/models/registration-provider'; import { notFoundURL } from 'ember-osf-web/utils/clean-url'; export default class BrandedRegistriesDiscoverRoute extends Route { - // this route uses the registries.discover page template where the custom branding is handled - templateName = 'discover'; + @service router!: RouterService; model() { return this.modelFor('branded'); @@ -14,7 +15,11 @@ export default class BrandedRegistriesDiscoverRoute extends Route { afterModel(provider: RegistrationProviderModel) { if (!provider.brandedDiscoveryPage) { if (provider.id === 'osf') { - this.transitionTo('discover'); + this.router.transitionTo('search', { + queryParams: { + resourceType: 'Registration,RegistrationComponent', + }, + }); } else { this.transitionTo('page-not-found', notFoundURL(window.location.pathname)); } @@ -25,6 +30,7 @@ export default class BrandedRegistriesDiscoverRoute extends Route { return { osfMetrics: { isSearch: true, + providerId: this.controller.model.id, }, }; } diff --git a/lib/registries/addon/branded/discover/styles.scss b/lib/registries/addon/branded/discover/styles.scss deleted file mode 100644 index 4c457cdfd7b..00000000000 --- a/lib/registries/addon/branded/discover/styles.scss +++ /dev/null @@ -1,11 +0,0 @@ -.BrandedRegistriesSearchResult { - composes: RegistriesSearchResult from '../../discover/styles'; - - svg { - color: var(--primary-color); - } -} - -.Pagination { - composes: Pagination from '../../discover/styles'; -} diff --git a/lib/registries/addon/branded/discover/template.hbs b/lib/registries/addon/branded/discover/template.hbs index 47e7abedca6..1d9d0d6b45e 100644 --- a/lib/registries/addon/branded/discover/template.hbs +++ b/lib/registries/addon/branded/discover/template.hbs @@ -13,5 +13,6 @@ @sort={{this.sort}} @onSearch={{action this.onSearch}} @showResourceTypeFilter={{false}} + @activeFilters={{this.activeFilters}} /> diff --git a/lib/registries/addon/branded/index/route.ts b/lib/registries/addon/branded/index/route.ts index b2b0de51ff7..40e145ec067 100644 --- a/lib/registries/addon/branded/index/route.ts +++ b/lib/registries/addon/branded/index/route.ts @@ -1,5 +1,5 @@ import Route from '@ember/routing/route'; -import config from 'ember-get-config'; +import config from 'ember-osf-web/config/environment'; export default class BrandedRegistriesIndexRoute extends Route { beforeModel() { const params: { providerId?: string } = this.paramsFor('branded'); diff --git a/lib/registries/addon/branded/route.ts b/lib/registries/addon/branded/route.ts index 075ec91b58c..166234793d8 100644 --- a/lib/registries/addon/branded/route.ts +++ b/lib/registries/addon/branded/route.ts @@ -4,14 +4,20 @@ import { inject as service } from '@ember/service'; import RegistrationProviderModel from 'ember-osf-web/models/registration-provider'; import MetaTags, { HeadTagDef } from 'ember-osf-web/services/meta-tags'; +import { notFoundURL } from 'ember-osf-web/utils/clean-url'; export default class BrandedRegistriesRoute extends Route { @service store!: Store; @service metaTags!: MetaTags; headTags?: HeadTagDef[]; - model(params: { providerId: string }) { - return this.store.findRecord('registration-provider', params.providerId, { include: 'brand' }); + async model(params: { providerId: string }) { + try { + return await this.store.findRecord('registration-provider', params.providerId, { include: 'brand' }); + } catch (e) { + this.transitionTo('page-not-found', notFoundURL(window.location.pathname)); + return null; + } } afterModel(model: RegistrationProviderModel) { diff --git a/lib/registries/addon/components/registries-discover-results-header/styles.scss b/lib/registries/addon/components/registries-discover-results-header/styles.scss deleted file mode 100644 index 9482e9caf53..00000000000 --- a/lib/registries/addon/components/registries-discover-results-header/styles.scss +++ /dev/null @@ -1,55 +0,0 @@ - - -.results-container { - composes: ResultsHeader from '../../discover/styles'; - - display: flex; - flex-direction: row; - justify-content: flex-start; - align-items: flex-start; - width: 100%; - - .header { - width: 50%; - } - - .dropdown-container { - width: 50%; - display: flex; - flex-direction: row; - justify-content: flex-end; - align-items: flex-start; - - } -} - -/** -I have no idea why but these classes are needed just like they are spelled -even though they do not exist in the hbs file. -*/ -.SortDropDown__List { - composes: SortDropDown__List from '../../discover/styles'; -} - -.SortDropDown__Option { - composes: SortDropDown__Option from '../../discover/styles'; -} - -.DropdownTrigger { - composes: Button from 'osf-components/components/button/styles'; - composes: MediumButton from 'osf-components/components/button/styles'; - composes: SecondaryButton from 'osf-components/components/button/styles'; - - color: $color-text-black; - - // Recreate the browser focus state for buttons - &:focus { - outline: auto 2px Highlight; - outline: auto 5px -webkit-focus-ring-color; - } -} - -.DropdownContent { - border: 1px solid $color-border-gray; - box-shadow: 0 1px 2px $primary-box-shadow; -} diff --git a/lib/registries/addon/components/registries-discover-results-header/template.hbs b/lib/registries/addon/components/registries-discover-results-header/template.hbs deleted file mode 100644 index 4f8b4193452..00000000000 --- a/lib/registries/addon/components/registries-discover-results-header/template.hbs +++ /dev/null @@ -1,33 +0,0 @@ -
-
-

- {{t 'registries.discover.registration_count' count=@totalResults}} -

-
-
- - - {{t 'registries.discover.sort_by'}}: {{t @searchOptions.order.display}} - - - - {{#each @sortOptions as |option index|}} -
- -
- {{/each}} -
-
-
-
diff --git a/lib/registries/addon/components/registries-discover-search/component.ts b/lib/registries/addon/components/registries-discover-search/component.ts deleted file mode 100644 index c5723273a14..00000000000 --- a/lib/registries/addon/components/registries-discover-search/component.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { A } from '@ember/array'; -import Component from '@ember/component'; - -import { layout, requiredAction } from 'ember-osf-web/decorators/component'; -import { SearchOptions } from 'registries/services/search'; -import template from './template'; - -@layout(template) -export default class Discover extends Component { - results = A([]); - searchOptions!: SearchOptions; - @requiredAction onSearchOptionsUpdated!: (options: SearchOptions) => void; -} diff --git a/lib/registries/addon/components/registries-discover-search/styles.scss b/lib/registries/addon/components/registries-discover-search/styles.scss deleted file mode 100644 index ae8b6c6b48c..00000000000 --- a/lib/registries/addon/components/registries-discover-search/styles.scss +++ /dev/null @@ -1,12 +0,0 @@ -.Discover__Body { - background-color: #f5f5f5; - border-top: 1px solid $color-border-gray; - border-bottom: 1px solid #dedede; - padding-bottom: 50px; - padding-top: 50px; -} - -.row { - margin-right: -15px; - margin-left: -15px; -} diff --git a/lib/registries/addon/components/registries-discover-search/template.hbs b/lib/registries/addon/components/registries-discover-search/template.hbs deleted file mode 100644 index 5532ff79811..00000000000 --- a/lib/registries/addon/components/registries-discover-search/template.hbs +++ /dev/null @@ -1,15 +0,0 @@ -
-
-
- {{yield (hash - results=(component 'registries-discover-search/x-results' @results) - sidebar=(component 'registries-discover-search/x-sidebar' - searchOptions=@searchOptions - additionalFilters=@additionalFilters - provider=@provider - onSearchOptionsUpdated=(action @onSearchOptionsUpdated) - ) - )}} -
-
-
diff --git a/lib/registries/addon/components/registries-discover-search/x-result/component.ts b/lib/registries/addon/components/registries-discover-search/x-result/component.ts deleted file mode 100644 index 4fa8a4fcac5..00000000000 --- a/lib/registries/addon/components/registries-discover-search/x-result/component.ts +++ /dev/null @@ -1,11 +0,0 @@ -import Component from '@ember/component'; -import { localClassNames } from 'ember-css-modules'; - -import { layout } from 'ember-osf-web/decorators/component'; -import template from './template'; - -@layout(template) -@localClassNames('SearchResult') -export default class SearchResult extends Component { - result!: T; -} diff --git a/lib/registries/addon/components/registries-discover-search/x-result/styles.scss b/lib/registries/addon/components/registries-discover-search/x-result/styles.scss deleted file mode 100644 index f8de59acaef..00000000000 --- a/lib/registries/addon/components/registries-discover-search/x-result/styles.scss +++ /dev/null @@ -1,10 +0,0 @@ -.SearchResult { - background: $color-bg-white; - margin-bottom: 30px; - word-wrap: break-word; - word-break: break-word; - overflow-wrap: break-word; - box-shadow: 0 1px 2px #ddd; - border-width: 0; - padding: 10px; -} diff --git a/lib/registries/addon/components/registries-discover-search/x-result/template.hbs b/lib/registries/addon/components/registries-discover-search/x-result/template.hbs deleted file mode 100644 index b7555a22e07..00000000000 --- a/lib/registries/addon/components/registries-discover-search/x-result/template.hbs +++ /dev/null @@ -1 +0,0 @@ -{{yield @result}} diff --git a/lib/registries/addon/components/registries-discover-search/x-results/component.ts b/lib/registries/addon/components/registries-discover-search/x-results/component.ts deleted file mode 100644 index 0968fb1eac0..00000000000 --- a/lib/registries/addon/components/registries-discover-search/x-results/component.ts +++ /dev/null @@ -1,24 +0,0 @@ -import Component from '@ember/component'; -import { action } from '@ember/object'; -import { localClassNames } from 'ember-css-modules'; - -import { layout, requiredAction } from 'ember-osf-web/decorators/component'; -import { SearchOptions } from 'registries/services/search'; - -import template from './template'; - -@layout(template) -@localClassNames('SearchResults') -export default class SearchResults extends Component { - static positionalParams = ['results']; - - searchOptions!: SearchOptions; - @requiredAction onSearchOptionsUpdated!: (options: SearchOptions) => void; - - results!: T[]; - - @action - _onSearchOptionsUpdated(options: SearchOptions) { - this.onSearchOptionsUpdated(options); - } -} diff --git a/lib/registries/addon/components/registries-discover-search/x-results/styles.scss b/lib/registries/addon/components/registries-discover-search/x-results/styles.scss deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/lib/registries/addon/components/registries-discover-search/x-results/template.hbs b/lib/registries/addon/components/registries-discover-search/x-results/template.hbs deleted file mode 100644 index 7e598ef765a..00000000000 --- a/lib/registries/addon/components/registries-discover-search/x-results/template.hbs +++ /dev/null @@ -1,5 +0,0 @@ -{{#each @results as |result|}} - {{#registries-discover-search/x-result result=result as |ctxResult|}} - {{yield ctxResult}} - {{/registries-discover-search/x-result}} -{{/each}} \ No newline at end of file diff --git a/lib/registries/addon/components/registries-discover-search/x-sidebar/component.ts b/lib/registries/addon/components/registries-discover-search/x-sidebar/component.ts deleted file mode 100644 index 5c692756a85..00000000000 --- a/lib/registries/addon/components/registries-discover-search/x-sidebar/component.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { A } from '@ember/array'; -import Component from '@ember/component'; -import { action, computed } from '@ember/object'; -import { inject as service } from '@ember/service'; -import { localClassNames } from 'ember-css-modules'; -import { is, OrderedSet } from 'immutable'; - -import { layout, requiredAction } from 'ember-osf-web/decorators/component'; -import ProviderModel from 'ember-osf-web/models/provider'; -import Analytics from 'ember-osf-web/services/analytics'; -import { SearchFilter, SearchOptions } from 'registries/services/search'; -import template from './template'; - -function includesImmutable(someArray: unknown[], someValue: unknown) { - return someArray.any(val => is(val, someValue)); -} - -@layout(template) -@localClassNames('Sidebar') -export default class SideBar extends Component { - @service analytics!: Analytics; - - searchOptions!: SearchOptions; - additionalFilters!: SearchFilter[]; - provider?: ProviderModel; - @requiredAction onSearchOptionsUpdated!: (options: SearchOptions) => void; - - @computed('additionalFilters', 'searchOptions.filters') - get filters() { - const filters = A([]); - for (const filter of this.searchOptions.filters) { - if (!includesImmutable(this.additionalFilters, filter)) { - filters.addObject({ - filter, - display: filter.display, - }); - } - } - return filters; - } - - @action - _onSearchOptionsUpdated(options: SearchOptions) { - this.onSearchOptionsUpdated(options); - } - - @action - removeFilter(filter: SearchFilter) { - if (this.provider) { - this.analytics.click('link', `Discover - Remove Filter ${this.provider.name}`, filter); - } else { - this.analytics.click('link', 'Discover - Remove Filter', filter); - } - this.onSearchOptionsUpdated(this.searchOptions.removeFilters(filter)); - } - - @action - clearFilters() { - this.analytics.track('button', 'click', 'Discover - Clear Filters'); - this.onSearchOptionsUpdated(this.searchOptions.set('filters', OrderedSet())); - } -} diff --git a/lib/registries/addon/components/registries-discover-search/x-sidebar/styles.scss b/lib/registries/addon/components/registries-discover-search/x-sidebar/styles.scss deleted file mode 100644 index 427112fd2d6..00000000000 --- a/lib/registries/addon/components/registries-discover-search/x-sidebar/styles.scss +++ /dev/null @@ -1,39 +0,0 @@ -// stylelint-disable selector-no-qualifying-type -h2.Sidebar__FilterHeading { - font-size: 18px; - font-weight: 700; -} -// stylelint-enable selector-no-qualifying-type -.Sidebar__ActiveFilters { - display: flex; - flex-wrap: wrap; -} - -.Sidebar__ActiveFilter { - line-height: 30px; - display: flex; - margin-top: 5px; - margin-right: 5px; -} - -.Sidebar__ActiveFilterLabel { - background-color: $color-filter-bg; - color: $color-text-white; - padding: 1.5px 8px 4px; - border-radius: 2px 0 0 2px; -} - -.Sidebar__RemoveFilter { - line-height: inherit; - border-radius: 0 2px 2px 0; -} - -.Sidebar__Facets { - padding-top: 15px; -} - -.sidebar-container { - width: 100%; - padding-left: 20px; - padding-right: 20px; -} diff --git a/lib/registries/addon/components/registries-discover-search/x-sidebar/template.hbs b/lib/registries/addon/components/registries-discover-search/x-sidebar/template.hbs deleted file mode 100644 index 58b8ac9d308..00000000000 --- a/lib/registries/addon/components/registries-discover-search/x-sidebar/template.hbs +++ /dev/null @@ -1,30 +0,0 @@ -
-

- {{t 'registries.discover.sidebar.refine_search'}} -

-
- {{#each this.filters as |filter|}} -
- - {{filter.display}} - - -
- {{/each}} -
-
- {{yield (hash - filters=this.filters - searchOptions=@searchOptions - onSearchOptionsUpdated=(action this._onSearchOptionsUpdated) - )}} -
-
\ No newline at end of file diff --git a/lib/registries/addon/components/registries-navbar/template.hbs b/lib/registries/addon/components/registries-navbar/template.hbs index b2e075c5f5a..f20ea0e4570 100644 --- a/lib/registries/addon/components/registries-navbar/template.hbs +++ b/lib/registries/addon/components/registries-navbar/template.hbs @@ -9,7 +9,7 @@ void; - - title = 'Provider'; - options: EmberArray<{ - count: number, - filter: SearchFilter, - }> = A([]); - - @computed('options', 'searchOptions.filters') - get providers() { - return this.options.map(option => ({ - ...option, - checked: this.searchOptions.filters.has(option.filter), - })); - } - - @computed('options.length') - get shouldLinkToAggregateDiscover() { - return this.options.length === 1; - } - - @action - providerChecked(filter: SearchFilter, remove: boolean) { - if (this.provider) { - this.analytics.track( - 'filter', - remove - ? 'remove' - : 'add', - `Discover - providers ${filter.display} ${this.provider.name}`, - ); - } else { - this.analytics.track('filter', remove ? 'remove' : 'add', `Discover - providers ${filter.display}`); - } - if (remove) { - this.onSearchOptionsUpdated(this.searchOptions.removeFilters(filter)); - } else { - this.onSearchOptionsUpdated(this.searchOptions.addFilters(filter)); - } - } -} diff --git a/lib/registries/addon/components/registries-provider-facet/styles.scss b/lib/registries/addon/components/registries-provider-facet/styles.scss deleted file mode 100644 index 3ed1819148c..00000000000 --- a/lib/registries/addon/components/registries-provider-facet/styles.scss +++ /dev/null @@ -1,35 +0,0 @@ -.ProviderFacet__List { - list-style: none; - padding: 0; -} - -.ProviderFacet__ListItem { - display: flex; - justify-content: space-between; -} - -.ProviderFacet__Label { - font-weight: 400; - min-width: 0; - white-space: nowrap; - text-overflow: ellipsis; - overflow: hidden; - flex-grow: 1; - - &:hover { - color: var(--primary-color); - } -} - -.ProviderFacet__Checkbox.ProviderFacet__Checkbox { - margin-right: 5px; - float: left; -} - -.ProviderFacet__Count { - font-size: 85%; - font-weight: bold; - color: $color-text-gray; - align-self: center; - white-space: nowrap; -} diff --git a/lib/registries/addon/components/registries-provider-facet/template.hbs b/lib/registries/addon/components/registries-provider-facet/template.hbs deleted file mode 100644 index ca40c9baf71..00000000000 --- a/lib/registries/addon/components/registries-provider-facet/template.hbs +++ /dev/null @@ -1,31 +0,0 @@ -{{#registries-search-facet-container title='Provider'}} -
    - {{#each this.providers as |provider|}} -
  • - - - {{t 'registries.discover.sidebar.provider_count' count=provider.count}} - -
  • - {{/each}} -
- {{#if this.shouldLinkToAggregateDiscover}} - - {{t 'registries.facets.provider.other_registries'}} - - {{/if}} -{{/registries-search-facet-container}} diff --git a/lib/registries/addon/components/registries-registration-type-facet/component.ts b/lib/registries/addon/components/registries-registration-type-facet/component.ts deleted file mode 100644 index a4c55fd366e..00000000000 --- a/lib/registries/addon/components/registries-registration-type-facet/component.ts +++ /dev/null @@ -1,117 +0,0 @@ -import Store from '@ember-data/store'; -import EmberArray, { A } from '@ember/array'; -import Component from '@ember/component'; -import { action, computed } from '@ember/object'; -import { inject as service } from '@ember/service'; -import { waitFor } from '@ember/test-waiters'; -import { task } from 'ember-concurrency'; -import Features from 'ember-feature-flags/services/features'; -import Intl from 'ember-intl/services/intl'; -import Toast from 'ember-toastr/services/toast'; - -import { layout, requiredAction } from 'ember-osf-web/decorators/component'; -import RegistrationProviderModel from 'ember-osf-web/models/registration-provider'; -import Analytics from 'ember-osf-web/services/analytics'; -import captureException, { getApiErrorMessage } from 'ember-osf-web/utils/capture-exception'; - -import registriesConfig from 'registries/config/environment'; -import { SearchOptions } from 'registries/services/search'; -import { ShareTermsFilter } from 'registries/services/share-search'; -import template from './template'; - -@layout(template) -export default class RegistriesRegistrationTypeFacet extends Component { - @service intl!: Intl; - @service toast!: Toast; - @service store!: Store; - @service analytics!: Analytics; - @service features!: Features; - - searchOptions!: SearchOptions; - provider?: RegistrationProviderModel; - @requiredAction onSearchOptionsUpdated!: (options: SearchOptions) => void; - - registrationTypes: EmberArray = A([]); - - @task({ on: 'didReceiveAttrs' }) - @waitFor - async fetchRegistrationTypes() { - const { defaultProviderId } = registriesConfig; - - try { - if (!this.provider){ - this.provider = await this.store.findRecord('registration-provider', defaultProviderId); - } - const metaschemas = await this.provider.queryHasMany('schemas', { - 'page[size]': 100, - }); - const metaschemaNames = metaschemas.mapBy('name'); - if (this.provider.id === defaultProviderId) { - metaschemaNames.push( - // Manually add 'Election Research Preacceptance Competition' to the list of possible - // facets. Metaschema was removed from the API as a possible registration type - // but should still be searchable - 'Election Research Preacceptance Competition', - ); - } - this.set('registrationTypes', A(metaschemaNames.sort())); - } catch (e) { - const errorMessage = this.intl.t('registries.facets.registration_type.registration_schema_error'); - captureException(e, { errorMessage }); - this.toast.error(getApiErrorMessage(e), errorMessage); - throw e; - } - } - - get title() { - return this.intl.t('registries.facets.registration_type.title'); - } - - @computed('searchOptions.filters') - get onlyOSF() { - const osfSelected = this.searchOptions.filters.find( - item => item instanceof ShareTermsFilter - && item.key === 'sources' - && item.value === 'OSF Registries', - ); - return this.searchOptions.filters.filter(filter => filter.key === 'sources').size === 1 && osfSelected; - } - - @computed('registrationTypes', 'searchOptions.filters') - get types() { - return this.registrationTypes.map(name => { - const filter = new ShareTermsFilter('registration_type', name, name); - - return { - name, - filter, - checked: this.searchOptions.filters.contains(filter), - }; - }); - } - - @action - typeChecked(filter: ShareTermsFilter, checked: boolean) { - if (!this.onlyOSF) { - return undefined; - } - - if (this.provider) { - this.analytics.track( - 'filter', - checked - ? 'remove' - : 'add', - `Discover - type ${filter.display} ${this.provider.name}`, - ); - } else { - this.analytics.track('filter', checked ? 'remove' : 'add', `Discover - type ${filter.display}`); - } - - if (checked) { - return this.onSearchOptionsUpdated(this.searchOptions.removeFilters(filter)); - } - - return this.onSearchOptionsUpdated(this.searchOptions.addFilters(filter)); - } -} diff --git a/lib/registries/addon/components/registries-registration-type-facet/styles.scss b/lib/registries/addon/components/registries-registration-type-facet/styles.scss deleted file mode 100644 index 863a0af9bde..00000000000 --- a/lib/registries/addon/components/registries-registration-type-facet/styles.scss +++ /dev/null @@ -1,54 +0,0 @@ -.RegistrationType { - opacity: 1; - animation: enable 0.5s ease-in-out; -} - -@keyframes disable { - 0% { - opacity: 1; - } - - 100% { - opacity: 0.5; - } -} - -@keyframes enable { - 0% { - opacity: 0.5; - } - - 100% { - opacity: 1; - } -} - -.RegistrationType__Warning { - font-weight: 700; - color: #000; - padding-left: 0; - display: block; -} - -.RegistrationType__List { - list-style: none; - padding: 0; -} - -.RegistrationType__Item { - label { - font-weight: 400; - - &:hover { - color: var(--primary-color); - } - } -} - -.RegistrationType__Filter { - background-color: #d7e6e9; -} - -.m-t-sm { - margin-top: 10px; -} diff --git a/lib/registries/addon/components/registries-registration-type-facet/template.hbs b/lib/registries/addon/components/registries-registration-type-facet/template.hbs deleted file mode 100644 index 4f6f2853de9..00000000000 --- a/lib/registries/addon/components/registries-registration-type-facet/template.hbs +++ /dev/null @@ -1,25 +0,0 @@ -{{#if this.onlyOSF}} - {{#registries-search-facet-container title=this.title}} -
- - {{t 'registries.facets.registration_type.only_available_with_osf'}} - - -
    - {{#each this.types as |type index|}} -
  • - -
  • - {{/each}} -
-
- {{/registries-search-facet-container}} -{{/if}} diff --git a/lib/registries/addon/components/registries-search-facet-container/component.ts b/lib/registries/addon/components/registries-search-facet-container/component.ts deleted file mode 100644 index 38b3ccea31c..00000000000 --- a/lib/registries/addon/components/registries-search-facet-container/component.ts +++ /dev/null @@ -1,11 +0,0 @@ -import Component from '@ember/component'; -import { localClassNames } from 'ember-css-modules'; - -import { layout } from 'ember-osf-web/decorators/component'; -import template from './template'; - -@layout(template) -@localClassNames('SearchFacet') -export default class SearchFacetContainer extends Component { - title!: string; -} diff --git a/lib/registries/addon/components/registries-search-facet-container/styles.scss b/lib/registries/addon/components/registries-search-facet-container/styles.scss deleted file mode 100644 index 479fa378dcb..00000000000 --- a/lib/registries/addon/components/registries-search-facet-container/styles.scss +++ /dev/null @@ -1,14 +0,0 @@ -.SearchFacet { - border-width: 0; - box-shadow: none; -} -// stylelint-disable selector-no-qualifying-type -h3.SearchFacet__Heading { - font-size: 15px; - font-weight: 700; - margin-top: 10px; -} - -.SearchFacet__Body { - padding: 10px 0; -} diff --git a/lib/registries/addon/components/registries-search-facet-container/template.hbs b/lib/registries/addon/components/registries-search-facet-container/template.hbs deleted file mode 100644 index fcb847f2bab..00000000000 --- a/lib/registries/addon/components/registries-search-facet-container/template.hbs +++ /dev/null @@ -1,4 +0,0 @@ -

{{@title}}

-
- {{yield}} -
diff --git a/lib/registries/addon/components/registries-search-result/component.ts b/lib/registries/addon/components/registries-search-result/component.ts deleted file mode 100644 index eabf489d525..00000000000 --- a/lib/registries/addon/components/registries-search-result/component.ts +++ /dev/null @@ -1,46 +0,0 @@ -import Component from '@ember/component'; -import { computed } from '@ember/object'; -import { localClassNames } from 'ember-css-modules'; -import { inject as service } from '@ember/service'; -import Media from 'ember-responsive'; - -import { layout } from 'ember-osf-web/decorators/component'; -import { ShareRegistration } from 'registries/services/share-search'; - -import template from './template'; - -const OSF_GUID_REGEX = /^https?:\/\/.*osf\.io\/([^/]+)/; - -@layout(template) -@localClassNames('RegistriesSearchResult') -export default class RegistriesSearchResult extends Component { - @service media!: Media; - // Required - result!: ShareRegistration; - - // For use later, when the registration overview page is implemented - // @computed('result') - // get osfID() { - // const res = OSF_GUID_REGEX.exec(this.result.mainLink || ''); - - // if (res) { - // return res[1]; - // } - - // return false; - // } - - @computed('result.contributors') - get contributors() { - return this.result.contributors.filter( - contrib => contrib.bibliographic, - ).map(contrib => ({ - name: contrib.name, - link: contrib.identifiers.filter(ident => OSF_GUID_REGEX.test(ident))[0], - })); - } - - get isMobile() { - return this.media.isMobile; - } -} diff --git a/lib/registries/addon/components/registries-search-result/styles.scss b/lib/registries/addon/components/registries-search-result/styles.scss deleted file mode 100644 index 4382a0b490d..00000000000 --- a/lib/registries/addon/components/registries-search-result/styles.scss +++ /dev/null @@ -1,107 +0,0 @@ -// stylelint-disable max-nesting-depth, selector-max-compound-selectors - -.search-container { - display: flex; - flex-direction: row; - justify-content: flex-start; - align-items: flex-start; - width: 100%; - - &.mobile { - .search-result-container { - width: calc(100% - 50px); - } - - .badges-container { - width: 50px; - min-width: 50px; - padding-left: 0; - } - } - - .search-result-container { - display: flex; - flex-direction: column; - justify-content: flex-start; - align-items: flex-start; - width: 75%; - - .title { - width: 100%; - line-height: inherit; - margin-top: 10px; - - :global(.label-default) { - background-color: $color-bg-gray-darker; - } - } - - .updated { - width: 100%; - font-style: italic; - } - - .sources { - width: 100%; - font-weight: 700; - margin-top: 10px; - } - - .contributors { - width: 100%; - display: flex; - flex-direction: row; - flex-wrap: wrap; - list-style: none; - padding-left: 0; - - & > li { - display: inline-flex; - padding-right: 5px; - - &::after { - content: ','; - } - } - - & > li:last-child::after { - content: ''; - } - } - - .description { - max-height: 120px; - height: fit-content; - overflow: hidden; - transition: max-height 0.5s cubic-bezier(0, 1, 0, 1); - margin-top: 10px; - width: 100%; - padding-right: 10px; - } - } - - .badges-container { - min-width: 150px; - width: 25%; - border-left-width: thin; - border-left-color: $color-bg-gray-light; - border-left-style: solid; - padding-left: 10px; - margin-top: 12px; - } - -} - -.label { - display: inline; - padding: 0.2em 0.6em 0.3em; - font-size: 75%; - font-weight: 700; - line-height: 1; - color: $color-text-white; - background-color: $color-bg-gray-darker; - text-align: center; - white-space: nowrap; - vertical-align: baseline; - border-radius: 0.25em; -} diff --git a/lib/registries/addon/components/registries-search-result/template.hbs b/lib/registries/addon/components/registries-search-result/template.hbs deleted file mode 100644 index 60d924c1f8e..00000000000 --- a/lib/registries/addon/components/registries-search-result/template.hbs +++ /dev/null @@ -1,84 +0,0 @@ -
-
-

- {{#if @result.relatedResourceTypes}} - {{!-- This means it's an OSF resource, which means it'll have a guid --}} - - {{math @result.title}} - - {{else}} - - {{math @result.title}} - - {{/if}} - {{#if @result.withdrawn}} - {{t 'registries.discover.search_result.withdrawn'}} - {{/if}} -

- - {{#if this.contributors}} -
    - {{#each this.contributors as |contrib|}} -
  • - {{#if contrib.link}} - - {{contrib.name}} - - {{else}} - {{contrib.name}} - {{/if}} -
  • - {{/each}} -
- {{/if}} - - {{#if @result.dateUpdated}} -
- {{t 'registries.discover.search_result.last_edited' date=(moment-format (utc @result.dateUpdated) 'MMMM D, YYYY UTC')}} -
- {{/if}} - -
- {{#each @result.sources as |source index|}} - {{if index '| '}}{{source}} - {{/each}} - - {{#if @result.registrationType}} - | {{@result.registrationType}} - {{/if}} -
- -

- {{math @result.description}} -

-
- {{#if @result.relatedResourceTypes}} -
- -
- {{/if}} -
diff --git a/lib/registries/addon/components/registries-subjects-facet/component.ts b/lib/registries/addon/components/registries-subjects-facet/component.ts deleted file mode 100644 index a9b15516201..00000000000 --- a/lib/registries/addon/components/registries-subjects-facet/component.ts +++ /dev/null @@ -1,184 +0,0 @@ -import { action } from '@ember/object'; -import { inject as service } from '@ember/service'; -import Component from '@glimmer/component'; - -import ProviderModel from 'ember-osf-web/models/provider'; -import SubjectModel from 'ember-osf-web/models/subject'; -import Analytics from 'ember-osf-web/services/analytics'; -import { SubjectManager } from 'osf-components/components/subjects/manager/component'; -import { SearchOptions } from 'registries/services/search'; -import { ShareTermsFilter } from 'registries/services/share-search'; - -interface Args { - provider: ProviderModel; - searchOptions: SearchOptions; - onSearchOptionsUpdated(options: SearchOptions): void; -} - -// TODO: memoize some of these functions? could get expensive with lots of subjects expanded - -// WARNING: assumes subject.parent (and subject.parent.parent...) is already loaded -function getSubjectTerm(subject: SubjectModel): string { - // subjects are indexed with their full hierarchy, including taxonomy name - // 'taxonomy|parent subject text|this subject text' - // e.g. 'bepress|Law|Bird Law' - const parentSubject = subject.belongsTo('parent').value() as SubjectModel | null; - - return parentSubject - ? `${getSubjectTerm(parentSubject)}|${subject.text}` - : `${subject.taxonomyName}|${subject.text}`; -} - -function newSubjectFilter(subject: SubjectModel): ShareTermsFilter { - return new ShareTermsFilter('subjects', getSubjectTerm(subject), subject.text); -} - -/* want to get filters for all ancestors, e.g. - * given `bepress|foo|bar|baz` - * get [`bepress|foo`, `bepress|foo|bar`] - */ -function getAncestry(subjectTerm: string): string[] { - const parentTerms: string[] = []; - const [taxonomyName, ...subjectAncestry] = subjectTerm.split('|'); - for (let i = 1; i < subjectAncestry.length; i++) { - const ancestorLineage = subjectAncestry.slice(0, i).join('|'); - parentTerms.push(`${taxonomyName}|${ancestorLineage}`); - } - return parentTerms; -} - -function getAncestryFilters(subjectTerm: string): ShareTermsFilter[] { - const parentFilters: ShareTermsFilter[] = []; - const [taxonomyName, ...subjectAncestry] = subjectTerm.split('|'); - for (let i = 1; i < subjectAncestry.length; i++) { - const ancestorText = subjectAncestry[i]; - const ancestorLineage = subjectAncestry.slice(0, i).join('|'); - parentFilters.push(new ShareTermsFilter( - 'subjects', - `${taxonomyName}|${ancestorLineage}`, - ancestorText, - )); - } - return parentFilters; -} - -export default class RegistriesSubjectsFacet extends Component { - @service analytics!: Analytics; - - provider?: ProviderModel; - - get selectedSubjectFilters() { - const { searchOptions: { filters } } = this.args; - return filters.filter(f => f.key === 'subjects').toArray(); - } - - get selectedSubjectTerms(): Set { - return new Set( - this.selectedSubjectFilters.map(f => f.value as string), - ); - } - - get parentTermsWithSelectedChild(): Set { - const { selectedSubjectTerms } = this; - const parentTerms = new Set(); - - selectedSubjectTerms.forEach( - subjectTerm => getAncestry(subjectTerm).forEach( - ancestorTerm => parentTerms.add(ancestorTerm), - ), - ); - - return parentTerms; - } - - get subjectsManager(): SubjectManager { - const { - args: { provider }, - selectSubject, - unselectSubject, - selectedSubjectTerms, - parentTermsWithSelectedChild, - } = this; - - return { - provider, - selectSubject, - unselectSubject, - - subjectIsSelected(subject: SubjectModel): boolean { - // display a subject as selected if any of its children are selected - return selectedSubjectTerms.has(getSubjectTerm(subject)) - || parentTermsWithSelectedChild.has(getSubjectTerm(subject)); - }, - - subjectHasSelectedChildren(subject: SubjectModel) { - return parentTermsWithSelectedChild.has(getSubjectTerm(subject)); - }, - - subjectIsSaved: () => false, // TODO: should this return true? - - // NOTE: everything below is not needed by Subjects::Browse, so they're - // just here to fit the interface that assumes we're saving subjects - // on a model instance - savedSubjects: [], - selectedSubjects: [], - isSaving: false, - hasChanged: false, - discardChanges: () => undefined, - saveChanges: () => Promise.resolve(), - }; - } - - @action - selectSubject(subject: SubjectModel): void { - const { - searchOptions, - onSearchOptionsUpdated, - } = this.args; - - if (this.provider) { - this.analytics.track( - 'filter', - 'add', - `Discover - subject ${subject.text} ${this.provider.name}`, - ); - } else { - this.analytics.track('filter', 'add', `Discover - subject ${subject.taxonomyName}`); - } - - const filterToAdd = newSubjectFilter(subject); - const subjectTerm = getSubjectTerm(subject); - const parentFilters = getAncestryFilters(subjectTerm); - - onSearchOptionsUpdated(searchOptions.addFilters(filterToAdd, ...parentFilters)); - } - - @action - unselectSubject(subject: SubjectModel): void { - const { - args: { - searchOptions, - onSearchOptionsUpdated, - }, - selectedSubjectFilters, - } = this; - - if (this.provider) { - this.analytics.track( - 'filter', - 'remove', - `Discover - subject ${subject.text} ${this.provider.name}`, - ); - } else { - this.analytics.track('filter', 'remove', `Discover - subject ${subject.taxonomyName}`); - } - - const subjectTerm = getSubjectTerm(subject); - - const filtersToRemove = selectedSubjectFilters.filter( - f => (f.value as string).startsWith(subjectTerm), - ); - - onSearchOptionsUpdated(searchOptions.removeFilters(...filtersToRemove)); - } -} diff --git a/lib/registries/addon/components/registries-subjects-facet/template.hbs b/lib/registries/addon/components/registries-subjects-facet/template.hbs deleted file mode 100644 index 9ae740ef8cb..00000000000 --- a/lib/registries/addon/components/registries-subjects-facet/template.hbs +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/lib/registries/addon/discover/controller.ts b/lib/registries/addon/discover/controller.ts deleted file mode 100644 index 82b9b7bf823..00000000000 --- a/lib/registries/addon/discover/controller.ts +++ /dev/null @@ -1,397 +0,0 @@ -import Store from '@ember-data/store'; -import EmberArray, { A } from '@ember/array'; -import Controller from '@ember/controller'; -import { action, computed } from '@ember/object'; -import { inject as service } from '@ember/service'; -import { waitFor } from '@ember/test-waiters'; -import { restartableTask, task, timeout } from 'ember-concurrency'; -import { taskFor } from 'ember-concurrency-ts'; -import Intl from 'ember-intl/services/intl'; -import QueryParams from 'ember-parachute'; -import { is, OrderedSet } from 'immutable'; -import Media from 'ember-responsive'; - -import config from 'ember-osf-web/config/environment'; -import ProviderModel from 'ember-osf-web/models/provider'; -import Analytics from 'ember-osf-web/services/analytics'; -import discoverStyles from 'registries/components/registries-discover-search/styles'; -import { SearchFilter, SearchOptions, SearchOrder, SearchResults } from 'registries/services/search'; -import ShareSearch, { - ShareRegistration, - ShareTermsAggregation, - ShareTermsFilter, -} from 'registries/services/share-search'; - -// Helper for Immutable.is as it doesn't like Native Arrays -function isEqual(obj1: any, obj2: any) { - if (Array.isArray(obj1) && Array.isArray(obj2)) { - if (obj1.length !== obj2.length) { - return false; - } - - for (let i = 0; i < obj1.length; i++) { - if (!(isEqual(obj1[i], obj2[i]))) { - return false; - } - } - - return true; - } - - return is(obj1, obj2); -} - -interface DiscoverQueryParams { - page: number; - query: string; - size: number; - sort: SearchOrder; - registrationTypes: ShareTermsFilter[]; - sourceNames: string[]; - subjects: ShareTermsFilter[]; -} - -const sortOptions = [ - new SearchOrder({ - ascending: true, - display: 'registries.discover.order.relevance', - key: undefined, - }), - new SearchOrder({ - ascending: true, - display: 'registries.discover.order.modified_ascending', - key: 'date', - }), - new SearchOrder({ - ascending: false, - display: 'registries.discover.order.modified_descending', - key: 'date', - }), -]; - -const queryParams = { - sourceNames: { - as: 'provider', - defaultValue: [] as string[], - serialize(value: string[]) { - return value.join('|'); - }, - deserialize(value: string) { - return value.split('|'); - }, - }, - registrationTypes: { - as: 'type', - refresh: true, - defaultValue: [] as ShareTermsFilter[], - serialize(value: ShareTermsFilter[]) { - return value.map(filter => filter.value).join('|'); - }, - deserialize(value: string) { - // Handle empty strings - if (value.trim().length < 1) { - return []; - } - return value.split('|').map( - registrationType => new ShareTermsFilter('registration_type', registrationType, registrationType), - ); - }, - }, - query: { - as: 'q', - defaultValue: '', - replace: true, - }, - size: { - defaultValue: 10, - serialize(value: number) { - return value.toString(); - }, - deserialize(value: string) { - return parseInt(value, 10) || this.defaultValue; - }, - }, - sort: { - defaultValue: sortOptions[0], - serialize(value: SearchOrder) { - if (value.key === 'date_modified') { - return ''; - } - - return `${value.ascending ? '' : '-'}${value.key || ''}`; - }, - deserialize(value: string) { - return sortOptions.find( - option => !!option.key - && value.endsWith(option.key) - && option.ascending === !value.startsWith('-'), - ) || sortOptions[0]; - }, - }, - page: { - defaultValue: 1, - serialize(value: number) { - return value.toString(); - }, - deserialize(value: string) { - return parseInt(value, 10) || this.defaultValue; - }, - }, - subjects: { - defaultValue: [] as ShareTermsFilter[], - serialize(value: ShareTermsFilter[]) { - return value.map(filter => filter.value).join(',,'); - }, - deserialize(value: string) { - return value.split(',,').map( - subjectTerm => { - const subjectPieces = subjectTerm.split('|'); - const display = subjectPieces[subjectPieces.length - 1]; - return new ShareTermsFilter('subjects', subjectTerm, display); - }, - ); - }, - }, -}; - -export const discoverQueryParams = new QueryParams(queryParams); - -export default class Discover extends Controller.extend(discoverQueryParams.Mixin) { - @service media!: Media; - @service intl!: Intl; - @service analytics!: Analytics; - @service store!: Store; - @service shareSearch!: ShareSearch; - - sortOptions = sortOptions; - - results: EmberArray = A([]); - searchable!: number; - totalResults = 0; - searchOptions!: SearchOptions; - - filterableSources: Array<{ - count: number, - filter: SearchFilter, - }> = []; - - get providerModel(): ProviderModel | undefined { - return undefined; - } - - // used to filter the counts/aggregations and all search results - get additionalFilters(): ShareTermsFilter[] { - return []; - } - - @computed('sourceNames.[]', 'shareSearch.allRegistries.[]') - get sourceFilters() { - return this.sourceNames.map( - name => this.shareSearch.allRegistries.find(r => r.name === name), - ).filter(Boolean).map( - source => new ShareTermsFilter('sources', source!.name, source!.display || source!.name), - ); - } - - @computed('searchOptions.size', 'totalResults') - get maxPage() { - const max = Math.ceil(this.totalResults / this.searchOptions.size); - if (max > (10000 / this.searchOptions.size)) { - return Math.ceil(10000 / this.searchOptions.size); - } - return max; - } - - @task - @waitFor - async getCountsAndAggs() { - const results = await this.shareSearch.registrations(new SearchOptions({ - size: 0, - modifiers: OrderedSet([ - new ShareTermsAggregation('sources', 'sources'), - ]), - filters: OrderedSet([ - ...this.additionalFilters, - ]), - })); - - const osfProviders = await this.store.query('registration-provider', { - 'page[size]': 100, - }); - - // Setting osfProviders on the share-search service - const urlRegex = config.OSF.url.replace(/^https?/, '^https?'); - const filteredProviders = osfProviders.filter(provider => provider.shareSource).map(provider => ({ - name: provider.shareSource!, // `name` should match what SHARE calls it - display: provider.name, - https: true, - urlRegex, - })); - this.shareSearch.set('osfProviders', filteredProviders); - - const filterableSources: Array<{count: number, filter: SearchFilter}> = []; - /* eslint-disable camelcase */ - const buckets = results.aggregations.sources.buckets as Array<{key: string, doc_count: number}>; - - // NOTE: config.externalRegistries is iterated over here to match its order. - for (const source of this.shareSearch.allRegistries) { - const bucket = buckets.find(x => x.key === source.name); - if (!bucket) { - continue; - } - - filterableSources.push({ - count: bucket.doc_count, - filter: new ShareTermsFilter( - 'sources', - bucket.key, - source.display || source.name, - ), - }); - } - /* eslint-enable camelcase */ - - this.set('searchable', results.total); - this.set('filterableSources', filterableSources); - taskFor(this.doSearch).perform(); - } - - @restartableTask - @waitFor - async doSearch() { - // TODO-mob don't hard-code 'OSF' - - // Unless OSF is the only source, registration_type filters must be cleared - if (!(this.sourceNames.length === 1 && this.sourceNames[0]! === 'OSF Registries')) { - this.set('registrationTypes', A([])); - } - - // If query has changed but page has not changed reset page to 1. - // The page check stops other tests from breaking - if (this.searchOptions && this.searchOptions.query !== this.query && this.searchOptions.page === this.page) { - this.set('page', 1); - } - - let options = new SearchOptions({ - query: this.query, - size: this.size, - page: this.page, - order: this.sort, - filters: OrderedSet([ - ...this.sourceFilters, - ...this.registrationTypes, - ...this.additionalFilters, - ]), - }); - - // If there is no query, no filters, and no sort, default to -date_modified rather - // than relevance. - if (!options.order.key && (!options.query || options.query === '') && options.filters.size === 0) { - options = options.set('order', new SearchOrder({ - display: 'registries.discover.order.relevance', - ascending: false, - key: 'date_modified', - })); - } - - this.set('searchOptions', options); - - await timeout(250); - - const results: SearchResults = await this.shareSearch.registrations(options); - - this.set('results', A(results.results)); - this.set('totalResults', results.total); - } - - setup() { - taskFor(this.getCountsAndAggs).perform(); - } - - queryParamsDidChange() { - taskFor(this.doSearch).perform(); - } - - @action - onSearchOptionsUpdated(options: SearchOptions) { - const sources: ShareTermsFilter[] = []; - const registrationTypes: ShareTermsFilter[] = []; - const subjects: ShareTermsFilter[] = []; - - for (const filter of options.filters.values()) { - if (filter.key === 'sources') { - sources.push(filter as ShareTermsFilter); - } - - if (filter.key === 'registration_type') { - registrationTypes.push(filter as ShareTermsFilter); - } - - if (filter.key === 'subjects') { - subjects.push(filter as ShareTermsFilter); - } - } - - const changes = {} as Discover; - - if (!isEqual(this.sourceFilters, sources)) { - changes.page = 1; - changes.sourceNames = sources.map(filter => filter.value.toString()); - } - - if (!isEqual(this.registrationTypes, registrationTypes)) { - changes.page = 1; - changes.registrationTypes = registrationTypes; - } - - if (!isEqual(this.subjects, subjects)) { - changes.page = 1; - changes.subjects = subjects; - } - - // If any filters are changed page is reset to 1 - this.setProperties(changes); - } - - @action - changePage(page: number) { - this.set('page', page); - - // Get the application owner by using - // passed down services as rootElement - // isn't defined on engines' owners - const element = document.querySelector(`.${discoverStyles.Discover__Body}`) as HTMLElement; - if (!element) { - return; - } - element.scrollIntoView(); - } - - @action - onSearch(value: string) { - // Set page to 1 here to ensure page is always reset when updating a query - this.setProperties({ page: 1, query: value }); - // If query or page don't actually change ember won't fire related events - // So always kick off a doSearch task to allow forcing a "re-search" - taskFor(this.doSearch).perform(); - } - - @action - setOrder(value: SearchOrder) { - if (this.providerModel) { - this.analytics.track( - 'dropdown', - 'select', - `Discover - Sort By: ${this.intl.t(value.display)} ${this.providerModel.name}`, - ); - } else { - this.analytics.track('dropdown', 'select', `Discover - Sort By: ${this.intl.t(value.display)}`); - } - // Set page to 1 here to ensure page is always reset when changing the order/sorting of a search - this.setProperties({ page: 1, sort: value }); - } - - get isMobile() { - return this.media.isMobile; - } -} diff --git a/lib/registries/addon/discover/route.ts b/lib/registries/addon/discover/route.ts deleted file mode 100644 index 9c10dfe213f..00000000000 --- a/lib/registries/addon/discover/route.ts +++ /dev/null @@ -1,19 +0,0 @@ -import Route from '@ember/routing/route'; -import RouterService from '@ember/routing/router-service'; -import { inject as service } from '@ember/service'; - -export default class RegistriesDiscoverRoute extends Route { - @service router!: RouterService; - afterModel() { - this.router.transitionTo('search', { queryParams: { resourceType: 'Registration,RegistrationComponent' }}); - } - - buildRouteInfoMetadata() { - return { - osfMetrics: { - isSearch: true, - providerId: 'osf', - }, - }; - } -} diff --git a/lib/registries/addon/discover/styles.scss b/lib/registries/addon/discover/styles.scss deleted file mode 100644 index 04033d43ce1..00000000000 --- a/lib/registries/addon/discover/styles.scss +++ /dev/null @@ -1,97 +0,0 @@ -// stylelint-disable max-nesting-depth, selector-max-compound-selectors - -.ResultsHeader { - padding: 0 0 15px; - - h2 { - margin: 0; - } -} - -.SortDropDown__List { - background-color: $color-bg-gray-light; - left: auto; -} - -.SortDropDown__Option { - text-align: left; - background-color: transparent; - cursor: pointer; - color: #000; - text-decoration: none; - width: 100%; -} - -.RegistriesSearchResult { - h4 { - font-size: 24px; - font-weight: 400; - } - - svg { - color: var(--primary-color); - } -} - -.search-container { - width: 100%; - display: flex; - flex-direction: row; - justify-content: flex-start; - align-items: flex-start; - - &.mobile { - flex-direction: column; - - .search-options-container { - width: 100%; - } - - .search-header-container { - width: 100%; - } - } - - .search-options-container { - width: 30%; - } - - .search-header-container { - width: 70%; - padding-left: 15px; - - .no-results { - width: 100%; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - } - - .Pagination { - width: 100%; - display: flex; - flex-direction: row; - justify-content: flex-end; - align-items: center; - padding-right: 12px; - } - - .loading-container, - .error-container { - width: 100%; - display: flex; - flex-direction: row; - justify-content: center; - align-items: center; - padding-top: 15px; - padding-bottom: 15px; - } - } -} - -.lead { - margin-bottom: 20px; - font-weight: 300; - line-height: 1.4; -} diff --git a/lib/registries/addon/discover/template.hbs b/lib/registries/addon/discover/template.hbs deleted file mode 100644 index 6819a9ed580..00000000000 --- a/lib/registries/addon/discover/template.hbs +++ /dev/null @@ -1,107 +0,0 @@ -{{page-title (t 'registries.discover.page_title') prepend=false}} - - - - {{registries-header - providerModel=this.providerModel - showHelp=true - value=(mut this.query) - onSearch=(action 'onSearch') - searchable=this.searchable - }} - - {{#registries-discover-search - results=this.results - isLoading=this.doSearch.isIdle - searchOptions=this.searchOptions - additionalFilters=this.additionalFilters - provider=this.providerModel - onSearchOptionsUpdated=(action this.onSearchOptionsUpdated) - as |discover| - }} -
-
- {{#if this.searchOptions}} - {{#discover.sidebar as |sidebar|}} - {{registries-provider-facet - options=this.filterableSources - searchOptions=this.searchOptions - onSearchOptionsUpdated=sidebar.onSearchOptionsUpdated - provider=this.providerModel - }} - - {{registries-registration-type-facet - searchOptions=this.searchOptions - onSearchOptionsUpdated=sidebar.onSearchOptionsUpdated - provider=this.providerModel - }} - - {{!-- TODO: this feature is not ready yet. Will be implemented in Phase2 Branded Reg --}} - {{!-- {{#if this.providerModel}} - {{registries-subjects-facet - provider=this.providerModel - searchOptions=this.searchOptions - onSearchOptionsUpdated=sidebar.onSearchOptionsUpdated - provider=this.providerModel - }} - {{/if}} --}} - {{/discover.sidebar}} - {{/if}} -
-
- {{#if (and this.doSearch.lastSuccessful this.searchOptions)}} - {{registries-discover-results-header - totalResults=this.totalResults - searchOptions=this.searchOptions - sortOptions=this.sortOptions - provider=this.providerModel - setOrder=(action this.setOrder) - }} - {{#discover.results - as |result| - }} - - {{/discover.results}} - - {{#unless this.totalResults}} -
-

- {{t 'registries.discover.no_results'}} -

- {{t 'registries.discover.try_broadening'}} -
- {{/unless}} - - {{#if (gt this.maxPage 1) }} -
- {{search-paginator - current=this.searchOptions.page - maximum=this.maxPage - pageChanged=(action 'changePage') - }} -
- {{/if}} - {{else if this.doSearch.isError}} -
- {{t 'registries.discover.error_loading'}} -
- {{else}} -
- {{loading-indicator dark=true}} -
- {{/if}} -
-
- - {{/registries-discover-search}} -
diff --git a/lib/registries/addon/routes.ts b/lib/registries/addon/routes.ts index e26f07fd5b7..544dd5f8141 100644 --- a/lib/registries/addon/routes.ts +++ b/lib/registries/addon/routes.ts @@ -2,7 +2,6 @@ import buildRoutes from 'ember-engines/routes'; export default buildRoutes(function() { this.route('index', { path: '/registries' }); - this.route('discover', { path: '/registries/discover' }); this.route('branded', { path: '/registries/:providerId' }, function() { this.route('discover'); this.route('new'); diff --git a/mirage/factories/institution.ts b/mirage/factories/institution.ts index fbbf35a0181..d13536b1095 100644 --- a/mirage/factories/institution.ts +++ b/mirage/factories/institution.ts @@ -26,6 +26,8 @@ export default Factory.extend({ lastUpdated() { return faker.date.recent(); }, + iri: faker.internet.url, + rorIri: faker.internet.url, withMetrics: trait({ afterCreate(institution, server) { const userMetrics = server.createList('institution-user', 15); diff --git a/mirage/factories/user.ts b/mirage/factories/user.ts index 5404f19779c..33fa6b1db7d 100644 --- a/mirage/factories/user.ts +++ b/mirage/factories/user.ts @@ -52,6 +52,7 @@ export default Factory.extend({ }, acceptedTermsOfService: true, canViewReviews: false, + allowIndexing: null, social: {}, employment() { const employmentCount = faker.random.number({ min: 1, max: 3 }); diff --git a/mirage/scenarios/registrations.ts b/mirage/scenarios/registrations.ts index c070527e04d..65cdb0e6426 100644 --- a/mirage/scenarios/registrations.ts +++ b/mirage/scenarios/registrations.ts @@ -49,6 +49,7 @@ export function registrationScenario( server.create('registration-provider', { id: defaultProvider, + brandedDiscoveryPage: false, shareSource: 'OSF Registries', name: 'OSF Registries', }, 'withAllSchemas'); diff --git a/mirage/serializers/preprint-provider.ts b/mirage/serializers/preprint-provider.ts index e2349cab283..d504ed68abb 100644 --- a/mirage/serializers/preprint-provider.ts +++ b/mirage/serializers/preprint-provider.ts @@ -1,5 +1,5 @@ import { ModelInstance } from 'ember-cli-mirage'; -import config from 'ember-get-config'; +import config from 'ember-osf-web/config/environment'; import PreprintProvider from 'ember-osf-web/models/preprint-provider'; import ApplicationSerializer, { SerializedRelationships } from './application'; diff --git a/mirage/views/search.ts b/mirage/views/search.ts index bbdabe24d1b..36fafeab240 100644 --- a/mirage/views/search.ts +++ b/mirage/views/search.ts @@ -303,6 +303,7 @@ export function cardSearch(_: Schema, __: Request) { type: 'related-property-path', id: 'propertyMatch1', attributes: { + propertyPathKey: 'rights', matchEvidence: [ { '@type': ['https://share.osf.io/vocab/2023/trove/IriMatchEvidence'], @@ -333,12 +334,14 @@ export function cardSearch(_: Schema, __: Request) { ], }, ], + suggestedFilterOperator: 'any-of', }, }, { type: 'related-property-path', id: 'propertyMatch2', attributes: { + propertyPathKey: 'datePublished', matchEvidence: [ { '@type': ['https://share.osf.io/vocab/2023/trove/IriMatchEvidence'], @@ -369,12 +372,14 @@ export function cardSearch(_: Schema, __: Request) { ], }, ], + suggestedFilterOperator: 'any-of', }, }, { type: 'related-property-path', id: 'propertyMatch3', attributes: { + propertyPathKey: 'funder', matchEvidence: [ { '@type': ['https://share.osf.io/vocab/2023/trove/IriMatchEvidence'], @@ -405,6 +410,7 @@ export function cardSearch(_: Schema, __: Request) { ], }, ], + suggestedFilterOperator: 'any-of', }, }, ], diff --git a/package.json b/package.json index 158131d737e..1989376b7ce 100644 --- a/package.json +++ b/package.json @@ -188,7 +188,6 @@ "ember-moment": "^10.0.0", "ember-onbeforeunload": "^2.0.0", "ember-page-title": "^6.2.2", - "ember-parachute": "^1.0.2", "ember-percy": "^1.5.0", "ember-power-select": "6.0.1", "ember-qrcode-shim": "^0.4.0", diff --git a/tests/acceptance/institutions/discover-test.ts b/tests/acceptance/institutions/discover-test.ts index e7925b7322a..05eaa4fd6f2 100644 --- a/tests/acceptance/institutions/discover-test.ts +++ b/tests/acceptance/institutions/discover-test.ts @@ -35,7 +35,7 @@ module('Acceptance | institutions | discover', hooks => { await visit('/institutions/has-users'); assert.equal(currentURL(), '/institutions/has-users', 'Current route is institutions discover'); // verify logo and description - assert.dom('[data-test-institution-logo]').exists('Institution header logo shown'); + assert.dom('[data-test-institution-banner]').exists('Institution banner shown'); assert.dom('[data-test-institution-description]').exists('Institution description is shown'); // verify mobile menu display assert.dom('[data-test-topbar-wrapper]').doesNotExist('Topbar not shown on mobile'); diff --git a/tests/engines/registries/acceptance/branded/discover-test.ts b/tests/engines/registries/acceptance/branded/discover-test.ts index ac34ab7b46c..37dde17b15e 100644 --- a/tests/engines/registries/acceptance/branded/discover-test.ts +++ b/tests/engines/registries/acceptance/branded/discover-test.ts @@ -27,30 +27,10 @@ module('Registries | Acceptance | branded.discover', hooks => { server.createList('registration', 3, { provider: this.brandedProvider }); }); - test('branded discover with no external providers', async function(this: ThisTestContext, assert) { + test('branded discover page renders', async function(this: ThisTestContext, assert) { await visit(`/registries/${this.brandedProvider.id}/discover`); await percySnapshot('branded discover page'); assert.equal(currentRouteName(), 'registries.branded.discover', 'On the branded discover page'); - - assert.dom('[data-test-active-filter]').doesNotExist('The given provider is not shown as an active filter'); - assert.dom('[data-test-source-filter-id]').exists({ count: 1 }, 'Only one provider is available'); - assert.dom('[data-test-source-filter-id]').isChecked('Provider facet checkbox is checked'); - assert.dom('[data-test-source-filter-id]').isDisabled('Provider facet checkbox is disabled'); - assert.dom('[data-test-link-other-registries]').exists('Link to other registries is shown'); - assert.dom('[data-test-provider-description]').containsText('Find out more', 'Provider description exists'); - assert.dom('[data-test-provider-description] a').exists('There is a link in the provider description'); - assert.ok(document.querySelector('link[rel="icon"][href="fakelink"]')); - }); - - test('branded discover with external providers', async function(this: ThisTestContext, assert) { - const externalProvider = server.create('external-provider', { shareSource: 'ClinicalTrials.gov' }); - server.createList('external-registration', 3, { provider: externalProvider }); - - await visit(`/registries/${this.brandedProvider.id}/discover`); - assert.dom('[data-test-source-filter-id]').exists({ count: 1 }, 'Only brand provider is shown'); - assert.dom(`[data-test-source-filter-id="${externalProvider.shareSource}"]`) - .doesNotExist('External provider is not shown'); - assert.ok(document.querySelector('link[rel="icon"][href="fakelink"]')); }); test('redirects branded.index to branded.discover', async function(this: ThisTestContext, assert) { @@ -58,7 +38,6 @@ module('Registries | Acceptance | branded.discover', hooks => { assert.equal(currentRouteName(), 'registries.branded.discover', 'successfully redirects index to discover'); - assert.dom(`[data-test-source-filter-id="${this.brandedProvider.shareSource}"]`).exists({ count: 1 }); }); test('redirects', async function(assert) { diff --git a/tests/integration/routes/settings/account/-components/opt-out-test.ts b/tests/integration/routes/settings/account/-components/opt-out-test.ts new file mode 100644 index 00000000000..1369ec2de30 --- /dev/null +++ b/tests/integration/routes/settings/account/-components/opt-out-test.ts @@ -0,0 +1,44 @@ +import { TestContext } from 'ember-test-helpers'; +import Service from '@ember/service'; +import { render } from '@ember/test-helpers'; +import { hbs } from 'ember-cli-htmlbars'; +import { percySnapshot } from 'ember-percy'; +import { setupRenderingTest } from 'ember-qunit'; +import { module, test } from 'qunit'; +import { setupIntl } from 'ember-intl/test-support'; + +const currentUserStub = Service.extend({ + user: { + allowIndexing: null, + }, +}); + +module('Integration | routes | settings | account | -components | opt-out', hooks => { + setupRenderingTest(hooks); + setupIntl(hooks); + + test('default allowIndexing: null', async function(this: TestContext, assert) { + this.owner.register('service:currentUser', currentUserStub); + await render(hbs``); + + assert.dom('[data-test-opt-out-panel]').exists('opt-out panel exists'); + assert.dom('[data-test-opt-out-panel] [data-test-panel-heading]') + .hasText( + 'Opt out of SHARE indexing', + 'title is correct', + ); + assert.dom('[data-test-opt-out-help-text]').exists('description exists'); + assert.dom('[data-test-indexing-opt-out-label]').containsText( + 'Opt out of SHARE indexing', 'opt out label is correct', + ); + assert.dom('[data-test-indexing-opt-in-label]').containsText( + 'Opt in to SHARE indexing', 'opt in label is correct', + ); + assert.dom('[data-test-indexing-opt-out-label] input').isNotChecked('Opt out radio button is not checked'); + assert.dom('[data-test-indexing-opt-in-label] input').isNotChecked('Opt in radio button is not checked'); + assert.dom('[data-test-update-indexing-preference-button]').containsText( + 'Update', 'update button is correct', + ); + await percySnapshot(assert); + }); +}); diff --git a/translations/en-us.yml b/translations/en-us.yml index c01af574c07..99c02c3ec78 100644 --- a/translations/en-us.yml +++ b/translations/en-us.yml @@ -233,10 +233,10 @@ search: textbox-placeholder: 'Enter search term(s) here' search-button-label: 'Search' search-help-label: 'Help tutorial' - total-results: '{count} results' + total-results: '{count} {count, plural, one {result} other {results} }' no-results: 'No results found' resource-type: - search-by: 'Search by resource type' + search-by: 'Search by object type' all: 'All' projects: 'Projects' registrations: 'Registrations' @@ -265,8 +265,9 @@ search: see-more-modal-text: 'Please select a filter to apply to your search.' see-more-modal-placeholder: 'Search for a filter to apply' load-more: 'Load more' - has-resource: 'Has {resource}' no-resource: 'No {resource}' + boolean-filters: + dropdown-label: 'Has related resource' institutions: institution-logo: 'Logo for ' @@ -1858,6 +1859,8 @@ routes: email: Email osf-components: search-result-card: + show_additional_metadata: 'Show additional metadata' + hide_additional_metadata: 'Hide additional metadata' language: Language url: URL resource_type: Resource type @@ -2560,6 +2563,14 @@ settings: confirm_button_text: Remove remove_fail: 'Revocation request failed. Please contact {supportEmail} if the problem persists.' remove_success: 'You have revoked this connected identity.' + opt-out: + title: 'Opt out of SHARE indexing' + description: 'By default, OSF users are indexed into SHARE, a free, open dataset of research metadata. This allows SHARE to include your user profile and research in its database, which is used by search engines and other services to make research more discoverable. You can opt out of this indexing by checking the box below. NOTE: Public projects, files, registrations, and preprints will still be indexed in SHARE.
Learn more about SHARE' + opt-out-label: 'Opt out of SHARE indexing' + opt-in-label: 'Opt in to SHARE indexing' + success: 'Successfully updated SHARE indexing preference.' + error: 'Could not update SHARE indexing preference. Try again in a few minutes. If the issue persists, please report it to {supportEmail}.' + button-label: 'Update' addons: title: 'Configure add-on accounts' notifications: diff --git a/yarn.lock b/yarn.lock index bcdfd3edcc5..7ffd33553ae 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12517,13 +12517,6 @@ ember-page-title@^6.2.2: dependencies: ember-cli-babel "^7.23.1" -ember-parachute@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/ember-parachute/-/ember-parachute-1.0.2.tgz#f147f6b29df100d467acc1ddf13761aef92e95de" - integrity sha512-wpTn179au/BE2+p4z9mQRh2Cy/a5J038oPpY2Buja3gDO/Qh64ny3klr4dxMRQ5GePniglkWK/LW3qitcs902A== - dependencies: - ember-cli-babel "^7.1.2" - ember-percy@^1.5.0: version "1.6.0" resolved "https://registry.yarnpkg.com/ember-percy/-/ember-percy-1.6.0.tgz#b0b7f27a4f60ac8992648337fc6d64404109d325"