Skip to content

Commit

Permalink
Use colum filters
Browse files Browse the repository at this point in the history
Signed-off-by: Gloria Ciavarrini <gciavarrini@redhat.com>
  • Loading branch information
gciavarrini committed Sep 30, 2024
1 parent b69b3b9 commit 10a6fdc
Show file tree
Hide file tree
Showing 6 changed files with 249 additions and 37 deletions.
129 changes: 124 additions & 5 deletions plugins/orchestrator-backend/src/helpers/queryBuilder.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { FilterInfo } from '@janus-idp/backstage-plugin-orchestrator-common';
import {
FieldFilterOperatorEnum,
Filter,
IntrospectionField,
LogicalFilter,
TypeName,
} from '@janus-idp/backstage-plugin-orchestrator-common';

import { Pagination } from '../types/pagination';

Expand Down Expand Up @@ -53,8 +59,121 @@ function buildPaginationClause(pagination?: Pagination): string {
return parts.join(', ');
}

export function buildFilterCondition(filter?: FilterInfo): string {
return filter?.fieldName && filter?.operator && filter?.fieldValue
? `${filter?.fieldName}:{ ${filter?.operator}: ${filter?.fieldValue}}`
: '';
function isLogicalFilter(filter: Filter): filter is LogicalFilter {
return (filter as LogicalFilter).filters !== undefined;
}

export function buildFilterCondition(
introspection: IntrospectionField[],
filters?: Filter,
): string {
if (!filters) {
return '';
}

if (isLogicalFilter(filters)) {
if (filters.operator) {
const subClauses =
filters.filters.map(f => buildFilterCondition(introspection, f)) ?? [];
const joinedSubClauses = `${filters.operator.toLowerCase()}: {${subClauses.join(', ')}}`;
return joinedSubClauses;
}
}

if (!isOperatorSupported(filters.operator)) {
throw new Error(`Unsopported operator ${filters.operator}`);
}

let value = filters.value;

if (filters.operator === FieldFilterOperatorEnum.IsNull) {
let booleanValue = false;
if (typeof value === 'string') {
booleanValue = value.toLowerCase() === 'true';
} else if (typeof value === 'number') {
booleanValue = value === 1;
}
return `${filters.field}: {${getGraphQLOperator(filters.operator)}: ${booleanValue}}`;
}

if (Array.isArray(value)) {
value = `[${value.map(v => formatValue(filters.field, v, introspection)).join(', ')}]`;
} else {
value = formatValue(filters.field, value, introspection);
}

if (
filters.operator === FieldFilterOperatorEnum.Eq ||
filters.operator === FieldFilterOperatorEnum.Like ||
filters.operator === FieldFilterOperatorEnum.In
) {
return `${filters.field}: {${getGraphQLOperator(filters.operator)}: ${value}}`;
}

throw new Error(`Can't build filter condition`);
}

function isOperatorSupported(operator: FieldFilterOperatorEnum): boolean {
return (
operator === FieldFilterOperatorEnum.Eq ||
operator === FieldFilterOperatorEnum.Like ||
operator === FieldFilterOperatorEnum.In ||
operator === FieldFilterOperatorEnum.IsNull
);
}

function isFieldFilterSupported(fieldDef: IntrospectionField): boolean {
return fieldDef?.type.name === TypeName.String;
}

function formatValue(
fieldName: string,
fieldValue: any,
instrospection: IntrospectionField[],
): string {
const fieldDef = instrospection.find(f => f.name === fieldName);
if (!fieldDef) {
throw new Error(`Can't find field ${fieldName} definition`);
}

if (!isFieldFilterSupported) {
throw new Error(`Unsupported field type ${fieldDef.type.name}`);
}

if (fieldDef.type.name === TypeName.String) {
return `"${fieldValue}"`;
}
throw new Error(
`Failed to format value for ${fieldName} ${fieldValue} with type ${fieldDef.type.name}`,
);
}

function getGraphQLOperator(operator: FieldFilterOperatorEnum): string {
switch (operator) {
case 'EQ':
return 'equal';
case 'LIKE':
return 'like';
case 'IN':
return 'in';
case 'IS_NULL':
return 'isNull';
// case 'GT':
// // return "greaterThan"
// case 'GTE':
// return "greaterThanEqual"
// case 'LT':
// return "lessThan"
// case 'LTE':
// return "lessThanEqual"
// case 'CONTAINS':
// return "contains"
// case 'CONTAINS_ALL':
// case 'CONTAINS_ANY':
// case 'BETWEEN':
// case 'FROM':
// case 'TO':
default:
throw new Error(`Operation "${operator}" not supported`);
}
}
117 changes: 112 additions & 5 deletions plugins/orchestrator-backend/src/service/DataIndexService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ import { LoggerService } from '@backstage/backend-plugin-api';
import { Client, fetchExchange, gql } from '@urql/core';

import {
FilterInfo,
Filter,
fromWorkflowSource,
getWorkflowCategory,
IntrospectionField,
IntrospectionQuery,
parseWorkflowVariables,
ProcessInstance,
WorkflowDefinition,
Expand All @@ -22,6 +24,7 @@ import { FETCH_PROCESS_INSTANCES_SORT_FIELD } from './constants';

export class DataIndexService {
private client: Client;
public readonly processDefinitionArguments: Promise<IntrospectionField[]>;

public constructor(
private readonly dataIndexUrl: string,
Expand All @@ -32,6 +35,8 @@ export class DataIndexService {
}

this.client = this.getNewGraphQLClient();
this.processDefinitionArguments =
this.inspectInputArgument('ProcessDefinition');
}

private getNewGraphQLClient(): Client {
Expand All @@ -42,6 +47,103 @@ export class DataIndexService {
});
}

public graphQLArgumentQuery(type: string): string {
return `query ${type}Argument {
__type(name: "${type}Argument") {
kind
name
inputFields {
name
type {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
}
}
}
}
}
}
}`;
}
public async inspectInputArgument(
type: string,
): Promise<IntrospectionField[]> {
const result = await this.client.query(this.graphQLArgumentQuery(type), {});

this.logger.debug(`Introspection query result: ${JSON.stringify(result)}`);

if (result?.error) {
this.logger.error(`Error executing introspection query ${result.error}`);
throw result.error;
}

const pairs: IntrospectionField[] = [];
if (result?.data.__type.inputFields) {
for (const field of result.data.__type.inputFields) {
if (
field.name !== 'and' &&
field.name !== 'or' &&
field.name !== 'not'
) {
pairs.push({
name: field.name,
type: {
name: field.type.name,
kind: field.type.kind,
ofType: field.type.ofType,
},
});
}
}
}
return pairs;
}

public async getSchemaTypes(type: string): Promise<IntrospectionQuery> {
const graphQlQuery = `query IntrospectionQuery {
__type(name: "${type}") {
name
kind
description
fields {
name
type {
kind
name
ofType {
kind
name
ofType {
kind
name
}
}
}
}
}
}
`;

const result = await this.client.query(graphQlQuery, {});

this.logger.debug(`Introspection query result: ${JSON.stringify(result)}`);

if (result.error) {
this.logger.error(`Error executing introspection query ${result.error}`);
throw result.error;
}
return result as unknown as IntrospectionQuery;
}

public async abortWorkflowInstance(instanceId: string): Promise<void> {
this.logger.info(`Aborting workflow instance ${instanceId}`);
const ProcessInstanceAbortMutationDocument = gql`
Expand Down Expand Up @@ -117,7 +219,7 @@ export class DataIndexService {
public async fetchWorkflowInfos(args: {
definitionIds?: string[];
pagination?: Pagination;
filter?: FilterInfo;
filter?: Filter;
}): Promise<WorkflowInfo[]> {
this.logger.info(`fetchWorkflowInfos() called: ${this.dataIndexUrl}`);
const { definitionIds, pagination, filter } = args;
Expand All @@ -126,7 +228,10 @@ export class DataIndexService {
definitionIds !== undefined && definitionIds.length > 0
? `id: {in: ${JSON.stringify(definitionIds)}}`
: undefined;
const filterCondition = buildFilterCondition(filter);

const filterCondition = filter
? buildFilterCondition(await this.processDefinitionArguments, filter)
: '';

let whereClause: string | undefined;
if (definitionIds && filter) {
Expand Down Expand Up @@ -161,7 +266,7 @@ export class DataIndexService {
public async fetchInstances(args: {
definitionIds?: string[];
pagination?: Pagination;
filter?: FilterInfo;
filter?: Filter;
}): Promise<ProcessInstance[]> {
const { pagination, definitionIds, filter } = args;
if (pagination) pagination.sortField ??= FETCH_PROCESS_INSTANCES_SORT_FIELD;
Expand All @@ -170,7 +275,9 @@ export class DataIndexService {
const definitionIdsCondition = definitionIds
? `processId: {in: ${JSON.stringify(definitionIds)}}`
: undefined;
const filterCondition = buildFilterCondition(filter);
const filterCondition = filter
? buildFilterCondition(await this.processDefinitionArguments, filter)
: '';

let whereClause = '';
const conditions = [];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {
FilterInfo,
Filter,
ProcessInstance,
ProcessInstanceVariables,
WorkflowDefinition,
Expand Down Expand Up @@ -54,7 +54,7 @@ export class OrchestratorService {

public async fetchInstances(args: {
pagination?: Pagination;
filter?: FilterInfo;
filter?: Filter;
}): Promise<ProcessInstance[]> {
return await this.dataIndexService.fetchInstances({
definitionIds: this.workflowCacheService.definitionIds,
Expand Down Expand Up @@ -145,7 +145,7 @@ export class OrchestratorService {

public async fetchWorkflowOverviews(args: {
pagination?: Pagination;
filter?: FilterInfo;
filter?: Filter;
}): Promise<WorkflowOverview[] | undefined> {
return await this.sonataFlowService.fetchWorkflowOverviews({
definitionIds: this.workflowCacheService.definitionIds,
Expand Down
4 changes: 2 additions & 2 deletions plugins/orchestrator-backend/src/service/SonataFlowService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { LoggerService } from '@backstage/backend-plugin-api';

import {
extractWorkflowFormat,
FilterInfo,
Filter,
fromWorkflowSource,
getWorkflowCategory,
ProcessInstance,
Expand Down Expand Up @@ -66,7 +66,7 @@ export class SonataFlowService {
public async fetchWorkflowOverviews(args: {
definitionIds?: string[];
pagination?: Pagination;
filter?: FilterInfo;
filter?: Filter;
}): Promise<WorkflowOverview[] | undefined> {
const { definitionIds, pagination, filter } = args;
try {
Expand Down
6 changes: 3 additions & 3 deletions plugins/orchestrator-backend/src/service/api/v2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
AssessedProcessInstanceDTO,
ExecuteWorkflowRequestDTO,
ExecuteWorkflowResponseDTO,
FilterInfo,
Filter,
ProcessInstance,
ProcessInstanceListResultDTO,
ProcessInstanceState,
Expand Down Expand Up @@ -35,7 +35,7 @@ export class V2 {

public async getWorkflowsOverview(
pagination: Pagination,
filter?: FilterInfo,
filter?: Filter,
): Promise<WorkflowOverviewListResultDTO> {
const overviews = await this.orchestratorService.fetchWorkflowOverviews({
pagination,
Expand Down Expand Up @@ -89,7 +89,7 @@ export class V2 {

public async getInstances(
pagination?: Pagination,
filter?: FilterInfo,
filter?: Filter,
): Promise<ProcessInstanceListResultDTO> {
const instances = await this.orchestratorService.fetchInstances({
pagination,
Expand Down
Loading

0 comments on commit 10a6fdc

Please sign in to comment.