Feature/ghostfolio in numbers (#175)
* Add Ghostfolio in numbers section * Update changelog
This commit is contained in:
parent
a2440fc067
commit
66c955ad6c
@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added a _Ghostfolio in Numbers_ section to the about page
|
||||||
|
|
||||||
## 1.18.0 - 16.06.2021
|
## 1.18.0 - 16.06.2021
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
@ -5,6 +5,8 @@ import { permissions } from '@ghostfolio/common/permissions';
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { JwtService } from '@nestjs/jwt';
|
import { JwtService } from '@nestjs/jwt';
|
||||||
import { Currency } from '@prisma/client';
|
import { Currency } from '@prisma/client';
|
||||||
|
import * as bent from 'bent';
|
||||||
|
import { subDays } from 'date-fns';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class InfoService {
|
export class InfoService {
|
||||||
@ -28,6 +30,10 @@ export class InfoService {
|
|||||||
globalPermissions.push(permissions.enableSocialLogin);
|
globalPermissions.push(permissions.enableSocialLogin);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.configurationService.get('ENABLE_FEATURE_STATISTICS')) {
|
||||||
|
globalPermissions.push(permissions.enableStatistics);
|
||||||
|
}
|
||||||
|
|
||||||
if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) {
|
if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) {
|
||||||
globalPermissions.push(permissions.enableSubscription);
|
globalPermissions.push(permissions.enableSubscription);
|
||||||
}
|
}
|
||||||
@ -37,7 +43,8 @@ export class InfoService {
|
|||||||
platforms,
|
platforms,
|
||||||
currencies: Object.values(Currency),
|
currencies: Object.values(Currency),
|
||||||
demoAuthToken: this.getDemoAuthToken(),
|
demoAuthToken: this.getDemoAuthToken(),
|
||||||
lastDataGathering: await this.getLastDataGathering()
|
lastDataGathering: await this.getLastDataGathering(),
|
||||||
|
statistics: await this.getStatistics()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,4 +61,67 @@ export class InfoService {
|
|||||||
|
|
||||||
return lastDataGathering?.value ? new Date(lastDataGathering.value) : null;
|
return lastDataGathering?.value ? new Date(lastDataGathering.value) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async getStatistics() {
|
||||||
|
if (!this.configurationService.get('ENABLE_FEATURE_STATISTICS')) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const activeUsers1d = await this.countActiveUsers(1);
|
||||||
|
const activeUsers30d = await this.countActiveUsers(30);
|
||||||
|
const gitHubStargazers = await this.countGitHubStargazers();
|
||||||
|
|
||||||
|
return {
|
||||||
|
activeUsers1d,
|
||||||
|
activeUsers30d,
|
||||||
|
gitHubStargazers
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private async countActiveUsers(aDays: number) {
|
||||||
|
return await this.prisma.user.count({
|
||||||
|
orderBy: {
|
||||||
|
Analytics: {
|
||||||
|
updatedAt: 'desc'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
where: {
|
||||||
|
AND: [
|
||||||
|
{
|
||||||
|
NOT: {
|
||||||
|
Analytics: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Analytics: {
|
||||||
|
updatedAt: {
|
||||||
|
gt: subDays(new Date(), aDays)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async countGitHubStargazers(): Promise<number> {
|
||||||
|
try {
|
||||||
|
const get = bent(
|
||||||
|
`https://api.github.com/repos/ghostfolio/ghostfolio`,
|
||||||
|
'GET',
|
||||||
|
'json',
|
||||||
|
200,
|
||||||
|
{
|
||||||
|
'User-Agent': 'request'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const { stargazers_count } = await get();
|
||||||
|
return stargazers_count;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ export class ConfigurationService {
|
|||||||
ENABLE_FEATURE_CUSTOM_SYMBOLS: bool({ default: false }),
|
ENABLE_FEATURE_CUSTOM_SYMBOLS: bool({ default: false }),
|
||||||
ENABLE_FEATURE_FEAR_AND_GREED_INDEX: bool({ default: false }),
|
ENABLE_FEATURE_FEAR_AND_GREED_INDEX: bool({ default: false }),
|
||||||
ENABLE_FEATURE_SOCIAL_LOGIN: bool({ default: false }),
|
ENABLE_FEATURE_SOCIAL_LOGIN: bool({ default: false }),
|
||||||
|
ENABLE_FEATURE_STATISTICS: bool({ default: false }),
|
||||||
ENABLE_FEATURE_SUBSCRIPTION: bool({ default: false }),
|
ENABLE_FEATURE_SUBSCRIPTION: bool({ default: false }),
|
||||||
GOOGLE_CLIENT_ID: str({ default: 'dummyClientId' }),
|
GOOGLE_CLIENT_ID: str({ default: 'dummyClientId' }),
|
||||||
GOOGLE_SECRET: str({ default: 'dummySecret' }),
|
GOOGLE_SECRET: str({ default: 'dummySecret' }),
|
||||||
|
@ -8,6 +8,7 @@ export interface Environment extends CleanedEnvAccessors {
|
|||||||
ENABLE_FEATURE_CUSTOM_SYMBOLS: boolean;
|
ENABLE_FEATURE_CUSTOM_SYMBOLS: boolean;
|
||||||
ENABLE_FEATURE_FEAR_AND_GREED_INDEX: boolean;
|
ENABLE_FEATURE_FEAR_AND_GREED_INDEX: boolean;
|
||||||
ENABLE_FEATURE_SOCIAL_LOGIN: boolean;
|
ENABLE_FEATURE_SOCIAL_LOGIN: boolean;
|
||||||
|
ENABLE_FEATURE_STATISTICS: boolean;
|
||||||
ENABLE_FEATURE_SUBSCRIPTION: boolean;
|
ENABLE_FEATURE_SUBSCRIPTION: boolean;
|
||||||
GOOGLE_CLIENT_ID: string;
|
GOOGLE_CLIENT_ID: string;
|
||||||
GOOGLE_SECRET: string;
|
GOOGLE_SECRET: string;
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
import { ChangeDetectorRef, Component, OnInit } from '@angular/core';
|
import { ChangeDetectorRef, Component, OnInit } from '@angular/core';
|
||||||
|
import { DataService } from '@ghostfolio/client/services/data.service';
|
||||||
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
||||||
import { baseCurrency } from '@ghostfolio/common/config';
|
import { baseCurrency } from '@ghostfolio/common/config';
|
||||||
import { User } from '@ghostfolio/common/interfaces';
|
import { User } from '@ghostfolio/common/interfaces';
|
||||||
|
import { Statistics } from '@ghostfolio/common/interfaces/statistics.interface';
|
||||||
|
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
||||||
import { Subject } from 'rxjs';
|
import { Subject } from 'rxjs';
|
||||||
import { takeUntil } from 'rxjs/operators';
|
import { takeUntil } from 'rxjs/operators';
|
||||||
|
|
||||||
@ -14,8 +17,10 @@ import { environment } from '../../../environments/environment';
|
|||||||
})
|
})
|
||||||
export class AboutPageComponent implements OnInit {
|
export class AboutPageComponent implements OnInit {
|
||||||
public baseCurrency = baseCurrency;
|
public baseCurrency = baseCurrency;
|
||||||
|
public hasPermissionForStatistics: boolean;
|
||||||
public isLoggedIn: boolean;
|
public isLoggedIn: boolean;
|
||||||
public lastPublish = environment.lastPublish;
|
public lastPublish = environment.lastPublish;
|
||||||
|
public statistics: Statistics;
|
||||||
public user: User;
|
public user: User;
|
||||||
public version = environment.version;
|
public version = environment.version;
|
||||||
|
|
||||||
@ -26,6 +31,7 @@ export class AboutPageComponent implements OnInit {
|
|||||||
*/
|
*/
|
||||||
public constructor(
|
public constructor(
|
||||||
private changeDetectorRef: ChangeDetectorRef,
|
private changeDetectorRef: ChangeDetectorRef,
|
||||||
|
private dataService: DataService,
|
||||||
private userService: UserService
|
private userService: UserService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@ -33,6 +39,19 @@ export class AboutPageComponent implements OnInit {
|
|||||||
* Initializes the controller
|
* Initializes the controller
|
||||||
*/
|
*/
|
||||||
public ngOnInit() {
|
public ngOnInit() {
|
||||||
|
this.dataService
|
||||||
|
.fetchInfo()
|
||||||
|
.subscribe(({ globalPermissions, statistics }) => {
|
||||||
|
this.hasPermissionForStatistics = hasPermission(
|
||||||
|
globalPermissions,
|
||||||
|
permissions.enableStatistics
|
||||||
|
);
|
||||||
|
|
||||||
|
this.statistics = statistics;
|
||||||
|
|
||||||
|
this.changeDetectorRef.markForCheck();
|
||||||
|
});
|
||||||
|
|
||||||
this.userService.stateChanged
|
this.userService.stateChanged
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
.subscribe((state) => {
|
.subscribe((state) => {
|
||||||
|
@ -60,6 +60,40 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="hasPermissionForStatistics" class="mb-5 row">
|
||||||
|
<div class="col">
|
||||||
|
<h3 class="mb-3 text-center" i18n>Ghostfolio in Numbers</h3>
|
||||||
|
<mat-card class="mb-3">
|
||||||
|
<mat-card-content>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-12 col-md-4">
|
||||||
|
<h3 class="mb-0" [hidden]="!statistics?.activeUsers1d">
|
||||||
|
{{ statistics?.activeUsers1d ?? '-' }}
|
||||||
|
</h3>
|
||||||
|
<div class="h6 mb-0">
|
||||||
|
Active Users <small class="text-muted">(Last 24 hours)</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-12 col-md-4">
|
||||||
|
<h3 class="mb-0" [hidden]="!statistics?.activeUsers30d">
|
||||||
|
{{ statistics?.activeUsers30d ?? '-' }}
|
||||||
|
</h3>
|
||||||
|
<div class="h6 m-b0">
|
||||||
|
Active Users <small class="text-muted">(Last 30 days)</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-12 col-md-4">
|
||||||
|
<h3 class="mb-0" [hidden]="!statistics?.gitHubStargazers">
|
||||||
|
{{ statistics?.gitHubStargazers ?? '-' }}
|
||||||
|
</h3>
|
||||||
|
<div class="h6 mb-0">Stars on GitHub</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="mb-5 row">
|
<div class="mb-5 row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<h3 class="mb-3 text-center" i18n>Changelog</h3>
|
<h3 class="mb-3 text-center" i18n>Changelog</h3>
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import { Currency } from '@prisma/client';
|
import { Currency } from '@prisma/client';
|
||||||
|
|
||||||
|
import { Statistics } from './statistics.interface';
|
||||||
|
|
||||||
export interface InfoItem {
|
export interface InfoItem {
|
||||||
currencies: Currency[];
|
currencies: Currency[];
|
||||||
demoAuthToken: string;
|
demoAuthToken: string;
|
||||||
@ -10,4 +12,5 @@ export interface InfoItem {
|
|||||||
type: string;
|
type: string;
|
||||||
};
|
};
|
||||||
platforms: { id: string; name: string }[];
|
platforms: { id: string; name: string }[];
|
||||||
|
statistics: Statistics;
|
||||||
}
|
}
|
||||||
|
5
libs/common/src/lib/interfaces/statistics.interface.ts
Normal file
5
libs/common/src/lib/interfaces/statistics.interface.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export interface Statistics {
|
||||||
|
activeUsers1d: number;
|
||||||
|
activeUsers30d: number;
|
||||||
|
gitHubStargazers: number;
|
||||||
|
}
|
@ -15,6 +15,7 @@ export const permissions = {
|
|||||||
deleteOrder: 'deleteOrder',
|
deleteOrder: 'deleteOrder',
|
||||||
deleteUser: 'deleteUser',
|
deleteUser: 'deleteUser',
|
||||||
enableSocialLogin: 'enableSocialLogin',
|
enableSocialLogin: 'enableSocialLogin',
|
||||||
|
enableStatistics: 'enableStatistics',
|
||||||
enableSubscription: 'enableSubscription',
|
enableSubscription: 'enableSubscription',
|
||||||
readForeignPortfolio: 'readForeignPortfolio',
|
readForeignPortfolio: 'readForeignPortfolio',
|
||||||
updateAccount: 'updateAccount',
|
updateAccount: 'updateAccount',
|
||||||
|
Loading…
x
Reference in New Issue
Block a user