`
})
-export class MultiSelectComponent
implements AfterContentInit, OnChanges {
+export class MultiSelectComponent implements ControlValueAccessor, AfterContentInit, OnChanges {
@Input()
public size: SelectSize = SelectSize.Medium;
@@ -167,6 +176,9 @@ export class MultiSelectComponent implements AfterContentInit, OnChanges {
public popoverOpen: boolean = false;
public triggerValues$: Observable = new Observable();
+ private propagateControlValueChange?: (value: V[] | undefined) => void;
+ private propagateControlValueChangeOnTouch?: (value: V[] | undefined) => void;
+
public ngAfterContentInit(): void {
this.allOptions$ = this.allOptionsList !== undefined ? queryListAndChanges$(this.allOptionsList) : EMPTY;
this.filteredOptions$ = combineLatest([this.allOptions$, this.searchSubject]).pipe(
@@ -215,24 +227,37 @@ export class MultiSelectComponent implements AfterContentInit, OnChanges {
}
const selected = this.isSelectedItem(item)
- ? this.selected?.filter(value => value !== item.value)
+ ? this.selected?.filter(value => !isEqual(value, item.value))
: (this.selected ?? []).concat(item.value);
this.setSelection(selected ?? []);
}
public isSelectedItem(item: SelectOptionComponent): boolean {
- return this.selected !== undefined && this.selected.filter(value => value === item.value).length > 0;
+ return this.selected !== undefined && this.selected.filter(value => isEqual(value, item.value)).length > 0;
}
public preventClickDefault(event: Event): void {
event.preventDefault();
}
+ public writeValue(value?: V[]): void {
+ this.setSelection(value ?? []);
+ }
+
+ public registerOnChange(onChange: (value: V[] | undefined) => void): void {
+ this.propagateControlValueChange = onChange;
+ }
+
+ public registerOnTouched(onTouch: (value: V[] | undefined) => void): void {
+ this.propagateControlValueChangeOnTouch = onTouch;
+ }
+
private setSelection(selected: V[]): void {
this.selected = selected;
this.setTriggerLabel();
this.selectedChange.emit(this.selected);
+ this.propagateValueChangeToFormControl(this.selected);
}
private setTriggerLabel(): void {
@@ -256,6 +281,11 @@ export class MultiSelectComponent implements AfterContentInit, OnChanges {
})
);
}
+
+ private propagateValueChangeToFormControl(value: V[] | undefined): void {
+ this.propagateControlValueChange?.(value);
+ this.propagateControlValueChangeOnTouch?.(value);
+ }
}
interface TriggerValues {
diff --git a/projects/components/src/select/select.component.ts b/projects/components/src/select/select.component.ts
index 2e095a7ee..427563104 100644
--- a/projects/components/src/select/select.component.ts
+++ b/projects/components/src/select/select.component.ts
@@ -10,6 +10,7 @@ import {
Output,
QueryList
} from '@angular/core';
+import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { IconType } from '@hypertrace/assets-library';
import { LoggerService, queryListAndChanges$, SubscriptionLifecycle, TypedSimpleChanges } from '@hypertrace/common';
import { EMPTY, merge, Observable, of } from 'rxjs';
@@ -26,7 +27,14 @@ import { SelectSize } from './select-size';
selector: 'ht-select',
styleUrls: ['./select.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
- providers: [SubscriptionLifecycle],
+ providers: [
+ SubscriptionLifecycle,
+ {
+ provide: NG_VALUE_ACCESSOR,
+ useExisting: SelectComponent,
+ multi: true
+ }
+ ],
template: `
`
})
-export class SelectComponent
implements AfterContentInit, OnChanges {
+export class SelectComponent implements ControlValueAccessor, AfterContentInit, OnChanges {
@Input()
public size: SelectSize = SelectSize.Medium;
@@ -208,6 +216,8 @@ export class SelectComponent implements AfterContentInit, OnChanges {
public controlItems?: QueryList>;
public selected$?: Observable | undefined>;
+ private propagateControlValueChange?: (value: V | undefined) => void;
+ private propagateControlValueChangeOnTouch?: (value: V | undefined) => void;
public groupPosition: SelectGroupPosition = SelectGroupPosition.Ungrouped;
@@ -272,9 +282,14 @@ export class SelectComponent implements AfterContentInit, OnChanges {
return;
}
- this.selected = item.value;
- this.selected$ = this.buildObservableOfSelected();
+ this.setSelection(item.value);
this.selectedChange.emit(this.selected);
+ this.propagateValueChangeToFormControl(this.selected);
+ }
+
+ private setSelection(value?: V): void {
+ this.selected = value;
+ this.selected$ = this.buildObservableOfSelected();
}
private findItem(value: V | undefined): SelectOption | undefined {
@@ -296,6 +311,23 @@ export class SelectComponent implements AfterContentInit, OnChanges {
return styles;
}
+
+ public writeValue(value?: V): void {
+ this.setSelection(value);
+ }
+
+ public registerOnChange(onChange: (value: V | undefined) => void): void {
+ this.propagateControlValueChange = onChange;
+ }
+
+ public registerOnTouched(onTouch: (value: V | undefined) => void): void {
+ this.propagateControlValueChangeOnTouch = onTouch;
+ }
+
+ private propagateValueChangeToFormControl(value: V | undefined): void {
+ this.propagateControlValueChange?.(value);
+ this.propagateControlValueChangeOnTouch?.(value);
+ }
}
export const enum SelectTriggerDisplayMode {
diff --git a/projects/components/src/toggle-switch/toggle-switch.component.ts b/projects/components/src/toggle-switch/toggle-switch.component.ts
index 3c4340abf..ad068dc31 100644
--- a/projects/components/src/toggle-switch/toggle-switch.component.ts
+++ b/projects/components/src/toggle-switch/toggle-switch.component.ts
@@ -9,7 +9,7 @@ import { ToggleSwitchSize } from './toggle-switch-size';
template: `
{
name: string;
value: T;
@@ -23,3 +25,51 @@ export type GraphQlArgumentValue =
| GraphQlArgumentObject
| GraphQlArgumentArray
| GraphQlEnumArgument;
+
+/**
+ * @param value
+ * Convert the value to GraphQL argument value
+ */
+export const getValueAsGraphQlArgument = (
+ value: unknown,
+ considerAllCapsStringAsEnum: boolean = false,
+ isArrayElement: boolean = false
+): GraphQlArgumentValue => {
+ if (value instanceof Date) {
+ return value.toISOString();
+ }
+
+ if (Array.isArray(value)) {
+ return value.map(v => getValueAsGraphQlArgument(v, considerAllCapsStringAsEnum, true));
+ }
+
+ if (typeof value === 'object' && value !== null) {
+ return getObjectAsGraphQlArgument(value as Dictionary, considerAllCapsStringAsEnum);
+ }
+
+ if (typeof value === 'string' && considerAllCapsStringAsEnum && !isArrayElement && /^[A-Z_]+$/.test(value)) {
+ return new GraphQlEnumArgument(value);
+ }
+
+ return value as string | number | boolean;
+};
+
+/**
+ * @param object
+ * Convert the object to GraphQL argument object
+ */
+export const getObjectAsGraphQlArgument = (
+ object: Dictionary,
+ considerAllCapsStringAsEnum: boolean = false
+): GraphQlArgumentObject => {
+ const argumentObject: GraphQlArgumentObject = {};
+
+ for (const key of Object.keys(object)) {
+ if (object[key] === null || object[key] === undefined) {
+ continue;
+ }
+ argumentObject[key] = getValueAsGraphQlArgument(object[key], considerAllCapsStringAsEnum);
+ }
+
+ return argumentObject;
+};
diff --git a/projects/observability/src/shared/services/metadata/metadata.service.ts b/projects/observability/src/shared/services/metadata/metadata.service.ts
index 97d9d491c..d0937f81d 100644
--- a/projects/observability/src/shared/services/metadata/metadata.service.ts
+++ b/projects/observability/src/shared/services/metadata/metadata.service.ts
@@ -175,7 +175,7 @@ export class MetadataService {
);
}
- private getAllAttributes(scope: string): ReplayObservable {
+ public getAllAttributes(scope: string): ReplayObservable {
return this.getServerDefinedAttributes(scope);
}