diff --git a/src/app/app-config.service.spec.ts b/src/app/app-config.service.spec.ts
index e74b77dcfd..8c28356bef 100644
--- a/src/app/app-config.service.spec.ts
+++ b/src/app/app-config.service.spec.ts
@@ -50,6 +50,12 @@ const appConfig: AppConfigInterface = {
thumbnailFetchLimitPerPage: 500,
maxFileUploadSizeInMb: "16mb",
maxDirectDownloadSize: 5000000000,
+ metadataFloatFormatEnabled: true,
+ metadataFloatFormat: {
+ significantDigits: 3,
+ minCutoff: 0.001,
+ maxCutoff: 1000,
+ },
metadataPreviewEnabled: true,
metadataStructure: "",
multipleDownloadAction: "http://localhost:3012/zip",
diff --git a/src/app/app-config.service.ts b/src/app/app-config.service.ts
index 34645eba49..f066ba80d8 100644
--- a/src/app/app-config.service.ts
+++ b/src/app/app-config.service.ts
@@ -66,6 +66,12 @@ export class MainMenuConfiguration {
authenticatedUser: MainMenuOptions;
}
+export class MetadataFloatFormat {
+ significantDigits: number;
+ minCutoff: number; // using scientific notation below this cutoff
+ maxCutoff: number; // using scientific notation above this cutoff
+}
+
export interface AppConfigInterface {
allowConfigOverrides?: boolean;
skipSciCatLoginPageEnabled?: boolean;
@@ -111,6 +117,8 @@ export interface AppConfigInterface {
maxDirectDownloadSize: number | null;
metadataPreviewEnabled: boolean;
metadataStructure: string;
+ metadataFloatFormat?: MetadataFloatFormat;
+ metadataFloatFormatEnabled?: boolean;
multipleDownloadAction: string | null;
multipleDownloadEnabled: boolean;
multipleDownloadUseAuthToken: boolean;
@@ -247,6 +255,14 @@ export class AppConfigService {
config.dateFormat = "yyyy-MM-dd HH:mm";
}
+ if (config.metadataFloatFormatEnabled && !config.metadataFloatFormat) {
+ config.metadataFloatFormat = {
+ significantDigits: 3,
+ minCutoff: 0.001,
+ maxCutoff: 1000,
+ };
+ }
+
this.appConfig = config;
}
diff --git a/src/app/datasets/dataset-table/dataset-table.component.ts b/src/app/datasets/dataset-table/dataset-table.component.ts
index f253cd3d3e..d5d250b895 100644
--- a/src/app/datasets/dataset-table/dataset-table.component.ts
+++ b/src/app/datasets/dataset-table/dataset-table.component.ts
@@ -67,6 +67,7 @@ import { FileSizePipe } from "shared/pipes/filesize.pipe";
import { actionMenu } from "shared/modules/dynamic-material-table/utilizes/default-table-settings";
import { TableConfigService } from "shared/services/table-config.service";
import { selectInstruments } from "state-management/selectors/instruments.selectors";
+import { FormatNumberPipe } from "shared/pipes/format-number.pipe";
export interface SortChangeEvent {
active: string;
@@ -162,6 +163,7 @@ export class DatasetTableComponent implements OnInit, OnDestroy {
private datePipe: DatePipe,
private fileSize: FileSizePipe,
private tableConfigService: TableConfigService,
+ private formatNumberPipe: FormatNumberPipe,
) {}
private getInstrumentName(row: OutputDatasetObsoleteDto): string {
@@ -488,6 +490,19 @@ export class DatasetTableComponent implements OnInit, OnDestroy {
this.getInstrumentName(row);
}
+ if (column.name.startsWith("scientificMetadata.")) {
+ convertedColumn.customRender = (col, row) => {
+ return String(
+ this.formatNumberPipe.transform(lodashGet(row, col.name)),
+ );
+ };
+ convertedColumn.toExport = (row) => {
+ return String(
+ this.formatNumberPipe.transform(lodashGet(row, column.name)),
+ );
+ };
+ }
+
return convertedColumn;
});
}
diff --git a/src/app/shared/modules/scientific-metadata-tree/base-classes/metadata-input-base.spec.ts b/src/app/shared/modules/scientific-metadata-tree/base-classes/metadata-input-base.spec.ts
index bfc57d24bf..7488d6d6f8 100644
--- a/src/app/shared/modules/scientific-metadata-tree/base-classes/metadata-input-base.spec.ts
+++ b/src/app/shared/modules/scientific-metadata-tree/base-classes/metadata-input-base.spec.ts
@@ -6,6 +6,8 @@ import { MetadataInputComponent } from "../metadata-input/metadata-input.compone
import { FormatNumberPipe } from "shared/pipes/format-number.pipe";
import { ScientificMetadataTreeModule } from "../scientific-metadata-tree.module";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
+import { AppConfigService } from "app-config.service";
+import { provideHttpClient } from "@angular/common/http";
describe("MetadataInputBase", () => {
let component: MetadataInputComponent;
@@ -15,11 +17,25 @@ describe("MetadataInputBase", () => {
TestBed.configureTestingModule({
declarations: [MetadataInputComponent],
imports: [ScientificMetadataTreeModule, BrowserAnimationsModule],
- providers: [FormBuilder, FormatNumberPipe],
+ providers: [
+ FormBuilder,
+ FormatNumberPipe,
+ AppConfigService,
+ provideHttpClient(),
+ ],
}).compileComponents();
}));
beforeEach(() => {
+ const appConfigService = TestBed.inject(AppConfigService);
+ (appConfigService as any).appConfig = {
+ metadataFloatFormatEnabled: true,
+ metadataFloatFormat: {
+ significantDigits: 3,
+ minCutoff: 0.001,
+ maxCutoff: 1000,
+ },
+ };
fixture = TestBed.createComponent(MetadataInputComponent);
component = fixture.componentInstance;
const data = new FlatNodeEdit();
diff --git a/src/app/shared/modules/scientific-metadata-tree/base-classes/tree-base.spec.ts b/src/app/shared/modules/scientific-metadata-tree/base-classes/tree-base.spec.ts
index 70a4eaa1f7..2a88866314 100644
--- a/src/app/shared/modules/scientific-metadata-tree/base-classes/tree-base.spec.ts
+++ b/src/app/shared/modules/scientific-metadata-tree/base-classes/tree-base.spec.ts
@@ -7,6 +7,8 @@ import { FlatNode, TreeNode } from "../base-classes/tree-base";
import { TreeEditComponent } from "../tree-edit/tree-edit.component";
import { ScientificMetadataTreeModule } from "../scientific-metadata-tree.module";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
+import { AppConfigService } from "app-config.service";
+import { provideHttpClient } from "@angular/common/http";
describe("TreeBaseComponent", () => {
let component: TreeEditComponent;
@@ -16,11 +18,26 @@ describe("TreeBaseComponent", () => {
TestBed.configureTestingModule({
declarations: [TreeEditComponent],
imports: [ScientificMetadataTreeModule, BrowserAnimationsModule],
- providers: [MatDialog, MatSnackBar, DatePipe],
+ providers: [
+ MatDialog,
+ MatSnackBar,
+ DatePipe,
+ AppConfigService,
+ provideHttpClient(),
+ ],
}).compileComponents();
}));
beforeEach(() => {
+ const appConfigService = TestBed.inject(AppConfigService);
+ (appConfigService as any).appConfig = {
+ metadataFloatFormatEnabled: true,
+ metadataFloatFormat: {
+ significantDigits: 3,
+ minCutoff: 0.001,
+ maxCutoff: 1000,
+ },
+ };
fixture = TestBed.createComponent(TreeEditComponent);
component = fixture.componentInstance;
component.metadata = {
diff --git a/src/app/shared/modules/scientific-metadata-tree/base-classes/tree-base.ts b/src/app/shared/modules/scientific-metadata-tree/base-classes/tree-base.ts
index 79df24ce87..5032d76f0c 100644
--- a/src/app/shared/modules/scientific-metadata-tree/base-classes/tree-base.ts
+++ b/src/app/shared/modules/scientific-metadata-tree/base-classes/tree-base.ts
@@ -9,6 +9,7 @@ import { FormatNumberPipe } from "shared/pipes/format-number.pipe";
import { PrettyUnitPipe } from "shared/pipes/pretty-unit.pipe";
import { DateTimeService } from "shared/services/date-time.service";
import { UnitsService } from "shared/services/units.service";
+import { AppConfigService } from "app-config.service";
export class TreeNode {
children: TreeNode[];
@@ -43,10 +44,10 @@ export class TreeBaseComponent {
prettyUnitPipe: PrettyUnitPipe;
unitsService: UnitsService;
dateTimeService: DateTimeService;
- constructor() {
+ constructor(protected configService: AppConfigService) {
this.unitsService = new UnitsService();
this.prettyUnitPipe = new PrettyUnitPipe(this.unitsService);
- this.formatNumberPipe = new FormatNumberPipe();
+ this.formatNumberPipe = new FormatNumberPipe(this.configService);
this.dateTimeService = new DateTimeService();
}
buildDataTree(obj: { [key: string]: any }, level: number): TreeNode[] {
diff --git a/src/app/shared/modules/scientific-metadata-tree/metadata-input/metadata-input.component.spec.ts b/src/app/shared/modules/scientific-metadata-tree/metadata-input/metadata-input.component.spec.ts
index ba1e3773bd..ec5bf73507 100644
--- a/src/app/shared/modules/scientific-metadata-tree/metadata-input/metadata-input.component.spec.ts
+++ b/src/app/shared/modules/scientific-metadata-tree/metadata-input/metadata-input.component.spec.ts
@@ -9,6 +9,8 @@ import { ScientificMetadataTreeModule } from "../scientific-metadata-tree.module
import { MetadataInputComponent } from "./metadata-input.component";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
+import { AppConfigService } from "app-config.service";
+import { provideHttpClient } from "@angular/common/http";
describe("MetadataInputComponent", () => {
let component: MetadataInputComponent;
@@ -18,11 +20,25 @@ describe("MetadataInputComponent", () => {
TestBed.configureTestingModule({
declarations: [MetadataInputComponent],
imports: [ScientificMetadataTreeModule, BrowserAnimationsModule],
- providers: [FormBuilder, FormatNumberPipe],
+ providers: [
+ FormBuilder,
+ FormatNumberPipe,
+ AppConfigService,
+ provideHttpClient(),
+ ],
}).compileComponents();
}));
beforeEach(() => {
+ const appConfigService = TestBed.inject(AppConfigService);
+ (appConfigService as any).appConfig = {
+ metadataFloatFormatEnabled: true,
+ metadataFloatFormat: {
+ significantDigits: 3,
+ minCutoff: 0.001,
+ maxCutoff: 1000,
+ },
+ };
fixture = TestBed.createComponent(MetadataInputComponent);
component = fixture.componentInstance;
const data = new FlatNodeEdit();
@@ -76,7 +92,7 @@ describe("MetadataInputComponent", () => {
component.addCurrentMetadata(component.data);
expect(component.metadataForm.get("type").value).toEqual("quantity");
expect(component.metadataForm.get("key").value).toEqual("energy");
- expect(component.metadataForm.get("value").value).toEqual(3);
+ expect(component.metadataForm.get("value").value).toEqual("3");
expect(component.metadataForm.get("unit").value).toEqual("joule");
});
it("should set values in form control (number)", () => {
diff --git a/src/app/shared/modules/scientific-metadata-tree/tree-edit/tree-edit.component.spec.ts b/src/app/shared/modules/scientific-metadata-tree/tree-edit/tree-edit.component.spec.ts
index 857ea41827..d2532355f9 100644
--- a/src/app/shared/modules/scientific-metadata-tree/tree-edit/tree-edit.component.spec.ts
+++ b/src/app/shared/modules/scientific-metadata-tree/tree-edit/tree-edit.component.spec.ts
@@ -10,6 +10,8 @@ import { ScientificMetadataTreeModule } from "../scientific-metadata-tree.module
import { FlatNodeEdit, TreeEditComponent } from "./tree-edit.component";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
+import { AppConfigService } from "app-config.service";
+import { provideHttpClient } from "@angular/common/http";
describe("TreeEditComponent", () => {
let component: TreeEditComponent;
@@ -19,11 +21,26 @@ describe("TreeEditComponent", () => {
TestBed.configureTestingModule({
declarations: [TreeEditComponent],
imports: [ScientificMetadataTreeModule, BrowserAnimationsModule],
- providers: [MatDialog, MatSnackBar, DatePipe],
+ providers: [
+ MatDialog,
+ MatSnackBar,
+ DatePipe,
+ AppConfigService,
+ provideHttpClient(),
+ ],
}).compileComponents();
}));
beforeEach(() => {
+ const appConfigService = TestBed.inject(AppConfigService);
+ (appConfigService as any).appConfig = {
+ metadataFloatFormatEnabled: true,
+ metadataFloatFormat: {
+ significantDigits: 3,
+ minCutoff: 0.001,
+ maxCutoff: 1000,
+ },
+ };
fixture = TestBed.createComponent(TreeEditComponent);
component = fixture.componentInstance;
component.metadata = {
diff --git a/src/app/shared/modules/scientific-metadata-tree/tree-edit/tree-edit.component.ts b/src/app/shared/modules/scientific-metadata-tree/tree-edit/tree-edit.component.ts
index 525e962dfd..75dcfc3131 100644
--- a/src/app/shared/modules/scientific-metadata-tree/tree-edit/tree-edit.component.ts
+++ b/src/app/shared/modules/scientific-metadata-tree/tree-edit/tree-edit.component.ts
@@ -29,6 +29,7 @@ import { MatSnackBar } from "@angular/material/snack-bar";
import { DatePipe } from "@angular/common";
import { Type } from "../base-classes/metadata-input-base";
import { DateTime } from "luxon";
+import { AppConfigService } from "app-config.service";
export class FlatNodeEdit implements FlatNode {
key: string;
@@ -64,8 +65,9 @@ export class TreeEditComponent
public dialog: MatDialog,
private snackBar: MatSnackBar,
datePipe: DatePipe,
+ configService: AppConfigService,
) {
- super();
+ super(configService);
this.datePipe = datePipe;
this.treeFlattener = new MatTreeFlattener(
this.transformer,
diff --git a/src/app/shared/modules/scientific-metadata-tree/tree-view/tree-view.component.html b/src/app/shared/modules/scientific-metadata-tree/tree-view/tree-view.component.html
index a45e785506..cc38d73b17 100644
--- a/src/app/shared/modules/scientific-metadata-tree/tree-view/tree-view.component.html
+++ b/src/app/shared/modules/scientific-metadata-tree/tree-view/tree-view.component.html
@@ -38,7 +38,7 @@
- {{ getValueRepresentation(node) }}
+ {{ getValueRepresentation(node) | formatNumber }}
diff --git a/src/app/shared/modules/scientific-metadata-tree/tree-view/tree-view.component.spec.ts b/src/app/shared/modules/scientific-metadata-tree/tree-view/tree-view.component.spec.ts
index d0f99b062b..cb25aef150 100644
--- a/src/app/shared/modules/scientific-metadata-tree/tree-view/tree-view.component.spec.ts
+++ b/src/app/shared/modules/scientific-metadata-tree/tree-view/tree-view.component.spec.ts
@@ -3,23 +3,40 @@ import { waitForAsync, ComponentFixture, TestBed } from "@angular/core/testing";
import { FormatNumberPipe } from "shared/pipes/format-number.pipe";
import { PrettyUnitPipe } from "shared/pipes/pretty-unit.pipe";
import { ScientificMetadataTreeModule } from "../scientific-metadata-tree.module";
-
import { TreeViewComponent } from "./tree-view.component";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
+import { AppConfigService } from "app-config.service";
+import { provideHttpClient } from "@angular/common/http";
describe("TreeViewComponent", () => {
let component: TreeViewComponent;
let fixture: ComponentFixture;
beforeEach(waitForAsync(() => {
+ TestBed.resetTestingModule();
TestBed.configureTestingModule({
declarations: [TreeViewComponent],
imports: [ScientificMetadataTreeModule, BrowserAnimationsModule],
- providers: [DatePipe, PrettyUnitPipe, FormatNumberPipe],
+ providers: [
+ DatePipe,
+ PrettyUnitPipe,
+ FormatNumberPipe,
+ AppConfigService,
+ provideHttpClient(),
+ ],
}).compileComponents();
}));
beforeEach(() => {
+ const appConfigService = TestBed.inject(AppConfigService);
+ (appConfigService as any).appConfig = {
+ metadataFloatFormatEnabled: true,
+ metadataFloatFormat: {
+ significantDigits: 3,
+ minCutoff: 0.001,
+ maxCutoff: 1000,
+ },
+ };
fixture = TestBed.createComponent(TreeViewComponent);
component = fixture.componentInstance;
component.metadata = {
diff --git a/src/app/shared/modules/scientific-metadata-tree/tree-view/tree-view.component.ts b/src/app/shared/modules/scientific-metadata-tree/tree-view/tree-view.component.ts
index ab0c9e647c..c4712f0481 100644
--- a/src/app/shared/modules/scientific-metadata-tree/tree-view/tree-view.component.ts
+++ b/src/app/shared/modules/scientific-metadata-tree/tree-view/tree-view.component.ts
@@ -16,6 +16,7 @@ import {
TreeNode,
} from "shared/modules/scientific-metadata-tree/base-classes/tree-base";
import { DatePipe } from "@angular/common";
+import { AppConfigService } from "app-config.service";
@Component({
selector: "tree-view",
templateUrl: "./tree-view.component.html",
@@ -27,9 +28,11 @@ export class TreeViewComponent
implements OnInit, OnChanges
{
@Input() metadata: any;
- constructor(datePipe: DatePipe) {
- super();
- this.datePipe = datePipe;
+ constructor(
+ public datePipe: DatePipe,
+ configService: AppConfigService,
+ ) {
+ super(configService);
this.treeFlattener = new MatTreeFlattener(
this.transformer,
this.getLevel,
diff --git a/src/app/shared/modules/scientific-metadata/metadata-view/metadata-view.component.spec.ts b/src/app/shared/modules/scientific-metadata/metadata-view/metadata-view.component.spec.ts
index 657cfc1902..34d9eed8d3 100644
--- a/src/app/shared/modules/scientific-metadata/metadata-view/metadata-view.component.spec.ts
+++ b/src/app/shared/modules/scientific-metadata/metadata-view/metadata-view.component.spec.ts
@@ -8,6 +8,9 @@ import { ReplaceUnderscorePipe } from "shared/pipes/replace-underscore.pipe";
import { LinkyPipe } from "ngx-linky";
import { DatePipe, TitleCasePipe } from "@angular/common";
import { PrettyUnitPipe } from "shared/pipes/pretty-unit.pipe";
+import { AppConfigService } from "app-config.service";
+import { provideHttpClient } from "@angular/common/http";
+import { FormatNumberPipe } from "shared/pipes/format-number.pipe";
describe("MetadataViewComponent", () => {
let component: MetadataViewComponent;
@@ -23,12 +26,25 @@ describe("MetadataViewComponent", () => {
DatePipe,
LinkyPipe,
PrettyUnitPipe,
+ FormatNumberPipe,
+ AppConfigService,
+ provideHttpClient(),
],
declarations: [MetadataViewComponent],
}).compileComponents();
}));
beforeEach(() => {
+ const appConfigService = TestBed.inject(AppConfigService);
+ spyOn(appConfigService as any, "getConfig").and.returnValue({
+ metadataFloatFormatEnabled: true,
+ metadataFloatFormat: {
+ significantDigits: 3,
+ minCutoff: 0.001,
+ maxCutoff: 1000,
+ },
+ });
+
fixture = TestBed.createComponent(MetadataViewComponent);
component = fixture.componentInstance;
fixture.detectChanges();
@@ -53,6 +69,19 @@ describe("MetadataViewComponent", () => {
expect(metadataArray[0]["unit"]).toEqual("");
});
+ it("should round float value if float formatting enabled", () => {
+ const testMetadata = {
+ someMetadata: {
+ value: 12.39421321511,
+ unit: "m",
+ },
+ };
+ const metadataArray = component.createMetadataArray(testMetadata);
+
+ expect(metadataArray[0]["value"]).toEqual("12.4");
+ expect(metadataArray[0]["unit"]).toEqual("m");
+ });
+
it("should parse an untyped metadata object to an array", () => {
const testMetadata = {
untypedTestName: {
diff --git a/src/app/shared/modules/scientific-metadata/metadata-view/metadata-view.component.ts b/src/app/shared/modules/scientific-metadata/metadata-view/metadata-view.component.ts
index d080ea2841..ffdcef4475 100644
--- a/src/app/shared/modules/scientific-metadata/metadata-view/metadata-view.component.ts
+++ b/src/app/shared/modules/scientific-metadata/metadata-view/metadata-view.component.ts
@@ -23,6 +23,7 @@ import { DateTime } from "luxon";
import { MetadataTypes } from "../metadata-edit/metadata-edit.component";
import { actionMenu } from "shared/modules/dynamic-material-table/utilizes/default-table-settings";
import { TablePaginationMode } from "shared/modules/dynamic-material-table/models/table-pagination.model";
+import { FormatNumberPipe } from "shared/pipes/format-number.pipe";
@Component({
selector: "metadata-view",
@@ -179,6 +180,7 @@ export class MetadataViewComponent implements OnInit, OnChanges {
constructor(
private unitsService: UnitsService,
private datePipe: DatePipe,
+ private formatNumberPipe: FormatNumberPipe,
public linkyPipe: LinkyPipe,
public prettyUnit: PrettyUnitPipe,
) {}
@@ -195,9 +197,13 @@ export class MetadataViewComponent implements OnInit, OnChanges {
typeof metadata[key] === "object" &&
"value" in (metadata[key] as ScientificMetadata)
) {
+ const formattedValue = this.formatNumberPipe.transform(
+ metadata[key]["value"],
+ );
+
metadataObject = {
name: key,
- value: metadata[key]["value"],
+ value: formattedValue,
unit: metadata[key]["unit"],
human_name: humanReadableName,
type: metadata[key]["type"],
@@ -216,9 +222,11 @@ export class MetadataViewComponent implements OnInit, OnChanges {
? metadata[key]
: JSON.stringify(metadata[key]);
+ const formattedValue = this.formatNumberPipe.transform(metadataValue);
+
metadataObject = {
name: key,
- value: metadataValue,
+ value: formattedValue,
unit: "",
human_name: humanReadableName,
type: metadata[key]["type"],
diff --git a/src/app/shared/pipes/format-number.pipe.spec.ts b/src/app/shared/pipes/format-number.pipe.spec.ts
new file mode 100644
index 0000000000..c01b7e61de
--- /dev/null
+++ b/src/app/shared/pipes/format-number.pipe.spec.ts
@@ -0,0 +1,153 @@
+import { TestBed } from "@angular/core/testing";
+import { FormatNumberPipe } from "./format-number.pipe";
+import { AppConfigInterface, AppConfigService } from "app-config.service";
+
+describe("FormatNumberPipe", () => {
+ let mockConfigService: jasmine.SpyObj;
+
+ beforeEach(() => {
+ mockConfigService = jasmine.createSpyObj("AppConfigService", ["getConfig"]);
+
+ TestBed.configureTestingModule({
+ providers: [{ provide: AppConfigService, useValue: mockConfigService }],
+ });
+ });
+
+ function setConfig(
+ options?: Partial<{
+ enabled: boolean;
+ significantDigits: number;
+ minCutoff: number;
+ maxCutoff: number;
+ }>,
+ ) {
+ const mockConfig: Partial = {
+ metadataFloatFormatEnabled: options?.enabled ?? true,
+ metadataFloatFormat: {
+ significantDigits: options?.significantDigits ?? 3,
+ minCutoff: options?.minCutoff ?? 0.001,
+ maxCutoff: options?.maxCutoff ?? 1000,
+ },
+ };
+
+ mockConfigService.getConfig.and.returnValue(
+ mockConfig as AppConfigInterface,
+ );
+ }
+
+ describe("Legacy formatting", () => {
+ it("create an instance", () => {
+ setConfig({ enabled: false });
+ const pipe = new FormatNumberPipe(mockConfigService);
+ expect(pipe).toBeTruthy();
+ });
+
+ it("returns exponential number when number >= 1e5 ", () => {
+ setConfig({ enabled: false });
+ const pipe = new FormatNumberPipe(mockConfigService);
+ const nbr = 100000;
+ const formatted = pipe.transform(nbr);
+ expect(formatted.toString()).toEqual("1e+5");
+ });
+
+ it("returns exponential number when number <= 1e-5", () => {
+ setConfig({ enabled: false });
+ const pipe = new FormatNumberPipe(mockConfigService);
+ const nbr = 0.00001;
+ const formatted = pipe.transform(nbr);
+ expect(formatted.toString()).toEqual("1e-5");
+ });
+
+ it("returns number when 1e-5 <= number <= 1e5", () => {
+ setConfig({ enabled: false });
+ const pipe = new FormatNumberPipe(mockConfigService);
+ const nbr = 0.0001;
+ const formatted = pipe.transform(nbr);
+ expect(formatted).toEqual(String(nbr));
+ });
+
+ it("returns 'null' when number is null", () => {
+ setConfig({ enabled: false });
+ const pipe = new FormatNumberPipe(mockConfigService);
+ const nbr = null;
+ const formatted = pipe.transform(nbr);
+ expect(formatted).toEqual("null");
+ });
+
+ it("returns 'undefined' when number is undefined", () => {
+ setConfig({ enabled: false });
+ const pipe = new FormatNumberPipe(mockConfigService);
+ const nbr = undefined;
+ const formatted = pipe.transform(nbr);
+ expect(formatted).toEqual("undefined");
+ });
+
+ it("returns string when number is a string", () => {
+ setConfig({ enabled: false });
+ const pipe = new FormatNumberPipe(mockConfigService);
+ const nbr = "test";
+ const formatted = pipe.transform(nbr);
+ expect(formatted).toEqual("test");
+ });
+ });
+
+ describe("metadataFloatFormatEnabled is true", () => {
+ it("should return string as-is for non-number values", () => {
+ setConfig();
+ const pipe = new FormatNumberPipe(mockConfigService);
+ expect(pipe.transform("abc")).toBe("abc");
+ expect(pipe.transform("")).toBe("");
+ });
+
+ it("should return string representation for non-finite numbers", () => {
+ setConfig();
+ const pipe = new FormatNumberPipe(mockConfigService);
+ expect(pipe.transform(NaN)).toBe("NaN");
+ expect(pipe.transform(Infinity)).toBe("Infinity");
+ expect(pipe.transform(-Infinity)).toBe("-Infinity");
+ });
+
+ it("should return string representation for integers", () => {
+ setConfig();
+ const pipe = new FormatNumberPipe(mockConfigService);
+ expect(pipe.transform(42)).toBe("42");
+ expect(pipe.transform(-10)).toBe("-10");
+ });
+
+ it("should omit decimals when significant digits fit within the integer part", () => {
+ setConfig({ significantDigits: 2 });
+ const pipe = new FormatNumberPipe(mockConfigService);
+ expect(pipe.transform(12.3456)).toBe("12");
+ });
+
+ it("should use exponential notation for very small numbers", () => {
+ setConfig({ significantDigits: 3, minCutoff: 0.001 });
+ const pipe = new FormatNumberPipe(mockConfigService);
+ expect(pipe.transform(0.0000123)).toBe("1.23e-5");
+ });
+
+ it("should use exponential notation for very large numbers", () => {
+ setConfig({ significantDigits: 4, maxCutoff: 1e6 });
+ const pipe = new FormatNumberPipe(mockConfigService);
+ expect(pipe.transform(50004313.487)).toBe("5.000e+7");
+ });
+
+ it("should handle values just above minCutoff correctly", () => {
+ setConfig({ minCutoff: 0.001, significantDigits: 4 });
+ const pipe = new FormatNumberPipe(mockConfigService);
+ expect(pipe.transform(0.0023456)).toBe("0.002346");
+ });
+
+ it("should handle values just below maxCutoff correctly", () => {
+ setConfig({ maxCutoff: 1e6, significantDigits: 3 });
+ const pipe = new FormatNumberPipe(mockConfigService);
+ expect(pipe.transform(999999.99)).toBe("1.00e+6");
+ });
+
+ it("should respect significantDigits when using exponential notation", () => {
+ setConfig({ significantDigits: 5, minCutoff: 1e-3 });
+ const pipe = new FormatNumberPipe(mockConfigService);
+ expect(pipe.transform(0.0000123)).toBe("1.2300e-5");
+ });
+ });
+});
diff --git a/src/app/shared/pipes/format-number.pipe.spect.ts b/src/app/shared/pipes/format-number.pipe.spect.ts
deleted file mode 100644
index 12c478d039..0000000000
--- a/src/app/shared/pipes/format-number.pipe.spect.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-import { FormatNumberPipe } from "./format-number.pipe";
-
-describe("FormatNumberPipe", () => {
- it("create an instance", () => {
- const pipe = new FormatNumberPipe();
- expect(pipe).toBeTruthy();
- });
-
- it("returns exponential number when number >= 1e5 ", () => {
- const pipe = new FormatNumberPipe();
- const nbr = 100000;
- const formatted = pipe.transform(nbr);
- expect(formatted.toString()).toEqual("1e+5");
- });
-
- it("returns exponential number when number <= 1e-5", () => {
- const pipe = new FormatNumberPipe();
- const nbr = 0.00001;
- const formatted = pipe.transform(nbr);
- expect(formatted.toString()).toEqual("1e-5");
- });
- it("returns number when 1e-5 <= number <= 1e5", () => {
- const pipe = new FormatNumberPipe();
- const nbr = 0.0001;
- const formatted = pipe.transform(nbr);
- expect(formatted).toEqual(nbr);
- });
- it("returns null when number is null", () => {
- const pipe = new FormatNumberPipe();
- const nbr = null;
- const formatted = pipe.transform(nbr);
- expect(formatted).toBeNull();
- });
- it("returns undefined when number is undefined", () => {
- const pipe = new FormatNumberPipe();
- const nbr = undefined;
- const formatted = pipe.transform(nbr);
- expect(formatted).toBeUndefined();
- });
- it("returns string when number is a string", () => {
- const pipe = new FormatNumberPipe();
- const nbr = "test";
- const formatted = pipe.transform(nbr);
- expect(formatted).toEqual("test");
- });
-});
diff --git a/src/app/shared/pipes/format-number.pipe.ts b/src/app/shared/pipes/format-number.pipe.ts
index 19710d61c2..f7806ff2c1 100644
--- a/src/app/shared/pipes/format-number.pipe.ts
+++ b/src/app/shared/pipes/format-number.pipe.ts
@@ -1,13 +1,54 @@
import { Pipe, PipeTransform } from "@angular/core";
+import { AppConfigService } from "app-config.service";
@Pipe({
name: "formatNumber",
standalone: false,
+ pure: true,
})
export class FormatNumberPipe implements PipeTransform {
- transform(value: any) {
- if (typeof value === "number" && (value >= 1e5 || value <= 1e-5)) {
- return value.toExponential();
+ private enabled: boolean;
+ private significantDigits: number;
+ private minCutoff: number;
+ private maxCutoff: number;
+
+ constructor(private configService: AppConfigService) {
+ const config = this.configService.getConfig();
+ this.enabled = config.metadataFloatFormatEnabled ?? false;
+
+ if (this.enabled) {
+ const format = config.metadataFloatFormat;
+ this.significantDigits = format.significantDigits;
+ this.minCutoff = format.minCutoff;
+ this.maxCutoff = format.maxCutoff;
}
- return value;
+ }
+
+ transform(value: unknown): string | number {
+ // use old way if not enabled
+ if (!this.enabled) {
+ if (typeof value === "number" && (value >= 1e5 || value <= 1e-5)) {
+ return value.toExponential();
+ }
+ return String(value);
+ }
+
+ if (typeof value !== "number" || !Number.isFinite(value)) {
+ // value is not a finite number
+ return String(value);
+ }
+
+ // Do not format integers
+ if (Number.isInteger(value)) {
+ return String(value);
+ }
+
+ // use scientific notation if float value is large or small
+ const absoluteValue = Math.abs(value);
+ if (absoluteValue < this.minCutoff || absoluteValue > this.maxCutoff) {
+ // use scientific notation with (significantDigits - 1) decimals
+ return value.toExponential(this.significantDigits - 1);
+ }
+
+ return value.toPrecision(this.significantDigits);
}
}