Skip to content

Commit

Permalink
Support test data generation for services with parameters
Browse files Browse the repository at this point in the history
  • Loading branch information
Yannan authored and Yannan committed May 23, 2023
1 parent 7f264ad commit 619f024
Show file tree
Hide file tree
Showing 7 changed files with 209 additions and 52 deletions.
3 changes: 3 additions & 0 deletions .changeset/dull-snails-sit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
'@finos/legend-graph': patch
---
5 changes: 5 additions & 0 deletions .changeset/tricky-pigs-prove.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@finos/legend-application-studio': minor
---

Support test data generation for services with parameters.
Original file line number Diff line number Diff line change
Expand Up @@ -92,13 +92,20 @@ class LegendStudioApplicationCoreOptions {
TEMPORARY__serviceRegistrationConfig: ServiceRegistrationEnvironmentConfig[] =
[];

/**
* Indicates whether we should use new ParameterValues for service test data generation
* This flag will be removed once the backend code is deployed.
*/
TEMPORARY__enableTestDataGenerationNewFlow = true;

private static readonly serialization = new SerializationFactory(
createModelSchema(LegendStudioApplicationCoreOptions, {
enableGraphBuilderStrictMode: optional(primitive()),
projectCreationGroupIdSuggestion: optional(primitive()),
TEMPORARY__preserveSectionIndex: optional(primitive()),
TEMPORARY__enableFunctionActivatorSupport: optional(primitive()),
TEMPORARY__enableMappingTestableEditor: optional(primitive()),
TEMPORARY__enableTestDataGenerationNewFlow: optional(primitive()),
TEMPORARY__serviceRegistrationConfig: list(
object(ServiceRegistrationEnvironmentConfig),
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ import { prettyCONSTName } from '@finos/legend-shared';
import type { DSL_Data_LegendStudioApplicationPlugin_Extension } from '../../../../../stores/extensions/DSL_Data_LegendStudioApplicationPlugin_Extension.js';
import { useEditorStore } from '../../../EditorStoreProvider.js';
import { LEGEND_STUDIO_DOCUMENTATION_KEY } from '../../../../../__lib__/LegendStudioDocumentation.js';
import { LambdaParameterValuesEditor } from '@finos/legend-query-builder';

export const ConnectionTestDataEditor = observer(
(props: { connectionTestDataState: ConnectionTestDataState }) => {
Expand Down Expand Up @@ -99,6 +100,7 @@ export const ConnectionTestDataEditor = observer(
],
});
};

const generateTestData = (): void => {
if (!anonymizeGeneratedData) {
confirmGenerateUnAnonmyizedData();
Expand Down Expand Up @@ -163,6 +165,17 @@ export const ConnectionTestDataEditor = observer(
isReadOnly={isReadOnly}
embeddedDataEditorState={connectionTestDataState.embeddedEditorState}
/>
{connectionTestDataState.parameterState.parameterValuesEditorState
.showModal && (
<LambdaParameterValuesEditor
graph={connectionTestDataState.editorStore.graphManagerState.graph}
observerContext={
connectionTestDataState.editorStore.changeDetectionState
.observerContext
}
lambdaParametersState={connectionTestDataState.parameterState}
/>
)}
</div>
);
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,20 @@ import {
type EmbeddedData,
type RawLambda,
type DataElement,
type Mapping,
ConnectionTestData,
PureSingleExecution,
PureMultiExecution,
DatabaseConnection,
buildLambdaVariableExpressions,
VariableExpression,
PrimitiveType,
Enumeration,
DataElementReference,
PackageableElementExplicitReference,
ConnectionPointer,
reportGraphAnalytics,
observe_ValueSpecification,
Enumeration,
PrimitiveType,
} from '@finos/legend-graph';
import {
type GeneratorFn,
Expand All @@ -42,21 +44,17 @@ import {
deleteEntry,
filterByType,
guaranteeNonNullable,
isNonNullable,
returnUndefOnError,
getNullableFirstEntry,
uniq,
isNonNullable,
} from '@finos/legend-shared';
import { action, flow, makeObservable, observable } from 'mobx';
import { action, flow, flowResult, makeObservable, observable } from 'mobx';
import type { EditorStore } from '../../../../EditorStore.js';
import {
service_addConnectionTestData,
service_setConnectionTestDataEmbeddedData,
} from '../../../../../graph-modifier/DSL_Service_GraphModifierHelper.js';
import {
createMockEnumerationProperty,
createMockPrimitiveProperty,
} from '../../../../utils/MockDataUtils.js';
import {
TEMPORARY__createRelationalDataFromCSV,
EmbeddedDataConnectionTypeVisitor,
Expand All @@ -71,6 +69,15 @@ import {
import { createEmbeddedData } from '../../data/EmbeddedDataState.js';
import type { ServiceTestSuiteState } from './ServiceTestableState.js';
import { LegendStudioTelemetryHelper } from '../../../../../../__lib__/LegendStudioTelemetryHelper.js';
import {
LambdaParameterState,
LambdaParametersState,
PARAMETER_SUBMIT_ACTION,
buildExecutionParameterValues,
createMockEnumerationProperty,
getExecutionQueryFromRawLambda,
} from '@finos/legend-query-builder';
import { createMockPrimitiveProperty } from '../../../../utils/MockDataUtils.js';

const buildTestDataParameters = (
rawLambda: RawLambda,
Expand All @@ -91,10 +98,71 @@ const buildTestDataParameters = (
})
.filter(isNonNullable);

export class ServiceTestDataParameterState extends LambdaParametersState {
connectionTestDataState: ConnectionTestDataState;

constructor(connectionTestDataState: ConnectionTestDataState) {
super();
makeObservable(this, {
parameterValuesEditorState: observable,
parameterStates: observable,
openModal: action,
build: action,
});
this.connectionTestDataState = connectionTestDataState;
}

openModal(serviceExecutionParameters: {
query: RawLambda;
mapping: Mapping;
runtime: Runtime;
}): void {
this.parameterStates = this.build(serviceExecutionParameters.query);
this.parameterValuesEditorState.open(
(): Promise<void> =>
flowResult(
this.connectionTestDataState.generateTestDataForDatabaseConnection(
serviceExecutionParameters,
),
).catch(
this.connectionTestDataState.editorStore.applicationStore
.alertUnhandledError,
),
PARAMETER_SUBMIT_ACTION.RUN,
);
}

build(query: RawLambda): LambdaParameterState[] {
const parameters = buildLambdaVariableExpressions(
query,
this.connectionTestDataState.editorStore.graphManagerState,
)
.map((p) =>
observe_ValueSpecification(
p,
this.connectionTestDataState.editorStore.changeDetectionState
.observerContext,
),
)
.filter(filterByType(VariableExpression));
const states = parameters.map((p) => {
const parmeterState = new LambdaParameterState(
p,
this.connectionTestDataState.editorStore.changeDetectionState.observerContext,
this.connectionTestDataState.editorStore.graphManagerState.graph,
);
parmeterState.mockParameterValue();
return parmeterState;
});
return states;
}
}

export class ConnectionTestDataState {
readonly editorStore: EditorStore;
readonly testDataState: ServiceTestDataState;
connectionData: ConnectionTestData;
parameterState: ServiceTestDataParameterState;
embeddedEditorState: EmbeddedDataEditorState;
generatingTestDataSate = ActionState.create();
anonymizeGeneratedData = true;
Expand All @@ -105,10 +173,12 @@ export class ConnectionTestDataState {
) {
makeObservable(this, {
generatingTestDataSate: observable,
parameterState: observable,
embeddedEditorState: observable,
anonymizeGeneratedData: observable,
setAnonymizeGeneratedData: action,
generateTestData: flow,
generateTestDataForDatabaseConnection: flow,
});
this.testDataState = testDataState;
this.editorStore = testDataState.editorStore;
Expand All @@ -117,6 +187,7 @@ export class ConnectionTestDataState {
this.testDataState.editorStore,
connectionData.testData,
);
this.parameterState = new ServiceTestDataParameterState(this);
}
get identifiedConnection(): IdentifiedConnection | undefined {
return this.getAllIdentifiedConnections().find(
Expand All @@ -128,66 +199,121 @@ export class ConnectionTestDataState {
this.anonymizeGeneratedData = val;
}

*generateTestDataForDatabaseConnection(serviceExecutionParameters: {
query: RawLambda;
mapping: Mapping;
runtime: Runtime;
}): GeneratorFn<void> {
// NOTE: since we don't have a generic mechanism for test-data generation
// we will only report metrics around API usage, when we genericize, we will
// move this out
LegendStudioTelemetryHelper.logEvent_TestDataGenerationLaunched(
this.testDataState.editorStore.applicationStore.telemetryService,
);
const report = reportGraphAnalytics(
this.editorStore.graphManagerState.graph,
);
let value;
if (
this.editorStore.applicationStore.config.options
.TEMPORARY__enableTestDataGenerationNewFlow
) {
value =
(yield this.editorStore.graphManagerState.graphManager.generateExecuteTestData(
getExecutionQueryFromRawLambda(
serviceExecutionParameters.query,
this.parameterState.parameterStates,
this.editorStore.graphManagerState,
),
[],
serviceExecutionParameters.mapping,
serviceExecutionParameters.runtime,
this.editorStore.graphManagerState.graph,
{
anonymizeGeneratedData: this.anonymizeGeneratedData,
parameterValues: buildExecutionParameterValues(
this.parameterState.parameterStates,
this.editorStore.graphManagerState,
),
},
report,
)) as string;
} else {
// TODO: delete this once the backend code is in place
value =
(yield this.editorStore.graphManagerState.graphManager.generateExecuteTestData(
serviceExecutionParameters.query,
buildTestDataParameters(
serviceExecutionParameters.query,
this.editorStore,
),
serviceExecutionParameters.mapping,
serviceExecutionParameters.runtime,
this.editorStore.graphManagerState.graph,
{
anonymizeGeneratedData: this.anonymizeGeneratedData,
},
report,
)) as string;
}
// NOTE: since we don't have a generic mechanism for test-data generation
// we will only report metrics around API usage, when we genericize, we will
// move this out
LegendStudioTelemetryHelper.logEvent_TestDataGenerationSucceeded(
this.editorStore.applicationStore.telemetryService,
report,
);
service_setConnectionTestDataEmbeddedData(
this.connectionData,
TEMPORARY__createRelationalDataFromCSV(value),
this.editorStore.changeDetectionState.observerContext,
);
}

*generateTestData(): GeneratorFn<void> {
try {
this.generatingTestDataSate.inProgress();
const connection = guaranteeNonNullable(
this.resolveConnectionValue(this.connectionData.connectionId),
`Unable to resolve connection ID '${this.connectionData.connectionId}`,
);

let embeddedData: EmbeddedData;
if (connection instanceof DatabaseConnection) {
const serviceExecutionParameters = guaranteeNonNullable(
this.testDataState.testSuiteState.testableState.serviceEditorState
.executionState.serviceExecutionParameters,
);

// NOTE: since we don't have a generic mechanism for test-data generation
// we will only report metrics around API usage, when we genericize, we will
// move this out
LegendStudioTelemetryHelper.logEvent_TestDataGenerationLaunched(
this.testDataState.editorStore.applicationStore.telemetryService,
);
const report = reportGraphAnalytics(
this.editorStore.graphManagerState.graph,
);

const value =
(yield this.editorStore.graphManagerState.graphManager.generateExecuteTestData(
serviceExecutionParameters.query,
buildTestDataParameters(
serviceExecutionParameters.query,
this.editorStore,
if (
this.editorStore.applicationStore.config.options
.TEMPORARY__enableTestDataGenerationNewFlow
) {
const parameters = (serviceExecutionParameters.query.parameters ??
[]) as object[];
if (parameters.length) {
this.parameterState.openModal(serviceExecutionParameters);
} else {
yield flowResult(
this.generateTestDataForDatabaseConnection(
serviceExecutionParameters,
),
);
}
} else {
yield flowResult(
this.generateTestDataForDatabaseConnection(
serviceExecutionParameters,
),
serviceExecutionParameters.mapping,
serviceExecutionParameters.runtime,
this.editorStore.graphManagerState.graph,
{
anonymizeGeneratedData: this.anonymizeGeneratedData,
},
report,
)) as string;

// NOTE: since we don't have a generic mechanism for test-data generation
// we will only report metrics around API usage, when we genericize, we will
// move this out
LegendStudioTelemetryHelper.logEvent_TestDataGenerationSucceeded(
this.editorStore.applicationStore.telemetryService,
report,
);

embeddedData = TEMPORARY__createRelationalDataFromCSV(value);
);
}
} else {
embeddedData = connection.accept_ConnectionVisitor(
new TEMPORARY__EmbeddedDataConnectionVisitor(this.editorStore),
// TODO: delete this once the backend code is in place
service_setConnectionTestDataEmbeddedData(
this.connectionData,
connection.accept_ConnectionVisitor(
new TEMPORARY__EmbeddedDataConnectionVisitor(this.editorStore),
),
this.editorStore.changeDetectionState.observerContext,
);
}
service_setConnectionTestDataEmbeddedData(
this.connectionData,
embeddedData,
this.editorStore.changeDetectionState.observerContext,
);
this.embeddedEditorState = new EmbeddedDataEditorState(
this.testDataState.editorStore,
this.connectionData.testData,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,7 @@ export abstract class AbstractPureGraphManager {
options?: {
// Anonymizes data by hashing any string values in the generated data
anonymizeGeneratedData?: boolean;
parameterValues?: ParameterValue[];
},
report?: GraphManagerOperationReport,
): Promise<string>;
Expand Down
Loading

0 comments on commit 619f024

Please sign in to comment.