mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-03-25 19:21:49 +00:00
* added recursion and inition decorators * working version * added todo comment and removed console.log * Added Recursive add * removed type requirement * proper resolution of promises * added custom element and base class * Made selfServe standalone page * Added custom renderer as async type * Added overall defaults * added inital open from data explorer * removed landingpage * added feature for self serve type * renamed sqlx->example and added invalid type * Added comments for Example * removed unnecessary changes * Resolved PR comments Added tests Moved onSubmt and initialize inside base class Moved testExplorer to separate folder made fields of SelfServe Class non static * fixed lint errors * fixed compilation errors * Removed reactbinding changes * renamed dropdown -> choice * Added SelfServeComponent * Addressed PR comments * added toggle, visibility, text display,commandbar * added sqlx example * added onRefrssh * formatting changes * rmoved radioswitch display * updated smartui tests * Added more tests * onSubmit -> onSave * Resolved PR comments
185 lines
5.7 KiB
TypeScript
185 lines
5.7 KiB
TypeScript
import { MessageBarType } from "office-ui-fabric-react";
|
|
import "reflect-metadata";
|
|
import {
|
|
Node,
|
|
AnyDisplay,
|
|
BooleanInput,
|
|
ChoiceInput,
|
|
ChoiceItem,
|
|
Description,
|
|
DescriptionDisplay,
|
|
Info,
|
|
InputType,
|
|
InputTypeValue,
|
|
NumberInput,
|
|
SelfServeDescriptor,
|
|
SmartUiInput,
|
|
StringInput,
|
|
SelfServeNotificationType,
|
|
} from "./SelfServeTypes";
|
|
|
|
export enum SelfServeType {
|
|
// No self serve type passed, launch explorer
|
|
none = "none",
|
|
// Unsupported self serve type passed as feature flag
|
|
invalid = "invalid",
|
|
// Add your self serve types here
|
|
example = "example",
|
|
sqlx = "sqlx",
|
|
}
|
|
|
|
export interface DecoratorProperties {
|
|
id: string;
|
|
info?: (() => Promise<Info>) | Info;
|
|
type?: InputTypeValue;
|
|
label?: (() => Promise<string>) | string;
|
|
placeholder?: (() => Promise<string>) | string;
|
|
dataFieldName?: string;
|
|
min?: (() => Promise<number>) | number;
|
|
max?: (() => Promise<number>) | number;
|
|
step?: (() => Promise<number>) | number;
|
|
trueLabel?: (() => Promise<string>) | string;
|
|
falseLabel?: (() => Promise<string>) | string;
|
|
choices?: (() => Promise<ChoiceItem[]>) | ChoiceItem[];
|
|
uiType?: string;
|
|
errorMessage?: string;
|
|
description?: (() => Promise<Description>) | Description;
|
|
onChange?: (currentState: Map<string, SmartUiInput>, newValue: InputType) => Map<string, SmartUiInput>;
|
|
onSave?: (currentValues: Map<string, SmartUiInput>) => Promise<void>;
|
|
initialize?: () => Promise<Map<string, SmartUiInput>>;
|
|
}
|
|
|
|
const setValue = <T extends keyof DecoratorProperties, K extends DecoratorProperties[T]>(
|
|
name: T,
|
|
value: K,
|
|
fieldObject: DecoratorProperties
|
|
): void => {
|
|
fieldObject[name] = value;
|
|
};
|
|
|
|
const getValue = <T extends keyof DecoratorProperties>(name: T, fieldObject: DecoratorProperties): unknown => {
|
|
return fieldObject[name];
|
|
};
|
|
|
|
export const addPropertyToMap = <T extends keyof DecoratorProperties, K extends DecoratorProperties[T]>(
|
|
target: unknown,
|
|
propertyName: string,
|
|
className: string,
|
|
descriptorName: keyof DecoratorProperties,
|
|
descriptorValue: K
|
|
): void => {
|
|
const context =
|
|
(Reflect.getMetadata(className, target) as Map<string, DecoratorProperties>) ??
|
|
new Map<string, DecoratorProperties>();
|
|
updateContextWithDecorator(context, propertyName, className, descriptorName, descriptorValue);
|
|
Reflect.defineMetadata(className, context, target);
|
|
};
|
|
|
|
export const updateContextWithDecorator = <T extends keyof DecoratorProperties, K extends DecoratorProperties[T]>(
|
|
context: Map<string, DecoratorProperties>,
|
|
propertyName: string,
|
|
className: string,
|
|
descriptorName: keyof DecoratorProperties,
|
|
descriptorValue: K
|
|
): void => {
|
|
if (!(context instanceof Map)) {
|
|
throw new Error(`@SmartUi should be the first decorator for the class '${className}'.`);
|
|
}
|
|
|
|
const propertyObject = context.get(propertyName) ?? { id: propertyName };
|
|
|
|
if (getValue(descriptorName, propertyObject) && descriptorName !== "type" && descriptorName !== "dataFieldName") {
|
|
throw new Error(
|
|
`Duplicate value passed for '${descriptorName}' on property '${propertyName}' of class '${className}'`
|
|
);
|
|
}
|
|
|
|
setValue(descriptorName, descriptorValue, propertyObject);
|
|
context.set(propertyName, propertyObject);
|
|
};
|
|
|
|
export const buildSmartUiDescriptor = (className: string, target: unknown): void => {
|
|
const context = Reflect.getMetadata(className, target) as Map<string, DecoratorProperties>;
|
|
const smartUiDescriptor = mapToSmartUiDescriptor(context);
|
|
Reflect.defineMetadata(className, smartUiDescriptor, target);
|
|
};
|
|
|
|
export const mapToSmartUiDescriptor = (context: Map<string, DecoratorProperties>): SelfServeDescriptor => {
|
|
const root = context.get("root");
|
|
context.delete("root");
|
|
const inputNames: string[] = [];
|
|
|
|
const smartUiDescriptor: SelfServeDescriptor = {
|
|
root: {
|
|
id: "root",
|
|
info: root?.info,
|
|
children: [],
|
|
},
|
|
};
|
|
|
|
while (context.size > 0) {
|
|
const key = context.keys().next().value;
|
|
addToDescriptor(context, smartUiDescriptor.root, key, inputNames);
|
|
}
|
|
smartUiDescriptor.inputNames = inputNames;
|
|
|
|
return smartUiDescriptor;
|
|
};
|
|
|
|
const addToDescriptor = (
|
|
context: Map<string, DecoratorProperties>,
|
|
root: Node,
|
|
key: string,
|
|
inputNames: string[]
|
|
): void => {
|
|
const value = context.get(key);
|
|
inputNames.push(value.id);
|
|
const element = {
|
|
id: value.id,
|
|
info: value.info,
|
|
input: getInput(value),
|
|
children: [],
|
|
} as Node;
|
|
context.delete(key);
|
|
root.children.push(element);
|
|
};
|
|
|
|
const getInput = (value: DecoratorProperties): AnyDisplay => {
|
|
switch (value.type) {
|
|
case "number":
|
|
if (!value.label || !value.step || !value.uiType || !value.min || !value.max) {
|
|
value.errorMessage = `label, step, min, max and uiType are required for number input '${value.id}'.`;
|
|
}
|
|
return value as NumberInput;
|
|
case "string":
|
|
if (value.description) {
|
|
return value as DescriptionDisplay;
|
|
}
|
|
if (!value.label) {
|
|
value.errorMessage = `label is required for string input '${value.id}'.`;
|
|
}
|
|
return value as StringInput;
|
|
case "boolean":
|
|
if (!value.label || !value.trueLabel || !value.falseLabel) {
|
|
value.errorMessage = `label, truelabel and falselabel are required for boolean input '${value.id}'.`;
|
|
}
|
|
return value as BooleanInput;
|
|
default:
|
|
if (!value.label || !value.choices) {
|
|
value.errorMessage = `label and choices are required for Choice input '${value.id}'.`;
|
|
}
|
|
return value as ChoiceInput;
|
|
}
|
|
};
|
|
|
|
export const getMessageBarType = (type: SelfServeNotificationType): MessageBarType => {
|
|
switch (type) {
|
|
case SelfServeNotificationType.info:
|
|
return MessageBarType.info;
|
|
case SelfServeNotificationType.warning:
|
|
return MessageBarType.warning;
|
|
case SelfServeNotificationType.error:
|
|
return MessageBarType.error;
|
|
}
|
|
};
|