Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<section class="flex flex-column xl:mt-6">
@if (isInstitutionLoading()) {
<div class="h-screen flex flex-column align-items-center">
<osf-loading-spinner />
</div>
} @else {
<div class="flex flex-column gap-5 p-5">
<div class="flex align-items-center gap-2">
<img
[ngSrc]="institution().assets.logo"
[alt]="institution().name"
width="60"
height="60"
class="fit-contain"
/>

<h1>{{ institution().name }}</h1>
</div>
</div>

<p-tabs [value]="selectedTab" (valueChange)="onTabChange($event)" class="flex-1 px-3 hidden md:px-5 md:inline">
<p-tablist>
@for (item of resourceTabOptions; track $index) {
<p-tab [value]="item.value">{{ item.label | translate }}</p-tab>
}
</p-tablist>
</p-tabs>

<section class="bg-white p-3 md:p-4">
<router-outlet></router-outlet>
</section>
}
</section>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
:host {
display: flex;
flex-direction: column;
flex: 1;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { AdminInstitutionsComponent } from './admin-institutions.component';

describe('AdminInstitutionsComponent', () => {
let component: AdminInstitutionsComponent;
let fixture: ComponentFixture<AdminInstitutionsComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [AdminInstitutionsComponent],
}).compileComponents();

fixture = TestBed.createComponent(AdminInstitutionsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { createDispatchMap, select } from '@ngxs/store';

import { TranslateModule } from '@ngx-translate/core';

import { TabsModule } from 'primeng/tabs';

import { NgOptimizedImage } from '@angular/common';
import { ChangeDetectionStrategy, Component, inject, OnInit } from '@angular/core';
import { ActivatedRoute, Router, RouterOutlet } from '@angular/router';

import { resourceTabOptions } from '@osf/features/admin-institutions/constants/resource-tab-option.constant';
import { LoadingSpinnerComponent } from '@shared/components';
import { FetchInstitutionById, InstitutionsSearchSelectors } from '@shared/stores';

@Component({
selector: 'osf-admin-institutions',
imports: [TabsModule, TranslateModule, RouterOutlet, NgOptimizedImage, LoadingSpinnerComponent],
templateUrl: './admin-institutions.component.html',
styleUrl: './admin-institutions.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AdminInstitutionsComponent implements OnInit {
private readonly router = inject(Router);
private readonly route = inject(ActivatedRoute);

institution = select(InstitutionsSearchSelectors.getInstitution);
isInstitutionLoading = select(InstitutionsSearchSelectors.getInstitutionLoading);

private readonly actions = createDispatchMap({
fetchInstitution: FetchInstitutionById,
});

selectedTab = 'summary';

resourceTabOptions = resourceTabOptions;

ngOnInit() {
const institutionId = this.route.snapshot.params['institution-id'];

if (institutionId) {
this.actions.fetchInstitution(institutionId);
}

this.selectedTab = this.route.snapshot.firstChild?.routeConfig?.path || 'summary';
}

onTabChange(selectedValue: string | number) {
const value = String(selectedValue);
this.selectedTab = value;
const selectedTab = this.resourceTabOptions.find((tab) => tab.value === value);
if (selectedTab) {
this.router.navigate([selectedTab.value], { relativeTo: this.route });
}
}
}
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { CustomOption } from '@shared/models';

export const resourceTabOptions: CustomOption<string>[] = [
{ label: 'adminInstitutions.summary.title', value: 'summary' },
{ label: 'common.search.tabs.users', value: 'users' },
{ label: 'common.search.tabs.projects', value: 'projects' },
{ label: 'common.search.tabs.registrations', value: 'registrations' },
{ label: 'common.search.tabs.preprints', value: 'preprints' },
];
2 changes: 2 additions & 0 deletions src/app/features/admin-institutions/mappers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { mapInstitutionDepartment, mapInstitutionDepartments } from './institution-departments.mapper';
export { mapInstitutionSummaryMetrics } from './institution-summary-metrics.mapper';
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import {
InstitutionDepartment,
InstitutionDepartmentDataJsonApi,
InstitutionDepartmentsJsonApi,
} from '@osf/features/admin-institutions/models';

export function mapInstitutionDepartments(jsonApiData: InstitutionDepartmentsJsonApi): InstitutionDepartment[] {
return jsonApiData.data.map((department: InstitutionDepartmentDataJsonApi) => ({
id: department.id,
name: department.attributes.name,
numberOfUsers: department.attributes.number_of_users,
selfLink: department.links.self,
}));
}

export function mapInstitutionDepartment(department: InstitutionDepartmentDataJsonApi): InstitutionDepartment {
return {
id: department.id,
name: department.attributes.name,
numberOfUsers: department.attributes.number_of_users,
selfLink: department.links.self,
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import {
InstitutionIndexCardFilterJsonApi,
InstitutionIndexValueSearchIncludedJsonApi,
InstitutionSearchFilter,
InstitutionSearchResultCountJsonApi,
} from '@osf/features/admin-institutions/models';

export function mapIndexCardResults(included: InstitutionIndexValueSearchIncludedJsonApi[]): InstitutionSearchFilter[] {
const indexCardMap = included.reduce(
(acc, item) => {
if (item.type === 'index-card') {
acc[item.id] = {
id: item.id,
label:
(item as InstitutionIndexCardFilterJsonApi)?.attributes?.resourceMetadata?.displayLabel?.[0]?.['@value'] ||
(item as InstitutionIndexCardFilterJsonApi)?.attributes?.resourceMetadata?.name?.[0]?.['@value'] ||
item.id,
};
}
return acc;
},
{} as Record<string, { id: string; label: string }>
);

return included.reduce((result, item) => {
if (item.type === 'search-result') {
const indexCardId = (item as InstitutionSearchResultCountJsonApi).relationships?.indexCard?.data?.id;
const count = (item as InstitutionSearchResultCountJsonApi).attributes?.cardSearchResultCount ?? 0;
const indexCard = indexCardMap[indexCardId];
if (indexCard) {
result.push({
id: indexCard.id,
label: indexCard.label,
value: count,
} as InstitutionSearchFilter);
}
}
return result;
}, [] as InstitutionSearchFilter[]);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import {
InstitutionSummaryMetrics,
InstitutionSummaryMetricsAttributesJsonApi,
} from '@osf/features/admin-institutions/models';

export function mapInstitutionSummaryMetrics(
attributes: InstitutionSummaryMetricsAttributesJsonApi
): InstitutionSummaryMetrics {
return {
reportYearmonth: attributes.report_yearmonth,
userCount: attributes.user_count,
publicProjectCount: attributes.public_project_count,
privateProjectCount: attributes.private_project_count,
publicRegistrationCount: attributes.public_registration_count,
embargoedRegistrationCount: attributes.embargoed_registration_count,
publishedPreprintCount: attributes.published_preprint_count,
publicFileCount: attributes.public_file_count,
storageByteCount: attributes.storage_byte_count,
monthlyLoggedInUserCount: attributes.monthly_logged_in_user_count,
monthlyActiveUserCount: attributes.monthly_active_user_count,
};
}
6 changes: 6 additions & 0 deletions src/app/features/admin-institutions/models/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export * from './institution-department.model';
export * from './institution-departments-json-api.model';
export * from './institution-index-value-search-json-api.model';
export * from './institution-search-filter.model';
export * from './institution-summary-metric.model';
export * from './institution-summary-metrics-json-api.model';
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface InstitutionDepartment {
id: string;
name: string;
numberOfUsers: number;
selfLink: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export interface InstitutionDepartmentAttributesJsonApi {
name: string;
number_of_users: number;
}

export interface InstitutionDepartmentLinksJsonApi {
self: string;
}

export interface InstitutionDepartmentDataJsonApi {
id: string;
type: 'institution-departments';
attributes: InstitutionDepartmentAttributesJsonApi;
links: InstitutionDepartmentLinksJsonApi;
}

export interface InstitutionDepartmentsJsonApi {
data: InstitutionDepartmentDataJsonApi[];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { JsonApiResponse } from '@core/models';

export interface InstitutionSearchResultCountJsonApi {
attributes: {
cardSearchResultCount: number;
};
id: string;
type: string;
relationships: {
indexCard: {
data: {
id: string;
type: string;
};
};
};
}

export interface InstitutionIndexCardFilterJsonApi {
attributes: {
resourceIdentifier: string[];
resourceMetadata: {
displayLabel: { '@value': string }[];
'@id': string;
name: { '@value': string }[];
};
};
id: string;
type: string;
}

export type InstitutionIndexValueSearchIncludedJsonApi =
| InstitutionSearchResultCountJsonApi
| InstitutionIndexCardFilterJsonApi;

export interface InstitutionIndexValueSearchJsonApi
extends JsonApiResponse<null, InstitutionIndexValueSearchIncludedJsonApi[]> {
data: null;
included: InstitutionIndexValueSearchIncludedJsonApi[];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface InstitutionSearchFilter {
id: string;
label: string;
value: string | number;
count?: number;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export interface InstitutionSummaryMetrics {
reportYearmonth: string;
userCount: number;
publicProjectCount: number;
privateProjectCount: number;
publicRegistrationCount: number;
embargoedRegistrationCount: number;
publishedPreprintCount: number;
publicFileCount: number;
storageByteCount: number;
monthlyLoggedInUserCount: number;
monthlyActiveUserCount: number;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
export interface InstitutionSummaryMetricsAttributesJsonApi {
report_yearmonth: string;
user_count: number;
public_project_count: number;
private_project_count: number;
public_registration_count: number;
embargoed_registration_count: number;
published_preprint_count: number;
public_file_count: number;
storage_byte_count: number;
monthly_logged_in_user_count: number;
monthly_active_user_count: number;
}

export interface InstitutionSummaryMetricsRelationshipsJsonApi {
user: {
data: null;
};
institution: {
links: {
related: {
href: string;
meta: Record<string, unknown>;
};
};
data: {
id: string;
type: 'institutions';
};
};
}

export interface InstitutionSummaryMetricsDataJsonApi {
id: string;
type: 'institution-summary-metrics';
attributes: InstitutionSummaryMetricsAttributesJsonApi;
relationships: InstitutionSummaryMetricsRelationshipsJsonApi;
links: Record<string, unknown>;
}

export interface InstitutionSummaryMetricsJsonApi {
data: InstitutionSummaryMetricsDataJsonApi;
meta: {
version: string;
};
}
5 changes: 5 additions & 0 deletions src/app/features/admin-institutions/pages/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export { InstitutionsPreprintsComponent } from './institutions-preprints/institutions-preprints.component';
export { InstitutionsProjectsComponent } from './institutions-projects/institutions-projects.component';
export { InstitutionsRegistrationsComponent } from './institutions-registrations/institutions-registrations.component';
export { InstitutionsSummaryComponent } from './institutions-summary/institutions-summary.component';
export { InstitutionsUsersComponent } from './institutions-users/institutions-users.component';
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { InstitutionsPreprintsComponent } from './institutions-preprints.component';

describe('InstitutionsPreprintsComponent', () => {
let component: InstitutionsPreprintsComponent;
let fixture: ComponentFixture<InstitutionsPreprintsComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [InstitutionsPreprintsComponent],
}).compileComponents();

fixture = TestBed.createComponent(InstitutionsPreprintsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
Loading