only emit when things change (#2428)

* only emit when things change

* remove log

* remove test call

* more efficient, thanks BluJ

* point free
This commit is contained in:
Matt Hill
2023-09-29 14:36:40 -06:00
committed by GitHub
parent 5e3412d735
commit 1e6f583431
9 changed files with 127 additions and 72 deletions

View File

@@ -37,6 +37,7 @@
"cbor-web": "^8.1.0",
"core-js": "^3.21.1",
"dompurify": "^2.3.6",
"fast-deep-equal": "^3.1.3",
"fast-json-patch": "^3.1.1",
"fuse.js": "^6.4.6",
"jose": "^4.9.0",
@@ -7512,6 +7513,20 @@
"clone": "^1.0.2"
}
},
"node_modules/define-data-property": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.0.tgz",
"integrity": "sha512-UzGwzcjyv3OtAvolTj1GoyNYzfFR+iqbGjcnBEENZVCpM4/Ng1yhGNvS3lR/xDS74Tb2wGG9WzNSNIOS9UVb2g==",
"dev": true,
"dependencies": {
"get-intrinsic": "^1.2.1",
"gopd": "^1.0.1",
"has-property-descriptors": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/define-lazy-prop": {
"version": "2.0.0",
"devOptional": true,
@@ -7521,10 +7536,12 @@
}
},
"node_modules/define-properties": {
"version": "1.1.4",
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
"integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
"dev": true,
"license": "MIT",
"dependencies": {
"define-data-property": "^1.0.1",
"has-property-descriptors": "^1.0.0",
"object-keys": "^1.1.1"
},
@@ -8208,7 +8225,8 @@
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"license": "MIT"
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
},
"node_modules/fast-glob": {
"version": "3.2.12",
@@ -8560,12 +8578,14 @@
}
},
"node_modules/get-intrinsic": {
"version": "1.1.3",
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz",
"integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==",
"dev": true,
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.1",
"has": "^1.0.3",
"has-proto": "^1.0.1",
"has-symbols": "^1.0.3"
},
"funding": {
@@ -8704,6 +8724,18 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/gopd": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
"integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
"dev": true,
"dependencies": {
"get-intrinsic": "^1.1.3"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/graceful-fs": {
"version": "4.2.10",
"devOptional": true,
@@ -8758,6 +8790,18 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz",
"integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==",
"dev": true,
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-symbols": {
"version": "1.0.3",
"dev": true,

View File

@@ -62,6 +62,7 @@
"cbor-web": "^8.1.0",
"core-js": "^3.21.1",
"dompurify": "^2.3.6",
"fast-deep-equal": "^3.1.3",
"fast-json-patch": "^3.1.1",
"fuse.js": "^6.4.6",
"jose": "^4.9.0",

View File

@@ -27,7 +27,7 @@
sizeMd="6"
>
<app-list-pkg
*ngIf="pkg | packageInfo | async as info"
*ngIf="pkg.manifest.id | packageInfo | async as info"
[pkg]="info"
></app-list-pkg>
</ion-col>

View File

@@ -1,10 +1,7 @@
import { Pipe, PipeTransform } from '@angular/core'
import { Observable, combineLatest } from 'rxjs'
import { filter, map, startWith } from 'rxjs/operators'
import {
DataModel,
PackageDataEntry,
} from 'src/app/services/patch-db/data-model'
import { Observable, combineLatest, firstValueFrom } from 'rxjs'
import { map } from 'rxjs/operators'
import { DataModel } from 'src/app/services/patch-db/data-model'
import { getPackageInfo, PkgInfo } from '../../../util/get-package-info'
import { PatchDB } from 'patch-db-client'
import { DepErrorService } from 'src/app/services/dep-error.service'
@@ -18,12 +15,10 @@ export class PackageInfoPipe implements PipeTransform {
private readonly depErrorService: DepErrorService,
) {}
transform(pkg: PackageDataEntry): Observable<PkgInfo> {
transform(pkgId: string): Observable<PkgInfo> {
return combineLatest([
this.patch
.watch$('package-data', pkg.manifest.id)
.pipe(filter(Boolean), startWith(pkg)),
this.depErrorService.depErrors$,
this.patch.watch$('package-data', pkgId),
this.depErrorService.getPkgDepErrors$(pkgId),
]).pipe(map(([pkg, depErrors]) => getPackageInfo(pkg, depErrors)))
}
}

View File

@@ -21,7 +21,7 @@ import { DependentInfo } from 'src/app/types/dependent-info'
import {
DepErrorService,
DependencyErrorType,
PackageDependencyErrors,
PkgDependencyErrors,
} from 'src/app/services/dep-error.service'
import { combineLatest } from 'rxjs'
@@ -50,15 +50,14 @@ export class AppShowPage {
private readonly pkgId = getPkgId(this.route)
readonly pkgPlus$ = combineLatest([
this.patch.watch$('package-data'),
this.depErrorService.depErrors$,
this.patch.watch$('package-data', this.pkgId),
this.depErrorService.getPkgDepErrors$(this.pkgId),
]).pipe(
tap(([pkgs, _]) => {
tap(([pkg, _]) => {
// if package disappears, navigate to list page
if (!pkgs[this.pkgId]) this.navCtrl.navigateRoot('/services')
if (!pkg) this.navCtrl.navigateRoot('/services')
}),
map(([pkgs, depErrors]) => {
const pkg = pkgs[this.pkgId]
map(([pkg, depErrors]) => {
return {
pkg,
dependencies: this.getDepInfo(pkg, depErrors),
@@ -93,7 +92,7 @@ export class AppShowPage {
private getDepInfo(
pkg: PackageDataEntry,
depErrors: PackageDependencyErrors,
depErrors: PkgDependencyErrors,
): DependencyInfo[] {
const pkgInstalled = pkg.installed
@@ -107,7 +106,7 @@ export class AppShowPage {
private getDepValues(
pkgInstalled: InstalledPackageDataEntry,
depId: string,
depErrors: PackageDependencyErrors,
depErrors: PkgDependencyErrors,
): DependencyInfo {
const { errorText, fixText, fixAction } = this.getDepErrors(
pkgInstalled,
@@ -134,10 +133,10 @@ export class AppShowPage {
private getDepErrors(
pkgInstalled: InstalledPackageDataEntry,
depId: string,
depErrors: PackageDependencyErrors,
depErrors: PkgDependencyErrors,
) {
const pkgManifest = pkgInstalled.manifest
const depError = depErrors[pkgInstalled.manifest.id][depId]
const depError = depErrors[depId]
let errorText: string | null = null
let fixText: string | null = null

View File

@@ -7,6 +7,8 @@ import {
} from 'src/app/services/patch-db/data-model'
import { PrimaryStatus } from 'src/app/services/pkg-status-rendering.service'
import { getPackageInfo, PkgInfo } from '../../../../util/get-package-info'
import { combineLatest } from 'rxjs'
import { DepErrorService } from 'src/app/services/dep-error.service'
@Component({
selector: 'widget-health',
@@ -23,31 +25,32 @@ export class HealthComponent {
'Transitioning',
] as const
readonly data$ = inject(PatchDB<DataModel>)
.watch$('package-data')
.pipe(
map(data => {
const pkgs = Object.values<PackageDataEntry>(data).map(
pkg => getPackageInfo(pkg, {}), // @TODO hack because not currently using widget
)
const result = this.labels.reduce<Record<string, number>>(
(acc, label) => ({
...acc,
[label]: this.getCount(label, pkgs),
}),
{},
)
readonly data$ = combineLatest([
inject(PatchDB<DataModel>).watch$('package-data'),
inject(DepErrorService).depErrors$,
]).pipe(
map(([data, depErrors]) => {
const pkgs = Object.values<PackageDataEntry>(data).map(pkg =>
getPackageInfo(pkg, depErrors[pkg.manifest.id]),
)
const result = this.labels.reduce<Record<string, number>>(
(acc, label) => ({
...acc,
[label]: this.getCount(label, pkgs),
}),
{},
)
result['Healthy'] =
pkgs.length -
result['Error'] -
result['Needs Attention'] -
result['Stopped'] -
result['Transitioning']
result['Healthy'] =
pkgs.length -
result['Error'] -
result['Needs Attention'] -
result['Stopped'] -
result['Transitioning']
return this.labels.map(label => result[label])
}),
)
return this.labels.map(label => result[label])
}),
)
private getCount(label: string, pkgs: PkgInfo[]): number {
switch (label) {

View File

@@ -1,6 +1,14 @@
import { Injectable } from '@angular/core'
import { Emver } from '@start9labs/shared'
import { map, shareReplay } from 'rxjs/operators'
import {
distinctUntilChanged,
filter,
map,
pairwise,
shareReplay,
startWith,
tap,
} from 'rxjs/operators'
import { PatchDB } from 'patch-db-client'
import {
DataModel,
@@ -9,9 +17,10 @@ import {
InstalledPackageDataEntry,
PackageMainStatus,
} from './patch-db/data-model'
import * as deepEqual from 'fast-deep-equal'
export type PackageDependencyErrors = Record<string, DependencyErrors>
export type DependencyErrors = Record<string, DependencyError | null>
export type AllDependencyErrors = Record<string, PkgDependencyErrors>
export type PkgDependencyErrors = Record<string, DependencyError | null>
@Injectable({
providedIn: 'root',
@@ -26,13 +35,14 @@ export class DepErrorService {
}))
.sort((a, b) => (b.depth > a.depth ? -1 : 1))
.reduce(
(errors, { id }): PackageDependencyErrors => ({
(errors, { id }): AllDependencyErrors => ({
...errors,
[id]: this.getDepErrors(pkgs, id, errors),
}),
{} as PackageDependencyErrors,
{} as AllDependencyErrors,
),
),
distinctUntilChanged(deepEqual),
shareReplay(1),
)
@@ -41,21 +51,28 @@ export class DepErrorService {
private readonly patch: PatchDB<DataModel>,
) {}
getPkgDepErrors$(pkgId: string) {
return this.depErrors$.pipe(
map(depErrors => depErrors[pkgId]),
distinctUntilChanged(deepEqual),
)
}
private getDepErrors(
pkgs: DataModel['package-data'],
pkgId: string,
outerErrors: PackageDependencyErrors,
): DependencyErrors {
outerErrors: AllDependencyErrors,
): PkgDependencyErrors {
const pkgInstalled = pkgs[pkgId].installed
if (!pkgInstalled) return {}
return currentDeps(pkgs, pkgId).reduce(
(innerErrors, depId): DependencyErrors => ({
(innerErrors, depId): PkgDependencyErrors => ({
...innerErrors,
[depId]: this.getDepError(pkgs, pkgInstalled, depId, outerErrors),
}),
{} as DependencyErrors,
{} as PkgDependencyErrors,
)
}
@@ -63,7 +80,7 @@ export class DepErrorService {
pkgs: DataModel['package-data'],
pkgInstalled: InstalledPackageDataEntry,
depId: string,
outerErrors: PackageDependencyErrors,
outerErrors: AllDependencyErrors,
): DependencyError | null {
const depInstalled = pkgs[depId]?.installed

View File

@@ -1,13 +1,12 @@
import { isEmptyObject } from '@start9labs/shared'
import {
InstalledPackageDataEntry,
MainStatusStarting,
PackageDataEntry,
PackageMainStatus,
PackageState,
Status,
} from 'src/app/services/patch-db/data-model'
import { PackageDependencyErrors } from './dep-error.service'
import { PkgDependencyErrors } from './dep-error.service'
export interface PackageStatus {
primary: PrimaryStatus
@@ -17,7 +16,7 @@ export interface PackageStatus {
export function renderPkgStatus(
pkg: PackageDataEntry,
depErrors: PackageDependencyErrors,
depErrors: PkgDependencyErrors,
): PackageStatus {
let primary: PrimaryStatus
let dependency: DependencyStatus | null = null
@@ -25,7 +24,7 @@ export function renderPkgStatus(
if (pkg.state === PackageState.Installed && pkg.installed) {
primary = getPrimaryStatus(pkg.installed.status)
dependency = getDependencyStatus(pkg.installed, depErrors)
dependency = getDependencyStatus(depErrors)
health = getHealthStatus(
pkg.installed.status,
!isEmptyObject(pkg.manifest['health-checks']),
@@ -47,11 +46,8 @@ function getPrimaryStatus(status: Status): PrimaryStatus {
}
}
function getDependencyStatus(
pkg: InstalledPackageDataEntry,
depErrors: PackageDependencyErrors,
): DependencyStatus {
return Object.values(depErrors[pkg.manifest.id]).some(err => !!err)
function getDependencyStatus(depErrors: PkgDependencyErrors): DependencyStatus {
return Object.values(depErrors).some(err => !!err)
? DependencyStatus.Warning
: DependencyStatus.Satisfied
}

View File

@@ -10,11 +10,11 @@ import {
import { ProgressData } from 'src/app/types/progress-data'
import { Subscription } from 'rxjs'
import { packageLoadingProgress } from './package-loading-progress'
import { PackageDependencyErrors } from '../services/dep-error.service'
import { PkgDependencyErrors } from '../services/dep-error.service'
export function getPackageInfo(
entry: PackageDataEntry,
depErrors: PackageDependencyErrors,
depErrors: PkgDependencyErrors,
): PkgInfo {
const statuses = renderPkgStatus(entry, depErrors)
const primaryRendering = PrimaryRendering[statuses.primary]