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"