Skip to content

Commit

Permalink
Add tags view to overviews
Browse files Browse the repository at this point in the history
Closes gb-27
  • Loading branch information
mnhock committed Jul 30, 2023
1 parent 7fbf463 commit b27d042
Show file tree
Hide file tree
Showing 20 changed files with 498 additions and 26 deletions.
4 changes: 4 additions & 0 deletions naikan-client/src/app/app-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ export const APP_ROUTES: Routes = [
path: 'overview/deployments',
loadChildren: () => import('./overview/deployment/deployment-routing.module').then(m => m.DeploymentRoutingModule)
},
{
path: 'overview/tags',
loadChildren: () => import('./overview/tag/tag-routing.module').then(m => m.TagRoutingModule)
},
{
path: 'administration',
loadChildren: () => import('./administration/administration.routing.module').then(m => m.AdministrationRoutingModule)
Expand Down
13 changes: 3 additions & 10 deletions naikan-client/src/app/layout/app.menu.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,14 @@ export class AppMenuComponent implements OnInit {
{
label: 'Overviews',
items: [
{
label: 'Environments',
icon: 'pi pi-fw pi-box',
routerLink: ['/overview/environments']
},
{label: 'Environments', icon: 'pi pi-fw pi-box', routerLink: ['/overview/environments']},
{label: 'Teams', icon: 'pi pi-fw pi-users', routerLink: ['/overview/teams']},
{label: 'Developers', icon: 'pi pi-fw pi-user', routerLink: ['/overview/developers']},
{label: 'Contacts', icon: 'pi pi-fw pi-envelope', routerLink: ['/overview/contacts']},
{label: 'Integrations', icon: 'pi pi-fw pi-link', routerLink: ['/overview/integrations']},
{label: 'Technologies', icon: 'pi pi-fw pi-code', routerLink: ['/overview/technologies']},
{
label: 'Deployments',
icon: 'pi pi-fw pi-cloud-upload',
routerLink: ['/overview/deployments']
}
{label: 'Deployments', icon: 'pi pi-fw pi-cloud-upload', routerLink: ['/overview/deployments']},
{label: 'Tags', icon: 'pi pi-fw pi-tags', routerLink: ['/overview/tags']},
]
}
];
Expand Down
8 changes: 8 additions & 0 deletions naikan-client/src/app/overview/overview.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -62,5 +62,13 @@
<div class="text-900 mb-3 font-medium">Deployments</div>
<span class="text-700 text-sm line-height-3">See all deployments of your projects on all environments.</span>
</div>
<div class="col-12 md:col-4 md:mb-4 mb-0 px-3">
<span class="p-3 shadow-2 mb-3 inline-block surface-card" style="border-radius: 10px;">
<a routerLink="/overview/tags"><i
class="pi pi-tags text-4xl text-blue-500"></i></a>
</span>
<div class="text-900 mb-3 font-medium">Tags</div>
<span class="text-700 text-sm line-height-3">See all tags of your projects.</span>
</div>
</div>
</div>
14 changes: 14 additions & 0 deletions naikan-client/src/app/overview/tag/tag-routing.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import {NgModule} from '@angular/core';
import {RouterModule} from '@angular/router';
import {TagComponent} from './tag.component';
import {AuthGuard} from "../../shared";

@NgModule({
imports: [RouterModule.forChild([{
path: '', component: TagComponent,
canActivate: [AuthGuard]
}])],
exports: [RouterModule]
})
export class TagRoutingModule {
}
92 changes: 92 additions & 0 deletions naikan-client/src/app/overview/tag/tag.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<naikan-breadcrumb
[items]="[{label: 'Overviews', routerLink: '/overview'}, {label: 'Tags', routerLink: '/overview/tags'}]"></naikan-breadcrumb>

<div class="card card-border-none">
<h5> {{page?.totalElements}} Tags
<ng-container *ngIf="chartData?.datasets">
<i class="pi pi-angle-down" (click)="decreaseTopN()"></i>
<i class="pi pi-angle-up" (click)="increaseTopN()"></i>
</ng-container>
</h5>

<div id="overview-chart-wrapper" *ngIf="chartData?.datasets">
<p-chart type="bar" [data]="chartData" [options]="chartOptions"></p-chart>
</div>

<p-table
#tableOverviewGroups
dataKey="uuid"
styleClass="p-datatable-striped"
[rowHover]="true"
[value]="page?.content"
[lazy]="true"
(onLazyLoad)="loadOverviews($event)"
[paginator]="true"
[alwaysShowPaginator]="false"
[rows]="25"
[defaultSortOrder]="1"
[sortOrder]="1"
sortField="group"
sortMode="multiple"
[expandedRowKeys]="expandedRows"
[rowsPerPageOptions]="[10, 25, 50, 100, 200]"
[totalRecords]="page?.totalElements">

<ng-template pTemplate="caption">
<div class="flex justify-content-between flex-wrap mb-2 mt-4">
<div class="flex align-items-center justify-content-center">
<naikan-search #search [table]="tableOverviewGroups"></naikan-search>
</div>
<div class="flex align-items-center justify-content-center">
<button pButton label="Clear" class="p-button-outlined" icon="pi pi-filter-slash"
(click)="tableOverviewGroups.reset(); search.clear()"></button>
</div>
</div>
</ng-template>

<ng-template pTemplate="header">
<tr>
<th class="w-3rem">
<p-button *ngIf="page?.totalElements > 0" styleClass="p-button-primary"
icon="{{expandedRows && Object.keys(expandedRows).length ? 'pi pi-angle-down' : 'pi pi-angle-right'}}"
(click)="expand()" tooltipPosition="top" pTooltip="Toggle rows"></p-button>
</th>
<th pSortableColumn="group">Name
<p-columnFilter field="group" display="menu"
placeholder="Name">
</p-columnFilter>
<p-sortIcon field="group"></p-sortIcon>
</th>
<th pSortableColumn="count" class="w-10rem">Projects
<p-sortIcon field="count"></p-sortIcon>
</th>
</tr>
</ng-template>

<ng-template pTemplate="body" let-overviewGroup let-expanded="expanded">
<tr>
<td>
<button type="button" pButton [pRowToggler]="overviewGroup"
class="p-button-text p-button-rounded p-button-plain"
[icon]="expanded ? 'pi pi-chevron-down' : 'pi pi-chevron-right'"></button>
</td>
<td>
{{ overviewGroup.group.name }}
</td>
<td class="text-center w-4rem">
<p-tag severity="primary" value="{{ overviewGroup.count }}" [rounded]="true"></p-tag>
</td>
</tr>
</ng-template>

<ng-template pTemplate="rowexpansion" let-group>
<tr>
<td></td>
<td colspan="2">
<naikan-overview-project-table [overviewBoms]="group.boms">
</naikan-overview-project-table>
</td>
</tr>
</ng-template>
</p-table>
</div>
68 changes: 68 additions & 0 deletions naikan-client/src/app/overview/tag/tag.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import {Component} from '@angular/core';
import {TagService} from './tag.service';
import {Breadcrumb, Charts, Search, Url} from '../../shared';
import {SharedModule} from 'primeng/api';
import {LayoutService} from '../../layout/app.layout.service';
import {AbstractOverviewComponent} from "../abstract-overview.component";
import {OverviewProjectTable} from '../overview-project-table';
import {TagModule} from 'primeng/tag';
import {TooltipModule} from 'primeng/tooltip';
import {ButtonModule} from 'primeng/button';
import {TableLazyLoadEvent, TableModule} from 'primeng/table';
import {ChartModule} from 'primeng/chart';
import {DatePipe, NgIf} from '@angular/common';
import {OverviewGroup} from "../overview";

@Component({
templateUrl: './tag.component.html',
standalone: true,
imports: [
Breadcrumb,
NgIf,
ChartModule,
TableModule,
SharedModule,
Search,
ButtonModule,
TooltipModule,
Url,
TagModule,
OverviewProjectTable,
],
providers: [TagService, DatePipe]
})
export class TagComponent extends AbstractOverviewComponent<OverviewGroup> {

constructor(private readonly tagService: TagService, layoutService: LayoutService) {
super(layoutService);
}

loadOverviews(event?: TableLazyLoadEvent): void {
this.tagService.getOverviews(event)
.subscribe(data => this.page = data);
}

override initChart(): void {
this.tagService.getTopGroups(this.topN)
.subscribe(data => {
if (data) {
const documentStyle = Charts.documentStyle();
this.topN = data.names?.length;

this.chartData = {
labels: data.names,
datasets: [
{
label: "Projects",
data: data.counts,
backgroundColor: documentStyle,
borderColor: documentStyle
}
]
};

Object.assign(this.chartOptions.plugins.title, {'text': `Top ${this.topN} Tags`});
}
});
}
}
24 changes: 24 additions & 0 deletions naikan-client/src/app/overview/tag/tag.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import {HttpClient} from '@angular/common/http';
import {Injectable} from '@angular/core';
import {Observable} from 'rxjs';
import {Page, Pageables} from '../../shared';
import {OverviewTopGroups} from '../overview-top-groups';
import {OverviewGroup} from "../overview";
import {TableLazyLoadEvent} from "primeng/table";

const endpoint = 'overview/tags';

@Injectable()
export class TagService {

constructor(private readonly http: HttpClient) {
}

getOverviews(event?: TableLazyLoadEvent): Observable<Page<OverviewGroup>> {
return this.http.get<Page<OverviewGroup>>(`/${endpoint}`, {params: Pageables.toPageRequestHttpParams(event)});
}

getTopGroups(topN: number): Observable<OverviewTopGroups> {
return this.http.get<OverviewTopGroups>(`/${endpoint}/top/${topN}`);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.enofex.naikan.overview.tag;

import com.enofex.naikan.Filterable;
import com.enofex.naikan.overview.OverviewGroup;
import com.enofex.naikan.overview.OverviewTopGroups;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

public interface OverviewTagRepository {

Page<OverviewGroup> findAll(Filterable filterable, Pageable pageable);

OverviewTopGroups findTopTags(long topN);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.enofex.naikan.overview.tag;

import com.enofex.naikan.Filterable;
import com.enofex.naikan.overview.OverviewGroup;
import com.enofex.naikan.overview.OverviewTopGroups;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

public interface OverviewTagService {

Page<OverviewGroup> findAll(Filterable filterable, Pageable pageable);

OverviewTopGroups findTopTags(long topN);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.enofex.naikan.overview.tag.support;

import static org.springframework.data.mongodb.core.aggregation.Aggregation.group;
import static org.springframework.data.mongodb.core.aggregation.Aggregation.limit;
import static org.springframework.data.mongodb.core.aggregation.Aggregation.project;
import static org.springframework.data.mongodb.core.aggregation.Aggregation.sort;
import static org.springframework.data.mongodb.core.aggregation.Aggregation.unwind;

import com.enofex.naikan.Filterable;
import com.enofex.naikan.FilterableCriteriaBuilder;
import com.enofex.naikan.overview.OverviewGroup;
import com.enofex.naikan.overview.OverviewRepository;
import com.enofex.naikan.overview.OverviewTopGroups;
import com.enofex.naikan.overview.tag.OverviewTagRepository;
import java.util.List;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationOperation;
import org.springframework.stereotype.Repository;

@Repository
class OverviewTagMongoRepository extends OverviewRepository implements
OverviewTagRepository {

OverviewTagMongoRepository(MongoTemplate template) {
super(template);
}

@Override
public Page<OverviewGroup> findAll(Filterable filterable, Pageable pageable) {
FilterableCriteriaBuilder builder = new FilterableCriteriaBuilder(filterable);
List<AggregationOperation> operations = defaultOverviewGroupOperations(
"tags",
List.of("tags"),
builder.toSearch(List.of("group")),
builder.toFilters());

return findAll(OverviewGroup.class, operations, pageable);
}

@Override
public OverviewTopGroups findTopTags(long topN) {
Aggregation aggregation = Aggregation.newAggregation(
unwind("tags"),
group("tags").count().as("count"),
sort(Direction.DESC, "count").and(Direction.ASC, "_id"),
limit(topN),
group()
.push("_id").as("names")
.push("count").as("counts"),
project().andExclude("_id")
.and("names").as("names")
.and("counts").as("counts")
);

return template()
.aggregate(aggregation, collectionName(), OverviewTopGroups.class)
.getUniqueMappedResult();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.enofex.naikan.overview.tag.support;

import com.enofex.naikan.Filterable;
import com.enofex.naikan.overview.OverviewGroup;
import com.enofex.naikan.overview.OverviewTopGroups;
import com.enofex.naikan.overview.tag.OverviewTagRepository;
import com.enofex.naikan.overview.tag.OverviewTagService;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional
class OverviewTagServiceHandler implements OverviewTagService {

private final OverviewTagRepository overviewTagRepository;

OverviewTagServiceHandler(OverviewTagRepository overviewTagRepository) {
this.overviewTagRepository = overviewTagRepository;
}

@Override
public Page<OverviewGroup> findAll(Filterable filterable, Pageable pageable) {
return this.overviewTagRepository.findAll(filterable, pageable);
}

@Override
public OverviewTopGroups findTopTags(long topN) {
return this.overviewTagRepository.findTopTags(topN);
}

}
Loading

0 comments on commit b27d042

Please sign in to comment.