From f8b668337cbe179ca3d00ba665434c485f0c2af2 Mon Sep 17 00:00:00 2001 From: futa-ikeda <51409893+futa-ikeda@users.noreply.github.com> Date: Tue, 20 Aug 2024 17:03:39 -0400 Subject: [PATCH 01/96] [ENG-6074] Rework institutions dashboard page (#2289) - Ticket: [ENG-6074] - Feature flag: n/a ## Purpose - Rewrite existing institutional dashboard page to use OsfLayout and add tabs ## Summary of Changes - Update app router to add new subroutes to institutional dashboard - Add new component to handle layout of institutional dashboard subroutes (went this route instead of adding OsfLayout with an `{{outlet}}` to the parent route, as there may be some changes in layout based on subroutes) - Move components to Summary and Users tab - Trim any unneeded classes - Update test --- app/guid-node/registrations/styles.scss | 37 +------ .../styles.scss | 52 +++++++++ .../template.hbs | 84 +++++++++++++++ .../dashboard/-components/panel/styles.scss | 2 + app/institutions/dashboard/index/styles.scss | 5 + app/institutions/dashboard/index/template.hbs | 33 ++++++ .../dashboard/preprints/template.hbs | 8 ++ .../dashboard/projects/template.hbs | 8 ++ .../dashboard/registrations/template.hbs | 8 ++ app/institutions/dashboard/styles.scss | 100 ------------------ app/institutions/dashboard/template.hbs | 59 +---------- app/institutions/dashboard/users/styles.scss | 3 + app/institutions/dashboard/users/template.hbs | 9 ++ app/router.ts | 7 +- app/styles/_components.scss | 35 ++++++ .../metadata/metadata-tabs/styles.scss | 42 ++------ .../components/subjects/widget/styles.scss | 37 +------ .../acceptance/institutions/dashboard-test.ts | 34 +++++- translations/en-us.yml | 7 ++ 19 files changed, 302 insertions(+), 268 deletions(-) create mode 100644 app/institutions/dashboard/-components/institutional-dashboard-wrapper/styles.scss create mode 100644 app/institutions/dashboard/-components/institutional-dashboard-wrapper/template.hbs create mode 100644 app/institutions/dashboard/index/styles.scss create mode 100644 app/institutions/dashboard/index/template.hbs create mode 100644 app/institutions/dashboard/preprints/template.hbs create mode 100644 app/institutions/dashboard/projects/template.hbs create mode 100644 app/institutions/dashboard/registrations/template.hbs delete mode 100644 app/institutions/dashboard/styles.scss create mode 100644 app/institutions/dashboard/users/styles.scss create mode 100644 app/institutions/dashboard/users/template.hbs diff --git a/app/guid-node/registrations/styles.scss b/app/guid-node/registrations/styles.scss index 01662ce2136..36b839d970c 100644 --- a/app/guid-node/registrations/styles.scss +++ b/app/guid-node/registrations/styles.scss @@ -1,4 +1,5 @@ // stylelint-disable max-nesting-depth, selector-max-compound-selectors +@import 'app/styles/components'; .registration-container { margin: 30px; @@ -19,41 +20,7 @@ /* stylelint-disable selector-no-qualifying-type */ ul.tab-list { - margin-bottom: 10px; - border-bottom: 1px solid #ddd; - box-sizing: border-box; - color: rgb(51, 51, 51); - display: block; - line-height: 20px; - list-style-image: none; - list-style-position: outside; - list-style-type: none; - height: 41px; - padding: 0; - } - - /* stylelint-enable selector-no-qualifying-type */ - .tab-list { - li { - display: block; - position: relative; - margin-bottom: -1px; - float: left; - height: 41px; - padding: 10px 15px; - } - - li:global(.ember-tabs__tab--selected) { - background-color: #f8f8f8; - border-bottom: 2px solid #204762; - } - - li:hover { - border-color: transparent; - text-decoration: none; - background-color: #f8f8f8; - color: var(--primary-color); - } + @include tab-list; } } } diff --git a/app/institutions/dashboard/-components/institutional-dashboard-wrapper/styles.scss b/app/institutions/dashboard/-components/institutional-dashboard-wrapper/styles.scss new file mode 100644 index 00000000000..f3e5d70d4ce --- /dev/null +++ b/app/institutions/dashboard/-components/institutional-dashboard-wrapper/styles.scss @@ -0,0 +1,52 @@ +@import 'app/styles/layout'; +@import 'app/styles/components'; + +.container { + display: flex; + flex-direction: row; + flex-grow: 1; +} + +.heading-wrapper { + border-bottom: 1px solid #ddd; +} + +.banner { + padding: 15px 0; + display: flex; + align-items: center; + justify-content: space-between; + + div { + color: $color-text-gray-blue; + } +} + +.tab-list { + @include clamp-width; + @include tab-list; + white-space: nowrap; + position: relative; + overflow-x: auto; + overflow-y: hidden; + margin-bottom: 0; + border-bottom: 0; + + li { + display: inline-block; + + // match style in app/styles/_component.scss for selected tab + &:has(a:global(.active)) { + background-color: $bg-light; + border-bottom: 2px solid $color-blue; + } + + &:has(a:hover) { + border-color: transparent; + text-decoration: none; + background-color: $bg-light; + color: var(--primary-color); + } + } +} + diff --git a/app/institutions/dashboard/-components/institutional-dashboard-wrapper/template.hbs b/app/institutions/dashboard/-components/institutional-dashboard-wrapper/template.hbs new file mode 100644 index 00000000000..31771151a9e --- /dev/null +++ b/app/institutions/dashboard/-components/institutional-dashboard-wrapper/template.hbs @@ -0,0 +1,84 @@ + + +
+ {{@institution.name}} +
+ {{t 'institutions.dashboard.last_update'}} +
+
+ +
+ + {{yield (hash + left=layout.left + right=layout.right + top=layout.top + main=layout.main + )}} +
\ No newline at end of file diff --git a/app/institutions/dashboard/-components/panel/styles.scss b/app/institutions/dashboard/-components/panel/styles.scss index cba6f52f7da..0053c45c425 100644 --- a/app/institutions/dashboard/-components/panel/styles.scss +++ b/app/institutions/dashboard/-components/panel/styles.scss @@ -1,4 +1,6 @@ .panel { + margin-right: 12px; + .panel-overall { border: 0; margin-bottom: 30px; diff --git a/app/institutions/dashboard/index/styles.scss b/app/institutions/dashboard/index/styles.scss new file mode 100644 index 00000000000..72d5585b5b3 --- /dev/null +++ b/app/institutions/dashboard/index/styles.scss @@ -0,0 +1,5 @@ +.panel-wrapper { + display: flex; + padding-top: 12px; + text-align: right; +} diff --git a/app/institutions/dashboard/index/template.hbs b/app/institutions/dashboard/index/template.hbs new file mode 100644 index 00000000000..0b206aaf470 --- /dev/null +++ b/app/institutions/dashboard/index/template.hbs @@ -0,0 +1,33 @@ + + +
+ + {{#if this.model.taskInstance.value.summaryMetrics}} +

{{this.model.taskInstance.value.summaryMetrics.userCount}}

+ {{else}} + {{t 'institutions.dashboard.empty'}} + {{/if}} +
+ + + + + + +
+
+
diff --git a/app/institutions/dashboard/preprints/template.hbs b/app/institutions/dashboard/preprints/template.hbs new file mode 100644 index 00000000000..ee2f4e75caa --- /dev/null +++ b/app/institutions/dashboard/preprints/template.hbs @@ -0,0 +1,8 @@ + + +
+ {{t 'institutions.dashboard.tabs.preprints'}} +
+ {{t 'institutions.dashboard.content-placeholder'}} +
+
diff --git a/app/institutions/dashboard/projects/template.hbs b/app/institutions/dashboard/projects/template.hbs new file mode 100644 index 00000000000..3ddb8fe8e64 --- /dev/null +++ b/app/institutions/dashboard/projects/template.hbs @@ -0,0 +1,8 @@ + + +
+ {{t 'institutions.dashboard.tabs.projects'}} +
+ {{t 'institutions.dashboard.content-placeholder'}} +
+
diff --git a/app/institutions/dashboard/registrations/template.hbs b/app/institutions/dashboard/registrations/template.hbs new file mode 100644 index 00000000000..9e5e5edf0cc --- /dev/null +++ b/app/institutions/dashboard/registrations/template.hbs @@ -0,0 +1,8 @@ + + +
+ {{t 'institutions.dashboard.tabs.registrations'}} +
+ {{t 'institutions.dashboard.content-placeholder'}} +
+
diff --git a/app/institutions/dashboard/styles.scss b/app/institutions/dashboard/styles.scss deleted file mode 100644 index 3ee33c804b3..00000000000 --- a/app/institutions/dashboard/styles.scss +++ /dev/null @@ -1,100 +0,0 @@ -.banner { - padding: 15px 0; - display: flex; - align-items: center; - justify-content: space-between; - - div { - color: #263947; - } -} - -.dashboard-wrapper { - display: flex; -} - -.table-wrapper { - padding-right: 15px; - flex-grow: 1; -} - -.panel-wrapper { - padding-left: 15px; - text-align: right; -} - -.csv-button { - display: inline-block; - width: 40px; - height: 40px; - background: #fff; - padding: 7px 0; - border: 1px solid #ddd; - margin-bottom: 15px; - text-align: center; - - &:active, - &:hover { - background: #15a5eb; - border-color: #15a5eb; - } -} - -.sso-users-connected { - :global(.panel-body) { - h3 { - margin: 0; - font-size: 6vw; - font-weight: bold; - } - } -} - -.projects { - :global(.panel-body) { - h3 { - margin: 0 0 10px; - font-size: 3.75vw; - font-weight: bold; - } - - p { - margin: 0; - font-size: 16px; - } - } -} - -// Extra large devices (large desktops, 1200px and up) -@media (min-width: 1200px) { - .sso-users-connected { - :global(.panel-body) { - h3 { - font-size: 96px; - } - } - } - - .projects { - :global(.panel-body) { - h3 { - font-size: 72px; - } - } - } -} - -@media (max-width: 767px) { - .dashboard-wrapper { - flex-wrap: wrap-reverse; - } - - .panel-wrapper { - padding-left: 0; - width: 100%; - } - - .table-wrapper { - padding-right: 0; - } -} diff --git a/app/institutions/dashboard/template.hbs b/app/institutions/dashboard/template.hbs index fd5e7b6a496..4e4759d933d 100644 --- a/app/institutions/dashboard/template.hbs +++ b/app/institutions/dashboard/template.hbs @@ -1,59 +1,2 @@ {{page-title (t 'institutions.dashboard.title' institutionName=this.institution.unsafeName)}} -
-
- {{this.institution.name}} -
- {{t 'institutions.dashboard.last_update'}} -
-
-
-
- -
-
- {{#if this.csvHref}} - - - - {{/if}} - - {{#if this.summaryMetrics}} -

{{this.summaryMetrics.userCount}}

- {{else}} - {{t 'institutions.dashboard.empty'}} - {{/if}} -
- - - - - - -
-
-
\ No newline at end of file +{{outlet}} diff --git a/app/institutions/dashboard/users/styles.scss b/app/institutions/dashboard/users/styles.scss new file mode 100644 index 00000000000..93ca0026725 --- /dev/null +++ b/app/institutions/dashboard/users/styles.scss @@ -0,0 +1,3 @@ +.panel-wrapper { + margin-top: 12px; +} diff --git a/app/institutions/dashboard/users/template.hbs b/app/institutions/dashboard/users/template.hbs new file mode 100644 index 00000000000..2092830c468 --- /dev/null +++ b/app/institutions/dashboard/users/template.hbs @@ -0,0 +1,9 @@ + + +
+ +
+
+
diff --git a/app/router.ts b/app/router.ts index 099d5179d8f..41dc8912f6a 100644 --- a/app/router.ts +++ b/app/router.ts @@ -23,7 +23,12 @@ Router.map(function() { this.route('search'); this.route('institutions', function() { this.route('discover', { path: '/:institution_id' }); - this.route('dashboard', { path: '/:institution_id/dashboard' }); + this.route('dashboard', { path: '/:institution_id/dashboard' }, function() { + this.route('projects'); + this.route('registrations'); + this.route('preprints'); + this.route('users'); + }); }); this.route('preprints', function() { diff --git a/app/styles/_components.scss b/app/styles/_components.scss index 7aa123fa52c..0e1db0303ad 100644 --- a/app/styles/_components.scss +++ b/app/styles/_components.scss @@ -902,3 +902,38 @@ button.nav-user-dropdown { .logoutLink { cursor: pointer; } + +@mixin tab-list { + margin-bottom: 10px; + border-bottom: 1px solid $color-border-gray; + box-sizing: border-box; + color: $color-text-black; + display: block; + line-height: 20px; + list-style-image: none; + list-style-position: outside; + list-style-type: none; + height: 41px; + padding: 0; + + li { + display: block; + position: relative; + margin-bottom: -1px; + float: left; + height: 41px; + padding: 10px 15px; + } + + li.ember-tabs__tab--selected { + background-color: $bg-light; + border-bottom: 2px solid $color-blue; + } + + li:hover { + border-color: transparent; + text-decoration: none; + background-color: $bg-light; + color: var(--primary-color); + } +} diff --git a/lib/osf-components/addon/components/metadata/metadata-tabs/styles.scss b/lib/osf-components/addon/components/metadata/metadata-tabs/styles.scss index 38429c3b567..507ef4712cf 100644 --- a/lib/osf-components/addon/components/metadata/metadata-tabs/styles.scss +++ b/lib/osf-components/addon/components/metadata/metadata-tabs/styles.scss @@ -1,4 +1,5 @@ // stylelint-disable max-nesting-depth, selector-max-compound-selectors +@import 'app/styles/components'; .metadata-tab-container { width: 100%; @@ -31,51 +32,20 @@ /* stylelint-disable selector-no-qualifying-type */ ul.tab-list { - margin-bottom: 10px; - border-bottom: 1px solid #ddd; - box-sizing: border-box; - color: rgb(51, 51, 51); - display: block; - line-height: 20px; - list-style-image: none; - list-style-position: outside; - list-style-type: none; + @include tab-list; height: 42px; - padding: 0; - - &.showMore { - height: fit-content; - } - } - - /* stylelint-enable selector-no-qualifying-type */ - .tab-list { overflow: hidden; .dark { font-weight: bold; } - li { - cursor: pointer; - display: block; - position: relative; - margin-bottom: -1px; - float: left; - height: 41px; - padding: 10px 15px; - } - - li:global(.ember-tabs__tab--selected) { - background-color: #f8f8f8; - border-bottom: 2px solid #204762; + &.showMore { + height: fit-content; } - li:hover { - border-color: transparent; - text-decoration: none; - background-color: #f8f8f8; - color: var(--primary-color); + li { + cursor: pointer; } } } diff --git a/lib/osf-components/addon/components/subjects/widget/styles.scss b/lib/osf-components/addon/components/subjects/widget/styles.scss index ce72b1bac90..7f7a59aa1a5 100644 --- a/lib/osf-components/addon/components/subjects/widget/styles.scss +++ b/lib/osf-components/addon/components/subjects/widget/styles.scss @@ -1,39 +1,8 @@ +@import 'app/styles/components'; + .Tabs { /* stylelint-disable selector-no-qualifying-type */ ul.TabList { - margin-bottom: 10px; - border-bottom: 1px solid #ddd; - box-sizing: border-box; - color: rgb(51, 51, 51); - display: block; - line-height: 20px; - list-style-image: none; - list-style-position: outside; - list-style-type: none; - height: 41px; - padding: 0; - } - /* stylelint-enable selector-no-qualifying-type */ - .TabList { - li { - display: block; - position: relative; - margin-bottom: -1px; - float: left; - height: 41px; - padding: 10px 15px; - } - - li:global(.ember-tabs__tab--selected) { - background-color: #f8f8f8; - border-bottom: 2px solid #204762; - } - - li:hover { - border-color: transparent; - text-decoration: none; - background-color: #f8f8f8; - color: var(--primary-color); - } + @include tab-list; } } diff --git a/tests/acceptance/institutions/dashboard-test.ts b/tests/acceptance/institutions/dashboard-test.ts index b90ea119ca0..65b07ac4f66 100644 --- a/tests/acceptance/institutions/dashboard-test.ts +++ b/tests/acceptance/institutions/dashboard-test.ts @@ -21,9 +21,35 @@ module(moduleName, hooks => { '/institutions/has-users/dashboard', "Still at '/institutions/has-users/dashboard'.", ); - await percySnapshot(`${moduleName} - default`); - assert.dom('[data-test-next-page-button]').exists({ count: 1 }, 'next page button exists!?'); - await click('[data-test-next-page-button]'); - await percySnapshot(`${moduleName} - next page`); + + assert.dom('[data-test-page-tab="summary"]').exists('Summary tab exists'); + assert.dom('[data-test-page-tab="users"]').exists('Users tab exists'); + assert.dom('[data-test-page-tab="projects"]').exists('Projects tab exists'); + assert.dom('[data-test-page-tab="registrations"]').exists('Regitrations tab exists'); + assert.dom('[data-test-page-tab="preprints"]').exists('Preprints tab exists'); + + // Summary tab + await percySnapshot(`${moduleName} - summary`); + assert.dom('[data-test-page-tab="summary"]').hasClass('active', 'Summary tab is active by default'); + + // Users tab + await click('[data-test-page-tab="users"]'); + await percySnapshot(`${moduleName} - users`); + assert.dom('[data-test-page-tab="users"]').hasClass('active', 'Users tab is active'); + + // Projects tab + await click('[data-test-page-tab="projects"]'); + await percySnapshot(`${moduleName} - projects`); + assert.dom('[data-test-page-tab="projects"]').hasClass('active', 'Projects tab is active'); + + // Registrations tab + await click('[data-test-page-tab="registrations"]'); + await percySnapshot(`${moduleName} - registrations`); + assert.dom('[data-test-page-tab="registrations"]').hasClass('active', 'Registrations tab is active'); + + // Preprints tab + await click('[data-test-page-tab="preprints"]'); + await percySnapshot(`${moduleName} - preprints`); + assert.dom('[data-test-page-tab="preprints"]').hasClass('active', 'Preprints tab is active'); }); }); diff --git a/translations/en-us.yml b/translations/en-us.yml index cc9858c2fcb..110d3a2c525 100644 --- a/translations/en-us.yml +++ b/translations/en-us.yml @@ -792,6 +792,13 @@ institutions: search_placeholder: 'Search institutions' load_more: 'Load more institutions' dashboard: + tabs: + summary: Summary + users: Users + projects: Projects + registrations: Registrations + preprints: Preprints + content-placeholder: Content coming soon # Delete this eventually pls title: '{institutionName} Dashboard' last_update: 'Updated every 24 hours' download_csv: 'Download CSV' From 9b3174a7f3cb36db24877b36fd08396b619aabc2 Mon Sep 17 00:00:00 2001 From: John Tordoff Date: Mon, 26 Aug 2024 12:28:58 -0400 Subject: [PATCH 02/96] update user model for institutional dashboard (#2299) ## Purpose Make sure the institutional dashboard can display all relevant information. ## Summary of Changes - add fields to user model - adds fields to mirage factories --- app/models/institution-user.ts | 10 ++++++++++ mirage/factories/institution-user.ts | 30 ++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/app/models/institution-user.ts b/app/models/institution-user.ts index 0ce79fde66a..4cc703cfed7 100644 --- a/app/models/institution-user.ts +++ b/app/models/institution-user.ts @@ -9,6 +9,16 @@ export default class InstitutionUserModel extends OsfModel { @attr('fixstring') department?: string; @attr('number') publicProjects!: number; @attr('number') privateProjects!: number; + @attr('number') publicRegistrationCount!: number; + @attr('number') embargoedRegistrationCount!: number; + @attr('number') publishedPreprintCount!: number; + @attr('number') publicFileCount!: number; + @attr('number') storageByteCount!: number; + @attr('number') totalObjectCount!: number; + @attr('date') monthLastLogin!: Date; + @attr('date') monthLastActive!: Date; + @attr('date') accountCreationDate!: Date; + @attr('fixstring') orcidId?: string; @belongsTo('user', { async: true }) user!: AsyncBelongsTo & UserModel; diff --git a/mirage/factories/institution-user.ts b/mirage/factories/institution-user.ts index ce3b3e276c8..beac8d9db6d 100644 --- a/mirage/factories/institution-user.ts +++ b/mirage/factories/institution-user.ts @@ -14,6 +14,36 @@ export default Factory.extend({ privateProjects() { return faker.random.number({ min: 0, max: 99 }); }, + publicRegistrationCount() { + return faker.random.number({ min: 0, max: 50 }); + }, + embargoedRegistrationCount() { + return faker.random.number({ min: 0, max: 50 }); + }, + publishedPreprintCount() { + return faker.random.number({ min: 0, max: 50 }); + }, + publicFileCount() { + return faker.random.number({ min: 0, max: 100 }); + }, + storageByteCount() { + return faker.random.number({ min: 1e7, max: 1e9 }); // Between 10MB and 1GB + }, + totalObjectCount() { + return faker.random.number({ min: 0, max: 1000 }); + }, + monthLastLogin() { + return faker.date.past(1); // Any date within the past year + }, + monthLastActive() { + return faker.date.past(1); // Any date within the past year + }, + accountCreationDate() { + return faker.date.past(10); // Any date within the past 10 years + }, + orcidId() { + return faker.random.uuid(); // Simulate an ORCID ID + }, afterCreate(institutionUser, server) { if (!institutionUser.userName && !institutionUser.userGuid) { const user = server.create('user'); From 6028288dc503df40feb36a62c13594f95942ebf0 Mon Sep 17 00:00:00 2001 From: futa-ikeda <51409893+futa-ikeda@users.noreply.github.com> Date: Mon, 26 Aug 2024 12:29:27 -0400 Subject: [PATCH 03/96] [ENG-6078][ENG-6080] Link to past reports (#2296) - Ticket: [ENG-6078][ENG-6080] - Feature flag: n/a ## Purpose - Add new URL field to institution model for getting to archived reports - Add link to archived reports to the institutional dashboard pages ## Summary of Changes - Update institution model and update mirage - Update institution-dashboard-wrapper to include new link - Update how model hook works so we don't have to pass around a taskInstance - Update args and such for this - Remove controller - Glimmer-ize the institutional-user-list component --- .../styles.scss | 17 ++++++-- .../template.hbs | 20 +++++++-- .../institutional-users-list/component.ts | 42 ++++++++----------- .../institutional-users-list/template.hbs | 2 +- app/institutions/dashboard/controller.ts | 37 ---------------- app/institutions/dashboard/index/template.hbs | 15 +++---- app/institutions/dashboard/route.ts | 17 ++------ app/institutions/dashboard/template.hbs | 2 +- app/institutions/dashboard/users/template.hbs | 3 +- app/models/institution.ts | 1 + mirage/factories/institution.ts | 1 + .../acceptance/institutions/dashboard-test.ts | 2 + translations/en-us.yml | 2 +- 13 files changed, 66 insertions(+), 95 deletions(-) delete mode 100644 app/institutions/dashboard/controller.ts diff --git a/app/institutions/dashboard/-components/institutional-dashboard-wrapper/styles.scss b/app/institutions/dashboard/-components/institutional-dashboard-wrapper/styles.scss index f3e5d70d4ce..248b9b47ede 100644 --- a/app/institutions/dashboard/-components/institutional-dashboard-wrapper/styles.scss +++ b/app/institutions/dashboard/-components/institutional-dashboard-wrapper/styles.scss @@ -12,14 +12,25 @@ } .banner { + @include clamp-width; padding: 15px 0; display: flex; align-items: center; justify-content: space-between; +} - div { - color: $color-text-gray-blue; - } +.download-button { + display: inline-block; + border: 1px solid $color-border-gray; + border-radius: 2px; + padding: 6px 12px; +} + +.download-button:focus, +.download-button:hover { + color: $color-text-black; + background-color: $color-shadow-dark; + border-color: $color-border-gray-darker; } .tab-list { diff --git a/app/institutions/dashboard/-components/institutional-dashboard-wrapper/template.hbs b/app/institutions/dashboard/-components/institutional-dashboard-wrapper/template.hbs index 31771151a9e..4626d999433 100644 --- a/app/institutions/dashboard/-components/institutional-dashboard-wrapper/template.hbs +++ b/app/institutions/dashboard/-components/institutional-dashboard-wrapper/template.hbs @@ -4,9 +4,23 @@ >
{{@institution.name}} -
- {{t 'institutions.dashboard.last_update'}} -
+ {{#if @institution.linkToExternalReportsArchive}} +
+ + + {{t 'institutions.dashboard.download_past_reports_label'}} + + + +
+ {{/if}}
    { @service analytics!: Analytics; @service intl!: Intl; - @reads('modelTaskInstance.value.institution') institution?: InstitutionModel; - @reads('modelTaskInstance.value.departmentMetrics') departmentMetrics?: InstitutionDepartmentsModel[]; // Private properties - modelTaskInstance!: TaskInstance; - department = this.intl.t('institutions.dashboard.select_default'); - sort = 'user_name'; + @tracked department = this.intl.t('institutions.dashboard.select_default'); + @tracked sort = 'user_name'; reloadUserList?: () => void; - @computed('intl.locale') get defaultDepartment() { return this.intl.t('institutions.dashboard.select_default'); } - @computed('defaultDepartment', 'department', 'departmentMetrics.[]', 'institution') get departments() { let departments = [this.defaultDepartment]; - if (this.institution && this.departmentMetrics) { - const institutionDepartments = this.departmentMetrics.map((x: InstitutionDepartmentsModel) => x.name); + if (this.args.institution && this.args.departmentMetrics) { + const institutionDepartments = this.args.departmentMetrics.map((x: InstitutionDepartmentsModel) => x.name); departments = departments.concat(institutionDepartments); } return departments; } - @computed('defaultDepartment', 'department') get isDefaultDepartment() { return this.department === this.defaultDepartment; } - @computed('department', 'isDefaultDepartment', 'sort') get queryUsers() { const query = {} as Record; if (this.department && !this.isDefaultDepartment) { @@ -66,7 +63,7 @@ export default class InstitutionalUsersList extends Component { async searchDepartment(name: string) { await timeout(500); if (this.institution) { - const depts: InstitutionDepartmentsModel[] = await this.institution.queryHasMany('departmentMetrics', { + const depts: InstitutionDepartmentsModel[] = await this.args.institution.queryHasMany('departmentMetrics', { filter: { name, }, @@ -78,12 +75,7 @@ export default class InstitutionalUsersList extends Component { @action onSelectChange(department: string) { - this.analytics.trackFromElement(this.element, { - name: 'Department Select - Change', - category: 'select', - action: 'change', - }); - this.set('department', department); + this.department = department; if (this.reloadUserList) { this.reloadUserList(); } @@ -91,6 +83,6 @@ export default class InstitutionalUsersList extends Component { @action sortInstitutionalUsers(sort: string) { - this.set('sort', sort); + this.sort = sort; } } diff --git a/app/institutions/dashboard/-components/institutional-users-list/template.hbs b/app/institutions/dashboard/-components/institutional-users-list/template.hbs index 26aa1a76a95..a50ce4837d1 100644 --- a/app/institutions/dashboard/-components/institutional-users-list/template.hbs +++ b/app/institutions/dashboard/-components/institutional-users-list/template.hbs @@ -16,7 +16,7 @@ +
    - {{#if this.model.taskInstance.value.summaryMetrics}} -

    {{this.model.taskInstance.value.summaryMetrics.userCount}}

    + {{#if this.model.summaryMetrics}} +

    {{this.model.summaryMetrics.userCount}}

    {{else}} {{t 'institutions.dashboard.empty'}} {{/if}}
    diff --git a/app/institutions/dashboard/route.ts b/app/institutions/dashboard/route.ts index e03dc35352d..82b11783560 100644 --- a/app/institutions/dashboard/route.ts +++ b/app/institutions/dashboard/route.ts @@ -1,10 +1,7 @@ import Route from '@ember/routing/route'; import RouterService from '@ember/routing/router-service'; import { inject as service } from '@ember/service'; -import { waitFor } from '@ember/test-waiters'; import Store from '@ember-data/store'; -import { task } from 'ember-concurrency'; -import { taskFor } from 'ember-concurrency-ts'; import InstitutionModel from 'ember-osf-web/models/institution'; import InstitutionDepartmentModel from 'ember-osf-web/models/institution-department'; @@ -21,11 +18,10 @@ export default class InstitutionsDashboardRoute extends Route { @service router!: RouterService; @service store!: Store; - @task - @waitFor - async modelTask(institutionId: string) { + // eslint-disable-next-line camelcase + async model(params: { institution_id: string }) { try { - const institution = await this.store.findRecord('institution', institutionId, { + const institution = await this.store.findRecord('institution', params.institution_id, { adapterOptions: { include: ['summary_metrics'], }, @@ -49,11 +45,4 @@ export default class InstitutionsDashboardRoute extends Route { return undefined; } } - - // eslint-disable-next-line camelcase - model(params: { institution_id: string }) { - return { - taskInstance: taskFor(this.modelTask).perform(params.institution_id), - }; - } } diff --git a/app/institutions/dashboard/template.hbs b/app/institutions/dashboard/template.hbs index 4e4759d933d..1980fe88290 100644 --- a/app/institutions/dashboard/template.hbs +++ b/app/institutions/dashboard/template.hbs @@ -1,2 +1,2 @@ -{{page-title (t 'institutions.dashboard.title' institutionName=this.institution.unsafeName)}} +{{page-title (t 'institutions.dashboard.title' institutionName=this.model.institution.unsafeName)}} {{outlet}} diff --git a/app/institutions/dashboard/users/template.hbs b/app/institutions/dashboard/users/template.hbs index 2092830c468..810b5ba3b99 100644 --- a/app/institutions/dashboard/users/template.hbs +++ b/app/institutions/dashboard/users/template.hbs @@ -2,7 +2,8 @@
    diff --git a/app/models/institution.ts b/app/models/institution.ts index 9e2fa60ce7a..9b0432faf02 100644 --- a/app/models/institution.ts +++ b/app/models/institution.ts @@ -29,6 +29,7 @@ export default class InstitutionModel extends OsfModel { @attr('string') authUrl!: string; @attr('object') assets?: Assets; @attr('boolean', { defaultValue: false }) currentUserIsAdmin!: boolean; + @attr('fixstring') linkToExternalReportsArchive?: string; // only serialized when currentUserIsAdmin @attr('date') lastUpdated!: Date; @attr('fixstring') rorIri!: string; // identifier_domain in the admin app diff --git a/mirage/factories/institution.ts b/mirage/factories/institution.ts index fa2259e8e75..61dbc2eeb0e 100644 --- a/mirage/factories/institution.ts +++ b/mirage/factories/institution.ts @@ -23,6 +23,7 @@ export default Factory.extend({ }; }, currentUserIsAdmin: true, + linkToExternalReportsArchive: faker.internet.url, lastUpdated() { return faker.date.recent(); }, diff --git a/tests/acceptance/institutions/dashboard-test.ts b/tests/acceptance/institutions/dashboard-test.ts index 65b07ac4f66..5e9be8ff58c 100644 --- a/tests/acceptance/institutions/dashboard-test.ts +++ b/tests/acceptance/institutions/dashboard-test.ts @@ -22,6 +22,8 @@ module(moduleName, hooks => { "Still at '/institutions/has-users/dashboard'.", ); + assert.dom('[data-test-link-to-reports-archive]').exists('Link to download prior reports exists'); + assert.dom('[data-test-page-tab="summary"]').exists('Summary tab exists'); assert.dom('[data-test-page-tab="users"]').exists('Users tab exists'); assert.dom('[data-test-page-tab="projects"]').exists('Projects tab exists'); diff --git a/translations/en-us.yml b/translations/en-us.yml index 110d3a2c525..0cfae5f4904 100644 --- a/translations/en-us.yml +++ b/translations/en-us.yml @@ -800,7 +800,7 @@ institutions: preprints: Preprints content-placeholder: Content coming soon # Delete this eventually pls title: '{institutionName} Dashboard' - last_update: 'Updated every 24 hours' + download_past_reports_label: Reports download_csv: 'Download CSV' select_default: 'All Departments' users_list: From 1ef837e4a302b271ec021ec61c33fb05b414af16 Mon Sep 17 00:00:00 2001 From: futa-ikeda <51409893+futa-ikeda@users.noreply.github.com> Date: Fri, 30 Aug 2024 11:19:09 -0400 Subject: [PATCH 04/96] Link to reports updates (#2302) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Ticket: [] - Feature flag: n/a ## Purpose - Address some CR comments from prior PR https://github.com/CenterForOpenScience/ember-osf-web/pull/2296 ## Summary of Changes - Add a new arg `@fakeButton` to `OsfLink` to easily make a link look like a button (which maybe isn't a pattern we want to encourage, but at least now it's DRY when we do it 😬 ) - Update wording for hover text for reports - Have link open in a new window --- .../institutional-dashboard-wrapper/styles.scss | 14 -------------- .../institutional-dashboard-wrapper/template.hbs | 3 ++- .../addon/components/osf-link/component.ts | 1 + .../addon/components/osf-link/styles.scss | 6 ++++++ .../addon/components/osf-link/template.hbs | 2 +- translations/en-us.yml | 2 +- 6 files changed, 11 insertions(+), 17 deletions(-) diff --git a/app/institutions/dashboard/-components/institutional-dashboard-wrapper/styles.scss b/app/institutions/dashboard/-components/institutional-dashboard-wrapper/styles.scss index 248b9b47ede..e5461ebfc5d 100644 --- a/app/institutions/dashboard/-components/institutional-dashboard-wrapper/styles.scss +++ b/app/institutions/dashboard/-components/institutional-dashboard-wrapper/styles.scss @@ -19,20 +19,6 @@ justify-content: space-between; } -.download-button { - display: inline-block; - border: 1px solid $color-border-gray; - border-radius: 2px; - padding: 6px 12px; -} - -.download-button:focus, -.download-button:hover { - color: $color-text-black; - background-color: $color-shadow-dark; - border-color: $color-border-gray-darker; -} - .tab-list { @include clamp-width; @include tab-list; diff --git a/app/institutions/dashboard/-components/institutional-dashboard-wrapper/template.hbs b/app/institutions/dashboard/-components/institutional-dashboard-wrapper/template.hbs index 4626d999433..2f30e4de054 100644 --- a/app/institutions/dashboard/-components/institutional-dashboard-wrapper/template.hbs +++ b/app/institutions/dashboard/-components/institutional-dashboard-wrapper/template.hbs @@ -9,7 +9,8 @@ diff --git a/lib/osf-components/addon/components/osf-link/component.ts b/lib/osf-components/addon/components/osf-link/component.ts index 610506a1cf0..10507995497 100644 --- a/lib/osf-components/addon/components/osf-link/component.ts +++ b/lib/osf-components/addon/components/osf-link/component.ts @@ -35,6 +35,7 @@ export default class OsfLink extends Component { href?: string; queryParams?: Record; fragment?: string; + fakeButton?: boolean; rel: AnchorRel = 'noopener noreferrer'; target: AnchorTarget = '_self'; diff --git a/lib/osf-components/addon/components/osf-link/styles.scss b/lib/osf-components/addon/components/osf-link/styles.scss index 6385b4bf190..6251ba32b6f 100644 --- a/lib/osf-components/addon/components/osf-link/styles.scss +++ b/lib/osf-components/addon/components/osf-link/styles.scss @@ -6,3 +6,9 @@ text-decoration: underline; } } + +.Button { + composes: Button from '../button/styles.scss'; + composes: MediumButton from '../button/styles.scss'; + composes: SecondaryButton from '../button/styles.scss'; +} diff --git a/lib/osf-components/addon/components/osf-link/template.hbs b/lib/osf-components/addon/components/osf-link/template.hbs index da22168c274..92c5a5f1a2e 100644 --- a/lib/osf-components/addon/components/osf-link/template.hbs +++ b/lib/osf-components/addon/components/osf-link/template.hbs @@ -3,7 +3,7 @@ target={{this.target}} rel={{this.rel}} class='{{this.class}} {{if this.isActive 'active'}}' - local-class='OsfLink' + local-class='OsfLink {{if this.fakeButton 'Button'}}' onclick={{action this._onClick}} ...attributes > diff --git a/translations/en-us.yml b/translations/en-us.yml index 0cfae5f4904..1693749f865 100644 --- a/translations/en-us.yml +++ b/translations/en-us.yml @@ -800,7 +800,7 @@ institutions: preprints: Preprints content-placeholder: Content coming soon # Delete this eventually pls title: '{institutionName} Dashboard' - download_past_reports_label: Reports + download_past_reports_label: 'Previous reports' download_csv: 'Download CSV' select_default: 'All Departments' users_list: From 5cc16540b5bfca8adf2926a502b43e151ae3341b Mon Sep 17 00:00:00 2001 From: John Tordoff Date: Mon, 9 Sep 2024 09:37:01 -0400 Subject: [PATCH 05/96] [ENG-6116] Sort User Columns for Institutional Dashboard User table (#2300) - Ticket: [https://openscience.atlassian.net/browse/ENG-6116] - Feature flag: n/a ## Purpose This updates the old flaky institutional dashboard table with a new one that ## Summary of Changes - updates templates and css for new sorting arrow - adds new sorting arrow component (Product conversations supported not reusing SortButton) - adds tests to new componet - add to component code to allow toggling the arrow --- .../institutional-users-list/component.ts | 16 +- .../institutional-users-list/styles.scss | 56 +++-- .../institutional-users-list/template.hbs | 214 ++++++++++++------ app/models/institution-user.ts | 6 +- .../addon/components/sort-arrow/component.ts | 33 +++ .../addon/components/sort-arrow/styles.scss | 31 +++ .../addon/components/sort-arrow/template.hbs | 17 ++ .../app/components/sort-arrow/component.js | 1 + .../app/components/sort-arrow/template.js | 1 + .../components/sort-arrow/component-test.ts | 86 +++++++ .../component-test.ts | 16 +- translations/en-us.yml | 9 + 12 files changed, 385 insertions(+), 101 deletions(-) create mode 100644 lib/osf-components/addon/components/sort-arrow/component.ts create mode 100644 lib/osf-components/addon/components/sort-arrow/styles.scss create mode 100644 lib/osf-components/addon/components/sort-arrow/template.hbs create mode 100644 lib/osf-components/app/components/sort-arrow/component.js create mode 100644 lib/osf-components/app/components/sort-arrow/template.js create mode 100644 tests/integration/components/sort-arrow/component-test.ts diff --git a/app/institutions/dashboard/-components/institutional-users-list/component.ts b/app/institutions/dashboard/-components/institutional-users-list/component.ts index 6d002e9bb9d..a6632c482d3 100644 --- a/app/institutions/dashboard/-components/institutional-users-list/component.ts +++ b/app/institutions/dashboard/-components/institutional-users-list/component.ts @@ -82,7 +82,19 @@ export default class InstitutionalUsersList extends Component {{department}} - - - - {{#let (component 'sort-button' - class=(local-class 'sort-button') - sortAction=(action this.sortInstitutionalUsers) - sort=this.sort - ) as |SortButton|}} - - - {{t 'institutions.dashboard.users_list.name'}} - - - - {{t 'institutions.dashboard.users_list.department'}} - - - - {{t 'institutions.dashboard.users_list.projects'}} - - - - - {{t - - - {{t - - - {{/let}} - - - {{#if institutionalUser}} - - - {{institutionalUser.userName}} ({{institutionalUser.userGuid}}) - - - {{institutionalUser.department}} - {{institutionalUser.publicProjects}} - {{institutionalUser.privateProjects}} - {{else}} - {{placeholder.text lines=1}} - {{placeholder.text lines=1}} - {{placeholder.text lines=1}} - {{placeholder.text lines=1}} - {{/if}} - - - {{t 'institutions.dashboard.users_list.empty'}} - - - + + + {{#let (component 'sort-arrow' + class=(local-class 'sort-arrow') + sortAction=(action this.sortInstitutionalUsers) + sort=this.sort + ) as |SortArrow|}} + + +
    + {{t 'institutions.dashboard.users_list.name'}} + + + +
    + + +
    + {{t 'institutions.dashboard.users_list.department'}} + + + +
    + + +
    + {{t 'institutions.dashboard.users_list.osf_link'}} +
    + + +
    + {{t 'institutions.dashboard.users_list.public_projects'}} + + + +
    + + +
    + {{t 'institutions.dashboard.users_list.private_projects'}} + + + +
    + + +
    + {{t 'institutions.dashboard.users_list.public_registration_count'}} + + + +
    + + +
    + {{t 'institutions.dashboard.users_list.private_registration_count'}} + + + +
    + + +
    + {{t 'institutions.dashboard.users_list.published_preprint_count'}} + + + +
    + + +
    + {{t 'institutions.dashboard.users_list.public_file_count'}} + + + +
    + + +
    + {{t 'institutions.dashboard.users_list.storage_byte_count'}} + + + +
    + + +
    + {{t 'institutions.dashboard.users_list.account_created'}} + + + +
    + + +
    + {{t 'institutions.dashboard.users_list.month_last_login'}} + + + +
    + + +
    + {{t 'institutions.dashboard.users_list.month_last_active'}} + + + +
    + + + {{/let}} +
    + + {{#if institutionalUser}} + + + {{institutionalUser.userName}} + + + {{institutionalUser.department}} + + + {{institutionalUser.userGuid}} + + + {{institutionalUser.publicProjects}} + {{institutionalUser.privateProjects}} + {{institutionalUser.publicRegistrationCount}} + {{institutionalUser.embargoedRegistrationCount}} + {{institutionalUser.publishedPreprintCount}} + {{institutionalUser.publicFileCount}} + {{institutionalUser.userDataUsage}} + {{moment-format institutionalUser.accountCreationDate 'MM/YYYY'}} + {{moment-format institutionalUser.monthLastLogin 'MM/YYYY'}} + {{moment-format institutionalUser.monthLastActive 'MM/YYYY'}} + {{/if}} + + + {{t 'institutions.dashboard.users_list.empty'}} + +
    {{/if}} diff --git a/app/models/institution-user.ts b/app/models/institution-user.ts index 4cc703cfed7..44046c5bb0d 100644 --- a/app/models/institution-user.ts +++ b/app/models/institution-user.ts @@ -1,6 +1,6 @@ import { attr, belongsTo, AsyncBelongsTo } from '@ember-data/model'; - import UserModel from 'ember-osf-web/models/user'; +import humanFileSize from 'ember-osf-web/utils/human-file-size'; import OsfModel from './osf-model'; @@ -26,6 +26,10 @@ export default class InstitutionUserModel extends OsfModel { get userGuid() { return (this as InstitutionUserModel).belongsTo('user').id(); } + + get userDataUsage() { + return humanFileSize(this.storageByteCount); + } } declare module 'ember-data/types/registries/model' { diff --git a/lib/osf-components/addon/components/sort-arrow/component.ts b/lib/osf-components/addon/components/sort-arrow/component.ts new file mode 100644 index 00000000000..5eb645f5002 --- /dev/null +++ b/lib/osf-components/addon/components/sort-arrow/component.ts @@ -0,0 +1,33 @@ +import Component from '@glimmer/component'; +import { action } from '@ember/object'; + +interface SortArrowArgs { + sort: string; + sortBy: string; + sortAction: (sortField: string) => void; +} + +export default class SortArrow extends Component { + + get sortBy() { + return this.args.sortBy || 'user_name'; + } + + get isCurrentAscending() { + return this.args.sort === this.args.sortBy; + } + + get isCurrentDescending() { + return this.args.sort === `-${this.sortBy}`; + } + + get isSelected() { + return this.isCurrentAscending || this.isCurrentDescending; + } + + @action + handleSort() { + this.args.sortAction(this.args.sortBy); + } +} + diff --git a/lib/osf-components/addon/components/sort-arrow/styles.scss b/lib/osf-components/addon/components/sort-arrow/styles.scss new file mode 100644 index 00000000000..db354890b61 --- /dev/null +++ b/lib/osf-components/addon/components/sort-arrow/styles.scss @@ -0,0 +1,31 @@ +.sort-arrow-container { + vertical-align: middle; + + .selected { + color: $color-bg-gray-light; + } + + .not-selected { + color: $color-bg-blue-dark; + } +} + + +.arrow-button, +.arrow-button:active, +.arrow-button:focus, +.arrow-button:focus:active { + outline: none; + background: transparent; + border: 0; + align-items: center; + justify-content: space-between; + cursor: pointer; + color: $color-bg-gray-light; + width: 100%; + height: 100%; +} + +.arrow-button:hover { + color: $color-bg-gray-lighter; +} diff --git a/lib/osf-components/addon/components/sort-arrow/template.hbs b/lib/osf-components/addon/components/sort-arrow/template.hbs new file mode 100644 index 00000000000..58e88101ea7 --- /dev/null +++ b/lib/osf-components/addon/components/sort-arrow/template.hbs @@ -0,0 +1,17 @@ + + + diff --git a/lib/osf-components/app/components/sort-arrow/component.js b/lib/osf-components/app/components/sort-arrow/component.js new file mode 100644 index 00000000000..1dd776decb6 --- /dev/null +++ b/lib/osf-components/app/components/sort-arrow/component.js @@ -0,0 +1 @@ +export { default } from 'osf-components/components/sort-arrow/component'; diff --git a/lib/osf-components/app/components/sort-arrow/template.js b/lib/osf-components/app/components/sort-arrow/template.js new file mode 100644 index 00000000000..e9d4355e272 --- /dev/null +++ b/lib/osf-components/app/components/sort-arrow/template.js @@ -0,0 +1 @@ +export { default } from 'osf-components/components/sort-arrow/template'; diff --git a/tests/integration/components/sort-arrow/component-test.ts b/tests/integration/components/sort-arrow/component-test.ts new file mode 100644 index 00000000000..9dfb5a6be9f --- /dev/null +++ b/tests/integration/components/sort-arrow/component-test.ts @@ -0,0 +1,86 @@ +import { render, click } from '@ember/test-helpers'; +import { hbs } from 'ember-cli-htmlbars'; +import { setupRenderingTest } from 'ember-qunit'; +import { module, test } from 'qunit'; +import { setupMirage } from 'ember-cli-mirage/test-support'; +import { setupIntl } from 'ember-intl/test-support'; + +module('Integration | Component | sort-arrow', function(hooks) { + setupRenderingTest(hooks); + setupMirage(hooks); + setupIntl(hooks); + + hooks.beforeEach(async function(this: TestContext) { + this.store = this.owner.lookup('service:store'); + this.intl = this.owner.lookup('service:intl'); + this.set('noop', () => {/* noop */}); + }); + + test('it renders the correct icon when ascending', async function(assert) { + // Set properties before rendering the component + this.set('sortBy', 'user_name'); + this.set('sort', 'user_name'); + + // Render the component + await render(hbs``); + + // Assert that the component is rendered with the correct data-test-sort attribute + assert + .dom('[data-test-sort="user_name"]') + .exists('The sort arrow button is rendered with the correct data-test-sort attribute'); + + // Assert that the correct icon is displayed for ascending sort + assert + .dom('[data-test-sort="user_name"] [data-icon="arrow-up"]') + .exists('Displays the correct arrow-up icon when sort is ascending'); + }); + + test('it renders the correct icon when descending', async function(assert) { + this.set('sortBy', 'user_name'); + this.set('sort', '-user_name'); + + await render(hbs``); + + assert + .dom('[data-test-sort="user_name"]') + .exists('The sort arrow button is rendered with the correct data-test-sort attribute'); + + assert + .dom('[data-test-sort="user_name"] [data-icon="arrow-down"]') + .exists('Displays the correct arrow-down icon when sort is descending'); + }); + + test('it triggers the sort action on click', async function(assert) { + assert.expect(1); + + this.set('sortBy', 'user_name'); + this.set('sortAction', sortField => { + assert.strictEqual( + sortField, + 'user_name', + 'sortAction was called with the correct sort field', + ); + }); + + await render(hbs``); + + await click('[data-test-sort="user_name"]'); + }); + + test('it applies the correct attributes and classes', async function(assert) { + this.set('sortBy', 'user_name'); + this.set('sort', 'user_name'); + + await render(hbs``); + + assert + .dom('[data-test-sort="user_name"]') + .hasAttribute('data-analytics-name', 'Sort user_name', 'The correct data-analytics-name is applied'); + assert + .dom('[data-test-sort="user_name"]') + .hasAttribute('title', 'Sort descending', 'The correct title attribute is applied when ascending'); + assert + .dom('[data-test-sort="user_name"]') + .hasAttribute('aria-label', 'Sort descending', 'The correct aria-label is applied when ascending'); + }); +}); diff --git a/tests/integration/routes/institutions/dashboard/-components/institutional-users-list/component-test.ts b/tests/integration/routes/institutions/dashboard/-components/institutional-users-list/component-test.ts index 17509c6ad2c..ffe28b333d2 100644 --- a/tests/integration/routes/institutions/dashboard/-components/institutional-users-list/component-test.ts +++ b/tests/integration/routes/institutions/dashboard/-components/institutional-users-list/component-test.ts @@ -111,23 +111,23 @@ module('Integration | routes | institutions | dashboard | -components | institut assert.dom('[data-test-item-name]') .exists({ count: 3 }, '3 users'); - await click('[data-test-ascending-sort="user_name"]'); assert.dom('[data-test-item-name]') - .containsText('Hulk Hogan', 'Sorts by name ascendening'); + .containsText('Hulk Hogan', 'Sorts by name ascending by default'); assert.dom('[data-test-item-name] a:first-of-type') .hasAttribute('href'); - await click('[data-test-descending-sort="user_name"]'); + await click('[data-test-sort="user_name"]'); assert.dom('[data-test-item-name]') - .containsText('John Doe', 'Sorts by name descendening'); + .containsText('John Doe', 'Sorts by name descending'); - await click('[data-test-ascending-sort="department"]'); + await click('[data-test-sort="department"]'); assert.dom('[data-test-item-department]') - .hasText('Architecture', 'Sorts by department ascendening'); + .hasText('Psychology', 'Sorts by department descending'); - await click('[data-test-descending-sort="department"]'); + await click('[data-test-sort="department"]'); assert.dom('[data-test-item-department]') - .hasText('Psychology', 'Sorts by department descendening'); + .hasText('Architecture', 'Sorts by department ascending'); + }); }); diff --git a/translations/en-us.yml b/translations/en-us.yml index 1693749f865..92114191b51 100644 --- a/translations/en-us.yml +++ b/translations/en-us.yml @@ -810,6 +810,15 @@ institutions: public_projects: 'Public Projects' private_projects: 'Private Projects' empty: 'No users found matching search criteria.' + public_registration_count: 'Public Registrations' + private_registration_count: 'Private Registrations' + published_preprint_count: 'Preprints' + osf_link: 'OSF Link' + public_file_count: 'Files on OSF' + storage_byte_count: 'Total Data Stored on OSF' + account_created: 'Account Created' + month_last_login: 'Last Login' + month_last_active: 'Last Action' users_connected_panel: 'SSO Users Connected' projects_panel: 'Total Projects' departments_panel: Departments From 8dbbb81f7273cf3f755c347d4f0723dbb5ae5af3 Mon Sep 17 00:00:00 2001 From: futa-ikeda <51409893+futa-ikeda@users.noreply.github.com> Date: Tue, 10 Sep 2024 18:00:36 -0400 Subject: [PATCH 06/96] [ENG-6181] Update search mirage view (#2318) - Ticket: [ENG-6181] - Feature flag: n/a ## Purpose - Update mirage view for trove index-card-search to return some more realistic results ## Summary of Changes - Replace placekittens with placecats - Remove hard-coded return object in mirage/views/search.ts - add a new factory-type object that creates resourceMetadata for index-cards of a given type --- app/adapters/share-adapter.ts | 2 +- app/models/index-card.ts | 20 +- mirage/config.ts | 6 +- mirage/utils.ts | 2 +- mirage/views/search.ts | 639 ++++++++++++++++++++-------------- 5 files changed, 408 insertions(+), 261 deletions(-) diff --git a/app/adapters/share-adapter.ts b/app/adapters/share-adapter.ts index 5716e5b5b0a..ec624e2b900 100644 --- a/app/adapters/share-adapter.ts +++ b/app/adapters/share-adapter.ts @@ -5,7 +5,7 @@ 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'; + namespace = 'trove'; queryRecord(store: any, type: any, query: any) { // check if we aren't serving locally, otherwise add accessService query param to card/value searches diff --git a/app/models/index-card.ts b/app/models/index-card.ts index 3f64d81888a..81202784e03 100644 --- a/app/models/index-card.ts +++ b/app/models/index-card.ts @@ -16,6 +16,21 @@ export interface LanguageText { '@value': string; } +export enum OsfmapResourceTypes { + Project = 'Project', + ProjectComponent = 'ProjectComponent', + Registration = 'Registration', + RegistrationComponent = 'RegistrationComponent', + Preprint = 'Preprint', + File = 'File', + Person = 'Person', + Agent = 'Agent', + Organization = 'Organization', + Concept = 'Concept', + ConceptScheme = 'Concept:Scheme', +} + + export default class IndexCardModel extends Model { @service intl!: IntlService; @@ -36,7 +51,8 @@ export default class IndexCardModel extends Model { } get osfModelType() { - const types = this.resourceMetadata.resourceType.map( (item: any) => item['@id']); + const types: OsfmapResourceTypes = this.resourceMetadata.resourceType + .map((item: Record<'@id', OsfmapResourceTypes>) => item['@id']); if (types.includes('Project') || types.includes('ProjectComponent')) { return 'node'; } else if (types.includes('Registration') || types.includes('RegistrationComponent')) { @@ -74,7 +90,7 @@ export default class IndexCardModel extends Model { async getOsfModel(options?: object) { const identifier = this.resourceIdentifier; if (identifier && this.osfModelType) { - const guid = this.guidFromIdentifierList(identifier); + const guid = this.guidFromIdentifierList(); if (guid) { const osfModel = await this.store.findRecord(this.osfModelType, guid, options); this.osfModel = osfModel; diff --git a/mirage/config.ts b/mirage/config.ts index ee5a5acbb0c..c19f6aa379f 100644 --- a/mirage/config.ts +++ b/mirage/config.ts @@ -65,13 +65,9 @@ export default function(this: Server) { // SHARE-powered search endpoints this.urlPrefix = shareBaseUrl; - this.namespace = '/api/v3/'; + this.namespace = '/trove/'; // /api/v3/ works as well, but /trove/ is the preferred URL this.get('/index-card-search', cardSearch); this.get('/index-value-search', valueSearch); - // this.get('/index-card/:id', Detail); - this.get('/index-card-searches', cardSearch); - this.get('/index-value-searches', valueSearch); - // this.get('/index-cards/:id', Detail); this.urlPrefix = osfUrl; this.namespace = '/api/v1/'; diff --git a/mirage/utils.ts b/mirage/utils.ts index 616fe5bd192..b9fcd0569b6 100644 --- a/mirage/utils.ts +++ b/mirage/utils.ts @@ -1,7 +1,7 @@ import faker from 'faker'; export function placekitten(width: number, height: number, n = 12) { - return `https://placekitten.com/${width}/${height}?image=${n % 17}`; + return `https://placecats.com/${width}/${height}?image=${n % 17}`; } export function randomGravatar(size?: number) { diff --git a/mirage/views/search.ts b/mirage/views/search.ts index e26a1e9369d..d0eca519503 100644 --- a/mirage/views/search.ts +++ b/mirage/views/search.ts @@ -1,13 +1,233 @@ import { Request, Schema } from 'ember-cli-mirage'; import faker from 'faker'; +import { PaginationLinks } from 'jsonapi-typescript'; -export function cardSearch(_: Schema, __: Request) { - // TODO: replace with a real index-card-search and use request to populate attrs +import { OsfmapResourceTypes } from 'ember-osf-web/models/index-card'; + +const rdfString = 'https://www.w3.org/1999/02/22-rdf-syntax-ns#string'; +const resourceMetadataByType: Partial> = { + Registration: () => ({ + '@id': faker.random.uuid(), + // accessService: [{}], + archivedAt: [{}], // archive.org URL + conformsTo: [{ // Registration Schema + '@id': 'https://api.osf.io/v2/schemas/registrations/564d31db8c5e4a7c9694b2be/', + title: [{ + '@value': 'Open-Ended Registration', + '@type': rdfString, + }], + }], + creator: [_sharePersonField()], + dateAvailable: [_shareDateField()], + dateCopyrighted: [_shareDateField()], + dateCreated: [_shareDateField()], + dateModified: [_shareDateField()], + description: [{ + '@value': faker.lorem.sentence(), + '@type': rdfString, + }], + hasPart: [{}], // RegistrationComponent + hostingInstition: [_shareOrganizationField()], + identifier: [_shareIdentifierField()], + isVersionOf: [{}], // if this is from a project + keyword: [{ // tags + '@value': faker.random.word(), + '@type': rdfString, + }], + publisher: [_shareOrganizationField()], // Registration Provider + resourceNature: [{ // Registration Category + '@id': 'https://schema.datacite.org/meta/kernel-4/#StudyRegistration', + displayLabel: [{ + '@value': 'StudyRegistration', + '@language': 'en', + }], + }], + resourceType: [{ '@id': 'Registration' }], + rights: [_shareLicneseField()], + sameAs: [{}], // some DOI + subject: [_shareSubjectField()], + title: [{ + '@value': faker.lorem.words(3), + '@type': rdfString, + }], + }), + Project: () => ({ + '@id': faker.internet.url(), + // accessService: [{}], + creator: [_sharePersonField()], + dateCopyrighted: [_shareDateField()], + dateCreated: [_shareDateField()], + dateModified: [_shareDateField()], + description: [{ + '@value': faker.lorem.sentence(), + '@type': rdfString, + }], + hasPart: [{}], // ProjectComponent + hostingInstition: [_shareOrganizationField()], + identifier: [_shareIdentifierField()], + keyword: [{ // tags + '@value': faker.random.word(), + '@type': rdfString, + }], + publisher: [_shareOrganizationField()], + resourceType: [{ '@id': 'Project' }], + rights: [_shareLicneseField()], + sameAs: [{}], // some DOI + subject: [_shareSubjectField()], + title: [{ + '@value': faker.lorem.words(3), + '@type': rdfString, + }], + }), + Preprint: () => ({ + '@id': faker.internet.url(), + // accessService: [{}], + creator: [_sharePersonField()], + dateAccepted: [_shareDateField()], + dateCopyrighted: [_shareDateField()], + dateCreated: [_shareDateField()], + dateSubmitted: [_shareDateField()], + dateModified: [_shareDateField()], + description: [{ + '@value': faker.lorem.sentence(), + '@type': rdfString, + }], + hasPart: [{}], // File + hostingInstition: [_shareOrganizationField()], + identifier: [_shareIdentifierField()], + // isSupplementedBy: [{}], // if this links a project + keyword: [{ // tags + '@value': faker.random.word(), + '@type': rdfString, + }], + omits: [{ + ommittedMetadataProperty: [ + { '@id': 'hasPreregisteredStudyDesign' }, + { '@id': 'hasPreregisteredAnalysisPlan' }, + ], + }], + publisher: [_shareOrganizationField()], // Preprint Provider + resourceNature: [{ + '@id': 'https://schema.datacite.org/meta/kernel-4/#Preprint', + displayLabel: [{ + '@value': 'Preprint', + '@language': 'en', + }], + }], + resourceType: [{ '@id': 'Preprint' }], + rights: [_shareLicneseField()], + sameAs: [{}], // some DOI + statedConflictOfInterest: [{ + '@id': 'no-confict-of-interest', + }], + subject: [_shareSubjectField()], + title: [{ + '@value': faker.lorem.words(3), + '@type': rdfString, + }], + }), + Agent: () => ({ + '@id': faker.internet.url(), + // accessService: [{}], + affiliation: [_shareOrganizationField()], + identifier: [ + _shareIdentifierField(), + { + '@value': 'https://orcid.org/0000-0000-0000-0000', + '@type': rdfString, + }, + ], + name: [{ + '@value': faker.name.findName(), + '@type': rdfString, + }], + resourceType: [{ '@id': OsfmapResourceTypes.Person }, { '@id': OsfmapResourceTypes.Agent }], + sameAs: [{ '@id': 'https://orcid.org/0000-0000-0000-0000' }], // some ORCID + }), +}; + +resourceMetadataByType.ProjectComponent = function() { return { + ...resourceMetadataByType.Project(), + resourceType: [{ '@id': 'ProjectComponent' }], + isPartOf: [{ // Parent Project + ...resourceMetadataByType.Project(), + }], + hasRoot: [{ // Root Project + ...resourceMetadataByType.Project(), + }], + }; +}; +resourceMetadataByType.RegistrationComponent = function() { + return { + ...resourceMetadataByType.Registration(), + resourceType: [{ '@id': 'RegistrationComponent' }], + isPartOf: [{ // Parent Registration + ...resourceMetadataByType.Registration(), + }], + hasRoot: [{ // Root Registration + ...resourceMetadataByType.Registration(), + }], + }; +}; +resourceMetadataByType.File = function() { + return { + '@id': faker.internet.url(), + // accessService: [{}], + dateCreated: [_shareDateField()], + dateModified: [_shareDateField()], + description: [{ + '@value': faker.lorem.sentence(), + '@type': rdfString, + }], + fileName: [{ + '@value': faker.system.fileName(), + '@type': rdfString, + }], + filePath: [{ + '@value': faker.system.filePath(), + '@type': rdfString, + }], + identifier: [_shareIdentifierField()], + isContainedBy: [{ // Parent Project + ...resourceMetadataByType.Project(), + }], + language: [{ + '@value': 'eng', + '@type': rdfString, + }], + // 'osf:hasFileVersion': [{}], // FileVersion + resourceNature: [{ + '@id': 'https://schema.datacite.org/meta/kernel-4/#Dataset', + displayLabel: [{ + '@value': 'Dataset', + '@language': 'en', + }], + }], + resourceType: [{ '@id': 'File' }], + title: [{ + '@value': faker.lorem.words(3), + '@type': rdfString, + }], + }; +}; + +export function cardSearch(_: Schema, request: Request) { + const {queryParams} = request; + const pageCursor = queryParams['page[cursor]']; + const pageSize = queryParams['page[size]'] ? parseInt(queryParams['page[size]'], 10) : 10; + + // cardSearchFilter[resourceType] is a comma-separated list (e.g. 'Project,ProjectComponent') or undefined + let requestedResourceTypes = queryParams['cardSearchFilter[resourceType]']?.split(',') as OsfmapResourceTypes[]; + if (!requestedResourceTypes) { + requestedResourceTypes = Object.keys(resourceMetadataByType) as OsfmapResourceTypes[]; + } + + const indexCardSearch = { data: { type: 'index-card-search', - id: 'zzzzzz', - attributes:{ + id: faker.random.uuid(), + attributes: { cardSearchText: 'hello', cardSearchFilter: [ { @@ -28,27 +248,6 @@ export function cardSearch(_: Schema, __: Request) { totalResultCount: 3, }, relationships: { - searchResultPage: { - data: [ - { - type: 'search-result', - id: 'abc', - }, - { - type: 'search-result', - id: 'def', - }, - { - type: 'search-result', - id: 'ghi', - }, - ], - links: { - next: { - href: 'https://staging-share.osf.io/api/v3/index-card-search?page%5Bcursor%5D=lmnop', - }, - }, - }, relatedProperties: { data: [ { @@ -66,238 +265,14 @@ export function cardSearch(_: Schema, __: Request) { ], links: { next: { - href: 'https://staging-share.osf.io/api/v3/index-card-search?page%5Bcursor%5D=lmnop', + href: 'https://staging-share.osf.io/trove/index-card-search?page%5Bcursor%5D=lmnop', }, }, }, + searchResultPage: {}, }, }, included: [ - { - type: 'search-result', - id: 'abc', - attributes: { - matchEvidence: [ - { - '@type': ['https://share.osf.io/vocab/2023/trove/TextMatchEvidence'], - osfmapPropertyPath: 'description', - matchingHighlight: '... say hello!', - }, - { - '@type': ['https://share.osf.io/vocab/2023/trove/TextMatchEvidence'], - osfmapPropertyPath: 'title', - matchingHighlight: '... shout hello!', - }, - ], - }, - relationships: { - indexCard: { - data: { - type: 'index-card', - id: 'abc', - }, - links: { - related: 'https://share.osf.io/api/v2/index-card/abc', - }, - }, - }, - }, - { - type: 'search-result', - id: 'def', - attributes: { - matchEvidence: [ - { - '@type': ['https://share.osf.io/vocab/2023/trove/TextMatchEvidence'], - osfmapPropertyPath: 'description', - matchingHighlight: '... computer said hello world!', - }, - ], - }, - relationships: { - indexCard: { - data: { - type: 'index-card', - id: 'def', - }, - links: { - related: 'https://share.osf.io/api/v2/index-card/def', - }, - }, - }, - }, - { - type: 'search-result', - id: 'ghi', - attributes: { - matchEvidence: [ - { - '@type': ['https://share.osf.io/vocab/2023/trove/TextMatchEvidence'], - osfmapPropertyPath: 'title', - matchingHighlight: '... you said hello!', - }, - ], - }, - relationships: { - indexCard: { - data: { - type: 'index-card', - id: 'ghi', - }, - links: { - related: 'https://share.osf.io/api/v2/index-card/abc', - }, - }, - }, - }, - { - type: 'index-card', - id: 'abc', - attributes: { - resourceIdentifier: [ - 'https://osf.example/abcfoo', - 'https://doi.org/10.0000/osf.example/abcfoo', - ], - resourceMetadata: { - resourceType: [ - 'osf:Registration', - 'dcterms:Dataset', - ], - '@id': 'https://osf.example/abcfoo', - '@type': 'osf:Registration', - title: [ - { - '@value': 'I shout hello!', - '@language': 'en', - }, - ], - description: [ - { - '@value': 'I say hello!', - '@language': 'en', - }, - ], - isPartOf: [ - { - '@id': 'https://osf.example/xyzfoo', - '@type': 'osf:Registration', - title: [ - { - '@value': 'a parent!', - '@language': 'en', - }, - ], - }, - ], - hasPart: [ - { - '@id': 'https://osf.example/deffoo', - '@type': 'osf:Registration', - title: [ - { - '@value': 'a child!', - '@language': 'en', - }, - ], - }, - { - '@id': 'https://osf.example/ghifoo', - '@type': 'osf:Registration', - title: [ - { - '@value': 'another child!', - '@language': 'en', - }, - ], - }, - ], - subject: [ - { - '@id': 'https://subjects.org/subjectId', - '@type': 'dcterms:Subject', - label: [ - { - '@value': 'wibbleplop', - '@language': 'wi-bl', - }, - ], - }, - ], - creator: [{ - '@id': 'https://osf.example/person', - '@type': 'dcterms:Agent', - specificType: 'foaf:Person', - name: 'person person, prsn', - }], - }, - }, - links: { - self: 'https://share.osf.io/api/v2/index-card/abc', - resource: 'https://osf.example/abcfoo', - }, - }, - { - type: 'index-card', - id: 'def', - attributes: { - resourceIdentifier: [ - 'https://osf.example/abcfoo', - 'https://doi.org/10.0000/osf.example/abcfoo', - ], - resourceMetadata: { - resourceType: [ - 'osf:Registration', - 'dcterms:Dataset', - ], - '@id': 'https://osf.example/abcfoo', - '@type': 'osf:Registration', - title: [ - { - '@value': 'Hi!', - '@language': 'en', - }, - ], - }, - }, - links: { - self: 'https://share.osf.io/api/v2/index-card/ghi', - resource: 'https://osf.example/abcfoo', - }, - }, - { - type: 'index-card', - id: 'ghi', - attributes: { - resourceIdentifier: [ - 'https://osf.example/abcfoo', - 'https://doi.org/10.0000/osf.example/abcfoo', - ], - resourceMetadata: { - resourceType: [ - 'osf:Registration', - 'dcterms:Dataset', - ], - '@id': 'https://osf.example/abcfoo', - '@type': 'osf:Registration', - title: [ - { - '@value': 'Ahoj! That\'s hello in Czech!', - '@language': 'en', - }, - ], - description: [ - { - '@value': 'Some description', - '@language': 'en', - }, - ], - }, - }, - links: { - self: 'https://share.osf.io/api/v2/index-card/ghi', - resource: 'https://osf.example/abcfoo', - }, - }, // Related properties { type: 'related-property-path', @@ -415,6 +390,83 @@ export function cardSearch(_: Schema, __: Request) { }, ], }; + + const searchResultPageRelationship = { data: [] as any[], links: {} as PaginationLinks}; + const includedSearchResultPage: any[] = []; + const includedIndexCard: any[] = []; + Array.from({ length: pageSize }).forEach(() => { + const searchResultId = faker.random.uuid(); + const indexCardId = faker.random.uuid(); + const indexCardURL = `https://share.osf.io/api/v2/index-card/${indexCardId}`; + searchResultPageRelationship.data.push({ + type: 'search-result', + id: searchResultId, + }); + includedSearchResultPage.push({ + type: 'search-result', + id: searchResultId, + attributes: { + matchEvidence: [ + { + '@type': ['https://share.osf.io/vocab/2023/trove/TextMatchEvidence'], + evidenceCardIdentifier: indexCardURL, + matchingHighlight: [`...${faker.lorem.word()}...`], + osfmapPropertyPath: ['description'], + propertyPathKey: 'description', + }, + ], + }, + relationships: { + indexCard: { + data: { + type: 'index-card', + id: indexCardId, + }, + links: { + related: indexCardURL, + }, + }, + }, + }); + // pick a random resource type among the possible ones requested + const requestedResourceType: OsfmapResourceTypes + = requestedResourceTypes[Math.floor(Math.random() * requestedResourceTypes.length)]; + const resourceTypeMetadata = resourceMetadataByType[requestedResourceType]; + includedIndexCard.push({ + type: 'index-card', + id: indexCardId, + attributes: { + resourceIdentifier: [ + indexCardURL, + `https://doi.org/10.0000/osf.example/${indexCardId}`, + ], + resourceMetadata: resourceTypeMetadata(), + }, + links: { + self: indexCardURL, + resource: `https://osf.example/${indexCardId}`, + }, + }); + }); + + const cursorizedUrl = new URL(request.url); + cursorizedUrl.searchParams.set('page[cursor]', faker.random.uuid()); + searchResultPageRelationship.links = { + next: { + href: cursorizedUrl.toString(), + }, + }; + if (pageCursor) { + searchResultPageRelationship.links.prev = { + href: cursorizedUrl.toString(), + }; + searchResultPageRelationship.links.first = { + href: cursorizedUrl.toString(), + }; + } + indexCardSearch.data.relationships.searchResultPage = searchResultPageRelationship; + indexCardSearch.included.push(...includedSearchResultPage, ...includedIndexCard); + return indexCardSearch; } export function valueSearch(_: Schema, __: Request) { @@ -451,7 +503,7 @@ export function valueSearch(_: Schema, __: Request) { ], links: { next: { - href: 'https://staging-share.osf.io/api/v3/index-value-search?page%5Bcursor%5D=lmnop', + href: 'https://staging-share.osf.io/trove/index-value-search?page%5Bcursor%5D=lmnop', }, }, }, @@ -527,3 +579,86 @@ export function valueSearch(_: Schema, __: Request) { ], }; } + +function _sharePersonField() { + const fakeIdentifier = faker.internet.url(); + return { + '@id': fakeIdentifier, + resourceType: [{ '@id': OsfmapResourceTypes.Person }, { '@id': OsfmapResourceTypes.Agent }], + identifier: [{ + '@value': 'https://orcid.org/0000-0000-0000-0000', // hard-coded as search-result looks for orcid URL + '@type': rdfString, + }, + _shareIdentifierField(fakeIdentifier), + ], + name: [{ + '@value': faker.name.findName(), + '@type': rdfString, + }], + }; +} + +function _shareOrganizationField() { + const fakeIdentifier = faker.internet.url(); + return { + '@id': fakeIdentifier, + resourceType: [{ '@id': OsfmapResourceTypes.Organization }, { '@id': OsfmapResourceTypes.Agent }], + identifier: [_shareIdentifierField(fakeIdentifier)], + name: [{ + '@value': faker.company.companyName(), + '@type': rdfString, + }], + // sameAs: [{}], // some ROR + }; +} + +function _shareIdentifierField(idValue?: string) { + return { + '@value': idValue || faker.internet.url(), + '@type': rdfString, + }; +} + +function _shareDateField() { + return { + '@value': _randomPastYearMonthDay(), + '@type': rdfString, + }; +} + +function _shareLicneseField() { + return { + '@id': 'http://creativecommons.org/licenses/by/4.0/', + identifier: [{ + '@value': 'http://creativecommons.org/licenses/by/4.0/', + '@type': rdfString, + }], + name: [{ + '@value': 'CC-BY-4.0', + '@type': rdfString, + }], + }; +} + +function _shareSubjectField() { + return { + '@id': 'https://api.osf.io/v2/subjects/584240da54be81056cecac48', + resourceType: [{ '@id': OsfmapResourceTypes.Concept }], + inScheme: [{ + '@id': 'https://api.osf.io/v2/schemas/subjects/', + resourceType: [{ '@id': OsfmapResourceTypes.ConceptScheme }], + title: [{ + '@value': 'bepress Digital Commons Three-Tiered Taxonomy', + '@type': rdfString, + }], + }], + prefLabel: [{ + '@value': 'Social and Behavioral Sciences', + '@type': rdfString, + }], + }; +} + +function _randomPastYearMonthDay(): string { + return faker.date.past().toISOString().split('T')[0]; +} From f6311c4ef108c0dabd186a22b6efd27743558d35 Mon Sep 17 00:00:00 2001 From: John Tordoff Date: Wed, 11 Sep 2024 13:42:58 -0400 Subject: [PATCH 07/96] [ENG-6117] Add Filter for Orcid on Instutional Dashboard (#2315) - Ticket: [https://openscience.atlassian.net/browse/ENG-6117] - Feature flag: n/a ## Purpose Add the ability to filter users by Orcid status, ensure filter works with sorting. ## Summary of Changes - adds slider inputs to user institutional dashboard tab - adds component code to make Orcid filters work - adds template code for layout. --- .../institutional-users-list/component.ts | 15 +++ .../institutional-users-list/styles.scss | 92 ++++++++++++++++++- .../institutional-users-list/template.hbs | 37 +++++--- app/styles/_variables.scss | 1 + .../addon/components/sort-arrow/component.ts | 4 - translations/en-us.yml | 1 + 6 files changed, 134 insertions(+), 16 deletions(-) diff --git a/app/institutions/dashboard/-components/institutional-users-list/component.ts b/app/institutions/dashboard/-components/institutional-users-list/component.ts index a6632c482d3..edbcbdf0397 100644 --- a/app/institutions/dashboard/-components/institutional-users-list/component.ts +++ b/app/institutions/dashboard/-components/institutional-users-list/component.ts @@ -26,6 +26,7 @@ export default class InstitutionalUsersList extends Component void; @@ -52,6 +53,9 @@ export default class InstitutionalUsersList extends Component {{else}} - - {{department}} - +
    +
    +
    + + +
    + +
    + + {{department}} + +
    +
    +
    + { - get sortBy() { - return this.args.sortBy || 'user_name'; - } - get isCurrentAscending() { return this.args.sort === this.args.sortBy; } diff --git a/translations/en-us.yml b/translations/en-us.yml index 92114191b51..7aba4db600f 100644 --- a/translations/en-us.yml +++ b/translations/en-us.yml @@ -819,6 +819,7 @@ institutions: account_created: 'Account Created' month_last_login: 'Last Login' month_last_active: 'Last Action' + has_orcid: 'Has ORCID' users_connected_panel: 'SSO Users Connected' projects_panel: 'Total Projects' departments_panel: Departments From 28f7b4889a0dc659268fb3df1c33fc665fbe32c8 Mon Sep 17 00:00:00 2001 From: John Tordoff Date: Thu, 12 Sep 2024 12:58:01 -0400 Subject: [PATCH 08/96] [ENG-6194] Add functionality to show/hide certain columns in table (#2320) - Ticket: [https://openscience.atlassian.net/browse/ENG-6194] - Feature flag: n/a ## Purpose Allow institutional admin to show/hide columns with department filter and sort arrows. ## Summary of Changes - Made template columns somewhat dynamic - made component recognize filtering columns - fixed up css to make presentable --- .github/workflows/CI.yml | 10 +- .../institutional-users-list/component.ts | 134 +++++++++- .../institutional-users-list/styles.scss | 77 +++++- .../institutional-users-list/template.hbs | 246 +++++++----------- mirage/factories/institution-department.ts | 2 +- mirage/factories/institution.ts | 4 +- .../component-test.ts | 42 +-- translations/en-us.yml | 1 + 8 files changed, 319 insertions(+), 197 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index d8744ece95b..28ede2217e8 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -42,7 +42,7 @@ jobs: key: cached_node_modules_${{ secrets.CACHE_VERSION }}_${{ hashFiles('**/yarn.lock') }} restore-keys: cached_node_modules_${{ secrets.CACHE_VERSION }}_ - run: yarn build:test - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v4 with: name: dist path: ./dist @@ -67,7 +67,7 @@ jobs: steps: - uses: actions/checkout@v2 - uses: volta-cli/action@v1 - - uses: actions/download-artifact@v2 + - uses: actions/download-artifact@v4 with: name: dist path: ./dist @@ -91,7 +91,7 @@ jobs: steps: - uses: actions/checkout@v2 - uses: volta-cli/action@v1 - - uses: actions/download-artifact@v2 + - uses: actions/download-artifact@v4 with: name: dist path: ./dist @@ -115,7 +115,7 @@ jobs: steps: - uses: actions/checkout@v2 - uses: volta-cli/action@v1 - - uses: actions/download-artifact@v2 + - uses: actions/download-artifact@v4 with: name: dist path: ./dist @@ -139,7 +139,7 @@ jobs: steps: - uses: actions/checkout@v2 - uses: volta-cli/action@v1 - - uses: actions/download-artifact@v2 + - uses: actions/download-artifact@v4 with: name: dist path: ./dist diff --git a/app/institutions/dashboard/-components/institutional-users-list/component.ts b/app/institutions/dashboard/-components/institutional-users-list/component.ts index edbcbdf0397..1ee14b7a975 100644 --- a/app/institutions/dashboard/-components/institutional-users-list/component.ts +++ b/app/institutions/dashboard/-components/institutional-users-list/component.ts @@ -10,6 +10,13 @@ import InstitutionModel from 'ember-osf-web/models/institution'; import InstitutionDepartmentsModel from 'ember-osf-web/models/institution-department'; import Analytics from 'ember-osf-web/services/analytics'; +interface Column { + key: string; + selected: boolean; + label: string; + type: 'string' | 'date_by_month' | 'osf_link' | 'user_name'; +} + interface InstitutionalUsersListArgs { institution: InstitutionModel; departmentMetrics: InstitutionDepartmentsModel[]; @@ -23,6 +30,108 @@ export default class InstitutionalUsersList extends Component col.selected).map(col => col.key); // Private properties @tracked department = this.intl.t('institutions.dashboard.select_default'); @tracked sort = 'user_name'; @@ -30,6 +139,14 @@ export default class InstitutionalUsersList extends Component void; + @action + toggleColumnSelection(columnKey: string) { + const column = this.columns.find(col => col.key === columnKey); + if (column) { + column.selected = !column.selected; + } + } + get defaultDepartment() { return this.intl.t('institutions.dashboard.select_default'); } @@ -80,9 +197,6 @@ export default class InstitutionalUsersList extends Component col.selected).map(col => col.key); } @action diff --git a/app/institutions/dashboard/-components/institutional-users-list/styles.scss b/app/institutions/dashboard/-components/institutional-users-list/styles.scss index 916815c54b0..12f32a725f7 100644 --- a/app/institutions/dashboard/-components/institutional-users-list/styles.scss +++ b/app/institutions/dashboard/-components/institutional-users-list/styles.scss @@ -3,7 +3,7 @@ padding: 7px 16px 7px 14px; border-color: #ddd; border-radius: 2px; - color: #337ab7; + color: $color-select; } .table { @@ -19,10 +19,8 @@ th, td { - padding: 15px; - overflow: hidden; + padding: 10px; text-overflow: ellipsis; - white-space: nowrap; } :global(.text-center) { @@ -74,20 +72,16 @@ } } - .header-content { display: flex; align-items: center; justify-content: space-between; width: 100%; - overflow: hidden; } .header-text { text-overflow: ellipsis; flex-grow: 1; - overflow: hidden; - white-space: nowrap; } .sort-arrow-container { @@ -96,21 +90,82 @@ margin-left: 4px; } -th .sort-arrow { +.sort-arrow { display: inline-block; vertical-align: middle; color: #fff; } -.table-filters { +.select-container { + width: 100%; + display: flex; + justify-content: flex-end; + float: right; +} + +.select { + min-width: 120px; + padding: 7px 16px 7px 14px; + border-color: $color-border-gray; + border-radius: 2px; + color: $color-select; + text-align: center; + margin: 15px; + + span { + margin-left: 0; + } +} + +.filter-container { display: flex; justify-content: flex-end; align-items: center; width: 100%; - padding: 10px 0; +} + +.dropdown-panel { + position: absolute; + top: calc(100% + 5px); + right: 0; + background-color: $color-bg-white; + border: 1px solid $color-border-gray; + border-radius: 4px; + padding: 15px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + width: 220px; +} + +.dropdown-content { + display: flex; + flex-direction: column; margin-bottom: 10px; } +.dropdown-content label { + display: flex; + align-items: center; + padding: 4px 0; + font-size: 14px; + font-weight: lighter; +} + +.dropdown-content [type='checkbox'] { + margin-right: 8px; + cursor: pointer; +} + +.dropdown-actions { + display: flex; + justify-content: flex-end; + padding-top: 10px; + border-top: 1px solid $color-light; +} + +.icon-columns { + padding-right: 5px; +} + .filter-controls { display: flex; align-items: center; diff --git a/app/institutions/dashboard/-components/institutional-users-list/template.hbs b/app/institutions/dashboard/-components/institutional-users-list/template.hbs index 695890361df..0326e90f746 100644 --- a/app/institutions/dashboard/-components/institutional-users-list/template.hbs +++ b/app/institutions/dashboard/-components/institutional-users-list/template.hbs @@ -1,176 +1,118 @@ {{#if this.modelTaskInstance.isRunning}} {{else}} -
    -
    -
    - - -
    - -
    - - {{department}} - -
    +
    +
    + +
    + + {{department}} + + + + {{#if dd.isOpen}} +
    +
    + {{#each this.columns as |column|}} + + {{/each}} +
    +
    + + +
    +
    + {{/if}} +
    - {{#let (component 'sort-arrow' class=(local-class 'sort-arrow') - sortAction=(action this.sortInstitutionalUsers) + sortAction=this.sortInstitutionalUsers sort=this.sort - ) as |SortArrow|}} + ) as |SortArrow|}} - -
    - {{t 'institutions.dashboard.users_list.name'}} - - - -
    - - -
    - {{t 'institutions.dashboard.users_list.department'}} - - - -
    - - -
    - {{t 'institutions.dashboard.users_list.osf_link'}} -
    - - -
    - {{t 'institutions.dashboard.users_list.public_projects'}} - - - -
    - - -
    - {{t 'institutions.dashboard.users_list.private_projects'}} - - - -
    - - -
    - {{t 'institutions.dashboard.users_list.public_registration_count'}} - - - -
    - - -
    - {{t 'institutions.dashboard.users_list.private_registration_count'}} - - - -
    - - -
    - {{t 'institutions.dashboard.users_list.published_preprint_count'}} - - - -
    - - -
    - {{t 'institutions.dashboard.users_list.public_file_count'}} - - - -
    - - -
    - {{t 'institutions.dashboard.users_list.storage_byte_count'}} - - - -
    - - -
    - {{t 'institutions.dashboard.users_list.account_created'}} - - - -
    - - -
    - {{t 'institutions.dashboard.users_list.month_last_login'}} - - - -
    - - -
    - {{t 'institutions.dashboard.users_list.month_last_active'}} - - - -
    - + {{#each this.columns as |column|}} + {{#if (includes column.key this.selectedColumns)}} + +
    + {{column.label}} + {{#if column.sort_key}} + + + + {{/if}} +
    + + {{/if}} + {{/each}} {{/let}}
    - {{#if institutionalUser}} - - - {{institutionalUser.userName}} - - - {{institutionalUser.department}} - - - {{institutionalUser.userGuid}} - - - {{institutionalUser.publicProjects}} - {{institutionalUser.privateProjects}} - {{institutionalUser.publicRegistrationCount}} - {{institutionalUser.embargoedRegistrationCount}} - {{institutionalUser.publishedPreprintCount}} - {{institutionalUser.publicFileCount}} - {{institutionalUser.userDataUsage}} - {{moment-format institutionalUser.accountCreationDate 'MM/YYYY'}} - {{moment-format institutionalUser.monthLastLogin 'MM/YYYY'}} - {{moment-format institutionalUser.monthLastActive 'MM/YYYY'}} - {{/if}} + {{#each this.columns as |column|}} + {{#if (includes column.key this.selectedColumns)}} + + {{#if (eq column.type 'user_name')}} + + {{institutionalUser.userName}} + + {{else if (eq column.type 'osf_link')}} + + {{institutionalUser.userGuid}} + + {{else if (eq column.type 'date_by_month')}} + {{moment-format (get institutionalUser column.key) 'MM/YYYY'}} + {{else}} + {{get institutionalUser column.key}} + {{/if}} + + {{/if}} + {{/each}} {{t 'institutions.dashboard.users_list.empty'}} diff --git a/mirage/factories/institution-department.ts b/mirage/factories/institution-department.ts index a2e2cf3b682..201e3c806f3 100644 --- a/mirage/factories/institution-department.ts +++ b/mirage/factories/institution-department.ts @@ -5,7 +5,7 @@ import InstitutionDepartmentModel from 'ember-osf-web/models/institution-departm export default Factory.extend({ name() { - return faker.lorem.word(); + return faker.random.arrayElement(['Architecture', 'Biology', 'Psychology']); }, numberOfUsers() { return faker.random.number({ min: 100, max: 1000 }); diff --git a/mirage/factories/institution.ts b/mirage/factories/institution.ts index 61dbc2eeb0e..e0585adc80a 100644 --- a/mirage/factories/institution.ts +++ b/mirage/factories/institution.ts @@ -34,7 +34,9 @@ export default Factory.extend({ withMetrics: trait({ afterCreate(institution, server) { const userMetrics = server.createList('institution-user', 15); - const departmentMetrics = server.createList('institution-department', 12); + const departmentNames = ['Architecture', 'Biology', 'Psychology']; + const departmentMetrics = departmentNames.map(name => + server.create('institution-department', { name })); const userCount = userMetrics.length; let publicProjectCount = 0; let privateProjectCount = 0; diff --git a/tests/integration/routes/institutions/dashboard/-components/institutional-users-list/component-test.ts b/tests/integration/routes/institutions/dashboard/-components/institutional-users-list/component-test.ts index ffe28b333d2..c9c4115a323 100644 --- a/tests/integration/routes/institutions/dashboard/-components/institutional-users-list/component-test.ts +++ b/tests/integration/routes/institutions/dashboard/-components/institutional-users-list/component-test.ts @@ -18,7 +18,7 @@ module('Integration | routes | institutions | dashboard | -components | institut this.owner.register('service:router', OsfLinkRouterStub); }); - test('it renders and paginates', async function(assert) { + test('it renders and paginates 8 default columns', async function(assert) { server.create('institution', { id: 'testinstitution', }, 'withMetrics'); @@ -40,33 +40,33 @@ module('Integration | routes | institutions | dashboard | -components | institut @institution={{this.model.taskInstance.institution}} /> `); - assert.dom('[data-test-header-name]') - .exists({ count: 1 }, '1 name header'); - assert.dom('[data-test-header-department]') + assert.dom('[data-test-header]') + .exists({ count: 8 }, '8 default headers'); + assert.dom('[data-test-header="department"]') .exists({ count: 1 }, '1 departments header'); - assert.dom('[data-test-header-public-projects]') + assert.dom('[data-test-header="publicProjects"]') .exists({ count: 1 }, '1 public projects header'); - assert.dom('[data-test-header-private-projects]') + assert.dom('[data-test-header="privateProjects"]') .exists({ count: 1 }, '1 private projects header'); - assert.dom('[data-test-item-name]') - .exists({ count: 10 }, '10 in the list with a name'); - assert.dom('[data-test-item-department]') + assert.dom('[data-test-item]') + .exists({ count: 80 }, '80 items 10 rows and 8 columns by default'); + assert.dom('[data-test-item="department"]') .exists({ count: 10 }, '10 in the list with department'); - assert.dom('[data-test-item-public-projects]') + assert.dom('[data-test-item="publicProjects"]') .exists({ count: 10 }, '10 in the list with public project'); - assert.dom('[data-test-item-private-projects]') + assert.dom('[data-test-item="privateProjects"]') .exists({ count: 10 }, '10 in the list with private projects'); await click('[data-test-next-page-button]'); - assert.dom('[data-test-item-name]') + assert.dom('[data-test-item="user_name"]') .exists({ count: 5 }, '5 in the list with a name'); - assert.dom('[data-test-item-department]') + assert.dom('[data-test-item="department"]') .exists({ count: 5 }, '5 in the list with department'); - assert.dom('[data-test-item-public-projects]') + assert.dom('[data-test-item="publicProjects"]') .exists({ count: 5 }, '5 in the list with public project'); - assert.dom('[data-test-item-private-projects]') + assert.dom('[data-test-item="privateProjects"]') .exists({ count: 5 }, '5 in the list with private projects'); }); @@ -108,25 +108,25 @@ module('Integration | routes | institutions | dashboard | -components | institut @institution={{this.model.taskInstance.institution}} /> `); - assert.dom('[data-test-item-name]') + assert.dom('[data-test-item="user_name"]') .exists({ count: 3 }, '3 users'); - assert.dom('[data-test-item-name]') + assert.dom('[data-test-item="user_name"]') .containsText('Hulk Hogan', 'Sorts by name ascending by default'); - assert.dom('[data-test-item-name] a:first-of-type') + assert.dom('[data-test-item] a:first-of-type') .hasAttribute('href'); await click('[data-test-sort="user_name"]'); - assert.dom('[data-test-item-name]') + assert.dom('[data-test-item]') .containsText('John Doe', 'Sorts by name descending'); await click('[data-test-sort="department"]'); - assert.dom('[data-test-item-department]') + assert.dom('[data-test-item="department"]') .hasText('Psychology', 'Sorts by department descending'); await click('[data-test-sort="department"]'); - assert.dom('[data-test-item-department]') + assert.dom('[data-test-item="department"]') .hasText('Architecture', 'Sorts by department ascending'); }); diff --git a/translations/en-us.yml b/translations/en-us.yml index 7aba4db600f..641a8d12e20 100644 --- a/translations/en-us.yml +++ b/translations/en-us.yml @@ -820,6 +820,7 @@ institutions: month_last_login: 'Last Login' month_last_active: 'Last Action' has_orcid: 'Has ORCID' + select_columns: 'Customize' users_connected_panel: 'SSO Users Connected' projects_panel: 'Total Projects' departments_panel: Departments From ad2647a20d60034ef8100ca0ac2e33aa31bac310 Mon Sep 17 00:00:00 2001 From: John Tordoff <> Date: Fri, 13 Sep 2024 14:04:34 -0400 Subject: [PATCH 09/96] match dropdown select colors and details --- .../-components/institutional-users-list/styles.scss | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/institutions/dashboard/-components/institutional-users-list/styles.scss b/app/institutions/dashboard/-components/institutional-users-list/styles.scss index 12f32a725f7..8a3d252cf0a 100644 --- a/app/institutions/dashboard/-components/institutional-users-list/styles.scss +++ b/app/institutions/dashboard/-components/institutional-users-list/styles.scss @@ -119,7 +119,7 @@ .filter-container { display: flex; - justify-content: flex-end; + justify-content: flex-start; align-items: center; width: 100%; } @@ -142,6 +142,11 @@ margin-bottom: 10px; } +.dropdown-trigger { + padding: 9px; + color: #337ab7; +} + .dropdown-content label { display: flex; align-items: center; From ae93ff26446e9bb6363f96fb980feb845f19cd48 Mon Sep 17 00:00:00 2001 From: John Tordoff <> Date: Fri, 13 Sep 2024 14:04:57 -0400 Subject: [PATCH 10/96] add total user label to user list --- .../institutional-users-list/component.ts | 2 ++ .../institutional-users-list/styles.scss | 18 ++++++++++++++++++ .../institutional-users-list/template.hbs | 6 ++++++ app/institutions/dashboard/users/template.hbs | 1 + translations/en-us.yml | 1 + 5 files changed, 28 insertions(+) diff --git a/app/institutions/dashboard/-components/institutional-users-list/component.ts b/app/institutions/dashboard/-components/institutional-users-list/component.ts index 1ee14b7a975..26b02326b8a 100644 --- a/app/institutions/dashboard/-components/institutional-users-list/component.ts +++ b/app/institutions/dashboard/-components/institutional-users-list/component.ts @@ -20,6 +20,7 @@ interface Column { interface InstitutionalUsersListArgs { institution: InstitutionModel; departmentMetrics: InstitutionDepartmentsModel[]; + totalUsers: number; } export default class InstitutionalUsersList extends Component { @@ -29,6 +30,7 @@ export default class InstitutionalUsersList extends Component {{else}}
    +
    + + {{@totalUsers}} + + +
    diff --git a/translations/en-us.yml b/translations/en-us.yml index 641a8d12e20..ab8c3370aa6 100644 --- a/translations/en-us.yml +++ b/translations/en-us.yml @@ -821,6 +821,7 @@ institutions: month_last_active: 'Last Action' has_orcid: 'Has ORCID' select_columns: 'Customize' + total_users: 'total users' users_connected_panel: 'SSO Users Connected' projects_panel: 'Total Projects' departments_panel: Departments From 9de661ea11df2156336e774313fe677ccd529aaa Mon Sep 17 00:00:00 2001 From: John Tordoff <> Date: Tue, 17 Sep 2024 11:43:20 -0400 Subject: [PATCH 11/96] clean-up for CR --- .../dashboard/-components/institutional-users-list/styles.scss | 2 +- .../dashboard/-components/institutional-users-list/template.hbs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/institutions/dashboard/-components/institutional-users-list/styles.scss b/app/institutions/dashboard/-components/institutional-users-list/styles.scss index 8d4db4e3470..c330aece443 100644 --- a/app/institutions/dashboard/-components/institutional-users-list/styles.scss +++ b/app/institutions/dashboard/-components/institutional-users-list/styles.scss @@ -144,7 +144,7 @@ .dropdown-trigger { padding: 9px; - color: #337ab7; + color: $color-select; } .dropdown-content label { diff --git a/app/institutions/dashboard/-components/institutional-users-list/template.hbs b/app/institutions/dashboard/-components/institutional-users-list/template.hbs index 0ea33d59bc1..543de8f498e 100644 --- a/app/institutions/dashboard/-components/institutional-users-list/template.hbs +++ b/app/institutions/dashboard/-components/institutional-users-list/template.hbs @@ -6,7 +6,7 @@ {{@totalUsers}} - + {{t 'institutions.dashboard.users_list.total_users'}}
    From 403b8840dc067f2b33823cd81564bc6f6852b17b Mon Sep 17 00:00:00 2001 From: Brian Pilati Date: Tue, 27 Aug 2024 11:07:10 -0500 Subject: [PATCH 12/96] Added a new total count kpi --- .../styles.scss | 1 - .../dashboard/-components/panel/styles.scss | 36 ---------- .../dashboard/-components/panel/template.hbs | 12 ---- .../total-count-kpi-wrapper/component.ts | 67 +++++++++++++++++++ .../total-count-kpi-wrapper/styles.scss | 22 ++++++ .../total-count-kpi-wrapper/template.hbs | 14 ++++ .../total-count-kpi/styles.scss | 47 +++++++++++++ .../total-count-kpi/template.hbs | 15 +++++ app/institutions/dashboard/index/styles.scss | 8 ++- app/institutions/dashboard/index/template.hbs | 24 +++---- translations/en-us.yml | 6 +- 11 files changed, 187 insertions(+), 65 deletions(-) delete mode 100644 app/institutions/dashboard/-components/panel/styles.scss delete mode 100644 app/institutions/dashboard/-components/panel/template.hbs create mode 100644 app/institutions/dashboard/-components/total-count-kpi-wrapper/component.ts create mode 100644 app/institutions/dashboard/-components/total-count-kpi-wrapper/styles.scss create mode 100644 app/institutions/dashboard/-components/total-count-kpi-wrapper/template.hbs create mode 100644 app/institutions/dashboard/-components/total-count-kpi-wrapper/total-count-kpi/styles.scss create mode 100644 app/institutions/dashboard/-components/total-count-kpi-wrapper/total-count-kpi/template.hbs diff --git a/app/institutions/dashboard/-components/institutional-dashboard-wrapper/styles.scss b/app/institutions/dashboard/-components/institutional-dashboard-wrapper/styles.scss index e5461ebfc5d..fea5f0bfc0e 100644 --- a/app/institutions/dashboard/-components/institutional-dashboard-wrapper/styles.scss +++ b/app/institutions/dashboard/-components/institutional-dashboard-wrapper/styles.scss @@ -46,4 +46,3 @@ } } } - diff --git a/app/institutions/dashboard/-components/panel/styles.scss b/app/institutions/dashboard/-components/panel/styles.scss deleted file mode 100644 index 0053c45c425..00000000000 --- a/app/institutions/dashboard/-components/panel/styles.scss +++ /dev/null @@ -1,36 +0,0 @@ -.panel { - margin-right: 12px; - - .panel-overall { - border: 0; - margin-bottom: 30px; - box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); - } - - .panel-heading { - background: #365063; - padding: 15px; - border: 0; - } - - .panel-title { - display: block; - float: none; - color: #fff; - font-size: 14px; - font-weight: bold; - text-align: center; - text-transform: uppercase; - line-height: 20px; - } - - .panel-body { - color: #263947; - text-align: center; - - h3 { - font-size: 18pt; - font-weight: 800; - } - } -} diff --git a/app/institutions/dashboard/-components/panel/template.hbs b/app/institutions/dashboard/-components/panel/template.hbs deleted file mode 100644 index d8d1e4fa52f..00000000000 --- a/app/institutions/dashboard/-components/panel/template.hbs +++ /dev/null @@ -1,12 +0,0 @@ - - - {{@title}} - - - {{#if @isLoading}} - - {{else}} -
    {{yield}}
    - {{/if}} -
    -
    \ No newline at end of file diff --git a/app/institutions/dashboard/-components/total-count-kpi-wrapper/component.ts b/app/institutions/dashboard/-components/total-count-kpi-wrapper/component.ts new file mode 100644 index 00000000000..8a31f479538 --- /dev/null +++ b/app/institutions/dashboard/-components/total-count-kpi-wrapper/component.ts @@ -0,0 +1,67 @@ +import { waitFor } from '@ember/test-waiters'; +import Component from '@glimmer/component'; +import { tracked } from '@glimmer/tracking'; +import { task } from 'ember-concurrency'; +import { taskFor } from 'ember-concurrency-ts'; +import Intl from 'ember-intl/services/intl'; +import { inject as service } from '@ember/service'; + +interface TotalCountKpiWrapperArgs { + model: any; +} + +interface TotalCountKpiModel { + title: string; + total: number; + icon: string; +} + +export default class TotalCountKpiWrapperComponent extends Component { + @service intl!: Intl; + @tracked model = this.args.model; + @tracked totalCountKpis = [] as TotalCountKpiModel[]; + @tracked isLoading = true; + + constructor(owner: unknown, args: TotalCountKpiWrapperArgs) { + super(owner, args); + + taskFor(this.loadData).perform(); + } + + + @task + @waitFor + private async loadData(): Promise { + const metrics = await this.model.taskInstance; + + this.totalCountKpis.push( + { + title: this.intl.t('institutions.dashboard.panel.users'), + total: metrics.summaryMetrics.userCount, + icon: 'building', + }, + { + title: this.intl.t('institutions.dashboard.panel.projects'), + // total: metrics.summaryMetrics.userCount, + total: 100, + icon: 'atom', + }, + { + title: this.intl.t('institutions.dashboard.panel.registrations'), + // total: metrics.summaryMetrics.userCount, + total: 1000, + icon: 'flag', + }, + { + title: this.intl.t('institutions.dashboard.panel.preprints'), + // total: metrics.summaryMetrics.userCount, + total: 10000, + icon: 'file-alt', + }, + ); + + this.isLoading = false; + } +} + + diff --git a/app/institutions/dashboard/-components/total-count-kpi-wrapper/styles.scss b/app/institutions/dashboard/-components/total-count-kpi-wrapper/styles.scss new file mode 100644 index 00000000000..85ef244091e --- /dev/null +++ b/app/institutions/dashboard/-components/total-count-kpi-wrapper/styles.scss @@ -0,0 +1,22 @@ +.wrapper-container { + display: flex; + flex-direction: row; + justify-content: flex-start; + align-items: center; + width: 100%; + height: 145px; + + .loading { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + width: 100%; + height: 145px; + } + + &.mobile { + flex-direction: column; + height: fit-content; + } +} diff --git a/app/institutions/dashboard/-components/total-count-kpi-wrapper/template.hbs b/app/institutions/dashboard/-components/total-count-kpi-wrapper/template.hbs new file mode 100644 index 00000000000..8f57d9d5f68 --- /dev/null +++ b/app/institutions/dashboard/-components/total-count-kpi-wrapper/template.hbs @@ -0,0 +1,14 @@ +
    + {{#if this.isLoading}} +
    + +
    + {{else}} + {{#each this.totalCountKpis as |totalCountKpi index|}} + + {{/each}} + {{/if}} +
    \ No newline at end of file diff --git a/app/institutions/dashboard/-components/total-count-kpi-wrapper/total-count-kpi/styles.scss b/app/institutions/dashboard/-components/total-count-kpi-wrapper/total-count-kpi/styles.scss new file mode 100644 index 00000000000..ecea9128b58 --- /dev/null +++ b/app/institutions/dashboard/-components/total-count-kpi-wrapper/total-count-kpi/styles.scss @@ -0,0 +1,47 @@ +.kpi-container { + margin-right: 12px; + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + width: 321px; + height: 140px; + background-color: $color-bg-white; + + .left-container { + width: 221px; + padding-left: 15px; + + .data { + height: 75px; + width: 100%; + font-size: 84px; + font-style: normal; + font-weight: bolder; + display: flex; + justify-content: flex-start; + align-items: center; + margin-bottom: 10px; + } + + .title { + width: 100%; + font-size: 14px; + font-weight: normal; + height: 25px; + } + } + + .right-container { + width: 100px; + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + + .icon { + font-size: 60px; + color: $color-text-slate-gray; + } + } +} diff --git a/app/institutions/dashboard/-components/total-count-kpi-wrapper/total-count-kpi/template.hbs b/app/institutions/dashboard/-components/total-count-kpi-wrapper/total-count-kpi/template.hbs new file mode 100644 index 00000000000..cfbd4d321b0 --- /dev/null +++ b/app/institutions/dashboard/-components/total-count-kpi-wrapper/total-count-kpi/template.hbs @@ -0,0 +1,15 @@ +
    +
    +
    + {{#if @data.total}} + {{@data.total}} + {{else}} + {{t 'institutions.dashboard.empty'}} + {{/if}} +
    + {{@data.title}} +
    +
    + +
    +
    \ No newline at end of file diff --git a/app/institutions/dashboard/index/styles.scss b/app/institutions/dashboard/index/styles.scss index 72d5585b5b3..acf3db60f27 100644 --- a/app/institutions/dashboard/index/styles.scss +++ b/app/institutions/dashboard/index/styles.scss @@ -1,5 +1,9 @@ -.panel-wrapper { +.main-container { display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: center; padding-top: 12px; - text-align: right; + background-color: $color-bg-gray; + width: 100%; } diff --git a/app/institutions/dashboard/index/template.hbs b/app/institutions/dashboard/index/template.hbs index 7d9fd151d98..a6285f7d328 100644 --- a/app/institutions/dashboard/index/template.hbs +++ b/app/institutions/dashboard/index/template.hbs @@ -1,15 +1,11 @@ - - -
    - - {{#if this.model.summaryMetrics}} -

    {{this.model.summaryMetrics.userCount}}

    - {{else}} - {{t 'institutions.dashboard.empty'}} - {{/if}} -
    + + + + {{!--
    @@ -17,6 +13,8 @@ @summaryMetrics={{this.model.summaryMetrics}} /> +
    +
    @@ -25,6 +23,6 @@ @totalUsers={{this.model.totalUsers}} /> -
    +
    --}}
    diff --git a/translations/en-us.yml b/translations/en-us.yml index 641a8d12e20..9b0553da6b9 100644 --- a/translations/en-us.yml +++ b/translations/en-us.yml @@ -821,7 +821,11 @@ institutions: month_last_active: 'Last Action' has_orcid: 'Has ORCID' select_columns: 'Customize' - users_connected_panel: 'SSO Users Connected' + panel: + users: 'Total Users' + projects: 'OSF Projects' + registrations: 'OSF Registrations' + preprints: 'OSF Preprints' projects_panel: 'Total Projects' departments_panel: Departments public: Public From c8520968f20d271558aa5fc3c5cc72e43b5a8d84 Mon Sep 17 00:00:00 2001 From: Brian Pilati Date: Tue, 3 Sep 2024 15:01:50 -0500 Subject: [PATCH 13/96] Added a new test and removed an old one --- .../total-count-kpi/component-test.ts | 64 +++++++++++++++++++ .../-components/panel/component-test.ts | 57 ----------------- 2 files changed, 64 insertions(+), 57 deletions(-) create mode 100644 app/institutions/dashboard/-components/total-count-kpi-wrapper/total-count-kpi/component-test.ts delete mode 100644 tests/integration/routes/institutions/dashboard/-components/panel/component-test.ts diff --git a/app/institutions/dashboard/-components/total-count-kpi-wrapper/total-count-kpi/component-test.ts b/app/institutions/dashboard/-components/total-count-kpi-wrapper/total-count-kpi/component-test.ts new file mode 100644 index 00000000000..69897968789 --- /dev/null +++ b/app/institutions/dashboard/-components/total-count-kpi-wrapper/total-count-kpi/component-test.ts @@ -0,0 +1,64 @@ +import { render } from '@ember/test-helpers'; +import { hbs } from 'ember-cli-htmlbars'; +import { setupMirage } from 'ember-cli-mirage/test-support'; +import { EnginesIntlTestContext } from 'ember-engines/test-support'; +import { setupIntl } from 'ember-intl/test-support'; +import { setupRenderingTest } from 'ember-qunit'; +import { TestContext } from 'ember-test-helpers'; +import { module, test } from 'qunit'; + +module('Integration | institutions | dashboard | -components | total-count-kpi', hooks => { + setupRenderingTest(hooks); + setupMirage(hooks); + setupIntl(hooks); + + hooks.beforeEach(function(this: TestContext) { + const data = Object({ + total: 200, + title: 'This is the title', + icon: 'building', + }); + + this.set('data', data); + }); + + test('it renders the data correctly', async assert => { + + await render(hbs` + +`); + + assert.dom('[data-test-kpi-title]') + .hasText('This is the title'); + assert.dom('[data-test-kpi-data]') + .hasText('200'); + assert.dom('[data-test-kpi-icon]') + .hasAttribute('data-icon', 'building'); + }); + + test('it renders the without data correctly', async function(this: EnginesIntlTestContext, assert) { + const data = Object({ + total: 0, + title: 'This is the title', + icon: 'building', + }); + + this.set('data', data); + + + await render(hbs` + +`); + + assert.dom('[data-test-kpi-title]') + .hasText('This is the title'); + assert.dom('[data-test-kpi-data]') + .hasText('No data for institution found.'); + assert.dom('[data-test-kpi-icon]') + .hasAttribute('data-icon', 'building'); + }); +}); diff --git a/tests/integration/routes/institutions/dashboard/-components/panel/component-test.ts b/tests/integration/routes/institutions/dashboard/-components/panel/component-test.ts deleted file mode 100644 index 53ad4199aac..00000000000 --- a/tests/integration/routes/institutions/dashboard/-components/panel/component-test.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { render } from '@ember/test-helpers'; -import { hbs } from 'ember-cli-htmlbars'; -import { setupMirage } from 'ember-cli-mirage/test-support'; -import { setupRenderingTest } from 'ember-qunit'; -import { TestContext } from 'ember-test-helpers'; -import { module, test } from 'qunit'; - -module('Integration | routes | institutions | dashboard | -components | panel', hooks => { - setupRenderingTest(hooks); - setupMirage(hooks); - - hooks.beforeEach(function(this: TestContext) { - this.store = this.owner.lookup('service:store'); - }); - - test('it renders while loading', async function(assert) { - await render(hbs` - - Hello, World! - `); - - assert.dom('[data-test-panel-title]') - .exists({ count: 1 }, '1 title'); - assert.dom('[data-test-panel-title]') - .hasText('Test'); - assert.dom('[data-test-panel-body]') - .exists({ count: 1 }, '1 body'); - assert.dom('[data-test-panel-body]') - .hasText(''); - assert.dom('[data-test-loading-indicator]') - .exists({ count: 1 }, '1 loading indicator'); - }); - - test('it renders after loading', async function(assert) { - await render(hbs` - - Hello, World! - `); - - assert.dom('[data-test-panel-title]') - .exists({ count: 1 }, '1 title'); - assert.dom('[data-test-panel-title]') - .hasText('Test'); - assert.dom('[data-test-panel-body]') - .exists({ count: 1 }, '1 body'); - assert.dom('[data-test-panel-body]') - .hasText('Hello, World!'); - assert.dom('[data-test-loading-indicator]') - .doesNotExist(); - }); -}); From e4267cec0008d84d43709d7f3ef634aebc60cea8 Mon Sep 17 00:00:00 2001 From: Brian Pilati Date: Tue, 17 Sep 2024 11:34:21 -0500 Subject: [PATCH 14/96] Fixed some api issues --- .../dashboard/-components/total-count-kpi-wrapper/component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/institutions/dashboard/-components/total-count-kpi-wrapper/component.ts b/app/institutions/dashboard/-components/total-count-kpi-wrapper/component.ts index 8a31f479538..e3d118b7c19 100644 --- a/app/institutions/dashboard/-components/total-count-kpi-wrapper/component.ts +++ b/app/institutions/dashboard/-components/total-count-kpi-wrapper/component.ts @@ -32,7 +32,7 @@ export default class TotalCountKpiWrapperComponent extends Component { - const metrics = await this.model.taskInstance; + const metrics = await this.model; this.totalCountKpis.push( { From 168e2600f3000617fdbb3718455a2ce19de80473 Mon Sep 17 00:00:00 2001 From: Brian Pilati Date: Tue, 17 Sep 2024 12:47:21 -0500 Subject: [PATCH 15/96] Removed old tests --- .../acceptance/institutions/dashboard-test.ts | 57 ------------------- .../acceptance/institutions/discover-test.ts | 49 ---------------- 2 files changed, 106 deletions(-) delete mode 100644 tests/acceptance/institutions/dashboard-test.ts delete mode 100644 tests/acceptance/institutions/discover-test.ts diff --git a/tests/acceptance/institutions/dashboard-test.ts b/tests/acceptance/institutions/dashboard-test.ts deleted file mode 100644 index 5e9be8ff58c..00000000000 --- a/tests/acceptance/institutions/dashboard-test.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { currentURL, visit } from '@ember/test-helpers'; -import { setupMirage } from 'ember-cli-mirage/test-support'; -import { percySnapshot } from 'ember-percy'; -import { module, test } from 'qunit'; - -import { click, setupOSFApplicationTest } from 'ember-osf-web/tests/helpers'; - -const moduleName = 'Acceptance | institutions | dashboard'; - -module(moduleName, hooks => { - setupOSFApplicationTest(hooks); - setupMirage(hooks); - - test('institutions dashboard', async function(assert) { - server.create('institution', { - id: 'has-users', - }, 'withMetrics'); - await visit('/institutions/has-users/dashboard'); - assert.equal( - currentURL(), - '/institutions/has-users/dashboard', - "Still at '/institutions/has-users/dashboard'.", - ); - - assert.dom('[data-test-link-to-reports-archive]').exists('Link to download prior reports exists'); - - assert.dom('[data-test-page-tab="summary"]').exists('Summary tab exists'); - assert.dom('[data-test-page-tab="users"]').exists('Users tab exists'); - assert.dom('[data-test-page-tab="projects"]').exists('Projects tab exists'); - assert.dom('[data-test-page-tab="registrations"]').exists('Regitrations tab exists'); - assert.dom('[data-test-page-tab="preprints"]').exists('Preprints tab exists'); - - // Summary tab - await percySnapshot(`${moduleName} - summary`); - assert.dom('[data-test-page-tab="summary"]').hasClass('active', 'Summary tab is active by default'); - - // Users tab - await click('[data-test-page-tab="users"]'); - await percySnapshot(`${moduleName} - users`); - assert.dom('[data-test-page-tab="users"]').hasClass('active', 'Users tab is active'); - - // Projects tab - await click('[data-test-page-tab="projects"]'); - await percySnapshot(`${moduleName} - projects`); - assert.dom('[data-test-page-tab="projects"]').hasClass('active', 'Projects tab is active'); - - // Registrations tab - await click('[data-test-page-tab="registrations"]'); - await percySnapshot(`${moduleName} - registrations`); - assert.dom('[data-test-page-tab="registrations"]').hasClass('active', 'Registrations tab is active'); - - // Preprints tab - await click('[data-test-page-tab="preprints"]'); - await percySnapshot(`${moduleName} - preprints`); - assert.dom('[data-test-page-tab="preprints"]').hasClass('active', 'Preprints tab is active'); - }); -}); diff --git a/tests/acceptance/institutions/discover-test.ts b/tests/acceptance/institutions/discover-test.ts deleted file mode 100644 index 05eaa4fd6f2..00000000000 --- a/tests/acceptance/institutions/discover-test.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { currentURL, visit } from '@ember/test-helpers'; -import { setupMirage } from 'ember-cli-mirage/test-support'; -import { percySnapshot } from 'ember-percy'; -import { setBreakpoint } from 'ember-responsive/test-support'; -import { module, skip } from 'qunit'; -import { click, setupOSFApplicationTest} from 'ember-osf-web/tests/helpers'; - -module('Acceptance | institutions | discover', hooks => { - setupOSFApplicationTest(hooks); - setupMirage(hooks); - - skip('Desktop: Default colors', async assert => { - server.create('institution', { - id: 'has-users', - }, 'withMetrics'); - await visit('/institutions/has-users'); - // verify institutions route - assert.equal(currentURL(), '/institutions/has-users', 'Current route is institutions discover'); - assert.dom('[data-test-heading-wrapper]').exists('Institutions heading wrapper shown'); - // verify banner and description - assert.dom('[data-test-institution-banner]').exists('Institution banner shown'); - assert.dom('[data-test-institution-description]').exists('Institution description shown'); - // verify topbar and sort dropdown - assert.dom('[data-test-topbar-wrapper]').exists('Topbar not shown on mobile'); - assert.dom('[data-test-topbar-sort-dropdown]').exists('Sort dropdown shown on desktop'); - await percySnapshot(assert); - }); - - skip('Mobile: Default colors', async assert => { - setBreakpoint('mobile'); - server.create('institution', { - id: 'has-users', - }, 'withMetrics'); - // verify institutions route - 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-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'); - assert.dom('[data-test-toggle-side-panel]').exists('Institution header logo shown'); - await click('[data-test-toggle-side-panel]'); - // verify resource type and sort by dropdown - assert.dom('[data-test-left-panel-object-type-dropdown]').exists('Mobile resource type dropdown is shown'); - assert.dom('[data-test-left-panel-sort-dropdown]').exists('Mobile sort by dropdown is shown'); - await percySnapshot(assert); - }); -}); From e7a349624502d00be5b487d35e6078fbe9c89dac Mon Sep 17 00:00:00 2001 From: Brian Pilati Date: Tue, 17 Sep 2024 13:17:21 -0500 Subject: [PATCH 16/96] Updates to add the project total --- .../total-count-kpi-wrapper/component.ts | 15 +++++++++++++-- translations/en-us.yml | 2 +- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/app/institutions/dashboard/-components/total-count-kpi-wrapper/component.ts b/app/institutions/dashboard/-components/total-count-kpi-wrapper/component.ts index e3d118b7c19..a6e3aabe0c5 100644 --- a/app/institutions/dashboard/-components/total-count-kpi-wrapper/component.ts +++ b/app/institutions/dashboard/-components/total-count-kpi-wrapper/component.ts @@ -5,6 +5,7 @@ import { task } from 'ember-concurrency'; import { taskFor } from 'ember-concurrency-ts'; import Intl from 'ember-intl/services/intl'; import { inject as service } from '@ember/service'; +import InstitutionSummaryMetricModel from 'ember-osf-web/models/institution-summary-metric'; interface TotalCountKpiWrapperArgs { model: any; @@ -28,6 +29,17 @@ export default class TotalCountKpiWrapperComponent extends Component Date: Wed, 18 Sep 2024 09:29:06 -0500 Subject: [PATCH 17/96] Added a test with test-data elements --- .../total-count-kpi-wrapper/component-test.ts | 78 +++++++++++++++++++ .../total-count-kpi-wrapper/template.hbs | 2 +- .../total-count-kpi/template.hbs | 2 +- 3 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 app/institutions/dashboard/-components/total-count-kpi-wrapper/component-test.ts diff --git a/app/institutions/dashboard/-components/total-count-kpi-wrapper/component-test.ts b/app/institutions/dashboard/-components/total-count-kpi-wrapper/component-test.ts new file mode 100644 index 00000000000..bf62bc24e96 --- /dev/null +++ b/app/institutions/dashboard/-components/total-count-kpi-wrapper/component-test.ts @@ -0,0 +1,78 @@ +import { render } from '@ember/test-helpers'; +import { hbs } from 'ember-cli-htmlbars'; +import { setupMirage } from 'ember-cli-mirage/test-support'; +import { setupIntl } from 'ember-intl/test-support'; +import { setupRenderingTest } from 'ember-qunit'; +import { TestContext } from 'ember-test-helpers'; +import { module, test } from 'qunit'; + +module('Integration | institutions | dashboard | -components | total-count-kpi-wrapper', hooks => { + setupRenderingTest(hooks); + setupMirage(hooks); + setupIntl(hooks); + + hooks.beforeEach(function(this: TestContext) { + const model = Object({ + summaryMetrics: { + userCount: 10, + privateProjectCount: 10, + publicProjectCount: 10, + }, + }); + + this.set('model', model); + }); + + test('it renders the dashboard total kpis correctly', async assert => { + // Given the component is rendered + await render(hbs` + +`); + + // Then the first total kpi is tested + assert.dom('[data-test-total-count-kpi="0"]') + .exists('The User Widget exists'); + + assert.dom('[data-test-total-count-kpi="0"]') + .hasText('10 Total Users'); + + assert.dom('[data-test-total-count-kpi="0"] [data-test-kpi-icon]') + .hasAttribute('data-icon', 'building'); + + // And the second total kpi is tested + assert.dom('[data-test-total-count-kpi="1"]') + .exists('The Project Widget exists'); + + assert.dom('[data-test-total-count-kpi="1"]') + .hasText('20 OSF Public and Private Projects'); + + assert.dom('[data-test-total-count-kpi="1"] [data-test-kpi-icon]') + .hasAttribute('data-icon', 'atom'); + + // And the third total kpi is tested + assert.dom('[data-test-total-count-kpi="2"]') + .exists('The Registration Widget exists'); + + assert.dom('[data-test-total-count-kpi="2"]') + .hasText('1000 OSF Registrations'); + + assert.dom('[data-test-total-count-kpi="2"] [data-test-kpi-icon]') + .hasAttribute('data-icon', 'flag'); + + // And the fourth total kpi is tested + assert.dom('[data-test-total-count-kpi="3"]') + .exists('The Preprint Widget exists'); + + assert.dom('[data-test-total-count-kpi="3"]') + .hasText('10000 OSF Preprints'); + + assert.dom('[data-test-total-count-kpi="3"] [data-test-kpi-icon]') + .hasAttribute('data-icon', 'file-alt'); + + // Finally there are only 4 widgets + assert.dom('[data-test-total-count-kpi="4"]') + .doesNotExist('There are only 4 widgets'); + }); +}); diff --git a/app/institutions/dashboard/-components/total-count-kpi-wrapper/template.hbs b/app/institutions/dashboard/-components/total-count-kpi-wrapper/template.hbs index 8f57d9d5f68..943bca40371 100644 --- a/app/institutions/dashboard/-components/total-count-kpi-wrapper/template.hbs +++ b/app/institutions/dashboard/-components/total-count-kpi-wrapper/template.hbs @@ -1,6 +1,6 @@
    {{#if this.isLoading}} -
    +
    {{else}} diff --git a/app/institutions/dashboard/-components/total-count-kpi-wrapper/total-count-kpi/template.hbs b/app/institutions/dashboard/-components/total-count-kpi-wrapper/total-count-kpi/template.hbs index cfbd4d321b0..fb277c08c88 100644 --- a/app/institutions/dashboard/-components/total-count-kpi-wrapper/total-count-kpi/template.hbs +++ b/app/institutions/dashboard/-components/total-count-kpi-wrapper/total-count-kpi/template.hbs @@ -1,4 +1,4 @@ -
    +
    {{#if @data.total}} From e3e1c762a4eaa125d00e36814b2725e95ddbe7ad Mon Sep 17 00:00:00 2001 From: John Tordoff <> Date: Thu, 19 Sep 2024 10:15:04 -0400 Subject: [PATCH 18/96] make clicking apply close dropdown and make table scrollable for mobile --- .../institutional-users-list/styles.scss | 17 +++ .../institutional-users-list/template.hbs | 122 +++++++++--------- config/environment.js | 2 +- translations/en-us.yml | 10 +- 4 files changed, 85 insertions(+), 66 deletions(-) diff --git a/app/institutions/dashboard/-components/institutional-users-list/styles.scss b/app/institutions/dashboard/-components/institutional-users-list/styles.scss index c330aece443..8d8e0c5dc30 100644 --- a/app/institutions/dashboard/-components/institutional-users-list/styles.scss +++ b/app/institutions/dashboard/-components/institutional-users-list/styles.scss @@ -270,3 +270,20 @@ input:checked + .slider::before { margin-right: 5px; font-weight: bold; } + +/* Add media query for mobile devices */ +@media (max-width: 1000px) { + .table-wrapper { + overflow-x: auto; + -webkit-overflow-scrolling: touch; /* For smooth scrolling on iOS */ + } + + .table table { + min-width: 1000px; /* Set the table's width to be wider than the viewport */ + } + + .table th, + .table td { + white-space: nowrap; /* Prevent text from wrapping */ + } +} diff --git a/app/institutions/dashboard/-components/institutional-users-list/template.hbs b/app/institutions/dashboard/-components/institutional-users-list/template.hbs index 543de8f498e..a5d8c87ab09 100644 --- a/app/institutions/dashboard/-components/institutional-users-list/template.hbs +++ b/app/institutions/dashboard/-components/institutional-users-list/template.hbs @@ -57,7 +57,7 @@ -
    @@ -65,63 +65,65 @@ {{/if}}
    - - - {{#let (component 'sort-arrow' - class=(local-class 'sort-arrow') - sortAction=this.sortInstitutionalUsers - sort=this.sort - ) as |SortArrow|}} - - {{#each this.columns as |column|}} - {{#if (includes column.key this.selectedColumns)}} - -
    - {{column.label}} - {{#if column.sort_key}} - - - - {{/if}} -
    - - {{/if}} - {{/each}} - - {{/let}} -
    - - {{#each this.columns as |column|}} - {{#if (includes column.key this.selectedColumns)}} - - {{#if (eq column.type 'user_name')}} - - {{institutionalUser.userName}} - - {{else if (eq column.type 'osf_link')}} - - {{institutionalUser.userGuid}} - - {{else if (eq column.type 'date_by_month')}} - {{moment-format (get institutionalUser column.key) 'MM/YYYY'}} - {{else}} - {{get institutionalUser column.key}} - {{/if}} - - {{/if}} - {{/each}} - - - {{t 'institutions.dashboard.users_list.empty'}} - -
    +
    + + + {{#let (component 'sort-arrow' + class=(local-class 'sort-arrow') + sortAction=this.sortInstitutionalUsers + sort=this.sort + ) as |SortArrow|}} + + {{#each this.columns as |column|}} + {{#if (includes column.key this.selectedColumns)}} + +
    + {{column.label}} + {{#if column.sort_key}} + + + + {{/if}} +
    + + {{/if}} + {{/each}} + + {{/let}} +
    + + {{#each this.columns as |column|}} + {{#if (includes column.key this.selectedColumns)}} + + {{#if (eq column.type 'user_name')}} + + {{institutionalUser.userName}} + + {{else if (eq column.type 'osf_link')}} + + {{institutionalUser.userGuid}} + + {{else if (eq column.type 'date_by_month')}} + {{moment-format (get institutionalUser column.key) 'MM/YYYY'}} + {{else}} + {{get institutionalUser column.key}} + {{/if}} + + {{/if}} + {{/each}} + + + {{t 'institutions.dashboard.users_list.empty'}} + +
    +
    {{/if}} diff --git a/config/environment.js b/config/environment.js index 864e31cea9a..56f12bae31a 100644 --- a/config/environment.js +++ b/config/environment.js @@ -60,7 +60,7 @@ const { KEEN_CONFIG: keenConfig, LINT_ON_BUILD: lintOnBuild = false, WATER_BUTLER_ENABLED = true, - MIRAGE_ENABLED = true, + MIRAGE_ENABLED = false, MIRAGE_SCENARIOS = [ 'cedar', 'collections', diff --git a/translations/en-us.yml b/translations/en-us.yml index 32b0c182128..bb0247359f0 100644 --- a/translations/en-us.yml +++ b/translations/en-us.yml @@ -822,11 +822,11 @@ institutions: has_orcid: 'Has ORCID' select_columns: 'Customize' total_users: 'total users' - panel: - users: 'Total Users' - projects: 'OSF Projects' - registrations: 'OSF Registrations' - preprints: 'OSF Preprints' + panel: + users: 'Total Users' + projects: 'OSF Public and Private Projects' + registrations: 'OSF Registrations' + preprints: 'OSF Preprints' projects_panel: 'Total Projects' departments_panel: Departments public: Public From 7921928a4aa8ae6c8b28a1cb8d4fed08ddb8e416 Mon Sep 17 00:00:00 2001 From: John Tordoff <> Date: Thu, 19 Sep 2024 11:01:56 -0400 Subject: [PATCH 19/96] tweak slider animation --- .../dashboard/-components/institutional-users-list/styles.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/institutions/dashboard/-components/institutional-users-list/styles.scss b/app/institutions/dashboard/-components/institutional-users-list/styles.scss index 8d8e0c5dc30..5f2f8e6e1a8 100644 --- a/app/institutions/dashboard/-components/institutional-users-list/styles.scss +++ b/app/institutions/dashboard/-components/institutional-users-list/styles.scss @@ -242,7 +242,7 @@ input:checked + .slider:hover::before { } input:checked + .slider::before { - transform: translateX(26px); + transform: translateX(30px); } .slider.round { From 2a9cbc7dadef7ba6bc5ea34fab92a70a4805e881 Mon Sep 17 00:00:00 2001 From: Brian Pilati Date: Wed, 18 Sep 2024 09:37:38 -0500 Subject: [PATCH 20/96] Updated the model --- app/models/institution-summary-metric.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/models/institution-summary-metric.ts b/app/models/institution-summary-metric.ts index ac3675fafe6..750ff48e8a2 100644 --- a/app/models/institution-summary-metric.ts +++ b/app/models/institution-summary-metric.ts @@ -5,6 +5,8 @@ export default class InstitutionSummaryMetricModel extends OsfModel { @attr('number') publicProjectCount!: number; @attr('number') privateProjectCount!: number; @attr('number') userCount!: number; + @attr('number') publicRegistrationCount!: number; + @attr('number') preprintCount!: number; } declare module 'ember-data/types/registries/model' { From 736150126e25f1c2b87a441a81ad94400a6669ba Mon Sep 17 00:00:00 2001 From: Brian Pilati Date: Thu, 19 Sep 2024 12:26:45 -0500 Subject: [PATCH 21/96] Added all the new attributes --- app/models/institution-summary-metric.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/models/institution-summary-metric.ts b/app/models/institution-summary-metric.ts index 750ff48e8a2..0ffc1e8cbe3 100644 --- a/app/models/institution-summary-metric.ts +++ b/app/models/institution-summary-metric.ts @@ -7,6 +7,12 @@ export default class InstitutionSummaryMetricModel extends OsfModel { @attr('number') userCount!: number; @attr('number') publicRegistrationCount!: number; @attr('number') preprintCount!: number; + @attr('number') embargoedRegistrationCount!: number; + @attr('number') storageByteCount!: number; + @attr('number') publicFileCount!: number; + @attr('number') monthlyLoggedInUserCount!: number; + @attr('number') monthlyActiveUserCount!: number; + } declare module 'ember-data/types/registries/model' { From 84859d0077c7cdbc3a8608a29fb684e73616e587 Mon Sep 17 00:00:00 2001 From: John Tordoff <> Date: Fri, 20 Sep 2024 14:56:58 -0400 Subject: [PATCH 22/96] make user table always scrollable --- .../institutional-users-list/styles.scss | 26 +++++-------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/app/institutions/dashboard/-components/institutional-users-list/styles.scss b/app/institutions/dashboard/-components/institutional-users-list/styles.scss index 5f2f8e6e1a8..22fe74ea30c 100644 --- a/app/institutions/dashboard/-components/institutional-users-list/styles.scss +++ b/app/institutions/dashboard/-components/institutional-users-list/styles.scss @@ -6,13 +6,18 @@ color: $color-select; } +.table-wrapper { + overflow-x: auto; + display: block; +} + .table { margin-bottom: 45px; table { width: 100%; margin-bottom: 15px; - table-layout: fixed; + table-layout: auto; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); border-collapse: collapse; } @@ -21,6 +26,7 @@ td { padding: 10px; text-overflow: ellipsis; + white-space: nowrap; } :global(.text-center) { @@ -33,7 +39,6 @@ } .header { - width: 100%; background: #365063; color: #fff; } @@ -270,20 +275,3 @@ input:checked + .slider::before { margin-right: 5px; font-weight: bold; } - -/* Add media query for mobile devices */ -@media (max-width: 1000px) { - .table-wrapper { - overflow-x: auto; - -webkit-overflow-scrolling: touch; /* For smooth scrolling on iOS */ - } - - .table table { - min-width: 1000px; /* Set the table's width to be wider than the viewport */ - } - - .table th, - .table td { - white-space: nowrap; /* Prevent text from wrapping */ - } -} From bc05082af41815a46ead3feada0be59ee5969662 Mon Sep 17 00:00:00 2001 From: Brian Pilati Date: Wed, 18 Sep 2024 09:44:33 -0500 Subject: [PATCH 23/96] updated the mirage institution and institution-summary-metric factory --- mirage/factories/institution-summary-metric.ts | 6 ++++++ mirage/factories/institution.ts | 13 ++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/mirage/factories/institution-summary-metric.ts b/mirage/factories/institution-summary-metric.ts index 9531b488136..44bef2b1371 100644 --- a/mirage/factories/institution-summary-metric.ts +++ b/mirage/factories/institution-summary-metric.ts @@ -13,6 +13,12 @@ export default Factory.extend({ userCount() { return faker.random.number({ min: 100, max: 1000 }); }, + publicRegistrationCount() { + return faker.random.number({ min: 100, max: 1000 }); + }, + preprintCount() { + return faker.random.number({ min: 100, max: 1000 }); + }, }); declare module 'ember-cli-mirage/types/registries/schema' { diff --git a/mirage/factories/institution.ts b/mirage/factories/institution.ts index e0585adc80a..f07e0c105e6 100644 --- a/mirage/factories/institution.ts +++ b/mirage/factories/institution.ts @@ -35,8 +35,10 @@ export default Factory.extend({ afterCreate(institution, server) { const userMetrics = server.createList('institution-user', 15); const departmentNames = ['Architecture', 'Biology', 'Psychology']; + const departmentMetrics = departmentNames.map(name => server.create('institution-department', { name })); + const userCount = userMetrics.length; let publicProjectCount = 0; let privateProjectCount = 0; @@ -44,8 +46,17 @@ export default Factory.extend({ publicProjectCount += publicProjects; privateProjectCount += privateProjects; }); + const summaryMetrics = server.create('institution-summary-metric', { id: institution.id }); - summaryMetrics.update({ publicProjectCount, privateProjectCount, userCount }); + + summaryMetrics.update({ + publicProjectCount, + privateProjectCount, + userCount, + publicRegistrationCount: 1250, + preprintCount: 11250, + }); + institution.update({ userMetrics, departmentMetrics, summaryMetrics }); }, }), From 8052f7f0ff8723f783f2b4d94838c37877fd2277 Mon Sep 17 00:00:00 2001 From: Brian Pilati Date: Fri, 20 Sep 2024 13:45:57 -0500 Subject: [PATCH 24/96] Added the new attributes to the mirage files --- mirage/factories/institution-summary-metric.ts | 15 +++++++++++++++ mirage/factories/institution.ts | 5 +++++ 2 files changed, 20 insertions(+) diff --git a/mirage/factories/institution-summary-metric.ts b/mirage/factories/institution-summary-metric.ts index 44bef2b1371..96b8e39c8fb 100644 --- a/mirage/factories/institution-summary-metric.ts +++ b/mirage/factories/institution-summary-metric.ts @@ -19,6 +19,21 @@ export default Factory.extend({ preprintCount() { return faker.random.number({ min: 100, max: 1000 }); }, + embargoedRegistrationCount() { + return faker.random.number({ min: 100, max: 1000 }); + }, + storageByteCount() { + return faker.random.number({ min: 100, max: 1000 }); + }, + publicFileCount() { + return faker.random.number({ min: 100, max: 1000 }); + }, + monthlyActiveUserCount() { + return faker.random.number({ min: 100, max: 1000 }); + }, + monthlyLoggedInUserCount() { + return faker.random.number({ min: 100, max: 1000 }); + }, }); declare module 'ember-cli-mirage/types/registries/schema' { diff --git a/mirage/factories/institution.ts b/mirage/factories/institution.ts index f07e0c105e6..89af6200399 100644 --- a/mirage/factories/institution.ts +++ b/mirage/factories/institution.ts @@ -55,6 +55,11 @@ export default Factory.extend({ userCount, publicRegistrationCount: 1250, preprintCount: 11250, + embargoedRegistrationCount: 456, + storageByteCount: 47382032, + publicFileCount: 87, + monthlyLoggedInUserCount: 24563, + monthlyActiveUserCount:456, }); institution.update({ userMetrics, departmentMetrics, summaryMetrics }); From ac605ac5cb4805087dea55e004c6febedd770878 Mon Sep 17 00:00:00 2001 From: Brian Pilati Date: Fri, 20 Sep 2024 14:13:05 -0500 Subject: [PATCH 25/96] Updates to make mirage numbers more realistic --- .../factories/institution-summary-metric.ts | 18 ++++++++-------- mirage/factories/institution.ts | 21 ------------------- 2 files changed, 9 insertions(+), 30 deletions(-) diff --git a/mirage/factories/institution-summary-metric.ts b/mirage/factories/institution-summary-metric.ts index 96b8e39c8fb..878b81ad779 100644 --- a/mirage/factories/institution-summary-metric.ts +++ b/mirage/factories/institution-summary-metric.ts @@ -11,28 +11,28 @@ export default Factory.extend({ return faker.random.number({ min: 10, max: 100 }); }, userCount() { - return faker.random.number({ min: 100, max: 1000 }); + return faker.random.number({ min: 10, max: 50}); }, publicRegistrationCount() { return faker.random.number({ min: 100, max: 1000 }); }, - preprintCount() { - return faker.random.number({ min: 100, max: 1000 }); - }, embargoedRegistrationCount() { - return faker.random.number({ min: 100, max: 1000 }); + return faker.random.number({ min: 0, max: 25}); + }, + preprintCount() { + return faker.random.number({ min: 15, max: 175}); }, storageByteCount() { - return faker.random.number({ min: 100, max: 1000 }); + return faker.random.number({ min: 1000 * 100, max: 1000 * 1000 * 100 }); }, publicFileCount() { - return faker.random.number({ min: 100, max: 1000 }); + return faker.random.number({ min: 15, max: 1000 }); }, monthlyActiveUserCount() { - return faker.random.number({ min: 100, max: 1000 }); + return faker.random.number({ min: 10, max: 100 * 10 }); }, monthlyLoggedInUserCount() { - return faker.random.number({ min: 100, max: 1000 }); + return faker.random.number({ min: 10, max: 100 * 100 }); }, }); diff --git a/mirage/factories/institution.ts b/mirage/factories/institution.ts index 89af6200399..15de4602562 100644 --- a/mirage/factories/institution.ts +++ b/mirage/factories/institution.ts @@ -39,29 +39,8 @@ export default Factory.extend({ const departmentMetrics = departmentNames.map(name => server.create('institution-department', { name })); - const userCount = userMetrics.length; - let publicProjectCount = 0; - let privateProjectCount = 0; - userMetrics.forEach(({ publicProjects, privateProjects }) => { - publicProjectCount += publicProjects; - privateProjectCount += privateProjects; - }); - const summaryMetrics = server.create('institution-summary-metric', { id: institution.id }); - summaryMetrics.update({ - publicProjectCount, - privateProjectCount, - userCount, - publicRegistrationCount: 1250, - preprintCount: 11250, - embargoedRegistrationCount: 456, - storageByteCount: 47382032, - publicFileCount: 87, - monthlyLoggedInUserCount: 24563, - monthlyActiveUserCount:456, - }); - institution.update({ userMetrics, departmentMetrics, summaryMetrics }); }, }), From eed4a4db20064b1bdabea0e67b79ebfe4736ba3f Mon Sep 17 00:00:00 2001 From: Brian Pilati Date: Wed, 18 Sep 2024 09:47:02 -0500 Subject: [PATCH 26/96] Added the dynamic registration data to the component and fixed the tests --- .../-components/total-count-kpi-wrapper/component-test.ts | 3 ++- .../dashboard/-components/total-count-kpi-wrapper/component.ts | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/institutions/dashboard/-components/total-count-kpi-wrapper/component-test.ts b/app/institutions/dashboard/-components/total-count-kpi-wrapper/component-test.ts index bf62bc24e96..4fa5cfce11d 100644 --- a/app/institutions/dashboard/-components/total-count-kpi-wrapper/component-test.ts +++ b/app/institutions/dashboard/-components/total-count-kpi-wrapper/component-test.ts @@ -17,6 +17,7 @@ module('Integration | institutions | dashboard | -components | total-count-kpi-w userCount: 10, privateProjectCount: 10, publicProjectCount: 10, + publicRegistrationCount: 100, }, }); @@ -56,7 +57,7 @@ module('Integration | institutions | dashboard | -components | total-count-kpi-w .exists('The Registration Widget exists'); assert.dom('[data-test-total-count-kpi="2"]') - .hasText('1000 OSF Registrations'); + .hasText('100 OSF Registrations'); assert.dom('[data-test-total-count-kpi="2"] [data-test-kpi-icon]') .hasAttribute('data-icon', 'flag'); diff --git a/app/institutions/dashboard/-components/total-count-kpi-wrapper/component.ts b/app/institutions/dashboard/-components/total-count-kpi-wrapper/component.ts index a6e3aabe0c5..99b15b5c545 100644 --- a/app/institutions/dashboard/-components/total-count-kpi-wrapper/component.ts +++ b/app/institutions/dashboard/-components/total-count-kpi-wrapper/component.ts @@ -59,8 +59,7 @@ export default class TotalCountKpiWrapperComponent extends Component Date: Wed, 18 Sep 2024 09:49:04 -0500 Subject: [PATCH 27/96] Added the dynamic preprint data to the component and fixed the tests --- .../-components/total-count-kpi-wrapper/component-test.ts | 3 ++- .../dashboard/-components/total-count-kpi-wrapper/component.ts | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/institutions/dashboard/-components/total-count-kpi-wrapper/component-test.ts b/app/institutions/dashboard/-components/total-count-kpi-wrapper/component-test.ts index 4fa5cfce11d..a4a7fffd754 100644 --- a/app/institutions/dashboard/-components/total-count-kpi-wrapper/component-test.ts +++ b/app/institutions/dashboard/-components/total-count-kpi-wrapper/component-test.ts @@ -18,6 +18,7 @@ module('Integration | institutions | dashboard | -components | total-count-kpi-w privateProjectCount: 10, publicProjectCount: 10, publicRegistrationCount: 100, + preprintCount: 1000, }, }); @@ -67,7 +68,7 @@ module('Integration | institutions | dashboard | -components | total-count-kpi-w .exists('The Preprint Widget exists'); assert.dom('[data-test-total-count-kpi="3"]') - .hasText('10000 OSF Preprints'); + .hasText('1000 OSF Preprints'); assert.dom('[data-test-total-count-kpi="3"] [data-test-kpi-icon]') .hasAttribute('data-icon', 'file-alt'); diff --git a/app/institutions/dashboard/-components/total-count-kpi-wrapper/component.ts b/app/institutions/dashboard/-components/total-count-kpi-wrapper/component.ts index 99b15b5c545..59d80e15ae4 100644 --- a/app/institutions/dashboard/-components/total-count-kpi-wrapper/component.ts +++ b/app/institutions/dashboard/-components/total-count-kpi-wrapper/component.ts @@ -64,8 +64,7 @@ export default class TotalCountKpiWrapperComponent extends Component Date: Wed, 18 Sep 2024 10:29:52 -0500 Subject: [PATCH 28/96] Updates to css and html --- .../total-count-kpi-wrapper/styles.scss | 4 +- .../total-count-kpi/styles.scss | 72 ++++++++++++------- .../total-count-kpi/template.hbs | 24 ++++--- 3 files changed, 64 insertions(+), 36 deletions(-) diff --git a/app/institutions/dashboard/-components/total-count-kpi-wrapper/styles.scss b/app/institutions/dashboard/-components/total-count-kpi-wrapper/styles.scss index 85ef244091e..24f5068b5cc 100644 --- a/app/institutions/dashboard/-components/total-count-kpi-wrapper/styles.scss +++ b/app/institutions/dashboard/-components/total-count-kpi-wrapper/styles.scss @@ -3,8 +3,10 @@ flex-direction: row; justify-content: flex-start; align-items: center; - width: 100%; + width: calc(100% - 24px); height: 145px; + margin-left: 12px; + margin-right: 12px; .loading { display: flex; diff --git a/app/institutions/dashboard/-components/total-count-kpi-wrapper/total-count-kpi/styles.scss b/app/institutions/dashboard/-components/total-count-kpi-wrapper/total-count-kpi/styles.scss index ecea9128b58..29e9169585e 100644 --- a/app/institutions/dashboard/-components/total-count-kpi-wrapper/total-count-kpi/styles.scss +++ b/app/institutions/dashboard/-components/total-count-kpi-wrapper/total-count-kpi/styles.scss @@ -1,47 +1,65 @@ +// stylelint-disable max-nesting-depth, selector-max-compound-selectors + .kpi-container { margin-right: 12px; display: flex; - flex-direction: row; + flex-direction: column; justify-content: center; - align-items: center; - width: 321px; + align-items: flex-start; + width: 290px; height: 140px; background-color: $color-bg-white; - .left-container { - width: 221px; - padding-left: 15px; + .top-container { + width: 100%; + display: flex; + margin-bottom: 10px; - .data { + .left-container { + padding-left: 5px; height: 75px; - width: 100%; - font-size: 84px; - font-style: normal; - font-weight: bolder; + width: calc(220px - 5px); display: flex; justify-content: flex-start; align-items: center; - margin-bottom: 10px; + + .total-container { + font-size: 84px; + font-style: normal; + font-weight: bolder; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + } } - .title { - width: 100%; - font-size: 14px; - font-weight: normal; - height: 25px; + .right-container { + padding-right: 10px; + height: 75px; + width: 75px; + display: flex; + flex-direction: row; + justify-content: flex-end; + align-items: center; + + .icon { + font-size: 60px; + color: $color-text-slate-gray; + } } + } - .right-container { - width: 100px; - display: flex; - flex-direction: row; - justify-content: center; - align-items: center; + .title { + padding-left: 15px; + width: calc(100% - 15px); + font-size: 14px; + font-weight: normal; + height: 25px; + } - .icon { - font-size: 60px; - color: $color-text-slate-gray; - } + &.mobile { + margin-right: 0; + margin-bottom: 12px; } } diff --git a/app/institutions/dashboard/-components/total-count-kpi-wrapper/total-count-kpi/template.hbs b/app/institutions/dashboard/-components/total-count-kpi-wrapper/total-count-kpi/template.hbs index fb277c08c88..859e962cad4 100644 --- a/app/institutions/dashboard/-components/total-count-kpi-wrapper/total-count-kpi/template.hbs +++ b/app/institutions/dashboard/-components/total-count-kpi-wrapper/total-count-kpi/template.hbs @@ -1,15 +1,23 @@ -
    -
    -
    +
    +
    +
    {{#if @data.total}} - {{@data.total}} +
    + {{@data.total}} +
    + + {{@data.title}} - {{@data.total}} + {{else}} {{t 'institutions.dashboard.empty'}} {{/if}}
    - {{@data.title}} -
    -
    - +
    + +
    +
    {{@data.title}}
    \ No newline at end of file From fdc3cb7dab75a8d5088801a7c29d717233e2d526 Mon Sep 17 00:00:00 2001 From: futa-ikeda <51409893+futa-ikeda@users.noreply.github.com> Date: Tue, 24 Sep 2024 19:52:48 -0400 Subject: [PATCH 29/96] [ENG-6182] Add table to projects tab (#2335) - Ticket: [ENG-6182] - Feature flag: n/a ## Purpose - Add table showing project metadata to institutional dashboard ## Summary of Changes - Add new `` component to handle index-card-searches - Create a new `` component to generally handle fetching institution objects - Accepts some default query options as well as a list of columns that should be available - Add new components to render certain field types like DOI and Contributors - Add some styling for page - Fine tune mirage search view for institutional dashboard - revert a commit from prior PR that removed tests --- .../-components/object-list/component.ts | 48 +++++++++++ .../contributors-field/component.ts | 34 ++++++++ .../contributors-field/template.hbs | 8 ++ .../object-list/doi-field/component.ts | 15 ++++ .../object-list/doi-field/template.hbs | 3 + .../-components/object-list/styles.scss | 26 ++++++ .../-components/object-list/template.hbs | 55 +++++++++++++ app/institutions/dashboard/index/template.hbs | 2 +- .../dashboard/projects/controller.ts | 78 ++++++++++++++++++ app/institutions/dashboard/projects/route.ts | 4 + .../dashboard/projects/template.hbs | 13 ++- app/models/index-card.ts | 20 ++--- app/models/search-result.ts | 20 ++++- ember-cli-build.js | 4 +- .../index-card-searcher/component.ts | 79 +++++++++++++++++++ .../index-card-searcher/template.hbs | 15 ++++ .../search-result-card/component.ts | 18 +---- .../index-card-searcher/component.js | 1 + .../index-card-searcher/template.js | 1 + mirage/scenarios/dashboard.ts | 1 + mirage/views/search.ts | 40 +++++++--- .../acceptance/institutions/dashboard-test.ts | 70 ++++++++++++++++ .../acceptance/institutions/discover-test.ts | 49 ++++++++++++ translations/en-us.yml | 16 ++++ 24 files changed, 575 insertions(+), 45 deletions(-) create mode 100644 app/institutions/dashboard/-components/object-list/component.ts create mode 100644 app/institutions/dashboard/-components/object-list/contributors-field/component.ts create mode 100644 app/institutions/dashboard/-components/object-list/contributors-field/template.hbs create mode 100644 app/institutions/dashboard/-components/object-list/doi-field/component.ts create mode 100644 app/institutions/dashboard/-components/object-list/doi-field/template.hbs create mode 100644 app/institutions/dashboard/-components/object-list/styles.scss create mode 100644 app/institutions/dashboard/-components/object-list/template.hbs create mode 100644 app/institutions/dashboard/projects/controller.ts create mode 100644 app/institutions/dashboard/projects/route.ts create mode 100644 lib/osf-components/addon/components/index-card-searcher/component.ts create mode 100644 lib/osf-components/addon/components/index-card-searcher/template.hbs create mode 100644 lib/osf-components/app/components/index-card-searcher/component.js create mode 100644 lib/osf-components/app/components/index-card-searcher/template.js create mode 100644 tests/acceptance/institutions/dashboard-test.ts create mode 100644 tests/acceptance/institutions/discover-test.ts diff --git a/app/institutions/dashboard/-components/object-list/component.ts b/app/institutions/dashboard/-components/object-list/component.ts new file mode 100644 index 00000000000..76eb458e067 --- /dev/null +++ b/app/institutions/dashboard/-components/object-list/component.ts @@ -0,0 +1,48 @@ +import Component from '@glimmer/component'; +import { tracked } from '@glimmer/tracking'; + +import InstitutionModel from 'ember-osf-web/models/institution'; +import SearchResultModel from 'ember-osf-web/models/search-result'; + +interface ValueColumn { + name: string; + getValue(searchResult: SearchResultModel): string; +} + +interface LinkColumn { + name: string; + getHref(searchResult: SearchResultModel): string; + getLinkText(searchResult: SearchResultModel): string; + type: 'link'; +} + +interface ComponentColumn { + name: string; + type: 'doi' | 'contributors'; +} + +export type ObjectListColumn = ValueColumn | LinkColumn | ComponentColumn; + +interface InstitutionalObjectListArgs { + institution: InstitutionModel; + defaultQueryOptions: Record; + columns: ObjectListColumn[]; +} + +export default class InstitutionalObjectList extends Component { + @tracked filterObject = {}; // TODO: ENG-6183 Implement filter and type this + @tracked page = ''; // TODO: ENG-6184 Implement pagination + @tracked sort = '-relevance'; // TODO: ENG-6184 Implement sorting + + get queryOptions() { + return { + ...this.args.defaultQueryOptions, + // TODO: ENG-6183 Implement filter + // chance that this may overwrite the defaultQueryOptions.check SearchPageComponent for reference + cardSearchFilter: this.filterObject, + 'page[cursor]': this.page, + 'page[size]': 10, // TODO: ENG-6184 Implement pagination + sort: this.sort, + }; + } +} diff --git a/app/institutions/dashboard/-components/object-list/contributors-field/component.ts b/app/institutions/dashboard/-components/object-list/contributors-field/component.ts new file mode 100644 index 00000000000..cbc3e53b914 --- /dev/null +++ b/app/institutions/dashboard/-components/object-list/contributors-field/component.ts @@ -0,0 +1,34 @@ +import Component from '@glimmer/component'; + +import InstitutionModel from 'ember-osf-web/models/institution'; +import SearchResultModel from 'ember-osf-web/models/search-result'; + +interface ContributorsFieldArgs { + searchResult: SearchResultModel; + institution: InstitutionModel; +} + +export default class InstitutionalObjectListContributorsField extends Component { + // Return two contributors affiliated with the institution given with highest permission levels + get topInstitutionAffiliatedContributors() { + const { searchResult, institution } = this.args; + const contributors: any[] = searchResult.resourceMetadata.creator; + const institutionIris = institution.iris; + const affiliatedContributors = contributors + .filter((contributor: any) => hasInstitutionAffiliation(contributor, institutionIris)); + // TODO: get the two users with the highest permission level and add permission to the return object + return affiliatedContributors.slice(0, 2).map((contributor: any) => ({ + name: contributor.name[0]['@value'], + url: contributor['@id'], + })); + } + +} + +function hasInstitutionAffiliation(contributor: any, institutionIris: string[]) { + return contributor.affiliation.some( + (affiliation: any) => affiliation.identifier.some( + (affiliationIdentifier: any) => institutionIris.includes(affiliationIdentifier['@value']), + ), + ); +} diff --git a/app/institutions/dashboard/-components/object-list/contributors-field/template.hbs b/app/institutions/dashboard/-components/object-list/contributors-field/template.hbs new file mode 100644 index 00000000000..386eaf9f8ce --- /dev/null +++ b/app/institutions/dashboard/-components/object-list/contributors-field/template.hbs @@ -0,0 +1,8 @@ +{{#each this.topInstitutionAffiliatedContributors as |contributor|}} + + {{contributor.name}} + + {{!-- TODO: add permission level later --}} +{{/each}} diff --git a/app/institutions/dashboard/-components/object-list/doi-field/component.ts b/app/institutions/dashboard/-components/object-list/doi-field/component.ts new file mode 100644 index 00000000000..5a1e2733b16 --- /dev/null +++ b/app/institutions/dashboard/-components/object-list/doi-field/component.ts @@ -0,0 +1,15 @@ +import Component from '@glimmer/component'; + +import SearchResultModel from 'ember-osf-web/models/search-result'; +import { extractDoi } from 'ember-osf-web/utils/doi'; + +interface DoiFieldArgs { + searchResult: SearchResultModel; +} + +export default class InstitutionalObjectListDoiField extends Component { + get dois() { + const dois = this.args.searchResult.doi; + return dois.map((doi: string) => ({ fullLink: doi, displayText: extractDoi(doi) })); + } +} diff --git a/app/institutions/dashboard/-components/object-list/doi-field/template.hbs b/app/institutions/dashboard/-components/object-list/doi-field/template.hbs new file mode 100644 index 00000000000..6cdf6d523bb --- /dev/null +++ b/app/institutions/dashboard/-components/object-list/doi-field/template.hbs @@ -0,0 +1,3 @@ +{{#each this.dois as |doi|}} + {{doi.displayText}} +{{/each}} diff --git a/app/institutions/dashboard/-components/object-list/styles.scss b/app/institutions/dashboard/-components/object-list/styles.scss new file mode 100644 index 00000000000..d35bd9e72b5 --- /dev/null +++ b/app/institutions/dashboard/-components/object-list/styles.scss @@ -0,0 +1,26 @@ +.main-column { + width: 100%; + height: 100%; + overflow: auto; +} + +.object-table { + border-collapse: collapse; + + th, + td { + padding: 10px 15px; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + } + + td { + border: 1px solid $color-border-gray; + } +} + +.object-table-head { + background: $color-bg-gray-blue-dark; + color: $color-text-white; +} diff --git a/app/institutions/dashboard/-components/object-list/template.hbs b/app/institutions/dashboard/-components/object-list/template.hbs new file mode 100644 index 00000000000..3d4188e6402 --- /dev/null +++ b/app/institutions/dashboard/-components/object-list/template.hbs @@ -0,0 +1,55 @@ + + + + {{#if list.searchObjectsTask.isRunning}} + + {{else}} + + + + {{#each @columns as |column|}} + + {{/each}} + + + + + {{#each list.searchResults as |result|}} + + {{#each @columns as |column|}} + + {{/each}} + + {{/each}} + +
    + {{column.name}} +
    + {{#if (eq column.type 'link')}} + + {{call (fn column.getLinkText result)}} + + {{else if (eq column.type 'doi')}} + + {{else if (eq column.type 'contributors')}} + + {{else}} + {{call (fn column.getValue result)}} + {{/if}} +
    + {{/if}} +
    +
    +
    diff --git a/app/institutions/dashboard/index/template.hbs b/app/institutions/dashboard/index/template.hbs index a6285f7d328..0e8c5721bea 100644 --- a/app/institutions/dashboard/index/template.hbs +++ b/app/institutions/dashboard/index/template.hbs @@ -1,5 +1,5 @@ searchResult.displayTitle, + }, + { // Link + name: this.intl.t('institutions.dashboard.object_list.table_headers.link'), + type: 'link', + getHref: searchResult => searchResult.indexCard.get('osfIdentifier'), + getLinkText: searchResult => searchResult.indexCard.get('osfGuid'), + }, + { // Object type + name: this.intl.t('institutions.dashboard.object_list.table_headers.object_type'), + getValue: searchResult => searchResult.intlResourceType, + }, + { // Date created + name: this.intl.t('institutions.dashboard.object_list.table_headers.created_date'), + getValue : searchResult => searchResult.getResourceMetadataField('dateCreated'), + }, + { // Date modified + name: this.intl.t('institutions.dashboard.object_list.table_headers.modified_date'), + getValue : searchResult => searchResult.getResourceMetadataField('dateModified'), + }, + { // DOI + name: this.intl.t('institutions.dashboard.object_list.table_headers.doi'), + type: 'doi', + }, + { // Storage location + name: this.intl.t('institutions.dashboard.object_list.table_headers.storage_location'), + // TODO: Update when OsfMap representation is available + getValue: searchResult => searchResult.storageLocation, + }, + { // Total data stored + name: this.intl.t('institutions.dashboard.object_list.table_headers.total_data_stored'), + // TODO: Update when OsfMap representation is available + getValue: searchResult => searchResult.totalDataStored, + }, + { // Contributor name + permissions + name: this.intl.t('institutions.dashboard.object_list.table_headers.contributor_name'), + type: 'contributors', + }, + { // View count + name: this.intl.t('institutions.dashboard.object_list.table_headers.view_count'), + // TODO: Update when OsfMap representation is available + getValue: searchResult => searchResult.viewCount, + }, + { // Download count + name: this.intl.t('institutions.dashboard.object_list.table_headers.download_count'), + // TODO: Update when OsfMap representation is available + getValue: searchResult => searchResult.downloadCount, + }, + { // Has metadata + name: this.intl.t('institutions.dashboard.object_list.table_headers.has_metadata'), + // TODO: Update when OsfMap representation is available + getValue: searchResult => searchResult.hasMetadata, + }, + ]; + + get defaultQueryOptions() { + const identifiers = this.model.institution.iris.join(','); + return { + cardSearchFilter: { + affiliation: identifiers, + resourceType: ResourceTypeFilterValue.Projects, + }, + }; + } +} diff --git a/app/institutions/dashboard/projects/route.ts b/app/institutions/dashboard/projects/route.ts new file mode 100644 index 00000000000..92028943388 --- /dev/null +++ b/app/institutions/dashboard/projects/route.ts @@ -0,0 +1,4 @@ +import Route from '@ember/routing/route'; + +export default class InstitutionsDashboardRoute extends Route { +} diff --git a/app/institutions/dashboard/projects/template.hbs b/app/institutions/dashboard/projects/template.hbs index 3ddb8fe8e64..f6f512a906d 100644 --- a/app/institutions/dashboard/projects/template.hbs +++ b/app/institutions/dashboard/projects/template.hbs @@ -1,8 +1,5 @@ - - -
    - {{t 'institutions.dashboard.tabs.projects'}} -
    - {{t 'institutions.dashboard.content-placeholder'}} -
    -
    + diff --git a/app/models/index-card.ts b/app/models/index-card.ts index 81202784e03..9c5291964a7 100644 --- a/app/models/index-card.ts +++ b/app/models/index-card.ts @@ -90,7 +90,7 @@ export default class IndexCardModel extends Model { async getOsfModel(options?: object) { const identifier = this.resourceIdentifier; if (identifier && this.osfModelType) { - const guid = this.guidFromIdentifierList(); + const guid = this.osfGuid; if (guid) { const osfModel = await this.store.findRecord(this.osfModelType, guid, options); this.osfModel = osfModel; @@ -98,16 +98,16 @@ export default class IndexCardModel extends Model { } } - guidFromIdentifierList() { - for (const iri of this.resourceIdentifier) { - if (iri && iri.startsWith(osfUrl)) { - const pathSegments = iri.slice(osfUrl.length).split('/').filter(Boolean); - if (pathSegments.length === 1) { - return pathSegments[0]; // one path segment; looks like osf-id - } - } + get osfIdentifier() { + return this.resourceIdentifier.find(iri => iri.startsWith(osfUrl)) || ''; + } + + get osfGuid() { + const pathSegments = this.osfIdentifier.slice(osfUrl.length).split('/').filter(Boolean); + if (pathSegments.length === 1) { + return pathSegments[0]; // one path segment; looks like osf-id } - return null; + return ''; } } diff --git a/app/models/search-result.ts b/app/models/search-result.ts index 5d02d1c9c63..7ff6bae8ada 100644 --- a/app/models/search-result.ts +++ b/app/models/search-result.ts @@ -20,6 +20,17 @@ export interface TextMatchEvidence { osfmapPropertyPath: string[]; } +export const CardLabelTranslationKeys = { + project: 'osf-components.search-result-card.project', + project_component: 'osf-components.search-result-card.project_component', + registration: 'osf-components.search-result-card.registration', + registration_component: 'osf-components.search-result-card.registration_component', + preprint: 'osf-components.search-result-card.preprint', + file: 'osf-components.search-result-card.file', + user: 'osf-components.search-result-card.user', + unknown: 'osf-components.search-result-card.unknown', +}; + export default class SearchResultModel extends Model { @service intl!: IntlService; @@ -75,7 +86,6 @@ export default class SearchResultModel extends Model { // returns list of affilated institutions for users // returns list of contributors for osf objects - // returns list of affiliated institutions for osf users get affiliatedEntities() { if (this.resourceType === 'user') { if (this.resourceMetadata.affiliation) { @@ -242,6 +252,10 @@ export default class SearchResultModel extends Model { return 'unknown'; } + get intlResourceType() { + return this.intl.t(CardLabelTranslationKeys[this.resourceType]); + } + get orcids() { if (this.resourceMetadata.identifier) { const orcids = this.resourceMetadata.identifier.filter( @@ -283,6 +297,10 @@ export default class SearchResultModel extends Model { get isWithdrawn() { return this.resourceMetadata.dateWithdrawn || this.resourceMetadata['https://osf.io/vocab/2022/withdrawal']; } + + getResourceMetadataField(field: string) { + return this.resourceMetadata[field]?.[0]?.['@value']; + } } declare module 'ember-data/types/registries/model' { diff --git a/ember-cli-build.js b/ember-cli-build.js index ebbc8fe6086..b6b9e0abb1e 100644 --- a/ember-cli-build.js +++ b/ember-cli-build.js @@ -32,7 +32,9 @@ module.exports = function(defaults) { ], }, 'ember-composable-helpers': { - only: ['compose', 'contains', 'flatten', 'includes', 'range', 'queue', 'map-by', 'without', 'find-by'], + only: [ + 'call', 'compose', 'contains', 'find-by', 'flatten', 'includes', 'map-by', 'queue', 'range', 'without', + ], }, fingerprint: { enabled: true, diff --git a/lib/osf-components/addon/components/index-card-searcher/component.ts b/lib/osf-components/addon/components/index-card-searcher/component.ts new file mode 100644 index 00000000000..0467e0d32be --- /dev/null +++ b/lib/osf-components/addon/components/index-card-searcher/component.ts @@ -0,0 +1,79 @@ +import Component from '@glimmer/component'; +import { tracked } from '@glimmer/tracking'; +import { inject as service } from '@ember/service'; +import { waitFor } from '@ember/test-waiters'; +import { restartableTask, timeout } from 'ember-concurrency'; +import Store from '@ember-data/store'; +import Toast from 'ember-toastr/services/toast'; + +import SearchResultModel from 'ember-osf-web/models/search-result'; +import { taskFor } from 'ember-concurrency-ts'; +import RelatedPropertyPathModel from 'ember-osf-web/models/related-property-path'; + +interface IndexCardSearcherArgs { + queryOptions: Record; + debounceTime?: number; +} + +export default class IndexCardSearcher extends Component { + @service store!: Store; + @service toast!: Toast; + + debounceTime = this.args.debounceTime || 1000; + + @tracked searchResults: SearchResultModel[] = []; + @tracked totalResultCount = 0; + + @tracked relatedProperties?: RelatedPropertyPathModel[] = []; + + @tracked firstPageCursor?: string; + @tracked nextPageCursor?: string; + @tracked prevPageCursor?: string; + + get showFirstPageOption() { + return this.prevPageCursor !== this.firstPageCursor; + } + + get hasNextPage() { + return Boolean(this.nextPageCursor); + } + + get hasPrevPage() { + return Boolean(this.prevPageCursor); + } + + constructor(owner: unknown, args: IndexCardSearcherArgs) { + super(owner, args); + taskFor(this.searchObjectsTask).perform(); + } + + get isLoading() { + return taskFor(this.searchObjectsTask).isRunning; + } + + @restartableTask + @waitFor + async searchObjectsTask() { + try { + const searchResult = await this.store.queryRecord('index-card-search', this.args.queryOptions); + + this.relatedProperties = await searchResult.relatedProperties; + this.firstPageCursor = searchResult.firstPageCursor; + this.nextPageCursor = searchResult.nextPageCursor; + this.prevPageCursor = searchResult.prevPageCursor; + this.searchResults = searchResult.searchResultPage.toArray(); + this.totalResultCount = searchResult.totalResults; + + return searchResult; + } catch (error) { + this.toast.error(error); + } + } + + @restartableTask + @waitFor + async debouceSearchObjectsTask() { + await timeout(this.debounceTime); + return taskFor(this.searchObjectsTask).perform(); + } +} diff --git a/lib/osf-components/addon/components/index-card-searcher/template.hbs b/lib/osf-components/addon/components/index-card-searcher/template.hbs new file mode 100644 index 00000000000..7b9c1448ca3 --- /dev/null +++ b/lib/osf-components/addon/components/index-card-searcher/template.hbs @@ -0,0 +1,15 @@ +{{yield (hash + searchResults=this.searchResults + relatedProperties=this.relatedProperties + totalResultCount=this.totalResultCount + + debouceSearchObjectsTask=this.debouceSearchObjectsTask + searchObjectsTask=this.searchObjectsTask + + firstPageCursor=this.firstPageCursor + nextPageCursor=this.nextPageCursor + prevPageCursor=this.prevPageCursor + showFirstPageOption=this.showFirstPageOption + hasNextPage=this.hasNextPage + hasPrevPage=this.hasPrevPage +)}} diff --git a/lib/osf-components/addon/components/search-result-card/component.ts b/lib/osf-components/addon/components/search-result-card/component.ts index 2b9bf7b9d8a..ed497eb9982 100644 --- a/lib/osf-components/addon/components/search-result-card/component.ts +++ b/lib/osf-components/addon/components/search-result-card/component.ts @@ -9,18 +9,6 @@ import SearchResultModel from 'ember-osf-web/models/search-result'; import PreprintProviderModel from 'ember-osf-web/models/preprint-provider'; import InstitutionModel from 'ember-osf-web/models/institution'; - -const CardLabelTranslationKeys = { - project: 'osf-components.search-result-card.project', - project_component: 'osf-components.search-result-card.project_component', - registration: 'osf-components.search-result-card.registration', - registration_component: 'osf-components.search-result-card.registration_component', - preprint: 'osf-components.search-result-card.preprint', - file: 'osf-components.search-result-card.file', - user: 'osf-components.search-result-card.user', - unknown: 'osf-components.search-result-card.unknown', -}; - interface Args { result: SearchResultModel; provider?: PreprintProviderModel; @@ -39,8 +27,8 @@ export default class SearchResultCard extends Component { } get cardTypeLabel() { - const { provider, institution } = this.args; - const resourceType = this.args.result.resourceType; + const { provider, institution, result } = this.args; + const resourceType = result.resourceType; if (resourceType === 'preprint') { if (institution?.id === 'yls') { return this.intl.t('documentType.paper.singularCapitalized'); @@ -49,7 +37,7 @@ export default class SearchResultCard extends Component { return provider.documentType.singularCapitalized; } } - return this.intl.t(CardLabelTranslationKeys[resourceType]); + return result.intlResourceType; } get secondaryMetadataComponent() { diff --git a/lib/osf-components/app/components/index-card-searcher/component.js b/lib/osf-components/app/components/index-card-searcher/component.js new file mode 100644 index 00000000000..bd6c5fbde2e --- /dev/null +++ b/lib/osf-components/app/components/index-card-searcher/component.js @@ -0,0 +1 @@ +export { default } from 'osf-components/components/index-card-searcher/component'; diff --git a/lib/osf-components/app/components/index-card-searcher/template.js b/lib/osf-components/app/components/index-card-searcher/template.js new file mode 100644 index 00000000000..e45506dfbfe --- /dev/null +++ b/lib/osf-components/app/components/index-card-searcher/template.js @@ -0,0 +1 @@ +export { default } from 'osf-components/components/index-card-searcher/template'; diff --git a/mirage/scenarios/dashboard.ts b/mirage/scenarios/dashboard.ts index 029908aa506..18f108f86ed 100644 --- a/mirage/scenarios/dashboard.ts +++ b/mirage/scenarios/dashboard.ts @@ -83,6 +83,7 @@ export function dashboardScenario(server: Server, currentUser: ModelInstance> = { @@ -28,7 +32,7 @@ const resourceMetadataByType: Partial> = { }], hasPart: [{}], // RegistrationComponent hostingInstition: [_shareOrganizationField()], - identifier: [_shareIdentifierField()], + identifier: [_shareIdentifierField(), _shareOsfIdentifier()], isVersionOf: [{}], // if this is from a project keyword: [{ // tags '@value': faker.random.word(), @@ -64,7 +68,7 @@ const resourceMetadataByType: Partial> = { }], hasPart: [{}], // ProjectComponent hostingInstition: [_shareOrganizationField()], - identifier: [_shareIdentifierField()], + identifier: [_shareIdentifierField(), _shareOsfIdentifier()], keyword: [{ // tags '@value': faker.random.word(), '@type': rdfString, @@ -82,7 +86,7 @@ const resourceMetadataByType: Partial> = { Preprint: () => ({ '@id': faker.internet.url(), // accessService: [{}], - creator: [_sharePersonField()], + creator: [_sharePersonField('http://ror.org/has-users')], dateAccepted: [_shareDateField()], dateCopyrighted: [_shareDateField()], dateCreated: [_shareDateField()], @@ -94,7 +98,7 @@ const resourceMetadataByType: Partial> = { }], hasPart: [{}], // File hostingInstition: [_shareOrganizationField()], - identifier: [_shareIdentifierField()], + identifier: [_shareIdentifierField(), _shareOsfIdentifier()], // isSupplementedBy: [{}], // if this links a project keyword: [{ // tags '@value': faker.random.word(), @@ -136,6 +140,7 @@ const resourceMetadataByType: Partial> = { '@value': 'https://orcid.org/0000-0000-0000-0000', '@type': rdfString, }, + _shareOsfIdentifier(), ], name: [{ '@value': faker.name.findName(), @@ -188,7 +193,7 @@ resourceMetadataByType.File = function() { '@value': faker.system.filePath(), '@type': rdfString, }], - identifier: [_shareIdentifierField()], + identifier: [_shareIdentifierField(), _shareOsfIdentifier()], isContainedBy: [{ // Parent Project ...resourceMetadataByType.Project(), }], @@ -432,6 +437,7 @@ export function cardSearch(_: Schema, request: Request) { const requestedResourceType: OsfmapResourceTypes = requestedResourceTypes[Math.floor(Math.random() * requestedResourceTypes.length)]; const resourceTypeMetadata = resourceMetadataByType[requestedResourceType]; + const osfGuid = fakeOsfIdentifier(); includedIndexCard.push({ type: 'index-card', id: indexCardId, @@ -439,6 +445,7 @@ export function cardSearch(_: Schema, request: Request) { resourceIdentifier: [ indexCardURL, `https://doi.org/10.0000/osf.example/${indexCardId}`, + osfGuid, ], resourceMetadata: resourceTypeMetadata(), }, @@ -591,6 +598,10 @@ function _sharePersonField() { }, _shareIdentifierField(fakeIdentifier), ], + // Pass an IRI to the _shareOrganizationField to create an organization with the same IRI + // as one specified in your mirage scenario + // e.g. in mirage scenario: server.create('institution', { iris: ['http://ror.org/has-users']}); + affiliation: [_shareOrganizationField('http://ror.org/has-users')], name: [{ '@value': faker.name.findName(), '@type': rdfString, @@ -598,12 +609,12 @@ function _sharePersonField() { }; } -function _shareOrganizationField() { - const fakeIdentifier = faker.internet.url(); +function _shareOrganizationField(orgId?: string) { + const identifier = orgId || faker.internet.url(); return { - '@id': fakeIdentifier, + '@id': identifier, resourceType: [{ '@id': OsfmapResourceTypes.Organization }, { '@id': OsfmapResourceTypes.Agent }], - identifier: [_shareIdentifierField(fakeIdentifier)], + identifier: [_shareIdentifierField(identifier)], name: [{ '@value': faker.company.companyName(), '@type': rdfString, @@ -618,6 +629,17 @@ function _shareIdentifierField(idValue?: string) { '@type': rdfString, }; } +function fakeOsfIdentifier() { + const id = guid('share-result')(Math.random()); + return osfUrl + '/' + id; +} + +function _shareOsfIdentifier(identifier?: string) { + return { + '@value': identifier || fakeOsfIdentifier(), + '@type': rdfString, + }; +} function _shareDateField() { return { diff --git a/tests/acceptance/institutions/dashboard-test.ts b/tests/acceptance/institutions/dashboard-test.ts new file mode 100644 index 00000000000..142afe277b7 --- /dev/null +++ b/tests/acceptance/institutions/dashboard-test.ts @@ -0,0 +1,70 @@ +import { currentURL, visit } from '@ember/test-helpers'; +import { setupMirage } from 'ember-cli-mirage/test-support'; +import { percySnapshot } from 'ember-percy'; +import { module, test } from 'qunit'; + +import { click, setupOSFApplicationTest } from 'ember-osf-web/tests/helpers'; + +const moduleName = 'Acceptance | institutions | dashboard'; + +module(moduleName, hooks => { + setupOSFApplicationTest(hooks); + setupMirage(hooks); + + test('institutions dashboard: page layout', async function(assert) { + server.create('institution', { + id: 'has-users', + }, 'withMetrics'); + await visit('/institutions/has-users/dashboard'); + assert.equal( + currentURL(), + '/institutions/has-users/dashboard', + "Still at '/institutions/has-users/dashboard'.", + ); + + assert.dom('[data-test-link-to-reports-archive]').exists('Link to download prior reports exists'); + + assert.dom('[data-test-page-tab="summary"]').exists('Summary tab exists'); + assert.dom('[data-test-page-tab="users"]').exists('Users tab exists'); + assert.dom('[data-test-page-tab="projects"]').exists('Projects tab exists'); + assert.dom('[data-test-page-tab="registrations"]').exists('Regitrations tab exists'); + assert.dom('[data-test-page-tab="preprints"]').exists('Preprints tab exists'); + + // Summary tab + await percySnapshot(`${moduleName} - summary`); + assert.dom('[data-test-page-tab="summary"]').hasClass('active', 'Summary tab is active by default'); + + // Users tab + await click('[data-test-page-tab="users"]'); + await percySnapshot(`${moduleName} - users`); + assert.dom('[data-test-page-tab="users"]').hasClass('active', 'Users tab is active'); + + // Projects tab + await click('[data-test-page-tab="projects"]'); + await percySnapshot(`${moduleName} - projects`); + assert.dom('[data-test-page-tab="projects"]').hasClass('active', 'Projects tab is active'); + + // Registrations tab + await click('[data-test-page-tab="registrations"]'); + await percySnapshot(`${moduleName} - registrations`); + assert.dom('[data-test-page-tab="registrations"]').hasClass('active', 'Registrations tab is active'); + + // Preprints tab + await click('[data-test-page-tab="preprints"]'); + await percySnapshot(`${moduleName} - preprints`); + assert.dom('[data-test-page-tab="preprints"]').hasClass('active', 'Preprints tab is active'); + }); + + test('institutions dashboard: projects tab', async function(assert) { + server.create('institution', { + id: 'has-users', + }, 'withMetrics'); + + await visit('/institutions/has-users/dashboard/projects'); + + assert.dom('[data-test-page-tab="projects"]').hasClass('active', 'Projects tab is active'); + assert.dom('[data-test-object-list-table]').exists('Object list exists'); + await percySnapshot(assert); + }); +}); + diff --git a/tests/acceptance/institutions/discover-test.ts b/tests/acceptance/institutions/discover-test.ts new file mode 100644 index 00000000000..05eaa4fd6f2 --- /dev/null +++ b/tests/acceptance/institutions/discover-test.ts @@ -0,0 +1,49 @@ +import { currentURL, visit } from '@ember/test-helpers'; +import { setupMirage } from 'ember-cli-mirage/test-support'; +import { percySnapshot } from 'ember-percy'; +import { setBreakpoint } from 'ember-responsive/test-support'; +import { module, skip } from 'qunit'; +import { click, setupOSFApplicationTest} from 'ember-osf-web/tests/helpers'; + +module('Acceptance | institutions | discover', hooks => { + setupOSFApplicationTest(hooks); + setupMirage(hooks); + + skip('Desktop: Default colors', async assert => { + server.create('institution', { + id: 'has-users', + }, 'withMetrics'); + await visit('/institutions/has-users'); + // verify institutions route + assert.equal(currentURL(), '/institutions/has-users', 'Current route is institutions discover'); + assert.dom('[data-test-heading-wrapper]').exists('Institutions heading wrapper shown'); + // verify banner and description + assert.dom('[data-test-institution-banner]').exists('Institution banner shown'); + assert.dom('[data-test-institution-description]').exists('Institution description shown'); + // verify topbar and sort dropdown + assert.dom('[data-test-topbar-wrapper]').exists('Topbar not shown on mobile'); + assert.dom('[data-test-topbar-sort-dropdown]').exists('Sort dropdown shown on desktop'); + await percySnapshot(assert); + }); + + skip('Mobile: Default colors', async assert => { + setBreakpoint('mobile'); + server.create('institution', { + id: 'has-users', + }, 'withMetrics'); + // verify institutions route + 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-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'); + assert.dom('[data-test-toggle-side-panel]').exists('Institution header logo shown'); + await click('[data-test-toggle-side-panel]'); + // verify resource type and sort by dropdown + assert.dom('[data-test-left-panel-object-type-dropdown]').exists('Mobile resource type dropdown is shown'); + assert.dom('[data-test-left-panel-sort-dropdown]').exists('Mobile sort by dropdown is shown'); + await percySnapshot(assert); + }); +}); diff --git a/translations/en-us.yml b/translations/en-us.yml index a138f9cde50..1e67d05a273 100644 --- a/translations/en-us.yml +++ b/translations/en-us.yml @@ -827,6 +827,22 @@ institutions: projects: 'OSF Public and Private Projects' registrations: 'OSF Registrations' preprints: 'OSF Preprints' + object_list: + table_headers: + title: Title + link: Link + object_type: Type + created_date: 'Created Date' + modified_date: 'Modified Date' + # is_public: 'Is Public' # Not currently used + doi: DOI + storage_location: 'Storage Location' + total_data_stored: 'Total Data Stored on OSF' + contributor_name: 'Contributor Name' + contributor_permissions: 'Contributor Permissions' + view_count: Views (last 30 days) + download_count: Downloads (last 30 days) + has_metadata: 'Has Metadata' projects_panel: 'Total Projects' departments_panel: Departments public: Public From a9056a21444c957aacef390bbd7fc556938e1aa7 Mon Sep 17 00:00:00 2001 From: futa-ikeda <51409893+futa-ikeda@users.noreply.github.com> Date: Mon, 7 Oct 2024 15:31:11 -0400 Subject: [PATCH 30/96] [ENG-6183] Project list filtering (#2345) - Ticket: [ENG-6183] - Feature flag: n/a ## Purpose - Add filtering capability to Institutional Dashboard's Project page ## Summary of Changes - Update mirage's index-value-search view to handle filterable values - Add filtering logic to the page and show active filters on right-sidebar - Update OsfLayout to be able to manually set the left and right sidebar behavior regardless of device size - Add total results count to object-list component --- .../styles.scss | 12 ++ .../template.hbs | 8 +- .../-components/object-list/component.ts | 31 +++- .../-components/object-list/styles.scss | 61 +++++++- .../-components/object-list/template.hbs | 146 +++++++++++++----- .../dashboard/projects/controller.ts | 24 +-- .../dashboard/projects/template.hbs | 1 + .../index-card-searcher/component.ts | 2 +- .../addon/components/osf-layout/component.ts | 12 +- .../osf-layout/main-column/template.hbs | 2 + .../addon/components/osf-layout/template.hbs | 6 +- mirage/views/search.ts | 4 +- translations/en-us.yml | 12 +- 13 files changed, 248 insertions(+), 73 deletions(-) diff --git a/app/institutions/dashboard/-components/institutional-dashboard-wrapper/styles.scss b/app/institutions/dashboard/-components/institutional-dashboard-wrapper/styles.scss index fea5f0bfc0e..c63ad2ac6e6 100644 --- a/app/institutions/dashboard/-components/institutional-dashboard-wrapper/styles.scss +++ b/app/institutions/dashboard/-components/institutional-dashboard-wrapper/styles.scss @@ -5,6 +5,18 @@ display: flex; flex-direction: row; flex-grow: 1; + + > div { + // override OsfLayout styles for forcing drawer mode + max-width: none; + position: inherit; + } + + > :global(.media-mobile), + > :global(.media-tablet) { + // allow drawer to be hidden offscreen + position: relative; + } } .heading-wrapper { diff --git a/app/institutions/dashboard/-components/institutional-dashboard-wrapper/template.hbs b/app/institutions/dashboard/-components/institutional-dashboard-wrapper/template.hbs index 2f30e4de054..94d5a346efa 100644 --- a/app/institutions/dashboard/-components/institutional-dashboard-wrapper/template.hbs +++ b/app/institutions/dashboard/-components/institutional-dashboard-wrapper/template.hbs @@ -1,4 +1,8 @@ - + @@ -96,4 +100,4 @@ top=layout.top main=layout.main )}} - \ No newline at end of file + diff --git a/app/institutions/dashboard/-components/object-list/component.ts b/app/institutions/dashboard/-components/object-list/component.ts index 76eb458e067..62c45fedfdd 100644 --- a/app/institutions/dashboard/-components/object-list/component.ts +++ b/app/institutions/dashboard/-components/object-list/component.ts @@ -1,8 +1,10 @@ +import { action } from '@ember/object'; import Component from '@glimmer/component'; import { tracked } from '@glimmer/tracking'; import InstitutionModel from 'ember-osf-web/models/institution'; import SearchResultModel from 'ember-osf-web/models/search-result'; +import { Filter } from 'osf-components/components/search-page/component'; interface ValueColumn { name: string; @@ -25,24 +27,39 @@ export type ObjectListColumn = ValueColumn | LinkColumn | ComponentColumn; interface InstitutionalObjectListArgs { institution: InstitutionModel; - defaultQueryOptions: Record; + defaultQueryOptions: Record<'cardSearchFilter', Record>; columns: ObjectListColumn[]; + objectType: string; } export default class InstitutionalObjectList extends Component { - @tracked filterObject = {}; // TODO: ENG-6183 Implement filter and type this + @tracked activeFilters: Filter[] = []; @tracked page = ''; // TODO: ENG-6184 Implement pagination @tracked sort = '-relevance'; // TODO: ENG-6184 Implement sorting get queryOptions() { - return { - ...this.args.defaultQueryOptions, - // TODO: ENG-6183 Implement filter - // chance that this may overwrite the defaultQueryOptions.check SearchPageComponent for reference - cardSearchFilter: this.filterObject, + const options = { + ... this.args.defaultQueryOptions, 'page[cursor]': this.page, 'page[size]': 10, // TODO: ENG-6184 Implement pagination sort: this.sort, + }; + const fullQueryOptions = this.activeFilters.reduce((acc, filter: Filter) => { + const currentValue = acc.cardSearchFilter[filter.propertyPathKey]; + acc.cardSearchFilter[filter.propertyPathKey] = + currentValue ? [...currentValue, filter.value] : [filter.value]; + return acc; + }, options); + return fullQueryOptions; + } + + @action + toggleFilter(property: Filter) { + if (this.activeFilters.includes(property)) { + this.activeFilters.removeObject(property); + } else { + this.activeFilters.pushObject(property); + } } } diff --git a/app/institutions/dashboard/-components/object-list/styles.scss b/app/institutions/dashboard/-components/object-list/styles.scss index d35bd9e72b5..6fb9e25a168 100644 --- a/app/institutions/dashboard/-components/object-list/styles.scss +++ b/app/institutions/dashboard/-components/object-list/styles.scss @@ -1,12 +1,34 @@ -.main-column { - width: 100%; - height: 100%; +@import 'app/styles/layout'; + +.top-bar-wrapper { + display: flex; + justify-content: space-between; + align-items: center; + margin: 1rem 0; +} + +.total-object-count { + align-self: center; + font-size: large; + + .total-object-number { + font-weight: bold; + } +} + +.top-bar-button-wrapper { + button { + margin-left: 0.5rem; + } +} + +.table-wrapper { overflow: auto; } .object-table { border-collapse: collapse; - + th, td { padding: 10px 15px; @@ -24,3 +46,34 @@ background: $color-bg-gray-blue-dark; color: $color-text-white; } + +.right-wrapper { + min-width: 300px; + padding: 0.5rem; +} + +.right-panel-header { + font-size: 1.5rem; +} + +.close-button { + float: right; +} + +.active-filter-list { + list-style: none; + padding-left: 0; + margin-top: 1rem; + border-top: 1px solid $color-border-gray; + border-bottom: 1px solid $color-border-gray; +} + +.active-filter-item { + display: flex; + margin: 0.5rem 0.2rem; + justify-content: space-between; + + button { + margin-right: -5px; + } +} diff --git a/app/institutions/dashboard/-components/object-list/template.hbs b/app/institutions/dashboard/-components/object-list/template.hbs index 3d4188e6402..b364e80f20c 100644 --- a/app/institutions/dashboard/-components/object-list/template.hbs +++ b/app/institutions/dashboard/-components/object-list/template.hbs @@ -1,55 +1,123 @@ - + {{#if list.searchObjectsTask.isRunning}} {{else}} - - - - {{#each @columns as |column|}} - - {{/each}} - - - - - {{#each list.searchResults as |result|}} +
    + + {{list.totalResultCount}} + {{t 'institutions.dashboard.object-list.total-objects' objectType=@objectType}} + +
    + +
    +
    +
    +
    - {{column.name}} -
    + {{#each @columns as |column|}} - + {{/each}} - {{/each}} - -
    - {{#if (eq column.type 'link')}} - - {{call (fn column.getLinkText result)}} - - {{else if (eq column.type 'doi')}} - - {{else if (eq column.type 'contributors')}} - - {{else}} - {{call (fn column.getValue result)}} - {{/if}} - + {{column.name}} +
    + + + + {{#each list.searchResults as |result|}} + + {{#each @columns as |column|}} + + {{#if (eq column.type 'link')}} + + {{call (fn column.getLinkText result)}} + + {{else if (eq column.type 'doi')}} + + {{else if (eq column.type 'contributors')}} + + {{else}} + {{call (fn column.getValue result)}} + {{/if}} + + {{/each}} + + {{/each}} + + +
    {{/if}} + {{#if list.relatedProperties}} + + + {{t 'institutions.dashboard.object-list.filter-heading'}} + + + {{#if this.activeFilters}} +
      + {{#each this.activeFilters as |filter|}} +
    • + + {{filter.propertyVisibleLabel}}: + {{filter.label}} + + +
    • + {{/each}} +
    + {{/if}} + + {{#each list.relatedProperties as |property|}} + + {{/each}} +
    + {{/if}} diff --git a/app/institutions/dashboard/projects/controller.ts b/app/institutions/dashboard/projects/controller.ts index b1853afe708..fcb90c7c159 100644 --- a/app/institutions/dashboard/projects/controller.ts +++ b/app/institutions/dashboard/projects/controller.ts @@ -10,57 +10,57 @@ export default class InstitutionDashboardProjects extends Controller { columns: ObjectListColumn[] = [ { // Title - name: this.intl.t('institutions.dashboard.object_list.table_headers.title'), + name: this.intl.t('institutions.dashboard.object-list.table-headers.title'), getValue: searchResult => searchResult.displayTitle, }, { // Link - name: this.intl.t('institutions.dashboard.object_list.table_headers.link'), + name: this.intl.t('institutions.dashboard.object-list.table-headers.link'), type: 'link', getHref: searchResult => searchResult.indexCard.get('osfIdentifier'), getLinkText: searchResult => searchResult.indexCard.get('osfGuid'), }, { // Object type - name: this.intl.t('institutions.dashboard.object_list.table_headers.object_type'), + name: this.intl.t('institutions.dashboard.object-list.table-headers.object_type'), getValue: searchResult => searchResult.intlResourceType, }, { // Date created - name: this.intl.t('institutions.dashboard.object_list.table_headers.created_date'), + name: this.intl.t('institutions.dashboard.object-list.table-headers.created_date'), getValue : searchResult => searchResult.getResourceMetadataField('dateCreated'), }, { // Date modified - name: this.intl.t('institutions.dashboard.object_list.table_headers.modified_date'), + name: this.intl.t('institutions.dashboard.object-list.table-headers.modified_date'), getValue : searchResult => searchResult.getResourceMetadataField('dateModified'), }, { // DOI - name: this.intl.t('institutions.dashboard.object_list.table_headers.doi'), + name: this.intl.t('institutions.dashboard.object-list.table-headers.doi'), type: 'doi', }, { // Storage location - name: this.intl.t('institutions.dashboard.object_list.table_headers.storage_location'), + name: this.intl.t('institutions.dashboard.object-list.table-headers.storage_location'), // TODO: Update when OsfMap representation is available getValue: searchResult => searchResult.storageLocation, }, { // Total data stored - name: this.intl.t('institutions.dashboard.object_list.table_headers.total_data_stored'), + name: this.intl.t('institutions.dashboard.object-list.table-headers.total_data_stored'), // TODO: Update when OsfMap representation is available getValue: searchResult => searchResult.totalDataStored, }, { // Contributor name + permissions - name: this.intl.t('institutions.dashboard.object_list.table_headers.contributor_name'), + name: this.intl.t('institutions.dashboard.object-list.table-headers.contributor_name'), type: 'contributors', }, { // View count - name: this.intl.t('institutions.dashboard.object_list.table_headers.view_count'), + name: this.intl.t('institutions.dashboard.object-list.table-headers.view_count'), // TODO: Update when OsfMap representation is available getValue: searchResult => searchResult.viewCount, }, { // Download count - name: this.intl.t('institutions.dashboard.object_list.table_headers.download_count'), + name: this.intl.t('institutions.dashboard.object-list.table-headers.download_count'), // TODO: Update when OsfMap representation is available getValue: searchResult => searchResult.downloadCount, }, { // Has metadata - name: this.intl.t('institutions.dashboard.object_list.table_headers.has_metadata'), + name: this.intl.t('institutions.dashboard.object-list.table-headers.has_metadata'), // TODO: Update when OsfMap representation is available getValue: searchResult => searchResult.hasMetadata, }, diff --git a/app/institutions/dashboard/projects/template.hbs b/app/institutions/dashboard/projects/template.hbs index f6f512a906d..64a49591bea 100644 --- a/app/institutions/dashboard/projects/template.hbs +++ b/app/institutions/dashboard/projects/template.hbs @@ -2,4 +2,5 @@ @institution={{this.model.institution}} @defaultQueryOptions={{this.defaultQueryOptions}} @columns={{this.columns}} + @objectType={{t 'institutions.dashboard.object-type-word.projects'}} /> diff --git a/lib/osf-components/addon/components/index-card-searcher/component.ts b/lib/osf-components/addon/components/index-card-searcher/component.ts index 0467e0d32be..bb93c47e945 100644 --- a/lib/osf-components/addon/components/index-card-searcher/component.ts +++ b/lib/osf-components/addon/components/index-card-searcher/component.ts @@ -62,7 +62,7 @@ export default class IndexCardSearcher extends Component this.nextPageCursor = searchResult.nextPageCursor; this.prevPageCursor = searchResult.prevPageCursor; this.searchResults = searchResult.searchResultPage.toArray(); - this.totalResultCount = searchResult.totalResults; + this.totalResultCount = searchResult.totalResultCount; return searchResult; } catch (error) { diff --git a/lib/osf-components/addon/components/osf-layout/component.ts b/lib/osf-components/addon/components/osf-layout/component.ts index 775193dd3ea..7ea3499e2b8 100644 --- a/lib/osf-components/addon/components/osf-layout/component.ts +++ b/lib/osf-components/addon/components/osf-layout/component.ts @@ -17,14 +17,19 @@ export default class OsfLayout extends Component { sidenavGutterClosed = true; metadataGutterClosed = true; backgroundClass?: string; + forceMetadataGutterMode?: 'page' | 'drawer' | 'column'; + forceSidenavGutterMode?: 'page' | 'drawer' | 'column'; init() { super.init(); assert('@backgroundClass is required!', Boolean(this.backgroundClass)); } - @computed('media.{isMobile,isTablet,isDesktop}') + @computed('media.{isMobile,isTablet,isDesktop}', 'forceMetadataGutterMode') get metadataGutterMode() { + if (this.forceMetadataGutterMode) { + return this.forceMetadataGutterMode; + } if (this.media.isMobile) { return 'page'; } @@ -34,8 +39,11 @@ export default class OsfLayout extends Component { return 'column'; } - @computed('media.{isMobile,isTablet,isDesktop}') + @computed('media.{isMobile,isTablet,isDesktop}', 'forceSidenavGutterMode') get sidenavGutterMode() { + if (this.forceSidenavGutterMode) { + return this.forceSidenavGutterMode; + } if (this.media.isDesktop) { return 'column'; } diff --git a/lib/osf-components/addon/components/osf-layout/main-column/template.hbs b/lib/osf-components/addon/components/osf-layout/main-column/template.hbs index ce1bf493eb5..b15f3b982db 100644 --- a/lib/osf-components/addon/components/osf-layout/main-column/template.hbs +++ b/lib/osf-components/addon/components/osf-layout/main-column/template.hbs @@ -1,5 +1,7 @@ <@gutters.body ...attributes local-class='Main'> {{yield (hash main=(element 'div') + toggleMetadata=(action @toggleMetadata) + metadataGutterClosed=@metadataGutterClosed )}} diff --git a/lib/osf-components/addon/components/osf-layout/template.hbs b/lib/osf-components/addon/components/osf-layout/template.hbs index 0bf4dc3d098..067ecfba4e6 100644 --- a/lib/osf-components/addon/components/osf-layout/template.hbs +++ b/lib/osf-components/addon/components/osf-layout/template.hbs @@ -26,7 +26,11 @@ leftNavOld=(component 'osf-layout/left-nav-old' gutters=gutters toggleSidenav=(action this.toggleSidenav)) leftNav=(component 'osf-layout/left-nav' gutters=gutters toggleSidenav=(action this.toggleSidenav)) left=(component 'osf-layout/left-column' gutters=gutters toggleSidenav=(action this.toggleSidenav)) - main=(component 'osf-layout/main-column' gutters=gutters) + main=(component 'osf-layout/main-column' + gutters=gutters + toggleMetadata=(action this.toggleMetadata) + metadataGutterClosed=this.metadataGutterClosed + ) right=(component 'osf-layout/right-column' gutters=gutters toggleMetadata=(action this.toggleMetadata)) )}} diff --git a/mirage/views/search.ts b/mirage/views/search.ts index 8a71d42b94d..2a4500c35c4 100644 --- a/mirage/views/search.ts +++ b/mirage/views/search.ts @@ -562,7 +562,7 @@ export function valueSearch(_: Schema, __: Request) { id: property1Id, attributes: { resourceType: 'osf:Funder', - resourceIdentifier: 'http://dx.doi.org/10.10000/505000005050', + resourceIdentifier: ['http://dx.doi.org/10.10000/505000005050'], resourceMetadata: { '@id': 'http://dx.doi.org/10.10000/505000005050', '@type': 'datacite:Funder', @@ -575,7 +575,7 @@ export function valueSearch(_: Schema, __: Request) { id: property2Id, attributes: { resourceType: 'osf:Funder', - resourceIdentifier: 'https://doi.org/10.10000/100000001', + resourceIdentifier: ['https://doi.org/10.10000/100000001'], resourceMetadata: { '@id': 'http://dx.doi.org/10.10000/100000001', '@type': 'datacite:Funder', diff --git a/translations/en-us.yml b/translations/en-us.yml index 1e67d05a273..a3b8d20322c 100644 --- a/translations/en-us.yml +++ b/translations/en-us.yml @@ -798,6 +798,10 @@ institutions: projects: Projects registrations: Registrations preprints: Preprints + object-type-word: + projects: projects + registrations: registrations + preprints: preprints content-placeholder: Content coming soon # Delete this eventually pls title: '{institutionName} Dashboard' download_past_reports_label: 'Previous reports' @@ -827,14 +831,16 @@ institutions: projects: 'OSF Public and Private Projects' registrations: 'OSF Registrations' preprints: 'OSF Preprints' - object_list: - table_headers: + object-list: + filter-button-label: 'Filter' + filter-heading: 'Filter by:' + total-objects: 'total {objectType}' + table-headers: title: Title link: Link object_type: Type created_date: 'Created Date' modified_date: 'Modified Date' - # is_public: 'Is Public' # Not currently used doi: DOI storage_location: 'Storage Location' total_data_stored: 'Total Data Stored on OSF' From f3d5e1906ba4939505ee8675445870f5e40a36eb Mon Sep 17 00:00:00 2001 From: Brian Pilati Date: Tue, 1 Oct 2024 11:56:19 -0500 Subject: [PATCH 31/96] Added a new doughnut component --- .../component-test.ts | 80 ++++++++++++ .../doughnut-chart-kpi-wrapper/component.ts | 76 ++++++++++++ .../doughnut-kpi/component-test.ts | 64 ++++++++++ .../doughnut-kpi/component.ts | 101 +++++++++++++++ .../doughnut-kpi/styles.scss | 115 ++++++++++++++++++ .../doughnut-kpi/template.hbs | 53 ++++++++ .../doughnut-chart-kpi-wrapper/styles.scss | 25 ++++ .../doughnut-chart-kpi-wrapper/template.hbs | 14 +++ .../total-count-kpi-wrapper/component.ts | 6 +- app/institutions/dashboard/index/styles.scss | 44 +++++++ app/institutions/dashboard/index/template.hbs | 35 +++--- 11 files changed, 589 insertions(+), 24 deletions(-) create mode 100644 app/institutions/dashboard/-components/doughnut-chart-kpi-wrapper/component-test.ts create mode 100644 app/institutions/dashboard/-components/doughnut-chart-kpi-wrapper/component.ts create mode 100644 app/institutions/dashboard/-components/doughnut-chart-kpi-wrapper/doughnut-kpi/component-test.ts create mode 100644 app/institutions/dashboard/-components/doughnut-chart-kpi-wrapper/doughnut-kpi/component.ts create mode 100644 app/institutions/dashboard/-components/doughnut-chart-kpi-wrapper/doughnut-kpi/styles.scss create mode 100644 app/institutions/dashboard/-components/doughnut-chart-kpi-wrapper/doughnut-kpi/template.hbs create mode 100644 app/institutions/dashboard/-components/doughnut-chart-kpi-wrapper/styles.scss create mode 100644 app/institutions/dashboard/-components/doughnut-chart-kpi-wrapper/template.hbs diff --git a/app/institutions/dashboard/-components/doughnut-chart-kpi-wrapper/component-test.ts b/app/institutions/dashboard/-components/doughnut-chart-kpi-wrapper/component-test.ts new file mode 100644 index 00000000000..a4a7fffd754 --- /dev/null +++ b/app/institutions/dashboard/-components/doughnut-chart-kpi-wrapper/component-test.ts @@ -0,0 +1,80 @@ +import { render } from '@ember/test-helpers'; +import { hbs } from 'ember-cli-htmlbars'; +import { setupMirage } from 'ember-cli-mirage/test-support'; +import { setupIntl } from 'ember-intl/test-support'; +import { setupRenderingTest } from 'ember-qunit'; +import { TestContext } from 'ember-test-helpers'; +import { module, test } from 'qunit'; + +module('Integration | institutions | dashboard | -components | total-count-kpi-wrapper', hooks => { + setupRenderingTest(hooks); + setupMirage(hooks); + setupIntl(hooks); + + hooks.beforeEach(function(this: TestContext) { + const model = Object({ + summaryMetrics: { + userCount: 10, + privateProjectCount: 10, + publicProjectCount: 10, + publicRegistrationCount: 100, + preprintCount: 1000, + }, + }); + + this.set('model', model); + }); + + test('it renders the dashboard total kpis correctly', async assert => { + // Given the component is rendered + await render(hbs` + +`); + + // Then the first total kpi is tested + assert.dom('[data-test-total-count-kpi="0"]') + .exists('The User Widget exists'); + + assert.dom('[data-test-total-count-kpi="0"]') + .hasText('10 Total Users'); + + assert.dom('[data-test-total-count-kpi="0"] [data-test-kpi-icon]') + .hasAttribute('data-icon', 'building'); + + // And the second total kpi is tested + assert.dom('[data-test-total-count-kpi="1"]') + .exists('The Project Widget exists'); + + assert.dom('[data-test-total-count-kpi="1"]') + .hasText('20 OSF Public and Private Projects'); + + assert.dom('[data-test-total-count-kpi="1"] [data-test-kpi-icon]') + .hasAttribute('data-icon', 'atom'); + + // And the third total kpi is tested + assert.dom('[data-test-total-count-kpi="2"]') + .exists('The Registration Widget exists'); + + assert.dom('[data-test-total-count-kpi="2"]') + .hasText('100 OSF Registrations'); + + assert.dom('[data-test-total-count-kpi="2"] [data-test-kpi-icon]') + .hasAttribute('data-icon', 'flag'); + + // And the fourth total kpi is tested + assert.dom('[data-test-total-count-kpi="3"]') + .exists('The Preprint Widget exists'); + + assert.dom('[data-test-total-count-kpi="3"]') + .hasText('1000 OSF Preprints'); + + assert.dom('[data-test-total-count-kpi="3"] [data-test-kpi-icon]') + .hasAttribute('data-icon', 'file-alt'); + + // Finally there are only 4 widgets + assert.dom('[data-test-total-count-kpi="4"]') + .doesNotExist('There are only 4 widgets'); + }); +}); diff --git a/app/institutions/dashboard/-components/doughnut-chart-kpi-wrapper/component.ts b/app/institutions/dashboard/-components/doughnut-chart-kpi-wrapper/component.ts new file mode 100644 index 00000000000..7f3a329590b --- /dev/null +++ b/app/institutions/dashboard/-components/doughnut-chart-kpi-wrapper/component.ts @@ -0,0 +1,76 @@ +import { waitFor } from '@ember/test-waiters'; +import Component from '@glimmer/component'; +import { tracked } from '@glimmer/tracking'; +import { task } from 'ember-concurrency'; +import { taskFor } from 'ember-concurrency-ts'; +import Intl from 'ember-intl/services/intl'; +import { inject as service } from '@ember/service'; +import InstitutionSummaryMetricModel from 'ember-osf-web/models/institution-summary-metric'; + +interface TotalCountKpiWrapperArgs { + model: any; +} + +interface TotalCountKpiModel { + title: string; + total: number; + icon: string; +} + +export default class DoughnutChartKpiWrapperComponent extends Component { + @service intl!: Intl; + @tracked model = this.args.model; + @tracked totalCountKpis = [] as TotalCountKpiModel[]; + @tracked isLoading = true; + + constructor(owner: unknown, args: TotalCountKpiWrapperArgs) { + super(owner, args); + + taskFor(this.loadData).perform(); + } + + /** + * calculateProjects + * + * @description Abstracted method to calculate the private and public projects + * @param summaryMetrics The institutional summary metrics object + * + * @returns The total of private and public projects + */ + private calculateProjects(summaryMetrics: InstitutionSummaryMetricModel): number { + return summaryMetrics.privateProjectCount + summaryMetrics.publicProjectCount; + } + + @task + @waitFor + private async loadData(): Promise { + const metrics = await this.model; + + this.totalCountKpis.push( + { + title: this.intl.t('institutions.dashboard.panel.users'), + total: metrics.summaryMetrics.userCount, + icon: 'building', + }, + { + title: this.intl.t('institutions.dashboard.panel.projects'), + total: this.calculateProjects(metrics.summaryMetrics), + icon: 'atom', + }, + { + title: this.intl.t('institutions.dashboard.panel.registrations'), + total: metrics.summaryMetrics.publicRegistrationCount, + icon: 'flag', + }, + { + title: this.intl.t('institutions.dashboard.panel.preprints'), + total: metrics.summaryMetrics.preprintCount, + icon: 'file-alt', + }, + ); + + this.isLoading = false; + } +} + + diff --git a/app/institutions/dashboard/-components/doughnut-chart-kpi-wrapper/doughnut-kpi/component-test.ts b/app/institutions/dashboard/-components/doughnut-chart-kpi-wrapper/doughnut-kpi/component-test.ts new file mode 100644 index 00000000000..69897968789 --- /dev/null +++ b/app/institutions/dashboard/-components/doughnut-chart-kpi-wrapper/doughnut-kpi/component-test.ts @@ -0,0 +1,64 @@ +import { render } from '@ember/test-helpers'; +import { hbs } from 'ember-cli-htmlbars'; +import { setupMirage } from 'ember-cli-mirage/test-support'; +import { EnginesIntlTestContext } from 'ember-engines/test-support'; +import { setupIntl } from 'ember-intl/test-support'; +import { setupRenderingTest } from 'ember-qunit'; +import { TestContext } from 'ember-test-helpers'; +import { module, test } from 'qunit'; + +module('Integration | institutions | dashboard | -components | total-count-kpi', hooks => { + setupRenderingTest(hooks); + setupMirage(hooks); + setupIntl(hooks); + + hooks.beforeEach(function(this: TestContext) { + const data = Object({ + total: 200, + title: 'This is the title', + icon: 'building', + }); + + this.set('data', data); + }); + + test('it renders the data correctly', async assert => { + + await render(hbs` + +`); + + assert.dom('[data-test-kpi-title]') + .hasText('This is the title'); + assert.dom('[data-test-kpi-data]') + .hasText('200'); + assert.dom('[data-test-kpi-icon]') + .hasAttribute('data-icon', 'building'); + }); + + test('it renders the without data correctly', async function(this: EnginesIntlTestContext, assert) { + const data = Object({ + total: 0, + title: 'This is the title', + icon: 'building', + }); + + this.set('data', data); + + + await render(hbs` + +`); + + assert.dom('[data-test-kpi-title]') + .hasText('This is the title'); + assert.dom('[data-test-kpi-data]') + .hasText('No data for institution found.'); + assert.dom('[data-test-kpi-icon]') + .hasAttribute('data-icon', 'building'); + }); +}); diff --git a/app/institutions/dashboard/-components/doughnut-chart-kpi-wrapper/doughnut-kpi/component.ts b/app/institutions/dashboard/-components/doughnut-chart-kpi-wrapper/doughnut-kpi/component.ts new file mode 100644 index 00000000000..0d59ea9012b --- /dev/null +++ b/app/institutions/dashboard/-components/doughnut-chart-kpi-wrapper/doughnut-kpi/component.ts @@ -0,0 +1,101 @@ +import Component from '@ember/component'; +import { action, computed } from '@ember/object'; +import { inject as service } from '@ember/service'; +import { tracked } from '@glimmer/tracking'; +import { ChartData, ChartOptions, Shape } from 'ember-cli-chart'; +import Intl from 'ember-intl/services/intl'; +import InstitutionDepartmentsModel from 'ember-osf-web/models/institution-department'; + +export default class DoughnutKpi extends Component { + @service intl!: Intl; + + @tracked collapsed = true; + topDepartments!: InstitutionDepartmentsModel[]; + totalUsers!: number; + + chartHoverIndex = 0; + + get chartOptions(): ChartOptions { + return { + aspectRatio: 1, + legend: { + display: false, + }, + onHover: this.onChartHover, + }; + } + + @action + onChartHover(_: MouseEvent, shapes: Shape[]) { + if (shapes.length === 0 || this.chartHoverIndex === shapes[0]._index) { + return; + } + this.set('chartHoverIndex', shapes[0]._index); + } + + @computed('topDepartments', 'totalUsers') + get displayDepartments() { + const departments = this.topDepartments.map(({ name, numberOfUsers }) => ({ name, numberOfUsers })); + const departmentNumbers = this.topDepartments.map(x => x.numberOfUsers); + const otherDepartmentNumber = this.totalUsers - departmentNumbers.reduce((a, b) => a + b); + + return [...departments, { name: this.intl.t('general.other'), numberOfUsers: otherDepartmentNumber }]; + } + + @computed('chartHoverIndex', 'displayDepartments.[]') + get chartData(): ChartData { + /* + const backgroundColors = this.displayDepartments.map((_, i) => { + if (i === this.chartHoverIndex) { + return '#15a5eb'; + } + return '#a5b3bd'; + }); + + const displayDepartmentNames = this.displayDepartments.map(({ name }) => name); + const displayDepartmentNumbers = this.displayDepartments.map(({ numberOfUsers }) => numberOfUsers); + */ + + const backgroundColors = [ + '#00D1FF', + '#009CEF', + '#0063EF', + '#00568D', + '#00568D', + '#004673', + '#00375A', + '#263947', + ]; + + const displayDepartmentNames = ['a', 'b', 'c']; + const displayDepartmentNumbers = [1, 2, 3]; + + return { + labels: displayDepartmentNames, + datasets: [{ + data: displayDepartmentNumbers, + backgroundColor: backgroundColors, + }], + }; + } + + /* + @computed('chartHoverIndex', 'displayDepartments.[]') + get activeDepartment(): Department { + return this.displayDepartments[this.chartHoverIndex]; + } + + @computed('activeDepartment.numberOfUsers', 'displayDepartments') + get activeDepartmentPercentage(): string { + const numUsersArray = this.displayDepartments.map(({ numberOfUsers }) => numberOfUsers); + const count = numUsersArray.reduce((a, b) => a + b); + return ((this.activeDepartment.numberOfUsers / count) * 100).toFixed(2); + } + */ + + + @action + toggleFacet() { + this.collapsed = !this.collapsed; + } +} diff --git a/app/institutions/dashboard/-components/doughnut-chart-kpi-wrapper/doughnut-kpi/styles.scss b/app/institutions/dashboard/-components/doughnut-chart-kpi-wrapper/doughnut-kpi/styles.scss new file mode 100644 index 00000000000..c3bfb8f7dda --- /dev/null +++ b/app/institutions/dashboard/-components/doughnut-chart-kpi-wrapper/doughnut-kpi/styles.scss @@ -0,0 +1,115 @@ +// stylelint-disable max-nesting-depth, selector-max-compound-selectors + +.chart-container { + margin-right: 12px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: flex-start; + width: 290px; + min-height: 290px; + height: fit-content; + background-color: $color-bg-white; + + .top-container { + width: 100%; + height: 240px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + + .ember-chart { + max-width: 220px; + max-height: 220px; + } + } + + .bottom-container { + width: 100%; + min-height: 50px; + height: fit-content; + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: flex-start; + + .title-container { + width: 100%; + height: 50px; + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + + .title { + font-size: 14px; + font-weight: normal; + height: 25px; + } + + .button-container { + margin-left: 5px; + height: 25px; + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + + .facet-expand-button { + display: flex; + justify-content: space-between; + align-items: center; + + &:active { + box-shadow: none; + } + } + } + } + + .data-container { + width: 100%; + + .facet-list { + list-style: none; + max-height: 300px; + overflow-y: auto; + margin: 0; + padding: 0.2rem; + width: 100%; + + &.collapsed { + display: none; + } + + .facet-value { + display: flex; + justify-content: flex-start; + align-items: center; + margin: 10px 0; + + .facet-link { + margin: 0 5px; + width: 80%; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + } + + .facet-color, + .facet-count { + flex-shrink: 0; + } + } + } + } + } + + &.mobile { + margin-right: 0; + margin-bottom: 12px; + } +} + + diff --git a/app/institutions/dashboard/-components/doughnut-chart-kpi-wrapper/doughnut-kpi/template.hbs b/app/institutions/dashboard/-components/doughnut-chart-kpi-wrapper/doughnut-kpi/template.hbs new file mode 100644 index 00000000000..745d2e631f5 --- /dev/null +++ b/app/institutions/dashboard/-components/doughnut-chart-kpi-wrapper/doughnut-kpi/template.hbs @@ -0,0 +1,53 @@ +
    +
    +
    + +
    +
    +
    +
    +
    {{@data.title}}
    +
    + +
    +
    +
    +
      +
    • + + {{@data.icon}} + + + {{@data.title}} + + + {{@data.total}} + +
    • +
    +
    +
    +
    \ No newline at end of file diff --git a/app/institutions/dashboard/-components/doughnut-chart-kpi-wrapper/styles.scss b/app/institutions/dashboard/-components/doughnut-chart-kpi-wrapper/styles.scss new file mode 100644 index 00000000000..e64b882c79c --- /dev/null +++ b/app/institutions/dashboard/-components/doughnut-chart-kpi-wrapper/styles.scss @@ -0,0 +1,25 @@ +.wrapper-container { + display: flex; + flex-direction: row; + justify-content: flex-start; + align-items: flex-start; + width: calc(100% - 24px); + min-height: 290px; + height: fit-content; + margin-left: 12px; + margin-right: 12px; + + .loading { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + width: 100%; + height: 290px; + } + + &.mobile { + flex-direction: column; + height: fit-content; + } +} diff --git a/app/institutions/dashboard/-components/doughnut-chart-kpi-wrapper/template.hbs b/app/institutions/dashboard/-components/doughnut-chart-kpi-wrapper/template.hbs new file mode 100644 index 00000000000..5b5215dc38d --- /dev/null +++ b/app/institutions/dashboard/-components/doughnut-chart-kpi-wrapper/template.hbs @@ -0,0 +1,14 @@ +
    + {{#if this.isLoading}} +
    + +
    + {{else}} + {{#each this.totalCountKpis as |totalCountKpi index|}} + + {{/each}} + {{/if}} +
    \ No newline at end of file diff --git a/app/institutions/dashboard/-components/total-count-kpi-wrapper/component.ts b/app/institutions/dashboard/-components/total-count-kpi-wrapper/component.ts index 59d80e15ae4..a7891274f39 100644 --- a/app/institutions/dashboard/-components/total-count-kpi-wrapper/component.ts +++ b/app/institutions/dashboard/-components/total-count-kpi-wrapper/component.ts @@ -50,17 +50,17 @@ export default class TotalCountKpiWrapperComponent extends Component +//
    +// 1 +//
    +//
    +// 2 +//
    +//
    diff --git a/app/institutions/dashboard/index/template.hbs b/app/institutions/dashboard/index/template.hbs index 0e8c5721bea..691f2b71679 100644 --- a/app/institutions/dashboard/index/template.hbs +++ b/app/institutions/dashboard/index/template.hbs @@ -2,27 +2,20 @@ @institution={{this.model.institution}} as |wrapper| > - - {{!--
    - - - +
    + +
    +
    + +
    +
    +
    -
    - - - -
    --}} From f14f29277d974f79ed64dd2e7b6770e7114be5ec Mon Sep 17 00:00:00 2001 From: Brian Pilati Date: Wed, 2 Oct 2024 10:11:17 -0500 Subject: [PATCH 32/96] Fixed the mobile formatting and made the graphs dynamic --- .../doughnut-chart-kpi-wrapper/component.ts | 5 +++ .../doughnut-kpi/component.ts | 1 - .../doughnut-kpi/template.hbs | 2 +- .../doughnut-chart-kpi-wrapper/styles.scss | 3 ++ app/institutions/dashboard/index/styles.scss | 35 ++++++------------- app/institutions/dashboard/index/template.hbs | 4 +-- 6 files changed, 20 insertions(+), 30 deletions(-) diff --git a/app/institutions/dashboard/-components/doughnut-chart-kpi-wrapper/component.ts b/app/institutions/dashboard/-components/doughnut-chart-kpi-wrapper/component.ts index 7f3a329590b..2d5b2e3eb5d 100644 --- a/app/institutions/dashboard/-components/doughnut-chart-kpi-wrapper/component.ts +++ b/app/institutions/dashboard/-components/doughnut-chart-kpi-wrapper/component.ts @@ -15,6 +15,7 @@ interface TotalCountKpiModel { title: string; total: number; icon: string; + chart: string; } export default class DoughnutChartKpiWrapperComponent extends Component { @@ -51,21 +52,25 @@ export default class DoughnutChartKpiWrapperComponent extends Component diff --git a/app/institutions/dashboard/-components/doughnut-chart-kpi-wrapper/styles.scss b/app/institutions/dashboard/-components/doughnut-chart-kpi-wrapper/styles.scss index e64b882c79c..5a6eeda8084 100644 --- a/app/institutions/dashboard/-components/doughnut-chart-kpi-wrapper/styles.scss +++ b/app/institutions/dashboard/-components/doughnut-chart-kpi-wrapper/styles.scss @@ -8,6 +8,7 @@ height: fit-content; margin-left: 12px; margin-right: 12px; + margin-bottom: 12px; .loading { display: flex; @@ -21,5 +22,7 @@ &.mobile { flex-direction: column; height: fit-content; + align-items: center; + margin-bottom: 0; } } diff --git a/app/institutions/dashboard/index/styles.scss b/app/institutions/dashboard/index/styles.scss index 0a20a5d3cde..d458b390e56 100644 --- a/app/institutions/dashboard/index/styles.scss +++ b/app/institutions/dashboard/index/styles.scss @@ -12,14 +12,15 @@ flex-direction: row; justify-content: flex-start; align-items: flex-start; - margin-bottom: 20px; + margin-bottom: 12px; width: 100%; - height: 140px; + height: fit-content; } .chart-container { display: flex; flex-direction: row; + flex-wrap: wrap; justify-content: flex-start; align-items: flex-start; margin-bottom: 20px; @@ -27,27 +28,11 @@ min-height: 290px; height: fit-content; } -} - -// Example to be used later -// .parent { -// display: grid; -// grid-template-columns: 1fr; -// } - -// .parent div { -// padding: 50px; -// background-color: rgba(0,0,0,0.5); -// grid-row-start: 1; -// grid-column-start: 1; -// } - -//
    -//
    -// 1 -//
    -//
    -// 2 -//
    -//
    + &.mobile { + .kpi-container, + .chart-container { + margin-bottom: 0; + } + } +} diff --git a/app/institutions/dashboard/index/template.hbs b/app/institutions/dashboard/index/template.hbs index 691f2b71679..6e7b3340487 100644 --- a/app/institutions/dashboard/index/template.hbs +++ b/app/institutions/dashboard/index/template.hbs @@ -1,7 +1,7 @@ - +
    -
    -
    From 72ea5a3a1000808f41062f262e19dea50225fe2c Mon Sep 17 00:00:00 2001 From: Brian Pilati Date: Wed, 2 Oct 2024 12:22:35 -0500 Subject: [PATCH 33/96] Updates for all the love --- .../chart-kpi}/component-test.ts | 0 .../chart-kpi}/component.ts | 109 ++++++++++++++---- .../chart-kpi}/styles.scss | 37 +++--- .../chart-kpi}/template.hbs | 35 +++--- .../component-test.ts | 0 .../component.ts | 2 +- .../styles.scss | 0 .../template.hbs | 2 +- app/institutions/dashboard/index/template.hbs | 4 +- types/ember-cli-chart.d.ts | 15 +++ 10 files changed, 142 insertions(+), 62 deletions(-) rename app/institutions/dashboard/-components/{doughnut-chart-kpi-wrapper/doughnut-kpi => chart-kpi-wrapper/chart-kpi}/component-test.ts (100%) rename app/institutions/dashboard/-components/{doughnut-chart-kpi-wrapper/doughnut-kpi => chart-kpi-wrapper/chart-kpi}/component.ts (55%) rename app/institutions/dashboard/-components/{doughnut-chart-kpi-wrapper/doughnut-kpi => chart-kpi-wrapper/chart-kpi}/styles.scss (77%) rename app/institutions/dashboard/-components/{doughnut-chart-kpi-wrapper/doughnut-kpi => chart-kpi-wrapper/chart-kpi}/template.hbs (59%) rename app/institutions/dashboard/-components/{doughnut-chart-kpi-wrapper => chart-kpi-wrapper}/component-test.ts (100%) rename app/institutions/dashboard/-components/{doughnut-chart-kpi-wrapper => chart-kpi-wrapper}/component.ts (96%) rename app/institutions/dashboard/-components/{doughnut-chart-kpi-wrapper => chart-kpi-wrapper}/styles.scss (100%) rename app/institutions/dashboard/-components/{doughnut-chart-kpi-wrapper => chart-kpi-wrapper}/template.hbs (84%) diff --git a/app/institutions/dashboard/-components/doughnut-chart-kpi-wrapper/doughnut-kpi/component-test.ts b/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/component-test.ts similarity index 100% rename from app/institutions/dashboard/-components/doughnut-chart-kpi-wrapper/doughnut-kpi/component-test.ts rename to app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/component-test.ts diff --git a/app/institutions/dashboard/-components/doughnut-chart-kpi-wrapper/doughnut-kpi/component.ts b/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/component.ts similarity index 55% rename from app/institutions/dashboard/-components/doughnut-chart-kpi-wrapper/doughnut-kpi/component.ts rename to app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/component.ts index 57fd67a4696..4fa9fa808bf 100644 --- a/app/institutions/dashboard/-components/doughnut-chart-kpi-wrapper/doughnut-kpi/component.ts +++ b/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/component.ts @@ -2,37 +2,50 @@ import Component from '@ember/component'; import { action, computed } from '@ember/object'; import { inject as service } from '@ember/service'; import { tracked } from '@glimmer/tracking'; -import { ChartData, ChartOptions, Shape } from 'ember-cli-chart'; +import { ChartData, ChartOptions } from 'ember-cli-chart'; import Intl from 'ember-intl/services/intl'; import InstitutionDepartmentsModel from 'ember-osf-web/models/institution-department'; -export default class DoughnutKpi extends Component { +interface DataModel { + name: string; + count: number; + color: string; +} + +export default class ChartKpi extends Component { @service intl!: Intl; @tracked collapsed = true; + @tracked expandedData = [] as DataModel[]; topDepartments!: InstitutionDepartmentsModel[]; totalUsers!: number; chartHoverIndex = 0; - get chartOptions(): ChartOptions { + /** + * chartOptions + * + * @description A getter for the chartjs options + * + * @returns a ChartOptions model which is custom to COS + */ + get chartOptions(): ChartOptions{ return { aspectRatio: 1, legend: { display: false, }, - onHover: this.onChartHover, + scales: { + xAxes: [{ + display: false, + }], + yAxes: [{ + display: false, + }], + }, }; } - @action - onChartHover(_: MouseEvent, shapes: Shape[]) { - if (shapes.length === 0 || this.chartHoverIndex === shapes[0]._index) { - return; - } - this.set('chartHoverIndex', shapes[0]._index); - } - @computed('topDepartments', 'totalUsers') get displayDepartments() { const departments = this.topDepartments.map(({ name, numberOfUsers }) => ({ name, numberOfUsers })); @@ -42,7 +55,31 @@ export default class DoughnutKpi extends Component { return [...departments, { name: this.intl.t('general.other'), numberOfUsers: otherDepartmentNumber }]; } - @computed('chartHoverIndex', 'displayDepartments.[]') + /** + * getColor + * + * @description Gets a specific color using a modulus + * + * @param index The index to retrieve + * + * @returns the color + */ + private getColor(index: number): string { + const backgroundColors = [ + '#00D1FF', + '#009CEF', + '#0063EF', + '#00568D', + '#004673', + '#00375A', + '#263947', + ]; + + return backgroundColors[index % backgroundColors.length]; + + } + + @computed('chartHoverIndex', 'displayDepartments.[]', 'expandedData.[]') get chartData(): ChartData { /* const backgroundColors = this.displayDepartments.map((_, i) => { @@ -56,23 +93,45 @@ export default class DoughnutKpi extends Component { const displayDepartmentNumbers = this.displayDepartments.map(({ numberOfUsers }) => numberOfUsers); */ - const backgroundColors = [ - '#00D1FF', - '#009CEF', - '#0063EF', - '#00568D', - '#004673', - '#00375A', - '#263947', + const backgroundColors = [] as string[]; + + const displayDepartmentNames = ['a very long data set title that needs to be handled', 'b', 'c', + 'd', 'e', 'brian', 'g', 'repeated color']; + const displayDepartmentNumbers = [100000, 50000, + 25000, + 10000, + 5000, + 500, + 50, + 5, ]; - const displayDepartmentNames = ['a', 'b', 'c']; - const displayDepartmentNumbers = [1, 2, 3]; + displayDepartmentNames.map((departmentName: string, $index: number) => { + backgroundColors.push(this.getColor($index)); + + this.expandedData.push({ + name: departmentName , + count: displayDepartmentNumbers[$index], + color: this.getColor($index), + }); + }); return { - labels: displayDepartmentNames, + // labels: displayDepartmentNames, + labels: ['a', + 'b', + 'b', + 'b', + 'b', + 'b', + 'b', + 'b', + 'b', + + ], datasets: [{ data: displayDepartmentNumbers, + fill: false, backgroundColor: backgroundColors, }], }; @@ -94,7 +153,7 @@ export default class DoughnutKpi extends Component { @action - toggleFacet() { + public toggleExpandedData() { this.collapsed = !this.collapsed; } } diff --git a/app/institutions/dashboard/-components/doughnut-chart-kpi-wrapper/doughnut-kpi/styles.scss b/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/styles.scss similarity index 77% rename from app/institutions/dashboard/-components/doughnut-chart-kpi-wrapper/doughnut-kpi/styles.scss rename to app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/styles.scss index c3bfb8f7dda..5ccd05bcad7 100644 --- a/app/institutions/dashboard/-components/doughnut-chart-kpi-wrapper/doughnut-kpi/styles.scss +++ b/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/styles.scss @@ -68,38 +68,45 @@ } } - .data-container { + .expanded-data-container { width: 100%; + border-top: 2px solid $color-bg-gray; + padding-top: 10px; - .facet-list { + &.collapsed { + border-top: 0; + display: none; + } + + .data-list { list-style: none; - max-height: 300px; - overflow-y: auto; margin: 0; padding: 0.2rem; width: 100%; - &.collapsed { - display: none; - } - - .facet-value { + .data-container { display: flex; + flex-direction: row; justify-content: flex-start; align-items: center; - margin: 10px 0; + width: 282px; - .facet-link { + .name { margin: 0 5px; - width: 80%; + width: calc(282px - 100px); text-overflow: ellipsis; white-space: nowrap; overflow: hidden; } - .facet-color, - .facet-count { - flex-shrink: 0; + .color { + width: 20px; + height: 20px; + } + + .count { + width: 80px; + text-align: right; } } } diff --git a/app/institutions/dashboard/-components/doughnut-chart-kpi-wrapper/doughnut-kpi/template.hbs b/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/template.hbs similarity index 59% rename from app/institutions/dashboard/-components/doughnut-chart-kpi-wrapper/doughnut-kpi/template.hbs rename to app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/template.hbs index fc823ad12e9..6a1e4142d87 100644 --- a/app/institutions/dashboard/-components/doughnut-chart-kpi-wrapper/doughnut-kpi/template.hbs +++ b/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/template.hbs @@ -24,29 +24,28 @@ @layout='fake-link' local-class='facet-expand-button' aria-expanded={{not this.collapsed}} - {{on 'click' this.toggleFacet}} + {{on 'click' this.toggleExpandedData}} >
    -
    -
      -
    • - - {{@data.icon}} - - - {{@data.title}} - - - {{@data.total}} - -
    • +
      +
        + {{#each this.expandedData as |data|}} +
      • +
        +
        +
        + {{data.name}} +
        +
        + {{data.count}} +
        +
      • + {{/each}}
    diff --git a/app/institutions/dashboard/-components/doughnut-chart-kpi-wrapper/component-test.ts b/app/institutions/dashboard/-components/chart-kpi-wrapper/component-test.ts similarity index 100% rename from app/institutions/dashboard/-components/doughnut-chart-kpi-wrapper/component-test.ts rename to app/institutions/dashboard/-components/chart-kpi-wrapper/component-test.ts diff --git a/app/institutions/dashboard/-components/doughnut-chart-kpi-wrapper/component.ts b/app/institutions/dashboard/-components/chart-kpi-wrapper/component.ts similarity index 96% rename from app/institutions/dashboard/-components/doughnut-chart-kpi-wrapper/component.ts rename to app/institutions/dashboard/-components/chart-kpi-wrapper/component.ts index 2d5b2e3eb5d..369caf8266a 100644 --- a/app/institutions/dashboard/-components/doughnut-chart-kpi-wrapper/component.ts +++ b/app/institutions/dashboard/-components/chart-kpi-wrapper/component.ts @@ -18,7 +18,7 @@ interface TotalCountKpiModel { chart: string; } -export default class DoughnutChartKpiWrapperComponent extends Component { +export default class ChartKpiWrapperComponent extends Component { @service intl!: Intl; @tracked model = this.args.model; @tracked totalCountKpis = [] as TotalCountKpiModel[]; diff --git a/app/institutions/dashboard/-components/doughnut-chart-kpi-wrapper/styles.scss b/app/institutions/dashboard/-components/chart-kpi-wrapper/styles.scss similarity index 100% rename from app/institutions/dashboard/-components/doughnut-chart-kpi-wrapper/styles.scss rename to app/institutions/dashboard/-components/chart-kpi-wrapper/styles.scss diff --git a/app/institutions/dashboard/-components/doughnut-chart-kpi-wrapper/template.hbs b/app/institutions/dashboard/-components/chart-kpi-wrapper/template.hbs similarity index 84% rename from app/institutions/dashboard/-components/doughnut-chart-kpi-wrapper/template.hbs rename to app/institutions/dashboard/-components/chart-kpi-wrapper/template.hbs index 5b5215dc38d..f9d574f4d81 100644 --- a/app/institutions/dashboard/-components/doughnut-chart-kpi-wrapper/template.hbs +++ b/app/institutions/dashboard/-components/chart-kpi-wrapper/template.hbs @@ -5,7 +5,7 @@
    {{else}} {{#each this.totalCountKpis as |totalCountKpi index|}} - diff --git a/app/institutions/dashboard/index/template.hbs b/app/institutions/dashboard/index/template.hbs index 6e7b3340487..a49aa31c8c0 100644 --- a/app/institutions/dashboard/index/template.hbs +++ b/app/institutions/dashboard/index/template.hbs @@ -8,10 +8,10 @@ />
    - -
    diff --git a/types/ember-cli-chart.d.ts b/types/ember-cli-chart.d.ts index 23c208ad83d..93d77cdeae9 100644 --- a/types/ember-cli-chart.d.ts +++ b/types/ember-cli-chart.d.ts @@ -2,6 +2,7 @@ declare module 'ember-cli-chart' { interface DataSet { data?: number[]; backgroundColor?: string[]; + fill?: boolean; } export interface ChartData { @@ -15,6 +16,20 @@ declare module 'ember-cli-chart' { display?: boolean, }; onHover?: (_: MouseEvent, shapes: Shape[]) => void; + scales?: { + xAxes?: [ + { + display?: boolean + } + + ], + yAxes?: [ + { + display?: boolean + } + + ] + } } export interface Shape { From d8f54d64785963fa6babf3ad60fb0800b1b72949 Mon Sep 17 00:00:00 2001 From: Brian Pilati Date: Thu, 3 Oct 2024 12:22:53 -0500 Subject: [PATCH 34/96] Added a very rudimentary test for the chart-kpi wrapper --- .../chart-kpi-wrapper/component-test.ts | 58 ++++++++----------- .../chart-kpi-wrapper/component.ts | 17 ++---- .../chart-kpi-wrapper/template.hbs | 10 ++-- 3 files changed, 36 insertions(+), 49 deletions(-) diff --git a/app/institutions/dashboard/-components/chart-kpi-wrapper/component-test.ts b/app/institutions/dashboard/-components/chart-kpi-wrapper/component-test.ts index a4a7fffd754..4f1314f3816 100644 --- a/app/institutions/dashboard/-components/chart-kpi-wrapper/component-test.ts +++ b/app/institutions/dashboard/-components/chart-kpi-wrapper/component-test.ts @@ -6,7 +6,7 @@ import { setupRenderingTest } from 'ember-qunit'; import { TestContext } from 'ember-test-helpers'; import { module, test } from 'qunit'; -module('Integration | institutions | dashboard | -components | total-count-kpi-wrapper', hooks => { +module('Integration | institutions | dashboard | -components | total-count-chart-wrapper', hooks => { setupRenderingTest(hooks); setupMirage(hooks); setupIntl(hooks); @@ -25,56 +25,48 @@ module('Integration | institutions | dashboard | -components | total-count-kpi-w this.set('model', model); }); - test('it renders the dashboard total kpis correctly', async assert => { + test('it renders the dashboard total charts correctly', async assert => { // Given the component is rendered await render(hbs` - `); - // Then the first total kpi is tested - assert.dom('[data-test-total-count-kpi="0"]') - .exists('The User Widget exists'); + // Then the first total chart is tested + assert.dom('[data-test-total-count-chart="0"]') + .exists('The User Chart exists'); - assert.dom('[data-test-total-count-kpi="0"]') - .hasText('10 Total Users'); + assert.dom('[data-test-total-count-chart="0"]') + // eslint-disable-next-line max-len + .hasText('Total Users a very long data set title that needs to be handled 100000 b 50000 c 25000 d 10000 e 5000 brian 500 g 50 repeated color 5'); - assert.dom('[data-test-total-count-kpi="0"] [data-test-kpi-icon]') - .hasAttribute('data-icon', 'building'); - - // And the second total kpi is tested - assert.dom('[data-test-total-count-kpi="1"]') + // And the second total chart is tested + assert.dom('[data-test-total-count-chart="1"]') .exists('The Project Widget exists'); - assert.dom('[data-test-total-count-kpi="1"]') - .hasText('20 OSF Public and Private Projects'); - - assert.dom('[data-test-total-count-kpi="1"] [data-test-kpi-icon]') - .hasAttribute('data-icon', 'atom'); + assert.dom('[data-test-total-count-chart="1"]') + // eslint-disable-next-line max-len + .hasText('OSF Public and Private Projects a very long data set title that needs to be handled 100000 b 50000 c 25000 d 10000 e 5000 brian 500 g 50 repeated color 5'); - // And the third total kpi is tested - assert.dom('[data-test-total-count-kpi="2"]') + // And the third total chart is tested + assert.dom('[data-test-total-count-chart="2"]') .exists('The Registration Widget exists'); - assert.dom('[data-test-total-count-kpi="2"]') - .hasText('100 OSF Registrations'); + assert.dom('[data-test-total-count-chart="2"]') + // eslint-disable-next-line max-len + .hasText('OSF Registrations a very long data set title that needs to be handled 100000 b 50000 c 25000 d 10000 e 5000 brian 500 g 50 repeated color 5'); - assert.dom('[data-test-total-count-kpi="2"] [data-test-kpi-icon]') - .hasAttribute('data-icon', 'flag'); - - // And the fourth total kpi is tested - assert.dom('[data-test-total-count-kpi="3"]') + // And the fourth total chart is tested + assert.dom('[data-test-total-count-chart="3"]') .exists('The Preprint Widget exists'); - assert.dom('[data-test-total-count-kpi="3"]') - .hasText('1000 OSF Preprints'); - - assert.dom('[data-test-total-count-kpi="3"] [data-test-kpi-icon]') - .hasAttribute('data-icon', 'file-alt'); + assert.dom('[data-test-total-count-chart="3"]') + // eslint-disable-next-line max-len + .hasText('OSF Preprints a very long data set title that needs to be handled 100000 b 50000 c 25000 d 10000 e 5000 brian 500 g 50 repeated color 5'); // Finally there are only 4 widgets - assert.dom('[data-test-total-count-kpi="4"]') + assert.dom('[data-test-total-count-chart="4"]') .doesNotExist('There are only 4 widgets'); }); }); diff --git a/app/institutions/dashboard/-components/chart-kpi-wrapper/component.ts b/app/institutions/dashboard/-components/chart-kpi-wrapper/component.ts index 369caf8266a..f709c785e90 100644 --- a/app/institutions/dashboard/-components/chart-kpi-wrapper/component.ts +++ b/app/institutions/dashboard/-components/chart-kpi-wrapper/component.ts @@ -7,24 +7,23 @@ import Intl from 'ember-intl/services/intl'; import { inject as service } from '@ember/service'; import InstitutionSummaryMetricModel from 'ember-osf-web/models/institution-summary-metric'; -interface TotalCountKpiWrapperArgs { +interface TotalCountChartWrapperArgs { model: any; } -interface TotalCountKpiModel { +interface TotalCountChartModel { title: string; total: number; - icon: string; chart: string; } -export default class ChartKpiWrapperComponent extends Component { +export default class ChartKpiWrapperComponent extends Component { @service intl!: Intl; @tracked model = this.args.model; - @tracked totalCountKpis = [] as TotalCountKpiModel[]; + @tracked totalCountCharts = [] as TotalCountChartModel[]; @tracked isLoading = true; - constructor(owner: unknown, args: TotalCountKpiWrapperArgs) { + constructor(owner: unknown, args: TotalCountChartWrapperArgs) { super(owner, args); taskFor(this.loadData).perform(); @@ -47,29 +46,25 @@ export default class ChartKpiWrapperComponent extends Component { const metrics = await this.model; - this.totalCountKpis.push( + this.totalCountCharts.push( { title: this.intl.t('institutions.dashboard.panel.users'), total: metrics.summaryMetrics.userCount, - icon: 'building', chart: 'doughnut', }, { title: this.intl.t('institutions.dashboard.panel.projects'), total: this.calculateProjects(metrics.summaryMetrics), - icon: 'atom', chart: 'pie', }, { title: this.intl.t('institutions.dashboard.panel.registrations'), total: metrics.summaryMetrics.publicRegistrationCount, - icon: 'flag', chart: 'bar', }, { title: this.intl.t('institutions.dashboard.panel.preprints'), total: metrics.summaryMetrics.preprintCount, - icon: 'file-alt', chart: 'line', }, ); diff --git a/app/institutions/dashboard/-components/chart-kpi-wrapper/template.hbs b/app/institutions/dashboard/-components/chart-kpi-wrapper/template.hbs index f9d574f4d81..e14773b8f74 100644 --- a/app/institutions/dashboard/-components/chart-kpi-wrapper/template.hbs +++ b/app/institutions/dashboard/-components/chart-kpi-wrapper/template.hbs @@ -1,13 +1,13 @@
    {{#if this.isLoading}} -
    - +
    +
    {{else}} - {{#each this.totalCountKpis as |totalCountKpi index|}} + {{#each this.totalCountCharts as |totalCountChart index|}} {{/each}} {{/if}} From 0f41eab23e35252c2093bebc46443a12235d5ad8 Mon Sep 17 00:00:00 2001 From: Brian Pilati Date: Thu, 3 Oct 2024 13:38:01 -0500 Subject: [PATCH 35/96] Added some more tests --- .../chart-kpi/component-test.ts | 65 +++++++++++++++---- .../chart-kpi-wrapper/chart-kpi/component.ts | 18 +++-- .../chart-kpi-wrapper/chart-kpi/styles.scss | 14 ++-- .../chart-kpi-wrapper/chart-kpi/template.hbs | 56 +++++++++------- .../chart-kpi-wrapper/component-test.ts | 8 +-- 5 files changed, 108 insertions(+), 53 deletions(-) diff --git a/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/component-test.ts b/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/component-test.ts index 69897968789..549354cec22 100644 --- a/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/component-test.ts +++ b/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/component-test.ts @@ -1,13 +1,12 @@ -import { render } from '@ember/test-helpers'; +import { click, render } from '@ember/test-helpers'; import { hbs } from 'ember-cli-htmlbars'; import { setupMirage } from 'ember-cli-mirage/test-support'; -import { EnginesIntlTestContext } from 'ember-engines/test-support'; import { setupIntl } from 'ember-intl/test-support'; import { setupRenderingTest } from 'ember-qunit'; import { TestContext } from 'ember-test-helpers'; import { module, test } from 'qunit'; -module('Integration | institutions | dashboard | -components | total-count-kpi', hooks => { +module('Integration | institutions | dashboard | -components | chart-kpi', hooks => { setupRenderingTest(hooks); setupMirage(hooks); setupIntl(hooks); @@ -16,7 +15,7 @@ module('Integration | institutions | dashboard | -components | total-count-kpi', const data = Object({ total: 200, title: 'This is the title', - icon: 'building', + chart: 'pie', }); this.set('data', data); @@ -24,20 +23,63 @@ module('Integration | institutions | dashboard | -components | total-count-kpi', test('it renders the data correctly', async assert => { + // Given the component is rendered await render(hbs` - `); + // Then the chart is verified + assert.dom('[data-test-chart]') + .exists('The test chart exists'); - assert.dom('[data-test-kpi-title]') + // And the title is verified + assert.dom('[data-test-chart-title]') .hasText('This is the title'); - assert.dom('[data-test-kpi-data]') - .hasText('200'); - assert.dom('[data-test-kpi-icon]') - .hasAttribute('data-icon', 'building'); + + assert.dom('[data-test-toggle-icon]') + .hasAttribute('data-icon', 'caret-down'); + + // Finally the expanded data is not visible + assert.dom('[data-test-expansion-data]') + .doesNotExist('The expansion data is not visible'); + + }); + + test('it renders the expanded data correctly', async assert => { + + // Given the component is rendered + await render(hbs` + +`); + // When I click the expanded icon + await click('[data-test-expand-additional-data]'); + + // Then I verify the icon has changed + assert.dom('[data-test-toggle-icon]') + .hasAttribute('data-icon', 'caret-up'); + + // And the expanded data is visible + assert.dom('[data-test-expansion-data]') + .exists('The expansion data is visible'); + + // And the expanded data position 0 color is verified + assert.dom('[data-test-expanded-color="0"]') + .hasAttribute('style', 'background-color:#00D1FF'); + + // And the expanded data position 0 name is verified + assert.dom('[data-test-expanded-name="0"]') + .hasText('a very long data set title that needs to be handled'); + + // And the expanded data position 0 total is verified + assert.dom('[data-test-expanded-total="0"]') + .hasText('100000'); }); + /** + * I need to determine if this is going to be a feature or not test('it renders the without data correctly', async function(this: EnginesIntlTestContext, assert) { const data = Object({ total: 0, @@ -49,7 +91,7 @@ module('Integration | institutions | dashboard | -components | total-count-kpi', await render(hbs` - `); @@ -61,4 +103,5 @@ module('Integration | institutions | dashboard | -components | total-count-kpi', assert.dom('[data-test-kpi-icon]') .hasAttribute('data-icon', 'building'); }); + */ }); diff --git a/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/component.ts b/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/component.ts index 4fa9fa808bf..8b3e53be794 100644 --- a/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/component.ts +++ b/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/component.ts @@ -1,4 +1,4 @@ -import Component from '@ember/component'; +import Component from '@glimmer/component'; import { action, computed } from '@ember/object'; import { inject as service } from '@ember/service'; import { tracked } from '@glimmer/tracking'; @@ -6,16 +6,22 @@ import { ChartData, ChartOptions } from 'ember-cli-chart'; import Intl from 'ember-intl/services/intl'; import InstitutionDepartmentsModel from 'ember-osf-web/models/institution-department'; +interface KPIChartWrapperArgs { + title: string; + total: number; + chart: string; +} + interface DataModel { name: string; - count: number; + total: number; color: string; } -export default class ChartKpi extends Component { +export default class ChartKpi extends Component { @service intl!: Intl; - @tracked collapsed = true; + @tracked expanded = false; @tracked expandedData = [] as DataModel[]; topDepartments!: InstitutionDepartmentsModel[]; totalUsers!: number; @@ -111,7 +117,7 @@ export default class ChartKpi extends Component { this.expandedData.push({ name: departmentName , - count: displayDepartmentNumbers[$index], + total: displayDepartmentNumbers[$index], color: this.getColor($index), }); }); @@ -154,6 +160,6 @@ export default class ChartKpi extends Component { @action public toggleExpandedData() { - this.collapsed = !this.collapsed; + this.expanded = !this.expanded; } } diff --git a/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/styles.scss b/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/styles.scss index 5ccd05bcad7..c79f45e3d04 100644 --- a/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/styles.scss +++ b/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/styles.scss @@ -70,19 +70,17 @@ .expanded-data-container { width: 100%; - border-top: 2px solid $color-bg-gray; padding-top: 10px; - - &.collapsed { - border-top: 0; - display: none; - } + display: flex; + justify-content: center; + align-items: flex-start; .data-list { list-style: none; margin: 0; padding: 0.2rem; - width: 100%; + width: calc(100% - 0.2rem); + border-top: 2px solid $color-bg-gray; .data-container { display: flex; @@ -104,7 +102,7 @@ height: 20px; } - .count { + .total { width: 80px; text-align: right; } diff --git a/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/template.hbs b/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/template.hbs index 6a1e4142d87..575a7595562 100644 --- a/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/template.hbs +++ b/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/template.hbs @@ -16,37 +16,45 @@
    -
    {{@data.title}}
    +
    {{@data.title}}
    -
    -
      - {{#each this.expandedData as |data|}} -
    • -
      -
      -
      - {{data.name}} -
      -
      - {{data.count}} -
      -
    • - {{/each}} -
    -
    + {{#if this.expanded }} +
    +
      + {{#each this.expandedData as |data index |}} +
    • +
      +
      +
      + {{data.name}} +
      +
      + {{data.total}} +
      +
    • + {{/each}} +
    +
    + {{/if}}
    \ No newline at end of file diff --git a/app/institutions/dashboard/-components/chart-kpi-wrapper/component-test.ts b/app/institutions/dashboard/-components/chart-kpi-wrapper/component-test.ts index 4f1314f3816..d7d3e038467 100644 --- a/app/institutions/dashboard/-components/chart-kpi-wrapper/component-test.ts +++ b/app/institutions/dashboard/-components/chart-kpi-wrapper/component-test.ts @@ -39,7 +39,7 @@ module('Integration | institutions | dashboard | -components | total-count-chart assert.dom('[data-test-total-count-chart="0"]') // eslint-disable-next-line max-len - .hasText('Total Users a very long data set title that needs to be handled 100000 b 50000 c 25000 d 10000 e 5000 brian 500 g 50 repeated color 5'); + .hasText('Total Users'); // And the second total chart is tested assert.dom('[data-test-total-count-chart="1"]') @@ -47,7 +47,7 @@ module('Integration | institutions | dashboard | -components | total-count-chart assert.dom('[data-test-total-count-chart="1"]') // eslint-disable-next-line max-len - .hasText('OSF Public and Private Projects a very long data set title that needs to be handled 100000 b 50000 c 25000 d 10000 e 5000 brian 500 g 50 repeated color 5'); + .hasText('OSF Public and Private Projects'); // And the third total chart is tested assert.dom('[data-test-total-count-chart="2"]') @@ -55,7 +55,7 @@ module('Integration | institutions | dashboard | -components | total-count-chart assert.dom('[data-test-total-count-chart="2"]') // eslint-disable-next-line max-len - .hasText('OSF Registrations a very long data set title that needs to be handled 100000 b 50000 c 25000 d 10000 e 5000 brian 500 g 50 repeated color 5'); + .hasText('OSF Registrations'); // And the fourth total chart is tested assert.dom('[data-test-total-count-chart="3"]') @@ -63,7 +63,7 @@ module('Integration | institutions | dashboard | -components | total-count-chart assert.dom('[data-test-total-count-chart="3"]') // eslint-disable-next-line max-len - .hasText('OSF Preprints a very long data set title that needs to be handled 100000 b 50000 c 25000 d 10000 e 5000 brian 500 g 50 repeated color 5'); + .hasText('OSF Preprints'); // Finally there are only 4 widgets assert.dom('[data-test-total-count-chart="4"]') From c7ffc35f5d6a8168f3209b5ac4240b19e10cdc3c Mon Sep 17 00:00:00 2001 From: Brian Pilati Date: Mon, 7 Oct 2024 13:10:34 -0500 Subject: [PATCH 36/96] Added additional tests and more dynamic data --- .../chart-kpi/component-test.ts | 9 +- .../chart-kpi-wrapper/chart-kpi/component.ts | 97 ++++----------- .../chart-kpi-wrapper/chart-kpi/template.hbs | 4 +- .../chart-kpi-wrapper/component-test.ts | 115 +++++++++++++----- .../chart-kpi-wrapper/component.ts | 113 +++++++++++++---- .../chart-kpi-wrapper/template.hbs | 8 +- .../total-count-kpi-wrapper/component.ts | 8 +- app/institutions/dashboard/route.ts | 1 + translations/en-us.yml | 10 +- 9 files changed, 222 insertions(+), 143 deletions(-) diff --git a/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/component-test.ts b/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/component-test.ts index 549354cec22..f06fc4053ab 100644 --- a/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/component-test.ts +++ b/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/component-test.ts @@ -13,9 +13,14 @@ module('Integration | institutions | dashboard | -components | chart-kpi', hooks hooks.beforeEach(function(this: TestContext) { const data = Object({ - total: 200, title: 'This is the title', - chart: 'pie', + chartData: [ + Object({ + label: 'a very long data set title that needs to be handled', + total: 100000, + }), + ], + chartType: 'pie', }); this.set('data', data); diff --git a/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/component.ts b/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/component.ts index 8b3e53be794..dce4269a42f 100644 --- a/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/component.ts +++ b/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/component.ts @@ -1,15 +1,14 @@ import Component from '@glimmer/component'; -import { action, computed } from '@ember/object'; +import { action } from '@ember/object'; import { inject as service } from '@ember/service'; import { tracked } from '@glimmer/tracking'; import { ChartData, ChartOptions } from 'ember-cli-chart'; import Intl from 'ember-intl/services/intl'; -import InstitutionDepartmentsModel from 'ember-osf-web/models/institution-department'; +// eslint-disable-next-line max-len +import { ChartDataModel, KpiChartModel } from 'ember-osf-web/institutions/dashboard/-components/chart-kpi-wrapper/component'; interface KPIChartWrapperArgs { - title: string; - total: number; - chart: string; + data: KpiChartModel; } interface DataModel { @@ -23,10 +22,6 @@ export default class ChartKpi extends Component { @tracked expanded = false; @tracked expandedData = [] as DataModel[]; - topDepartments!: InstitutionDepartmentsModel[]; - totalUsers!: number; - - chartHoverIndex = 0; /** * chartOptions @@ -35,7 +30,7 @@ export default class ChartKpi extends Component { * * @returns a ChartOptions model which is custom to COS */ - get chartOptions(): ChartOptions{ + get chartOptions(): ChartOptions { return { aspectRatio: 1, legend: { @@ -52,15 +47,6 @@ export default class ChartKpi extends Component { }; } - @computed('topDepartments', 'totalUsers') - get displayDepartments() { - const departments = this.topDepartments.map(({ name, numberOfUsers }) => ({ name, numberOfUsers })); - const departmentNumbers = this.topDepartments.map(x => x.numberOfUsers); - const otherDepartmentNumber = this.totalUsers - departmentNumbers.reduce((a, b) => a + b); - - return [...departments, { name: this.intl.t('general.other'), numberOfUsers: otherDepartmentNumber }]; - } - /** * getColor * @@ -82,82 +68,43 @@ export default class ChartKpi extends Component { ]; return backgroundColors[index % backgroundColors.length]; - } - @computed('chartHoverIndex', 'displayDepartments.[]', 'expandedData.[]') + /** + * chartData + * + * @description Transforms the standard chart data into data the charts can display + * + * @returns void + */ get chartData(): ChartData { - /* - const backgroundColors = this.displayDepartments.map((_, i) => { - if (i === this.chartHoverIndex) { - return '#15a5eb'; - } - return '#a5b3bd'; - }); - - const displayDepartmentNames = this.displayDepartments.map(({ name }) => name); - const displayDepartmentNumbers = this.displayDepartments.map(({ numberOfUsers }) => numberOfUsers); - */ - const backgroundColors = [] as string[]; + const data = [] as number[]; + const labels = [] as string[]; - const displayDepartmentNames = ['a very long data set title that needs to be handled', 'b', 'c', - 'd', 'e', 'brian', 'g', 'repeated color']; - const displayDepartmentNumbers = [100000, 50000, - 25000, - 10000, - 5000, - 500, - 50, - 5, - ]; - - displayDepartmentNames.map((departmentName: string, $index: number) => { + this.args.data.chartData.map((chartData: ChartDataModel, $index: number) => { backgroundColors.push(this.getColor($index)); + data.push(chartData.total); + labels.push(chartData.label); + this.expandedData.push({ - name: departmentName , - total: displayDepartmentNumbers[$index], + name: chartData.label, + total: chartData.total, color: this.getColor($index), }); }); return { - // labels: displayDepartmentNames, - labels: ['a', - 'b', - 'b', - 'b', - 'b', - 'b', - 'b', - 'b', - 'b', - - ], + labels, datasets: [{ - data: displayDepartmentNumbers, + data, fill: false, backgroundColor: backgroundColors, }], }; } - /* - @computed('chartHoverIndex', 'displayDepartments.[]') - get activeDepartment(): Department { - return this.displayDepartments[this.chartHoverIndex]; - } - - @computed('activeDepartment.numberOfUsers', 'displayDepartments') - get activeDepartmentPercentage(): string { - const numUsersArray = this.displayDepartments.map(({ numberOfUsers }) => numberOfUsers); - const count = numUsersArray.reduce((a, b) => a + b); - return ((this.activeDepartment.numberOfUsers / count) * 100).toFixed(2); - } - */ - - @action public toggleExpandedData() { this.expanded = !this.expanded; diff --git a/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/template.hbs b/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/template.hbs index 575a7595562..c3aab8114e7 100644 --- a/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/template.hbs +++ b/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/template.hbs @@ -8,9 +8,9 @@ local-class='ember-chart' >
    diff --git a/app/institutions/dashboard/-components/chart-kpi-wrapper/component-test.ts b/app/institutions/dashboard/-components/chart-kpi-wrapper/component-test.ts index d7d3e038467..f0618dab044 100644 --- a/app/institutions/dashboard/-components/chart-kpi-wrapper/component-test.ts +++ b/app/institutions/dashboard/-components/chart-kpi-wrapper/component-test.ts @@ -1,4 +1,4 @@ -import { render } from '@ember/test-helpers'; +import { click, render } from '@ember/test-helpers'; import { hbs } from 'ember-cli-htmlbars'; import { setupMirage } from 'ember-cli-mirage/test-support'; import { setupIntl } from 'ember-intl/test-support'; @@ -6,7 +6,7 @@ import { setupRenderingTest } from 'ember-qunit'; import { TestContext } from 'ember-test-helpers'; import { module, test } from 'qunit'; -module('Integration | institutions | dashboard | -components | total-count-chart-wrapper', hooks => { +module('Integration | institutions | dashboard | -components | kpi-chart-wrapper', hooks => { setupRenderingTest(hooks); setupMirage(hooks); setupIntl(hooks); @@ -15,58 +15,109 @@ module('Integration | institutions | dashboard | -components | total-count-chart const model = Object({ summaryMetrics: { userCount: 10, - privateProjectCount: 10, - publicProjectCount: 10, + privateProjectCount: 15, + publicProjectCount: 20, publicRegistrationCount: 100, preprintCount: 1000, }, + departmentMetrics: [ + { + name: 'Math', + numberOfUsers: 25, + }, + { + name: 'Science', + numberOfUsers: 37, + }, + ], }); this.set('model', model); }); - test('it renders the dashboard total charts correctly', async assert => { + test('it calculates the Total Users by Department data correctly', async function(assert) { // Given the component is rendered await render(hbs` `); + const parentDom = '[data-test-kpi-chart="0"]'; + // When I click the expanded icon + await click(`${parentDom} [data-test-expand-additional-data]`); + + // And the title is verified + assert.dom(`${parentDom} [data-test-chart-title]`) + .hasText('Total Users by Department'); + + // And the expanded data position 0 name is verified + assert.dom(`${parentDom} [data-test-expanded-name="0"]`) + .hasText('Math'); - // Then the first total chart is tested - assert.dom('[data-test-total-count-chart="0"]') - .exists('The User Chart exists'); + // And the expanded data position 0 total is verified + assert.dom(`${parentDom} [data-test-expanded-total="0"]`) + .hasText('25'); - assert.dom('[data-test-total-count-chart="0"]') - // eslint-disable-next-line max-len - .hasText('Total Users'); + // And the expanded data position 1 name is verified + assert.dom(`${parentDom} [data-test-expanded-name="1"]`) + .hasText('Science'); + + // And the expanded data position 1 total is verified + assert.dom(`${parentDom} [data-test-expanded-total="1"]`) + .hasText('37'); + + // Finally there are only 2 expanded data points + assert.dom(`${parentDom} [data-test-expanded-name="2"]`) + .doesNotExist(); + }); - // And the second total chart is tested - assert.dom('[data-test-total-count-chart="1"]') - .exists('The Project Widget exists'); + test('it calculates the Public vs Private Project data correctly', async function(assert) { + // Given the component is rendered + await render(hbs` + +`); + const parentDom = '[data-test-kpi-chart="1"]'; - assert.dom('[data-test-total-count-chart="1"]') - // eslint-disable-next-line max-len - .hasText('OSF Public and Private Projects'); + // When I click the expanded icon + await click(`${parentDom} [data-test-expand-additional-data]`); - // And the third total chart is tested - assert.dom('[data-test-total-count-chart="2"]') - .exists('The Registration Widget exists'); + // And the title is verified + assert.dom(`${parentDom} [data-test-chart-title]`) + .hasText('Public vs Private Projects'); - assert.dom('[data-test-total-count-chart="2"]') - // eslint-disable-next-line max-len - .hasText('OSF Registrations'); + // And the expanded data position 0 name is verified + assert.dom(`${parentDom} [data-test-expanded-name="0"]`) + .hasText('Public Projects'); - // And the fourth total chart is tested - assert.dom('[data-test-total-count-chart="3"]') - .exists('The Preprint Widget exists'); + // And the expanded data position 0 total is verified + assert.dom(`${parentDom} [data-test-expanded-total="0"]`) + .hasText('20'); - assert.dom('[data-test-total-count-chart="3"]') - // eslint-disable-next-line max-len - .hasText('OSF Preprints'); + // And the expanded data position 1 name is verified + assert.dom(`${parentDom} [data-test-expanded-name="1"]`) + .hasText('Private Projects'); + + // And the expanded data position 1 total is verified + assert.dom(`${parentDom} [data-test-expanded-total="1"]`) + .hasText('15'); + + // Finally there are only 2 expanded data points + assert.dom(`${parentDom} [data-test-expanded-name="2"]`) + .doesNotExist(); + }); + + test('it renders the dashboard total charts correctly', async assert => { + // Given the component is rendered + await render(hbs` + +`); - // Finally there are only 4 widgets - assert.dom('[data-test-total-count-chart="4"]') - .doesNotExist('There are only 4 widgets'); + // Then there are only 2 charts + assert.dom('[data-test-kpi-chart="2"]') + .doesNotExist('There are only 2 charts'); }); }); diff --git a/app/institutions/dashboard/-components/chart-kpi-wrapper/component.ts b/app/institutions/dashboard/-components/chart-kpi-wrapper/component.ts index f709c785e90..268508bcc86 100644 --- a/app/institutions/dashboard/-components/chart-kpi-wrapper/component.ts +++ b/app/institutions/dashboard/-components/chart-kpi-wrapper/component.ts @@ -5,22 +5,31 @@ import { task } from 'ember-concurrency'; import { taskFor } from 'ember-concurrency-ts'; import Intl from 'ember-intl/services/intl'; import { inject as service } from '@ember/service'; +import InstitutionDepartmentModel from 'ember-osf-web/models/institution-department'; import InstitutionSummaryMetricModel from 'ember-osf-web/models/institution-summary-metric'; +/* +import InstitutionSummaryMetricModel from 'ember-osf-web/models/institution-summary-metric'; +*/ + +export interface ChartDataModel { + label: string; + total: number; +} interface TotalCountChartWrapperArgs { model: any; } -interface TotalCountChartModel { +export interface KpiChartModel { title: string; - total: number; - chart: string; + chartData: ChartDataModel[]; + chartType: string; } export default class ChartKpiWrapperComponent extends Component { @service intl!: Intl; @tracked model = this.args.model; - @tracked totalCountCharts = [] as TotalCountChartModel[]; + @tracked kpiCharts = [] as KpiChartModel[]; @tracked isLoading = true; constructor(owner: unknown, args: TotalCountChartWrapperArgs) { @@ -30,47 +39,105 @@ export default class ChartKpiWrapperComponent extends Component { const metrics = await this.model; - this.totalCountCharts.push( + this.kpiCharts.push( { - title: this.intl.t('institutions.dashboard.panel.users'), - total: metrics.summaryMetrics.userCount, - chart: 'doughnut', + title: this.intl.t('institutions.dashboard.kpi-chart.users-by-department'), + chartData: this.calculateUsersByDepartment(metrics.departmentMetrics), + chartType: 'doughnut', }, { - title: this.intl.t('institutions.dashboard.panel.projects'), - total: this.calculateProjects(metrics.summaryMetrics), - chart: 'pie', + title: this.intl.t('institutions.dashboard.kpi-chart.public-vs-private-projects.title'), + chartData: this.calculateProjects(metrics.summaryMetrics), + chartType: 'pie', }, + /* { title: this.intl.t('institutions.dashboard.panel.registrations'), - total: metrics.summaryMetrics.publicRegistrationCount, - chart: 'bar', + chartData: [ + { + label: 'a', + total: metrics.summaryMetrics.userCount, + } as ChartDataModel, + { + label: 'a', + total: metrics.summaryMetrics.userCount, + } as ChartDataModel, + ], + chartType: 'bar', }, { title: this.intl.t('institutions.dashboard.panel.preprints'), - total: metrics.summaryMetrics.preprintCount, - chart: 'line', + chartData: [ + { + label: 'a', + total: metrics.summaryMetrics.userCount, + } as ChartDataModel, + { + label: 'a', + total: metrics.summaryMetrics.userCount, + } as ChartDataModel, + ], + chartType: 'line', }, + */ ); this.isLoading = false; } + + /** + * calculateUserByDepartments + * + * @description Abstracted method to build the ChartData model for deparments + * @param departmentMetrics The department metrics object + * + * @returns The users by department ChartData model + */ + private calculateUsersByDepartment(departmentMetrics: InstitutionDepartmentModel[]): ChartDataModel[] { + const departmentData = [] as ChartDataModel[]; + + departmentMetrics.map((metric: InstitutionDepartmentModel ) => { + departmentData.push( + { + label: metric.name, + total: metric.numberOfUsers, + } as ChartDataModel, + ); + }); + return departmentData; + } + + /** + * calculateProjects + * + * @description Abstracted method to calculate the private and public projects + * @param summaryMetrics The institutional summary metrics object + * + * @returns The total of private and public projects + */ + private calculateProjects(summaryMetrics: InstitutionSummaryMetricModel): ChartDataModel[] { + return [ + { + label: this.intl.t('institutions.dashboard.kpi-chart.public-vs-private-projects.public'), + total: summaryMetrics.publicProjectCount, + } as ChartDataModel, + { + label: this.intl.t('institutions.dashboard.kpi-chart.public-vs-private-projects.private'), + total: summaryMetrics.privateProjectCount, + } as ChartDataModel, + ]; + } } diff --git a/app/institutions/dashboard/-components/chart-kpi-wrapper/template.hbs b/app/institutions/dashboard/-components/chart-kpi-wrapper/template.hbs index e14773b8f74..7dad4cbed7d 100644 --- a/app/institutions/dashboard/-components/chart-kpi-wrapper/template.hbs +++ b/app/institutions/dashboard/-components/chart-kpi-wrapper/template.hbs @@ -1,13 +1,13 @@
    {{#if this.isLoading}}
    - +
    {{else}} - {{#each this.totalCountCharts as |totalCountChart index|}} + {{#each this.kpiCharts as |kpiChart index|}} {{/each}} {{/if}} diff --git a/app/institutions/dashboard/-components/total-count-kpi-wrapper/component.ts b/app/institutions/dashboard/-components/total-count-kpi-wrapper/component.ts index a7891274f39..3bbe4a07b50 100644 --- a/app/institutions/dashboard/-components/total-count-kpi-wrapper/component.ts +++ b/app/institutions/dashboard/-components/total-count-kpi-wrapper/component.ts @@ -48,22 +48,22 @@ export default class TotalCountKpiWrapperComponent extends Component = await institution.queryHasMany( 'userMetrics', diff --git a/translations/en-us.yml b/translations/en-us.yml index a3b8d20322c..a7995572fb7 100644 --- a/translations/en-us.yml +++ b/translations/en-us.yml @@ -826,11 +826,19 @@ institutions: has_orcid: 'Has ORCID' select_columns: 'Customize' total_users: 'total users' - panel: + kpi-panel: users: 'Total Users' projects: 'OSF Public and Private Projects' registrations: 'OSF Registrations' preprints: 'OSF Preprints' + kpi-chart: + users-by-department: 'Total Users by Department' + public-vs-private-projects: + title: 'Public vs Private Projects' + public: 'Public Projects' + private: 'Private Projects' + registrations: 'OSF Registrations' + preprints: 'OSF Preprints' object-list: filter-button-label: 'Filter' filter-heading: 'Filter by:' From d4a3e4fe710ac6b8c7d8cd9946d6fcbb8d380bcd Mon Sep 17 00:00:00 2001 From: Brian Pilati Date: Tue, 8 Oct 2024 10:55:33 -0500 Subject: [PATCH 37/96] Minor updates for comments --- .../chart-kpi/component-test.ts | 6 ++- .../chart-kpi-wrapper/chart-kpi/component.ts | 4 +- .../chart-kpi-wrapper/chart-kpi/styles.scss | 14 ++---- .../chart-kpi-wrapper/chart-kpi/template.hbs | 43 +++++++++++-------- .../chart-kpi-wrapper/component.ts | 2 +- .../total-count-kpi-wrapper/component-test.ts | 6 +-- translations/en-us.yml | 2 + 7 files changed, 42 insertions(+), 35 deletions(-) diff --git a/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/component-test.ts b/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/component-test.ts index f06fc4053ab..9c74b86fce7 100644 --- a/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/component-test.ts +++ b/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/component-test.ts @@ -1,4 +1,4 @@ -import { click, render } from '@ember/test-helpers'; +import { click, pauseTest, render } from '@ember/test-helpers'; import { hbs } from 'ember-cli-htmlbars'; import { setupMirage } from 'ember-cli-mirage/test-support'; import { setupIntl } from 'ember-intl/test-support'; @@ -47,7 +47,9 @@ module('Integration | institutions | dashboard | -components | chart-kpi', hooks // Finally the expanded data is not visible assert.dom('[data-test-expansion-data]') - .doesNotExist('The expansion data is not visible'); + .hasStyle({display: 'none'}); + // eslint-disable-next-line ember/no-pause-test + await pauseTest(); }); diff --git a/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/component.ts b/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/component.ts index dce4269a42f..711a744af12 100644 --- a/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/component.ts +++ b/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/component.ts @@ -20,7 +20,7 @@ interface DataModel { export default class ChartKpi extends Component { @service intl!: Intl; - @tracked expanded = false; + @tracked collapsed = true; @tracked expandedData = [] as DataModel[]; /** @@ -107,6 +107,6 @@ export default class ChartKpi extends Component { @action public toggleExpandedData() { - this.expanded = !this.expanded; + this.collapsed = !this.collapsed; } } diff --git a/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/styles.scss b/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/styles.scss index c79f45e3d04..4b7974f775c 100644 --- a/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/styles.scss +++ b/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/styles.scss @@ -55,16 +55,6 @@ flex-direction: row; justify-content: center; align-items: center; - - .facet-expand-button { - display: flex; - justify-content: space-between; - align-items: center; - - &:active { - box-shadow: none; - } - } } } @@ -75,6 +65,10 @@ justify-content: center; align-items: flex-start; + &.collapsed { + display: none; + } + .data-list { list-style: none; margin: 0; diff --git a/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/template.hbs b/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/template.hbs index c3aab8114e7..c62c58f81f2 100644 --- a/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/template.hbs +++ b/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/template.hbs @@ -15,23 +15,32 @@
    -
    -
    {{@data.title}}
    -
    - + {{#let (unique-id 'expanded-data') as |expandedDataId|}} +
    +
    {{@data.title}}
    +
    + +
    -
    - {{#if this.expanded }} -
    -
      +
      +
        {{#each this.expandedData as |data index |}}
      - {{/if}} + {{/let}}
    \ No newline at end of file diff --git a/app/institutions/dashboard/-components/chart-kpi-wrapper/component.ts b/app/institutions/dashboard/-components/chart-kpi-wrapper/component.ts index 268508bcc86..c3ec684cf4a 100644 --- a/app/institutions/dashboard/-components/chart-kpi-wrapper/component.ts +++ b/app/institutions/dashboard/-components/chart-kpi-wrapper/component.ts @@ -107,7 +107,7 @@ export default class ChartKpiWrapperComponent extends Component { + departmentMetrics.forEach((metric: InstitutionDepartmentModel ) => { departmentData.push( { label: metric.name, diff --git a/app/institutions/dashboard/-components/total-count-kpi-wrapper/component-test.ts b/app/institutions/dashboard/-components/total-count-kpi-wrapper/component-test.ts index a4a7fffd754..07d1f434352 100644 --- a/app/institutions/dashboard/-components/total-count-kpi-wrapper/component-test.ts +++ b/app/institutions/dashboard/-components/total-count-kpi-wrapper/component-test.ts @@ -41,7 +41,7 @@ module('Integration | institutions | dashboard | -components | total-count-kpi-w .hasText('10 Total Users'); assert.dom('[data-test-total-count-kpi="0"] [data-test-kpi-icon]') - .hasAttribute('data-icon', 'building'); + .hasAttribute('data-icon', 'users'); // And the second total kpi is tested assert.dom('[data-test-total-count-kpi="1"]') @@ -51,7 +51,7 @@ module('Integration | institutions | dashboard | -components | total-count-kpi-w .hasText('20 OSF Public and Private Projects'); assert.dom('[data-test-total-count-kpi="1"] [data-test-kpi-icon]') - .hasAttribute('data-icon', 'atom'); + .hasAttribute('data-icon', 'flask'); // And the third total kpi is tested assert.dom('[data-test-total-count-kpi="2"]') @@ -61,7 +61,7 @@ module('Integration | institutions | dashboard | -components | total-count-kpi-w .hasText('100 OSF Registrations'); assert.dom('[data-test-total-count-kpi="2"] [data-test-kpi-icon]') - .hasAttribute('data-icon', 'flag'); + .hasAttribute('data-icon', 'archive'); // And the fourth total kpi is tested assert.dom('[data-test-total-count-kpi="3"]') diff --git a/translations/en-us.yml b/translations/en-us.yml index a7995572fb7..abe3b2e2b04 100644 --- a/translations/en-us.yml +++ b/translations/en-us.yml @@ -832,6 +832,8 @@ institutions: registrations: 'OSF Registrations' preprints: 'OSF Preprints' kpi-chart: + open-expanded-data: 'Expand Additionnal Data' + close-expanded-data: 'Collapse Additionnal Data' users-by-department: 'Total Users by Department' public-vs-private-projects: title: 'Public vs Private Projects' From 8596ddcfc2aebbfdf861c38c46c80ddcf96706e6 Mon Sep 17 00:00:00 2001 From: Brian Pilati Date: Tue, 8 Oct 2024 11:21:10 -0500 Subject: [PATCH 38/96] Removed a pauseTeset --- .../chart-kpi-wrapper/chart-kpi/component-test.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/component-test.ts b/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/component-test.ts index 9c74b86fce7..fd2f9bd1cf9 100644 --- a/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/component-test.ts +++ b/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/component-test.ts @@ -1,4 +1,4 @@ -import { click, pauseTest, render } from '@ember/test-helpers'; +import { click, render } from '@ember/test-helpers'; import { hbs } from 'ember-cli-htmlbars'; import { setupMirage } from 'ember-cli-mirage/test-support'; import { setupIntl } from 'ember-intl/test-support'; @@ -48,9 +48,6 @@ module('Integration | institutions | dashboard | -components | chart-kpi', hooks // Finally the expanded data is not visible assert.dom('[data-test-expansion-data]') .hasStyle({display: 'none'}); - // eslint-disable-next-line ember/no-pause-test - await pauseTest(); - }); test('it renders the expanded data correctly', async assert => { From 3259cfd4977ed11e0a21527eddaa195698d20285 Mon Sep 17 00:00:00 2001 From: futa-ikeda <51409893+futa-ikeda@users.noreply.github.com> Date: Wed, 9 Oct 2024 12:01:46 -0400 Subject: [PATCH 39/96] [ENG-6184] Allow sorting for project list (#2348) - Ticket: [ENG-6184] - Feature flag: n/a ## Purpose - Allow sorting for project table ## Summary of Changes - Add new optional `sortKey` to project column object - Show sort arrow if the sortKey is present on the column - Styling updates - Add pagination links to object list --- .../-components/object-list/component.ts | 31 +++++++++---- .../-components/object-list/styles.scss | 26 +++++++++-- .../-components/object-list/template.hbs | 43 ++++++++++++++++--- .../dashboard/projects/controller.ts | 6 ++- .../addon/components/sort-arrow/component.ts | 2 +- translations/en-us.yml | 3 ++ 6 files changed, 91 insertions(+), 20 deletions(-) diff --git a/app/institutions/dashboard/-components/object-list/component.ts b/app/institutions/dashboard/-components/object-list/component.ts index 62c45fedfdd..c5183e0dfbc 100644 --- a/app/institutions/dashboard/-components/object-list/component.ts +++ b/app/institutions/dashboard/-components/object-list/component.ts @@ -6,20 +6,21 @@ import InstitutionModel from 'ember-osf-web/models/institution'; import SearchResultModel from 'ember-osf-web/models/search-result'; import { Filter } from 'osf-components/components/search-page/component'; -interface ValueColumn { +interface Column { name: string; + sortKey?: string; +} +interface ValueColumn extends Column { getValue(searchResult: SearchResultModel): string; } -interface LinkColumn { - name: string; +interface LinkColumn extends Column { getHref(searchResult: SearchResultModel): string; getLinkText(searchResult: SearchResultModel): string; type: 'link'; } -interface ComponentColumn { - name: string; +interface ComponentColumn extends Column { type: 'doi' | 'contributors'; } @@ -34,14 +35,14 @@ interface InstitutionalObjectListArgs { export default class InstitutionalObjectList extends Component { @tracked activeFilters: Filter[] = []; - @tracked page = ''; // TODO: ENG-6184 Implement pagination - @tracked sort = '-relevance'; // TODO: ENG-6184 Implement sorting + @tracked page = ''; + @tracked sort = '-dateModified'; get queryOptions() { const options = { ... this.args.defaultQueryOptions, 'page[cursor]': this.page, - 'page[size]': 10, // TODO: ENG-6184 Implement pagination + 'page[size]': 10, sort: this.sort, }; @@ -62,4 +63,18 @@ export default class InstitutionalObjectList extends Component - {{#each @columns as |column|}} - - {{/each}} + {{#let (component 'sort-arrow' + sort=this.sort + ) as |SortArrow| + }} + {{#each @columns as |column|}} + + {{/each}} + {{/let}} @@ -65,6 +81,23 @@ as |list|>
    - {{column.name}} - + + {{column.name}} + {{#if column.sortKey}} + + {{/if}} + +
    +
    + {{#if list.showFirstPageOption}} + + {{/if}} + {{#if list.hasPrevPage}} + + {{/if}} + {{#if list.hasNextPage}} + + {{/if}} +
    {{/if}} {{#if list.relatedProperties}} diff --git a/app/institutions/dashboard/projects/controller.ts b/app/institutions/dashboard/projects/controller.ts index fcb90c7c159..0d62d299fef 100644 --- a/app/institutions/dashboard/projects/controller.ts +++ b/app/institutions/dashboard/projects/controller.ts @@ -25,11 +25,13 @@ export default class InstitutionDashboardProjects extends Controller { }, { // Date created name: this.intl.t('institutions.dashboard.object-list.table-headers.created_date'), - getValue : searchResult => searchResult.getResourceMetadataField('dateCreated'), + getValue: searchResult => searchResult.getResourceMetadataField('dateCreated'), + sortKey: 'dateCreated', }, { // Date modified name: this.intl.t('institutions.dashboard.object-list.table-headers.modified_date'), - getValue : searchResult => searchResult.getResourceMetadataField('dateModified'), + getValue: searchResult => searchResult.getResourceMetadataField('dateModified'), + sortKey: 'dateModified', }, { // DOI name: this.intl.t('institutions.dashboard.object-list.table-headers.doi'), diff --git a/lib/osf-components/addon/components/sort-arrow/component.ts b/lib/osf-components/addon/components/sort-arrow/component.ts index 0c8a55b0d29..08d1ca2b2ab 100644 --- a/lib/osf-components/addon/components/sort-arrow/component.ts +++ b/lib/osf-components/addon/components/sort-arrow/component.ts @@ -14,7 +14,7 @@ export default class SortArrow extends Component { } get isCurrentDescending() { - return this.args.sort === `-${this.sortBy}`; + return this.args.sort === `-${this.args.sortBy}`; } get isSelected() { diff --git a/translations/en-us.yml b/translations/en-us.yml index abe3b2e2b04..4d7150670f0 100644 --- a/translations/en-us.yml +++ b/translations/en-us.yml @@ -845,6 +845,9 @@ institutions: filter-button-label: 'Filter' filter-heading: 'Filter by:' total-objects: 'total {objectType}' + first: First + prev: Prev + next: Next table-headers: title: Title link: Link From f5a8b132d9696e7ffa841e597c3cfad04370c33d Mon Sep 17 00:00:00 2001 From: Brian Pilati Date: Tue, 8 Oct 2024 11:07:41 -0500 Subject: [PATCH 40/96] Added the registration chart --- .../chart-kpi-wrapper/component.ts | 36 ++++++++++++------- translations/en-us.yml | 5 ++- 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/app/institutions/dashboard/-components/chart-kpi-wrapper/component.ts b/app/institutions/dashboard/-components/chart-kpi-wrapper/component.ts index c3ec684cf4a..014fcbf71f7 100644 --- a/app/institutions/dashboard/-components/chart-kpi-wrapper/component.ts +++ b/app/institutions/dashboard/-components/chart-kpi-wrapper/component.ts @@ -61,21 +61,12 @@ export default class ChartKpiWrapperComponent extends Component Date: Tue, 8 Oct 2024 11:16:53 -0500 Subject: [PATCH 41/96] Added total osf projects --- .../chart-kpi-wrapper/chart-kpi/styles.scss | 1 + .../chart-kpi-wrapper/component.ts | 37 ++++++++++++------- .../-components/chart-kpi-wrapper/styles.scss | 1 + app/institutions/dashboard/index/styles.scss | 1 - app/institutions/dashboard/index/template.hbs | 3 -- translations/en-us.yml | 4 ++ 6 files changed, 30 insertions(+), 17 deletions(-) diff --git a/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/styles.scss b/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/styles.scss index 4b7974f775c..75fe4f855de 100644 --- a/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/styles.scss +++ b/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/styles.scss @@ -2,6 +2,7 @@ .chart-container { margin-right: 12px; + margin-bottom: 12px; display: flex; flex-direction: column; justify-content: center; diff --git a/app/institutions/dashboard/-components/chart-kpi-wrapper/component.ts b/app/institutions/dashboard/-components/chart-kpi-wrapper/component.ts index 014fcbf71f7..0a14bd0b4f3 100644 --- a/app/institutions/dashboard/-components/chart-kpi-wrapper/component.ts +++ b/app/institutions/dashboard/-components/chart-kpi-wrapper/component.ts @@ -66,22 +66,11 @@ export default class ChartKpiWrapperComponent extends Component -
    diff --git a/translations/en-us.yml b/translations/en-us.yml index 81ef0d93e49..860d07e1d51 100644 --- a/translations/en-us.yml +++ b/translations/en-us.yml @@ -843,6 +843,10 @@ institutions: title: 'Public vs Private Registrations' public: 'Public Registrations' private: 'Private Registrations' + total-osf-objects: + title: 'Total OSF Objects' + public: 'Public Registrations' + private: 'Private Registrations' preprints: 'OSF Preprints' object-list: filter-button-label: 'Filter' From e07510e6e2556f5504910edbfbd8d35fc91fceca Mon Sep 17 00:00:00 2001 From: Brian Pilati Date: Tue, 8 Oct 2024 11:36:49 -0500 Subject: [PATCH 42/96] Added licenses and updated tests --- .../chart-kpi-wrapper/component-test.ts | 141 +++++++++++++++++- .../chart-kpi-wrapper/component.ts | 45 +++++- translations/en-us.yml | 5 +- 3 files changed, 177 insertions(+), 14 deletions(-) diff --git a/app/institutions/dashboard/-components/chart-kpi-wrapper/component-test.ts b/app/institutions/dashboard/-components/chart-kpi-wrapper/component-test.ts index f0618dab044..694d1adbcf4 100644 --- a/app/institutions/dashboard/-components/chart-kpi-wrapper/component-test.ts +++ b/app/institutions/dashboard/-components/chart-kpi-wrapper/component-test.ts @@ -18,6 +18,7 @@ module('Integration | institutions | dashboard | -components | kpi-chart-wrapper privateProjectCount: 15, publicProjectCount: 20, publicRegistrationCount: 100, + embargoedRegistrationCount: 200, preprintCount: 1000, }, departmentMetrics: [ @@ -108,6 +109,140 @@ module('Integration | institutions | dashboard | -components | kpi-chart-wrapper .doesNotExist(); }); + test('it calculates the Public vs Private Registration data correctly', async function(assert) { + // Given the component is rendered + await render(hbs` + +`); + const parentDom = '[data-test-kpi-chart="2"]'; + + // When I click the expanded icon + await click(`${parentDom} [data-test-expand-additional-data]`); + + // And the title is verified + assert.dom(`${parentDom} [data-test-chart-title]`) + .hasText('Public vs Private Registrations'); + + // And the expanded data position 0 name is verified + assert.dom(`${parentDom} [data-test-expanded-name="0"]`) + .hasText('Public Registrations'); + + // And the expanded data position 0 total is verified + assert.dom(`${parentDom} [data-test-expanded-total="0"]`) + .hasText('100'); + + // And the expanded data position 1 name is verified + assert.dom(`${parentDom} [data-test-expanded-name="1"]`) + .hasText('Private Registrations'); + + // And the expanded data position 1 total is verified + assert.dom(`${parentDom} [data-test-expanded-total="1"]`) + .hasText('200'); + + // Finally there are only 2 expanded data points + assert.dom(`${parentDom} [data-test-expanded-name="2"]`) + .doesNotExist(); + }); + + test('it calculates the Total Objects data correctly', async function(assert) { + // Given the component is rendered + await render(hbs` + +`); + const parentDom = '[data-test-kpi-chart="3"]'; + + // When I click the expanded icon + await click(`${parentDom} [data-test-expand-additional-data]`); + + // And the title is verified + assert.dom(`${parentDom} [data-test-chart-title]`) + .hasText('Total OSF Objects'); + + // And the expanded data position 0 name is verified + assert.dom(`${parentDom} [data-test-expanded-name="0"]`) + .hasText('Preprints'); + + // And the expanded data position 0 total is verified + assert.dom(`${parentDom} [data-test-expanded-total="0"]`) + .hasText('1000'); + + // And the expanded data position 1 name is verified + assert.dom(`${parentDom} [data-test-expanded-name="1"]`) + .hasText('Public Projects'); + + // And the expanded data position 1 total is verified + assert.dom(`${parentDom} [data-test-expanded-total="1"]`) + .hasText('20'); + + // And the expanded data position 2 name is verified + assert.dom(`${parentDom} [data-test-expanded-name="2"]`) + .hasText('Private Projects'); + + // And the expanded data position 2 total is verified + assert.dom(`${parentDom} [data-test-expanded-total="2"]`) + .hasText('15'); + + // And the expanded data position 3 name is verified + assert.dom(`${parentDom} [data-test-expanded-name="3"]`) + .hasText('Public Registrations'); + + // And the expanded data position 3 total is verified + assert.dom(`${parentDom} [data-test-expanded-total="3"]`) + .hasText('100'); + + // And the expanded data position 4 name is verified + assert.dom(`${parentDom} [data-test-expanded-name="4"]`) + .hasText('Private Registrations'); + + // And the expanded data position 4 total is verified + assert.dom(`${parentDom} [data-test-expanded-total="4"]`) + .hasText('200'); + + // Finally there are only 5 expanded data points + assert.dom(`${parentDom} [data-test-expanded-name="5"]`) + .doesNotExist(); + }); + + test('it calculates the Licenses data correctly', async function(assert) { + // Given the component is rendered + await render(hbs` + +`); + const parentDom = '[data-test-kpi-chart="4"]'; + // When I click the expanded icon + await click(`${parentDom} [data-test-expand-additional-data]`); + + // And the title is verified + assert.dom(`${parentDom} [data-test-chart-title]`) + .hasText('Licenses'); + + // And the expanded data position 0 name is verified + assert.dom(`${parentDom} [data-test-expanded-name="0"]`) + .hasText('Math'); + + // And the expanded data position 0 total is verified + assert.dom(`${parentDom} [data-test-expanded-total="0"]`) + .hasText('25'); + + // And the expanded data position 1 name is verified + assert.dom(`${parentDom} [data-test-expanded-name="1"]`) + .hasText('Science'); + + // And the expanded data position 1 total is verified + assert.dom(`${parentDom} [data-test-expanded-total="1"]`) + .hasText('37'); + + // Finally there are only 2 expanded data points + assert.dom(`${parentDom} [data-test-expanded-name="2"]`) + .doesNotExist(); + }); + test('it renders the dashboard total charts correctly', async assert => { // Given the component is rendered await render(hbs` @@ -116,8 +251,8 @@ module('Integration | institutions | dashboard | -components | kpi-chart-wrapper /> `); - // Then there are only 2 charts - assert.dom('[data-test-kpi-chart="2"]') - .doesNotExist('There are only 2 charts'); + // Then there are only 5 charts + assert.dom('[data-test-kpi-chart="5"]') + .doesNotExist('There are only 5 charts'); }); }); diff --git a/app/institutions/dashboard/-components/chart-kpi-wrapper/component.ts b/app/institutions/dashboard/-components/chart-kpi-wrapper/component.ts index 0a14bd0b4f3..6b0f898e533 100644 --- a/app/institutions/dashboard/-components/chart-kpi-wrapper/component.ts +++ b/app/institutions/dashboard/-components/chart-kpi-wrapper/component.ts @@ -71,6 +71,11 @@ export default class ChartKpiWrapperComponent extends Component { + licenseData.push( + { + label: metric.name, + total: metric.numberOfUsers, + } as ChartDataModel, + ); + }); + return licenseData; + } } diff --git a/translations/en-us.yml b/translations/en-us.yml index 860d07e1d51..e93939138f5 100644 --- a/translations/en-us.yml +++ b/translations/en-us.yml @@ -845,9 +845,8 @@ institutions: private: 'Private Registrations' total-osf-objects: title: 'Total OSF Objects' - public: 'Public Registrations' - private: 'Private Registrations' - preprints: 'OSF Preprints' + preprints: 'Preprints' + licenses: 'Licenses' object-list: filter-button-label: 'Filter' filter-heading: 'Filter by:' From 763cc026461980d019f0b3cb2260b52ac8967986 Mon Sep 17 00:00:00 2001 From: Brian Pilati Date: Tue, 8 Oct 2024 11:44:21 -0500 Subject: [PATCH 43/96] Added add-ons and storage regions --- .../chart-kpi-wrapper/component-test.ts | 78 ++++++++++++++++++- .../chart-kpi-wrapper/component.ts | 56 ++++++++++++- .../total-count-kpi-wrapper/styles.scss | 1 + translations/en-us.yml | 2 + 4 files changed, 133 insertions(+), 4 deletions(-) diff --git a/app/institutions/dashboard/-components/chart-kpi-wrapper/component-test.ts b/app/institutions/dashboard/-components/chart-kpi-wrapper/component-test.ts index 694d1adbcf4..1322e7a76a3 100644 --- a/app/institutions/dashboard/-components/chart-kpi-wrapper/component-test.ts +++ b/app/institutions/dashboard/-components/chart-kpi-wrapper/component-test.ts @@ -243,6 +243,78 @@ module('Integration | institutions | dashboard | -components | kpi-chart-wrapper .doesNotExist(); }); + test('it calculates the Addon data correctly', async function(assert) { + // Given the component is rendered + await render(hbs` + +`); + const parentDom = '[data-test-kpi-chart="5"]'; + // When I click the expanded icon + await click(`${parentDom} [data-test-expand-additional-data]`); + + // And the title is verified + assert.dom(`${parentDom} [data-test-chart-title]`) + .hasText('Add-ons'); + + // And the expanded data position 0 name is verified + assert.dom(`${parentDom} [data-test-expanded-name="0"]`) + .hasText('Math'); + + // And the expanded data position 0 total is verified + assert.dom(`${parentDom} [data-test-expanded-total="0"]`) + .hasText('25'); + + // And the expanded data position 1 name is verified + assert.dom(`${parentDom} [data-test-expanded-name="1"]`) + .hasText('Science'); + + // And the expanded data position 1 total is verified + assert.dom(`${parentDom} [data-test-expanded-total="1"]`) + .hasText('37'); + + // Finally there are only 2 expanded data points + assert.dom(`${parentDom} [data-test-expanded-name="2"]`) + .doesNotExist(); + }); + + test('it calculates the Storage Regions data correctly', async function(assert) { + // Given the component is rendered + await render(hbs` + +`); + const parentDom = '[data-test-kpi-chart="6"]'; + // When I click the expanded icon + await click(`${parentDom} [data-test-expand-additional-data]`); + + // And the title is verified + assert.dom(`${parentDom} [data-test-chart-title]`) + .hasText('Storage Regions'); + + // And the expanded data position 0 name is verified + assert.dom(`${parentDom} [data-test-expanded-name="0"]`) + .hasText('Math'); + + // And the expanded data position 0 total is verified + assert.dom(`${parentDom} [data-test-expanded-total="0"]`) + .hasText('25'); + + // And the expanded data position 1 name is verified + assert.dom(`${parentDom} [data-test-expanded-name="1"]`) + .hasText('Science'); + + // And the expanded data position 1 total is verified + assert.dom(`${parentDom} [data-test-expanded-total="1"]`) + .hasText('37'); + + // Finally there are only 2 expanded data points + assert.dom(`${parentDom} [data-test-expanded-name="2"]`) + .doesNotExist(); + }); + test('it renders the dashboard total charts correctly', async assert => { // Given the component is rendered await render(hbs` @@ -251,8 +323,8 @@ module('Integration | institutions | dashboard | -components | kpi-chart-wrapper /> `); - // Then there are only 5 charts - assert.dom('[data-test-kpi-chart="5"]') - .doesNotExist('There are only 5 charts'); + // Then there are only 6 charts + assert.dom('[data-test-kpi-chart="7"]') + .doesNotExist('There are only 7 charts'); }); }); diff --git a/app/institutions/dashboard/-components/chart-kpi-wrapper/component.ts b/app/institutions/dashboard/-components/chart-kpi-wrapper/component.ts index 6b0f898e533..513c9101acd 100644 --- a/app/institutions/dashboard/-components/chart-kpi-wrapper/component.ts +++ b/app/institutions/dashboard/-components/chart-kpi-wrapper/component.ts @@ -76,6 +76,16 @@ export default class ChartKpiWrapperComponent extends Component { + addonData.push( + { + label: metric.name, + total: metric.numberOfUsers, + } as ChartDataModel, + ); + }); + return addonData; + } + + /** + * calculateStorageRegions + * + * @description Abstracted method to build the Storage Regions ChartData + * @param storageRegionsMetrics The storage regions metrics object + * + * @returns The storage regions ChartData model + */ + private calculateStorageRegions(storageRegionsMetrics: InstitutionDepartmentModel[]): ChartDataModel[] { + const storageRegionsData = [] as ChartDataModel[]; + + storageRegionsMetrics.forEach((metric: InstitutionDepartmentModel ) => { + storageRegionsData.push( + { + label: metric.name, + total: metric.numberOfUsers, + } as ChartDataModel, + ); + }); + return storageRegionsData; + } } diff --git a/app/institutions/dashboard/-components/total-count-kpi-wrapper/styles.scss b/app/institutions/dashboard/-components/total-count-kpi-wrapper/styles.scss index 24f5068b5cc..5ab9ab1d3de 100644 --- a/app/institutions/dashboard/-components/total-count-kpi-wrapper/styles.scss +++ b/app/institutions/dashboard/-components/total-count-kpi-wrapper/styles.scss @@ -1,6 +1,7 @@ .wrapper-container { display: flex; flex-direction: row; + flex-wrap: wrap; justify-content: flex-start; align-items: center; width: calc(100% - 24px); diff --git a/translations/en-us.yml b/translations/en-us.yml index e93939138f5..55257f10a25 100644 --- a/translations/en-us.yml +++ b/translations/en-us.yml @@ -847,6 +847,8 @@ institutions: title: 'Total OSF Objects' preprints: 'Preprints' licenses: 'Licenses' + add-ons: 'Add-ons' + storage-regions: 'Storage Regions' object-list: filter-button-label: 'Filter' filter-heading: 'Filter by:' From 9bc8adf8ca46f1a15f171639707d9d51a6ee0be2 Mon Sep 17 00:00:00 2001 From: Brian Pilati Date: Tue, 8 Oct 2024 11:54:52 -0500 Subject: [PATCH 44/96] Added data storage --- .../chart-kpi-wrapper/component-test.ts | 44 +++++++++++++++++-- .../chart-kpi-wrapper/component.ts | 28 +++++++++++- .../total-count-kpi-wrapper/styles.scss | 6 ++- .../total-count-kpi/styles.scss | 1 + translations/en-us.yml | 4 ++ 5 files changed, 77 insertions(+), 6 deletions(-) diff --git a/app/institutions/dashboard/-components/chart-kpi-wrapper/component-test.ts b/app/institutions/dashboard/-components/chart-kpi-wrapper/component-test.ts index 1322e7a76a3..5603074b0f6 100644 --- a/app/institutions/dashboard/-components/chart-kpi-wrapper/component-test.ts +++ b/app/institutions/dashboard/-components/chart-kpi-wrapper/component-test.ts @@ -20,6 +20,7 @@ module('Integration | institutions | dashboard | -components | kpi-chart-wrapper publicRegistrationCount: 100, embargoedRegistrationCount: 200, preprintCount: 1000, + storageByteCount: 2000, }, departmentMetrics: [ { @@ -315,6 +316,43 @@ module('Integration | institutions | dashboard | -components | kpi-chart-wrapper .doesNotExist(); }); + test('it calculates the Public vs Private Data Storage data correctly', async function(assert) { + // Given the component is rendered + await render(hbs` + +`); + const parentDom = '[data-test-kpi-chart="7"]'; + + // When I click the expanded icon + await click(`${parentDom} [data-test-expand-additional-data]`); + + // And the title is verified + assert.dom(`${parentDom} [data-test-chart-title]`) + .hasText('Public vs Private Data Storage'); + + // And the expanded data position 0 name is verified + assert.dom(`${parentDom} [data-test-expanded-name="0"]`) + .hasText('Public Data Storage'); + + // And the expanded data position 0 total is verified + assert.dom(`${parentDom} [data-test-expanded-total="0"]`) + .hasText('2000'); + + // And the expanded data position 1 name is verified + assert.dom(`${parentDom} [data-test-expanded-name="1"]`) + .hasText('Private Data Storage'); + + // And the expanded data position 1 total is verified + assert.dom(`${parentDom} [data-test-expanded-total="1"]`) + .hasText('2100'); + + // Finally there are only 2 expanded data points + assert.dom(`${parentDom} [data-test-expanded-name="2"]`) + .doesNotExist(); + }); + test('it renders the dashboard total charts correctly', async assert => { // Given the component is rendered await render(hbs` @@ -323,8 +361,8 @@ module('Integration | institutions | dashboard | -components | kpi-chart-wrapper /> `); - // Then there are only 6 charts - assert.dom('[data-test-kpi-chart="7"]') - .doesNotExist('There are only 7 charts'); + // Then there are only 8 charts + assert.dom('[data-test-kpi-chart="8"]') + .doesNotExist('There are only 8 charts'); }); }); diff --git a/app/institutions/dashboard/-components/chart-kpi-wrapper/component.ts b/app/institutions/dashboard/-components/chart-kpi-wrapper/component.ts index 513c9101acd..df43187cb09 100644 --- a/app/institutions/dashboard/-components/chart-kpi-wrapper/component.ts +++ b/app/institutions/dashboard/-components/chart-kpi-wrapper/component.ts @@ -84,7 +84,12 @@ export default class ChartKpiWrapperComponent extends Component Date: Thu, 10 Oct 2024 09:44:59 -0500 Subject: [PATCH 45/96] Finished the kpis --- .../total-count-kpi-wrapper/component-test.ts | 66 ++++++++++++++++--- .../total-count-kpi-wrapper/component.ts | 20 ++++++ .../total-count-kpi-wrapper/styles.scss | 2 + .../factories/institution-summary-metric.ts | 2 +- translations/en-us.yml | 5 ++ 5 files changed, 86 insertions(+), 9 deletions(-) diff --git a/app/institutions/dashboard/-components/total-count-kpi-wrapper/component-test.ts b/app/institutions/dashboard/-components/total-count-kpi-wrapper/component-test.ts index 07d1f434352..2048b1bbf53 100644 --- a/app/institutions/dashboard/-components/total-count-kpi-wrapper/component-test.ts +++ b/app/institutions/dashboard/-components/total-count-kpi-wrapper/component-test.ts @@ -14,11 +14,16 @@ module('Integration | institutions | dashboard | -components | total-count-kpi-w hooks.beforeEach(function(this: TestContext) { const model = Object({ summaryMetrics: { - userCount: 10, - privateProjectCount: 10, publicProjectCount: 10, + privateProjectCount: 10, + userCount: 10, publicRegistrationCount: 100, preprintCount: 1000, + embargoedRegistrationCount: 200, + storageByteCount: 104593230, + publicFileCount: 1567, + monthlyLoggedInUserCount: 300, + monthlyActiveUserCount:40, }, }); @@ -64,17 +69,62 @@ module('Integration | institutions | dashboard | -components | total-count-kpi-w .hasAttribute('data-icon', 'archive'); // And the fourth total kpi is tested - assert.dom('[data-test-total-count-kpi="3"]') + let parentContainer = '[data-test-total-count-kpi="3"]'; + assert.dom(parentContainer) .exists('The Preprint Widget exists'); - assert.dom('[data-test-total-count-kpi="3"]') + assert.dom(parentContainer) .hasText('1000 OSF Preprints'); - assert.dom('[data-test-total-count-kpi="3"] [data-test-kpi-icon]') + assert.dom(`${parentContainer} [data-test-kpi-icon]`) .hasAttribute('data-icon', 'file-alt'); - // Finally there are only 4 widgets - assert.dom('[data-test-total-count-kpi="4"]') - .doesNotExist('There are only 4 widgets'); + // And the total storage kpi is tested + parentContainer = '[data-test-total-count-kpi="4"]'; + assert.dom(parentContainer) + .exists('The Total Storage Widget exists'); + + assert.dom(parentContainer) + .hasText('104593230 Total Storage'); + + assert.dom(`${parentContainer} [data-test-kpi-icon]`) + .hasAttribute('data-icon', 'database'); + + // And the total file count kpi is tested + parentContainer = '[data-test-total-count-kpi="5"]'; + assert.dom(parentContainer) + .exists('The Total File Widget exists'); + + assert.dom(parentContainer) + .hasText('1567 Total Public File Count'); + + assert.dom(`${parentContainer} [data-test-kpi-icon]`) + .hasAttribute('data-icon', 'file-alt'); + + // And the total logged in users kpi is tested + parentContainer = '[data-test-total-count-kpi="6"]'; + assert.dom(parentContainer) + .exists('The Total Monthly Logged in Users Widget exists'); + + assert.dom(parentContainer) + .hasText('300 Total Monthly Logged in Users'); + + assert.dom(`${parentContainer} [data-test-kpi-icon]`) + .hasAttribute('data-icon', 'users'); + + // And the total active users kpi is tested + parentContainer = '[data-test-total-count-kpi="7"]'; + assert.dom(parentContainer) + .exists('The Total Active Usesrs Widget exists'); + + assert.dom(parentContainer) + .hasText('40 Total Monthly Active Users'); + + assert.dom(`${parentContainer} [data-test-kpi-icon]`) + .hasAttribute('data-icon', 'users'); + + // Finally there are only 8 widgets + assert.dom('[data-test-total-count-kpi="8"]') + .doesNotExist('There are only 8 widgets'); }); }); diff --git a/app/institutions/dashboard/-components/total-count-kpi-wrapper/component.ts b/app/institutions/dashboard/-components/total-count-kpi-wrapper/component.ts index 3bbe4a07b50..cae0939d6c5 100644 --- a/app/institutions/dashboard/-components/total-count-kpi-wrapper/component.ts +++ b/app/institutions/dashboard/-components/total-count-kpi-wrapper/component.ts @@ -67,6 +67,26 @@ export default class TotalCountKpiWrapperComponent extends Component({ return faker.random.number({ min: 10, max: 50}); }, publicRegistrationCount() { - return faker.random.number({ min: 100, max: 1000 }); + return faker.random.number({ min: 1000, max: 10000 }); }, embargoedRegistrationCount() { return faker.random.number({ min: 0, max: 25}); diff --git a/translations/en-us.yml b/translations/en-us.yml index 406ab00031d..8c14b6dc84b 100644 --- a/translations/en-us.yml +++ b/translations/en-us.yml @@ -831,6 +831,11 @@ institutions: projects: 'OSF Public and Private Projects' registrations: 'OSF Registrations' preprints: 'OSF Preprints' + storage: 'Total Storage' + file-count: 'Total Public File Count' + fileCount: 'Total Public File Count' + logged-in-users: 'Total Monthly Logged in Users' + active-users: 'Total Monthly Active Users' kpi-chart: open-expanded-data: 'Expand Additionnal Data' close-expanded-data: 'Collapse Additionnal Data' From afedf85ca36e55cf44afa2e128741a91f95b5dcf Mon Sep 17 00:00:00 2001 From: Brian Pilati Date: Thu, 10 Oct 2024 09:49:08 -0500 Subject: [PATCH 46/96] Added the correct chart types --- .../-components/chart-kpi-wrapper/component.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/institutions/dashboard/-components/chart-kpi-wrapper/component.ts b/app/institutions/dashboard/-components/chart-kpi-wrapper/component.ts index df43187cb09..34cd03de933 100644 --- a/app/institutions/dashboard/-components/chart-kpi-wrapper/component.ts +++ b/app/institutions/dashboard/-components/chart-kpi-wrapper/component.ts @@ -59,27 +59,27 @@ export default class ChartKpiWrapperComponent extends Component Date: Thu, 10 Oct 2024 10:28:21 -0500 Subject: [PATCH 47/96] Added a new util for human readable bytes to the model --- .../-components/total-count-kpi-wrapper/component-test.ts | 3 ++- .../-components/total-count-kpi-wrapper/component.ts | 2 +- app/models/institution-summary-metric.ts | 5 +++++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/app/institutions/dashboard/-components/total-count-kpi-wrapper/component-test.ts b/app/institutions/dashboard/-components/total-count-kpi-wrapper/component-test.ts index 2048b1bbf53..9e040bb8b63 100644 --- a/app/institutions/dashboard/-components/total-count-kpi-wrapper/component-test.ts +++ b/app/institutions/dashboard/-components/total-count-kpi-wrapper/component-test.ts @@ -24,6 +24,7 @@ module('Integration | institutions | dashboard | -components | total-count-kpi-w publicFileCount: 1567, monthlyLoggedInUserCount: 300, monthlyActiveUserCount:40, + convertedStorageCount: 104, }, }); @@ -85,7 +86,7 @@ module('Integration | institutions | dashboard | -components | total-count-kpi-w .exists('The Total Storage Widget exists'); assert.dom(parentContainer) - .hasText('104593230 Total Storage'); + .hasText('104 Total Storage'); assert.dom(`${parentContainer} [data-test-kpi-icon]`) .hasAttribute('data-icon', 'database'); diff --git a/app/institutions/dashboard/-components/total-count-kpi-wrapper/component.ts b/app/institutions/dashboard/-components/total-count-kpi-wrapper/component.ts index cae0939d6c5..ea784c1683c 100644 --- a/app/institutions/dashboard/-components/total-count-kpi-wrapper/component.ts +++ b/app/institutions/dashboard/-components/total-count-kpi-wrapper/component.ts @@ -69,7 +69,7 @@ export default class TotalCountKpiWrapperComponent extends Component Date: Fri, 11 Oct 2024 14:32:12 -0400 Subject: [PATCH 48/96] [ENG-6328] Show hide cols (#2350) - Ticket: [ENG-6328] - Feature flag: n/a ## Purpose - Allow user to show and hide certain columns ## Summary of Changes - Add new dropdown to show/hide columns - Add logic to support show/hide columns - Tests --- .../-components/object-list/component-test.ts | 169 ++++++++++++++++++ .../-components/object-list/component.ts | 21 +++ .../-components/object-list/styles.scss | 27 ++- .../-components/object-list/template.hbs | 125 +++++++++---- mirage/views/search.ts | 2 +- translations/en-us.yml | 2 + 6 files changed, 306 insertions(+), 40 deletions(-) create mode 100644 app/institutions/dashboard/-components/object-list/component-test.ts diff --git a/app/institutions/dashboard/-components/object-list/component-test.ts b/app/institutions/dashboard/-components/object-list/component-test.ts new file mode 100644 index 00000000000..eb90be3fb19 --- /dev/null +++ b/app/institutions/dashboard/-components/object-list/component-test.ts @@ -0,0 +1,169 @@ +import { click, render } from '@ember/test-helpers'; +import { hbs } from 'ember-cli-htmlbars'; +import { setupMirage } from 'ember-cli-mirage/test-support'; +import { setupIntl } from 'ember-intl/test-support'; +import { setupRenderingTest } from 'ember-qunit'; +import { TestContext } from 'ember-test-helpers'; +import { module, test } from 'qunit'; + +import { OsfLinkRouterStub } from 'ember-osf-web/tests/integration/helpers/osf-link-router-stub'; + +module('Integration | institutions | dashboard | -components | object-list', hooks => { + setupRenderingTest(hooks); + setupMirage(hooks); + setupIntl(hooks); + + hooks.beforeEach(function(this: TestContext) { + this.owner.unregister('service:router'); + this.owner.register('service:router', OsfLinkRouterStub); + const columns = [ + { + name: 'Title', + sortKey: 'title', + getValue: () => 'Title of some object', + }, + { + name: 'Description', + getValue: () => 'Description of some object', + }, + { + name: 'Contributors', + type: 'contributors', + }, + { + name: 'DOI', + type: 'doi', + }, + ]; + const institution = server.create('institution', { + id: 'my-institution', + }); + const defaultQueryOptions = { + cardSearchFilter: { + resourceType: 'Project,ProjectComponent', + }, + }; + this.setProperties({ + columns, + institution, + defaultQueryOptions, + objectType: 'thingies', + }); + }); + + test('the table headers are correct', async function(assert) { + await render(hbs` + + `); + + // Elements from InstitutionDashboarWrapper are present + assert.dom('[data-test-link-to-reports-archive]').exists('Link to download prior reports exists'); + assert.dom('[data-test-page-tab="summary"]').exists('Summary tab exists'); + + // Elements in the top bar are present + assert.dom('[data-test-object-count]').containsText('10 total thingies', 'Object count is correct'); + assert.dom('[data-test-toggle-filter-button]').exists('Filter button exists'); + assert.dom('[data-test-customize-columns-button]').exists('Columns button exists'); + + assert.dom('[data-test-object-list-table]').exists('Object list exists'); + + // The table headers are correct + assert.dom('[data-test-column-header]').exists({ count: 4 }, 'There are 4 columns'); + assert.dom('[data-test-column-header="Title"]').containsText('Title'); + assert.dom('[data-test-column-header="Title"] [data-test-sort="title"]').exists('Title is sortable'); + assert.dom('[data-test-column-header="Description"]').containsText('Description'); + assert.dom('[data-test-column-header="Description"] [data-test-sort="description"]') + .doesNotExist('Description is not sortable'); + + // The table data is not blatantly incorrect + assert.dom('[data-test-object-table-body-row]').exists({ count: 10 }, 'There are 10 rows'); + }); + + test('the table supports filtering', async function(assert) { + await render(hbs` + + `); + + await click('[data-test-toggle-filter-button]'); + + assert.dom('[data-test-filter-facet-toggle]').exists({ count: 3 }, '3 filters available'); + + // Open the filter facet and load the values and select the first filter value + await click('[data-test-filter-facet-toggle]'); + await click('[data-test-filter-facet-value] button'); + + assert.dom('[data-test-active-filter]').exists({ count: 1 }, '1 filter active'); + assert.dom('[data-test-remove-active-filter]').exists('Remove filter button exists'); + + await click('[data-test-remove-active-filter]'); + assert.dom('[data-test-active-filter]').doesNotExist('Filter removed'); + }); + + test('the table supports customizing columns', async function(assert) { + await render(hbs` + + `); + + assert.dom('[data-test-column-header]').exists({ count: 4 }, '4 columns available'); + const titleColumn = document.querySelector('[data-test-column-header="Title"]'); + assert.ok(titleColumn, 'Title column is visible'); + + // Open the column customization menu + await click('[data-test-customize-columns-button]'); + assert.dom('[data-test-column-toggle-input]').exists({ count: 4 }, '4 columns available to show/hide'); + assert.dom('[data-test-column-toggle-input="Title"]').isChecked('Title column checkbox is checked'); + assert.dom('[data-test-column-toggle-input="Description"]').isChecked('Description column checkbox is checked'); + + // Toggle off the first column + await click('[data-test-column-toggle-input="Title"]'); + assert.ok(titleColumn, 'Title column still visible after toggling off'); + + // Save changes + await click('[data-test-save-columns-button]'); + assert.dom('[data-test-column-toggle-input]').doesNotExist('Column toggle menu hidden'); + assert.dom('[data-test-column-header="Title"]').doesNotExist('Title column removed'); + assert.dom('[data-test-column-header]').exists({ count: 3 }, '3 columns available'); + + // Open the menu again + await click('[data-test-customize-columns-button]'); + assert.dom('[data-test-column-toggle-input]').exists({ count: 4 }, 'Column toggle menu reopened'); + assert.dom('[data-test-column-toggle-input="Title"]').isNotChecked('Title column checkbox is not checked'); + assert.dom('[data-test-column-toggle-input="Description"]') + .isChecked('Description column checkbox is still checked'); + + // Toggle off all columns, but reset + await click('[data-test-column-toggle-input="Description"]'); + await click('[data-test-column-toggle-input="Contributors"]'); + await click('[data-test-column-toggle-input="DOI"]'); + await click('[data-test-reset-columns-button]'); + assert.dom('[data-test-column-toggle-input]').doesNotExist('Column toggle menu hidden'); + assert.dom('[data-test-column-header]').exists({ count: 3 }, '3 columns available, as we did not save changes'); + + // Open the menu again + await click('[data-test-customize-columns-button]'); + assert.dom('[data-test-column-toggle-input]').exists({ count: 4 }, 'Column toggle menu reopened'); + assert.dom('[data-test-column-toggle-input="Title"]').isNotChecked('Title column checkbox is not checked'); + assert.dom('[data-test-column-toggle-input="Description"]') + .isChecked('Description column checkbox is still checked'); + + // Toggle title back on + await click('[data-test-column-toggle-input="Title"]'); + await click('[data-test-save-columns-button]'); + assert.ok(titleColumn, 'Title column visible again'); + }); +}); diff --git a/app/institutions/dashboard/-components/object-list/component.ts b/app/institutions/dashboard/-components/object-list/component.ts index c5183e0dfbc..383cce5fea7 100644 --- a/app/institutions/dashboard/-components/object-list/component.ts +++ b/app/institutions/dashboard/-components/object-list/component.ts @@ -37,6 +37,8 @@ export default class InstitutionalObjectList extends Component column.name); + @tracked dirtyVisibleColumns = [...this.visibleColumns]; // track changes to visible columns before they are saved get queryOptions() { const options = { @@ -55,6 +57,25 @@ export default class InstitutionalObjectList extends Component {{else}}
    - + {{list.totalResultCount}} {{t 'institutions.dashboard.object-list.total-objects' objectType=@objectType}}
    + + + + {{t 'institutions.dashboard.object-list.customize'}} + + + {{#each @columns as |column|}} + + {{/each}} +
    + + +
    +
    +
    @@ -31,20 +77,23 @@ as |list|> ) as |SortArrow| }} {{#each @columns as |column|}} - - - {{column.name}} - {{#if column.sortKey}} - - {{/if}} - - + {{#if (includes column.name this.visibleColumns)}} + + + {{column.name}} + {{#if column.sortKey}} + + {{/if}} + + + {{/if}} {{/each}} {{/let}} @@ -52,29 +101,31 @@ as |list|> {{#each list.searchResults as |result|}} - + {{#each @columns as |column|}} - - {{#if (eq column.type 'link')}} - - {{call (fn column.getLinkText result)}} - - {{else if (eq column.type 'doi')}} - - {{else if (eq column.type 'contributors')}} - - {{else}} - {{call (fn column.getValue result)}} - {{/if}} - + {{#if (includes column.name this.visibleColumns)}} + + {{#if (eq column.type 'link')}} + + {{call (fn column.getLinkText result)}} + + {{else if (eq column.type 'doi')}} + + {{else if (eq column.type 'contributors')}} + + {{else}} + {{call (fn column.getValue result)}} + {{/if}} + + {{/if}} {{/each}} {{/each}} diff --git a/mirage/views/search.ts b/mirage/views/search.ts index 2a4500c35c4..f47eb3430d1 100644 --- a/mirage/views/search.ts +++ b/mirage/views/search.ts @@ -250,7 +250,7 @@ export function cardSearch(_: Schema, request: Request) { ], }, ], - totalResultCount: 3, + totalResultCount: 10, }, relationships: { relatedProperties: { diff --git a/translations/en-us.yml b/translations/en-us.yml index 8c14b6dc84b..742cf13288e 100644 --- a/translations/en-us.yml +++ b/translations/en-us.yml @@ -87,6 +87,7 @@ general: registration: registration rename: Rename required: Required + reset: Reset revert: Revert revisions: Revisions save: Save @@ -862,6 +863,7 @@ institutions: filter-button-label: 'Filter' filter-heading: 'Filter by:' total-objects: 'total {objectType}' + customize: 'Customize' first: First prev: Prev next: Next From 449b9a8705d8fec815ea377b13b717a517f71394 Mon Sep 17 00:00:00 2001 From: John Tordoff <> Date: Mon, 14 Oct 2024 11:15:25 -0400 Subject: [PATCH 49/96] fix preprint typo for institutional dashboard --- .../-components/total-count-kpi-wrapper/component-test.ts | 2 +- .../dashboard/-components/total-count-kpi-wrapper/component.ts | 2 +- app/models/institution-summary-metric.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/institutions/dashboard/-components/total-count-kpi-wrapper/component-test.ts b/app/institutions/dashboard/-components/total-count-kpi-wrapper/component-test.ts index 9e040bb8b63..5046abd46b4 100644 --- a/app/institutions/dashboard/-components/total-count-kpi-wrapper/component-test.ts +++ b/app/institutions/dashboard/-components/total-count-kpi-wrapper/component-test.ts @@ -18,7 +18,7 @@ module('Integration | institutions | dashboard | -components | total-count-kpi-w privateProjectCount: 10, userCount: 10, publicRegistrationCount: 100, - preprintCount: 1000, + publishedPreprintCount: 1000, embargoedRegistrationCount: 200, storageByteCount: 104593230, publicFileCount: 1567, diff --git a/app/institutions/dashboard/-components/total-count-kpi-wrapper/component.ts b/app/institutions/dashboard/-components/total-count-kpi-wrapper/component.ts index ea784c1683c..f96f2d1d649 100644 --- a/app/institutions/dashboard/-components/total-count-kpi-wrapper/component.ts +++ b/app/institutions/dashboard/-components/total-count-kpi-wrapper/component.ts @@ -64,7 +64,7 @@ export default class TotalCountKpiWrapperComponent extends Component Date: Fri, 18 Oct 2024 15:07:15 -0400 Subject: [PATCH 50/96] make total user count dynamic for the user tab --- .../-components/institutional-users-list/component.ts | 4 +--- .../-components/institutional-users-list/template.hbs | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/app/institutions/dashboard/-components/institutional-users-list/component.ts b/app/institutions/dashboard/-components/institutional-users-list/component.ts index 26b02326b8a..d918aa5bd94 100644 --- a/app/institutions/dashboard/-components/institutional-users-list/component.ts +++ b/app/institutions/dashboard/-components/institutional-users-list/component.ts @@ -20,7 +20,6 @@ interface Column { interface InstitutionalUsersListArgs { institution: InstitutionModel; departmentMetrics: InstitutionDepartmentsModel[]; - totalUsers: number; } export default class InstitutionalUsersList extends Component { @@ -30,7 +29,6 @@ export default class InstitutionalUsersList extends Component void; @action toggleColumnSelection(columnKey: string) { diff --git a/app/institutions/dashboard/-components/institutional-users-list/template.hbs b/app/institutions/dashboard/-components/institutional-users-list/template.hbs index a5d8c87ab09..00d21a0e70c 100644 --- a/app/institutions/dashboard/-components/institutional-users-list/template.hbs +++ b/app/institutions/dashboard/-components/institutional-users-list/template.hbs @@ -4,7 +4,7 @@
    - {{@totalUsers}} + {{this.totalUsers}} {{t 'institutions.dashboard.users_list.total_users'}}
    @@ -72,8 +72,8 @@ @model={{@institution}} @usePlaceholders={{false}} @relationshipName='userMetrics' - @bindReload={{this.reloadUserList}} @query={{this.queryUsers}} + @totalCount={{this.totalUsers}} as |list| > From 73268f975d833aadefbeb89097369aae71e36365 Mon Sep 17 00:00:00 2001 From: futa-ikeda <51409893+futa-ikeda@users.noreply.github.com> Date: Fri, 18 Oct 2024 16:19:05 -0400 Subject: [PATCH 51/96] Update month data fields to be strings (#2358) --- app/models/institution-user.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/models/institution-user.ts b/app/models/institution-user.ts index 44046c5bb0d..2fb19f778d3 100644 --- a/app/models/institution-user.ts +++ b/app/models/institution-user.ts @@ -15,9 +15,9 @@ export default class InstitutionUserModel extends OsfModel { @attr('number') publicFileCount!: number; @attr('number') storageByteCount!: number; @attr('number') totalObjectCount!: number; - @attr('date') monthLastLogin!: Date; - @attr('date') monthLastActive!: Date; - @attr('date') accountCreationDate!: Date; + @attr('string') monthLastLogin!: string; // YYYY-MM + @attr('string') monthLastActive!: string; // YYYY-MM + @attr('string') accountCreationDate!: string; // YYYY-MM @attr('fixstring') orcidId?: string; @belongsTo('user', { async: true }) From faa926835b98101bf6348349b0eb58fff8f1def6 Mon Sep 17 00:00:00 2001 From: futa-ikeda <51409893+futa-ikeda@users.noreply.github.com> Date: Wed, 23 Oct 2024 13:48:56 -0400 Subject: [PATCH 52/96] [ENG-6368] Share summary graphs (#2361) - Ticket: [ENG-6368] - Feature flag: n/a ## Purpose - Hook up graphs for licenses, addon usage and storage locations to actual data ## Summary of Changes - Add loading/error state to chart component - Add task to fetch data from SHARE to chart-wrapper component --- .../chart-kpi-wrapper/chart-kpi/component.ts | 24 ++-- .../chart-kpi-wrapper/chart-kpi/styles.scss | 2 +- .../chart-kpi-wrapper/chart-kpi/template.hbs | 128 +++++++++-------- .../chart-kpi-wrapper/component-test.ts | 69 +++------ .../chart-kpi-wrapper/component.ts | 133 ++++++------------ .../factories/institution-summary-metric.ts | 2 +- translations/en-us.yml | 1 + types/ember-cli-chart.d.ts | 8 +- 8 files changed, 149 insertions(+), 218 deletions(-) diff --git a/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/component.ts b/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/component.ts index 711a744af12..252b2978d98 100644 --- a/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/component.ts +++ b/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/component.ts @@ -31,7 +31,7 @@ export default class ChartKpi extends Component { * @returns a ChartOptions model which is custom to COS */ get chartOptions(): ChartOptions { - return { + const options = { aspectRatio: 1, legend: { display: false, @@ -42,9 +42,14 @@ export default class ChartKpi extends Component { }], yAxes: [{ display: false, + ticks: { min: 0 }, }], }, }; + if (this.args.data.chartType === 'bar') { + options.scales.yAxes[0].display = true; + } + return options; } /** @@ -80,21 +85,22 @@ export default class ChartKpi extends Component { get chartData(): ChartData { const backgroundColors = [] as string[]; const data = [] as number[]; - const labels = [] as string[]; + const labels = [] as string[]; + const { taskInstance, chartData } = this.args.data; - this.args.data.chartData.map((chartData: ChartDataModel, $index: number) => { - backgroundColors.push(this.getColor($index)); + const rawData = taskInstance?.value || chartData || []; - data.push(chartData.total); - labels.push(chartData.label); + rawData.forEach((rawChartData: ChartDataModel, $index: number) => { + backgroundColors.push(this.getColor($index)); + data.push(rawChartData.total); + labels.push(rawChartData.label); this.expandedData.push({ - name: chartData.label, - total: chartData.total, + name: rawChartData.label, + total: rawChartData.total, color: this.getColor($index), }); }); - return { labels, datasets: [{ diff --git a/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/styles.scss b/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/styles.scss index 75fe4f855de..6dad4139b18 100644 --- a/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/styles.scss +++ b/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/styles.scss @@ -6,7 +6,7 @@ display: flex; flex-direction: column; justify-content: center; - align-items: flex-start; + text-align: center; width: 290px; min-height: 290px; height: fit-content; diff --git a/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/template.hbs b/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/template.hbs index c62c58f81f2..d55b0fd48ee 100644 --- a/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/template.hbs +++ b/app/institutions/dashboard/-components/chart-kpi-wrapper/chart-kpi/template.hbs @@ -2,68 +2,74 @@ local-class='chart-container {{if (is-mobile) 'mobile'}}' ...attributes > -
    -
    - -
    -
    -
    - {{#let (unique-id 'expanded-data') as |expandedDataId|}} -
    -
    {{@data.title}}
    -
    - -
    -
    -
    + {{else if @data.taskInstance.isError}} + {{t 'institutions.dashboard.kpi-chart.error'}} + {{else}} +
    +
    -
      - {{#each this.expandedData as |data index |}} -
    • +
    +
    +
    + {{#let (unique-id 'expanded-data') as |expandedDataId|}} +
    +
    {{@data.title}}
    +
    + +
    +
    +
    +
      + {{#each this.expandedData as |data index |}} +
    • - {{data.total}} -
    - - {{/each}} -
- - {{/let}} - +
+
+
+ {{data.name}} +
+
+ {{data.total}} +
+ + {{/each}} + + + {{/let}} + + {{/if}} \ No newline at end of file diff --git a/app/institutions/dashboard/-components/chart-kpi-wrapper/component-test.ts b/app/institutions/dashboard/-components/chart-kpi-wrapper/component-test.ts index 5603074b0f6..c12c97d4852 100644 --- a/app/institutions/dashboard/-components/chart-kpi-wrapper/component-test.ts +++ b/app/institutions/dashboard/-components/chart-kpi-wrapper/component-test.ts @@ -19,7 +19,7 @@ module('Integration | institutions | dashboard | -components | kpi-chart-wrapper publicProjectCount: 20, publicRegistrationCount: 100, embargoedRegistrationCount: 200, - preprintCount: 1000, + publishedPreprintCount: 1000, storageByteCount: 2000, }, departmentMetrics: [ @@ -32,6 +32,9 @@ module('Integration | institutions | dashboard | -components | kpi-chart-wrapper numberOfUsers: 37, }, ], + institution: { + iris: ['bleh'], + }, }); this.set('model', model); @@ -225,22 +228,21 @@ module('Integration | institutions | dashboard | -components | kpi-chart-wrapper // And the expanded data position 0 name is verified assert.dom(`${parentDom} [data-test-expanded-name="0"]`) - .hasText('Math'); + .exists(); // And the expanded data position 0 total is verified assert.dom(`${parentDom} [data-test-expanded-total="0"]`) - .hasText('25'); + .hasText('3'); // And the expanded data position 1 name is verified assert.dom(`${parentDom} [data-test-expanded-name="1"]`) - .hasText('Science'); + .exists(); // And the expanded data position 1 total is verified assert.dom(`${parentDom} [data-test-expanded-total="1"]`) - .hasText('37'); + .hasText('2'); - // Finally there are only 2 expanded data points - assert.dom(`${parentDom} [data-test-expanded-name="2"]`) + assert.dom(`${parentDom} [data-test-expanded-total="2"]`) .doesNotExist(); }); @@ -261,19 +263,19 @@ module('Integration | institutions | dashboard | -components | kpi-chart-wrapper // And the expanded data position 0 name is verified assert.dom(`${parentDom} [data-test-expanded-name="0"]`) - .hasText('Math'); + .exists(); // And the expanded data position 0 total is verified assert.dom(`${parentDom} [data-test-expanded-total="0"]`) - .hasText('25'); + .hasText('3'); // And the expanded data position 1 name is verified assert.dom(`${parentDom} [data-test-expanded-name="1"]`) - .hasText('Science'); + .exists(); // And the expanded data position 1 total is verified assert.dom(`${parentDom} [data-test-expanded-total="1"]`) - .hasText('37'); + .hasText('2'); // Finally there are only 2 expanded data points assert.dom(`${parentDom} [data-test-expanded-name="2"]`) @@ -297,56 +299,19 @@ module('Integration | institutions | dashboard | -components | kpi-chart-wrapper // And the expanded data position 0 name is verified assert.dom(`${parentDom} [data-test-expanded-name="0"]`) - .hasText('Math'); - - // And the expanded data position 0 total is verified - assert.dom(`${parentDom} [data-test-expanded-total="0"]`) - .hasText('25'); - - // And the expanded data position 1 name is verified - assert.dom(`${parentDom} [data-test-expanded-name="1"]`) - .hasText('Science'); - - // And the expanded data position 1 total is verified - assert.dom(`${parentDom} [data-test-expanded-total="1"]`) - .hasText('37'); - - // Finally there are only 2 expanded data points - assert.dom(`${parentDom} [data-test-expanded-name="2"]`) - .doesNotExist(); - }); - - test('it calculates the Public vs Private Data Storage data correctly', async function(assert) { - // Given the component is rendered - await render(hbs` - -`); - const parentDom = '[data-test-kpi-chart="7"]'; - - // When I click the expanded icon - await click(`${parentDom} [data-test-expand-additional-data]`); - - // And the title is verified - assert.dom(`${parentDom} [data-test-chart-title]`) - .hasText('Public vs Private Data Storage'); - - // And the expanded data position 0 name is verified - assert.dom(`${parentDom} [data-test-expanded-name="0"]`) - .hasText('Public Data Storage'); + .exists(); // And the expanded data position 0 total is verified assert.dom(`${parentDom} [data-test-expanded-total="0"]`) - .hasText('2000'); + .hasText('3'); // And the expanded data position 1 name is verified assert.dom(`${parentDom} [data-test-expanded-name="1"]`) - .hasText('Private Data Storage'); + .exists(); // And the expanded data position 1 total is verified assert.dom(`${parentDom} [data-test-expanded-total="1"]`) - .hasText('2100'); + .hasText('2'); // Finally there are only 2 expanded data points assert.dom(`${parentDom} [data-test-expanded-name="2"]`) diff --git a/app/institutions/dashboard/-components/chart-kpi-wrapper/component.ts b/app/institutions/dashboard/-components/chart-kpi-wrapper/component.ts index 34cd03de933..4bc74e2cdbe 100644 --- a/app/institutions/dashboard/-components/chart-kpi-wrapper/component.ts +++ b/app/institutions/dashboard/-components/chart-kpi-wrapper/component.ts @@ -1,12 +1,16 @@ +import Store from '@ember-data/store'; +import { inject as service } from '@ember/service'; import { waitFor } from '@ember/test-waiters'; import Component from '@glimmer/component'; import { tracked } from '@glimmer/tracking'; -import { task } from 'ember-concurrency'; +import { task, TaskInstance } from 'ember-concurrency'; import { taskFor } from 'ember-concurrency-ts'; import Intl from 'ember-intl/services/intl'; -import { inject as service } from '@ember/service'; + import InstitutionDepartmentModel from 'ember-osf-web/models/institution-department'; import InstitutionSummaryMetricModel from 'ember-osf-web/models/institution-summary-metric'; +import SearchResultModel from 'ember-osf-web/models/search-result'; + /* import InstitutionSummaryMetricModel from 'ember-osf-web/models/institution-summary-metric'; */ @@ -22,12 +26,16 @@ interface TotalCountChartWrapperArgs { export interface KpiChartModel { title: string; - chartData: ChartDataModel[]; chartType: string; + // Either chartData or taskInstance should be defined + chartData?: ChartDataModel[]; + taskInstance?: TaskInstance; } export default class ChartKpiWrapperComponent extends Component { @service intl!: Intl; + @service store!: Store; + @tracked model = this.args.model; @tracked kpiCharts = [] as KpiChartModel[]; @tracked isLoading = true; @@ -50,6 +58,11 @@ export default class ChartKpiWrapperComponent extends Component { const metrics = await this.model; + const getLicenseTask = taskFor(this.getShareData).perform('rights'); + const getAddonsTask = taskFor(this.getShareData).perform('hasOsfAddon'); + const getRegionTask = taskFor(this.getShareData) + .perform('storageRegion'); + this.kpiCharts.push( { title: this.intl.t('institutions.dashboard.kpi-chart.users-by-department'), @@ -73,23 +86,18 @@ export default class ChartKpiWrapperComponent extends Component { - licenseData.push( - { - label: metric.name, - total: metric.numberOfUsers, - } as ChartDataModel, - ); - }); - return licenseData; - } - - /** - * calculateAddons - * - * @description Abstracted method to build the ChartData model for add-ons - * @param addonMetrics The add-on metrics object + * @returns ChartDataModel[] The labels and totals for each section * - * @returns The add-ons ChartData model */ - private calculateAddons(addonMetrics: InstitutionDepartmentModel[]): ChartDataModel[] { - const addonData = [] as ChartDataModel[]; - - addonMetrics.forEach((metric: InstitutionDepartmentModel ) => { - addonData.push( - { - label: metric.name, - total: metric.numberOfUsers, - } as ChartDataModel, - ); - }); - return addonData; - } - - /** - * calculateStorageRegions - * - * @description Abstracted method to build the Storage Regions ChartData - * @param storageRegionsMetrics The storage regions metrics object - * - * @returns The storage regions ChartData model - */ - private calculateStorageRegions(storageRegionsMetrics: InstitutionDepartmentModel[]): ChartDataModel[] { - const storageRegionsData = [] as ChartDataModel[]; - - storageRegionsMetrics.forEach((metric: InstitutionDepartmentModel ) => { - storageRegionsData.push( - { - label: metric.name, - total: metric.numberOfUsers, - } as ChartDataModel, - ); + @task + @waitFor + private async getShareData( + propertyPath: string, + ): Promise { + const valueSearch = await this.store.queryRecord('index-value-search', { + cardSearchFilter: { + affiliation: this.args.model.institution.iris.join(','), + }, + 'page[size]': 10, + valueSearchPropertyPath: propertyPath, }); - return storageRegionsData; - } + const resultPage = valueSearch.searchResultPage.toArray(); - /** - * calculateDataStorage - * - * @description Abstracted method to calculate the private and public data storage - * @param summaryMetrics The institutional summary metrics object - * - * @returns The total of private and public data storage - */ - private calculateDataStorage(summaryMetrics: InstitutionSummaryMetricModel): ChartDataModel[] { - return [ - { - label: this.intl.t('institutions.dashboard.kpi-chart.public-vs-private-data-storage.public'), - total: summaryMetrics.storageByteCount, - } as ChartDataModel, - { - label: this.intl.t('institutions.dashboard.kpi-chart.public-vs-private-data-storage.private'), - total: summaryMetrics.storageByteCount + 100, - } as ChartDataModel, - ]; + return resultPage.map((result: SearchResultModel) => ({ + total: result.cardSearchResultCount, + label: result.indexCard.get('label'), + })); } } - - diff --git a/mirage/factories/institution-summary-metric.ts b/mirage/factories/institution-summary-metric.ts index e99994d5525..123c422d7d7 100644 --- a/mirage/factories/institution-summary-metric.ts +++ b/mirage/factories/institution-summary-metric.ts @@ -19,7 +19,7 @@ export default Factory.extend({ embargoedRegistrationCount() { return faker.random.number({ min: 0, max: 25}); }, - preprintCount() { + publishedPreprintCount() { return faker.random.number({ min: 15, max: 175}); }, storageByteCount() { diff --git a/translations/en-us.yml b/translations/en-us.yml index 742cf13288e..71cfbb0949f 100644 --- a/translations/en-us.yml +++ b/translations/en-us.yml @@ -859,6 +859,7 @@ institutions: title: 'Public vs Private Data Storage' public: 'Public Data Storage' private: 'Private Data Storage' + error: 'Error loading data' object-list: filter-button-label: 'Filter' filter-heading: 'Filter by:' diff --git a/types/ember-cli-chart.d.ts b/types/ember-cli-chart.d.ts index 93d77cdeae9..6d027a67b11 100644 --- a/types/ember-cli-chart.d.ts +++ b/types/ember-cli-chart.d.ts @@ -20,14 +20,18 @@ declare module 'ember-cli-chart' { xAxes?: [ { display?: boolean + ticks?: { + min?: number + } } - ], yAxes?: [ { display?: boolean + ticks?: { + min?: number + } } - ] } } From 6e42b4de3f8166820e7391f08b5f5aebf623fa7e Mon Sep 17 00:00:00 2001 From: futa-ikeda <51409893+futa-ikeda@users.noreply.github.com> Date: Wed, 23 Oct 2024 14:53:27 -0400 Subject: [PATCH 53/96] [ENG-6246][ENG-6269][ENG-6275] Add new property fields to mirage (#2353) - Ticket: [ENG-6246] [ENG-6269] [ENG-6275] - Feature flag: n/a ## Purpose - Add new metadata fields to mirage and update project list to get new properties ## Summary of Changes - Add new properties to mirage/views/search.ts based on the proposed changes to OSF MAP - Add new getters for these new metadata properties to the search-result model - Update project list columns to match what is actually requested in the mocks - Update contributor field component to show user's permission level --- .../contributors-field/component.ts | 43 ++++-- .../contributors-field/template.hbs | 2 +- .../dashboard/projects/controller.ts | 37 +++--- app/models/index-card.ts | 5 + app/models/search-result.ts | 16 +++ .../list/item/template.hbs | 4 +- .../add-unregistered-modal/template.hbs | 2 +- .../contributors/card/editable/template.hbs | 2 +- .../contributors/card/readonly/template.hbs | 2 +- .../user-search/card/template.hbs | 2 +- mirage/views/search.ts | 125 ++++++++++++------ .../components/contributors/component-test.ts | 4 +- translations/en-us.yml | 20 +-- 13 files changed, 180 insertions(+), 84 deletions(-) diff --git a/app/institutions/dashboard/-components/object-list/contributors-field/component.ts b/app/institutions/dashboard/-components/object-list/contributors-field/component.ts index cbc3e53b914..e71de161176 100644 --- a/app/institutions/dashboard/-components/object-list/contributors-field/component.ts +++ b/app/institutions/dashboard/-components/object-list/contributors-field/component.ts @@ -1,32 +1,55 @@ import Component from '@glimmer/component'; +import { inject as service } from '@ember/service'; +import Intl from 'ember-intl/services/intl'; import InstitutionModel from 'ember-osf-web/models/institution'; import SearchResultModel from 'ember-osf-web/models/search-result'; +import { AttributionRoleIris } from 'ember-osf-web/models/index-card'; interface ContributorsFieldArgs { searchResult: SearchResultModel; institution: InstitutionModel; } +const roleIriToTranslationKey: Record = { + [AttributionRoleIris.Admin]: 'general.permissions.admin', + [AttributionRoleIris.Write]: 'general.permissions.write', + [AttributionRoleIris.Read]: 'general.permissions.read', +}; + + export default class InstitutionalObjectListContributorsField extends Component { + @service intl!: Intl; + // Return two contributors affiliated with the institution given with highest permission levels get topInstitutionAffiliatedContributors() { const { searchResult, institution } = this.args; - const contributors: any[] = searchResult.resourceMetadata.creator; + const attributions: any[] = searchResult.resourceMetadata.qualifiedAttribution; const institutionIris = institution.iris; - const affiliatedContributors = contributors - .filter((contributor: any) => hasInstitutionAffiliation(contributor, institutionIris)); - // TODO: get the two users with the highest permission level and add permission to the return object - return affiliatedContributors.slice(0, 2).map((contributor: any) => ({ - name: contributor.name[0]['@value'], - url: contributor['@id'], - })); - } + const affiliatedAttributions = attributions + .filter((attribution: any) => hasInstitutionAffiliation(attribution, institutionIris)); + const adminAttributions = affiliatedAttributions + .filter(attribution => attribution.hadRole[0]['@id'] === AttributionRoleIris.Admin); + const writeAttributions = affiliatedAttributions + .filter(attribution => attribution.hadRole[0]['@id'] === AttributionRoleIris.Write); + const readAttributions = affiliatedAttributions + .filter(attribution => attribution.hadRole[0]['@id'] === AttributionRoleIris.Read); + + const prioritizedAttributions = adminAttributions.concat(writeAttributions, readAttributions); + return prioritizedAttributions.slice(0, 2).map((attribution: any) => { + const roleIri: AttributionRoleIris = attribution.hadRole[0]['@id']; + return { + name: attribution.agent[0].name[0]['@value'], + url: attribution.agent[0]['@id'], + permissionLevel: this.intl.t(roleIriToTranslationKey[roleIri]), + }; + }); + } } function hasInstitutionAffiliation(contributor: any, institutionIris: string[]) { - return contributor.affiliation.some( + return contributor.agent[0].affiliation.some( (affiliation: any) => affiliation.identifier.some( (affiliationIdentifier: any) => institutionIris.includes(affiliationIdentifier['@value']), ), diff --git a/app/institutions/dashboard/-components/object-list/contributors-field/template.hbs b/app/institutions/dashboard/-components/object-list/contributors-field/template.hbs index 386eaf9f8ce..6bf6e00b3ad 100644 --- a/app/institutions/dashboard/-components/object-list/contributors-field/template.hbs +++ b/app/institutions/dashboard/-components/object-list/contributors-field/template.hbs @@ -4,5 +4,5 @@ > {{contributor.name}} - {{!-- TODO: add permission level later --}} + {{t 'institutions.dashboard.object-list.table-items.permission-level' permissionLevel=contributor.permissionLevel}} {{/each}} diff --git a/app/institutions/dashboard/projects/controller.ts b/app/institutions/dashboard/projects/controller.ts index 0d62d299fef..84a2f4a12ef 100644 --- a/app/institutions/dashboard/projects/controller.ts +++ b/app/institutions/dashboard/projects/controller.ts @@ -2,6 +2,8 @@ import Controller from '@ember/controller'; import { inject as service } from '@ember/service'; import Intl from 'ember-intl/services/intl'; + +import humanFileSize from 'ember-osf-web/utils/human-file-size'; import { ResourceTypeFilterValue } from 'osf-components/components/search-page/component'; import { ObjectListColumn } from '../-components/object-list/component'; @@ -19,10 +21,6 @@ export default class InstitutionDashboardProjects extends Controller { getHref: searchResult => searchResult.indexCard.get('osfIdentifier'), getLinkText: searchResult => searchResult.indexCard.get('osfGuid'), }, - { // Object type - name: this.intl.t('institutions.dashboard.object-list.table-headers.object_type'), - getValue: searchResult => searchResult.intlResourceType, - }, { // Date created name: this.intl.t('institutions.dashboard.object-list.table-headers.created_date'), getValue: searchResult => searchResult.getResourceMetadataField('dateCreated'), @@ -39,13 +37,11 @@ export default class InstitutionDashboardProjects extends Controller { }, { // Storage location name: this.intl.t('institutions.dashboard.object-list.table-headers.storage_location'), - // TODO: Update when OsfMap representation is available - getValue: searchResult => searchResult.storageLocation, + getValue: searchResult => searchResult.storageRegion, }, { // Total data stored name: this.intl.t('institutions.dashboard.object-list.table-headers.total_data_stored'), - // TODO: Update when OsfMap representation is available - getValue: searchResult => searchResult.totalDataStored, + getValue: searchResult => humanFileSize(searchResult.getResourceMetadataField('storageByteCount')), }, { // Contributor name + permissions name: this.intl.t('institutions.dashboard.object-list.table-headers.contributor_name'), @@ -53,18 +49,23 @@ export default class InstitutionDashboardProjects extends Controller { }, { // View count name: this.intl.t('institutions.dashboard.object-list.table-headers.view_count'), - // TODO: Update when OsfMap representation is available - getValue: searchResult => searchResult.viewCount, + getValue: searchResult => searchResult.usageMetrics.viewCount, + }, + { // Resource type + name: this.intl.t('institutions.dashboard.object-list.table-headers.resource_nature'), + getValue: searchResult => searchResult.resourceNature, + }, + { // License + name: this.intl.t('institutions.dashboard.object-list.table-headers.license'), + getValue: searchResult => searchResult.license?.name, }, - { // Download count - name: this.intl.t('institutions.dashboard.object-list.table-headers.download_count'), - // TODO: Update when OsfMap representation is available - getValue: searchResult => searchResult.downloadCount, + { // addons associated + name: this.intl.t('institutions.dashboard.object-list.table-headers.addons'), + getValue: searchResult => searchResult.configuredAddonNames, }, - { // Has metadata - name: this.intl.t('institutions.dashboard.object-list.table-headers.has_metadata'), - // TODO: Update when OsfMap representation is available - getValue: searchResult => searchResult.hasMetadata, + { // Funder name + name: this.intl.t('institutions.dashboard.object-list.table-headers.funder_name'), + getValue: searchResult => searchResult.funders.map((funder: {name: string}) => funder.name).join(', '), }, ]; diff --git a/app/models/index-card.ts b/app/models/index-card.ts index 9c5291964a7..15fcd514373 100644 --- a/app/models/index-card.ts +++ b/app/models/index-card.ts @@ -30,6 +30,11 @@ export enum OsfmapResourceTypes { ConceptScheme = 'Concept:Scheme', } +export enum AttributionRoleIris { + Admin = 'osf:admin-contributor', + Write = 'osf:write-contributor', + Read = 'osf:readonly-contributor', +} export default class IndexCardModel extends Model { @service intl!: IntlService; diff --git a/app/models/search-result.ts b/app/models/search-result.ts index 7ff6bae8ada..9cec3d98273 100644 --- a/app/models/search-result.ts +++ b/app/models/search-result.ts @@ -298,6 +298,22 @@ export default class SearchResultModel extends Model { return this.resourceMetadata.dateWithdrawn || this.resourceMetadata['https://osf.io/vocab/2022/withdrawal']; } + get configuredAddonNames() { + return this.resourceMetadata.hasOsfAddon?.map((item: any) => item.prefLabel[0]['@value']); + } + + get storageRegion() { + return this.resourceMetadata.storageRegion?.[0]?.prefLabel[0]['@value']; + } + + get usageMetrics() { + return { + period: this.resourceMetadata.usage[0]['temporalCoverage'][0]['@value'], + viewCount: this.resourceMetadata.usage[0]['viewCount'][0]['@value'], + downloadCount: this.resourceMetadata.usage[0]['downloadCount'][0]['@value'], + }; + } + getResourceMetadataField(field: string) { return this.resourceMetadata[field]?.[0]?.['@value']; } diff --git a/lib/app-components/addon/components/project-contributors/list/item/template.hbs b/lib/app-components/addon/components/project-contributors/list/item/template.hbs index d7b13fa1c58..4211d83e60b 100644 --- a/lib/app-components/addon/components/project-contributors/list/item/template.hbs +++ b/lib/app-components/addon/components/project-contributors/list/item/template.hbs @@ -48,11 +48,11 @@ @selected={{@contributor.permission}} as |option| > - {{t (concat 'app_components.project_contributors.list.item.permissions.' option)}} + {{t (concat 'general.permissions.' option)}} {{else}}
- {{t (concat 'app_components.project_contributors.list.item.permissions.' @contributor.permission)}} + {{t (concat 'general.permissions.' @contributor.permission)}}
{{/if}} diff --git a/lib/osf-components/addon/components/contributors/add-unregistered-modal/template.hbs b/lib/osf-components/addon/components/contributors/add-unregistered-modal/template.hbs index a64301b3c5c..be16b23125c 100644 --- a/lib/osf-components/addon/components/contributors/add-unregistered-modal/template.hbs +++ b/lib/osf-components/addon/components/contributors/add-unregistered-modal/template.hbs @@ -42,7 +42,7 @@ data-test-select-permission as |permission| > - {{t (concat 'osf-components.contributors.permissions.' permission)}} + {{t (concat 'general.permissions.' permission)}} {{#let (unique-id 'citation-checkbox') as |id|}}