Type ahead widget (#205)

This commit is contained in:
jbirddog 2023-04-05 14:27:20 -04:00 committed by GitHub
parent 4fc72f92b7
commit 20cec0f2a2
5 changed files with 133 additions and 1 deletions

1
.gitignore vendored
View File

@ -1,5 +1,6 @@
pyrightconfig.json
.idea/
t
*~
.dccache
*~

View File

@ -2089,6 +2089,37 @@ paths:
schema:
$ref: "#/components/schemas/Secret"
/connector-proxy/type-ahead/{category}:
parameters:
- name: category
in: path
required: true
description: The category for the type-ahead search
schema:
type: string
- name: prefix
in: query
required: true
description: The prefix to search for
schema:
type: string
- name: limit
in: query
required: true
description: The maximum number of search results
schema:
type: integer
get:
operationId: spiffworkflow_backend.routes.connector_proxy_controller.type_ahead
summary: Return type ahead search results
tags:
- Type Ahead
responses:
"200":
description: We return type ahead search results
#content:
# - application/json
components:
securitySchemes:
jwt:

View File

@ -41,6 +41,10 @@ SPIFFWORKFLOW_BACKEND_URL = environ.get("SPIFFWORKFLOW_BACKEND_URL", default="ht
SPIFFWORKFLOW_BACKEND_CONNECTOR_PROXY_URL = environ.get(
"SPIFFWORKFLOW_BACKEND_CONNECTOR_PROXY_URL", default="http://localhost:7004"
)
SPIFFWORKFLOW_BACKEND_CONNECTOR_PROXY_TYPE_AHEAD_URL = environ.get(
"SPIFFWORKFLOW_BACKEND_CONNECTOR_PROXY_TYPE_AHEAD_URL",
default="https://emehvlxpwodjawtgi7ctkbvpse0vmaow.lambda-url.us-east-1.on.aws",
)
# Open ID server
# use "http://localhost:7000/openid" for running with simple openid

View File

@ -0,0 +1,25 @@
from typing import Any
import flask.wrappers
import requests
from flask import current_app
from flask.wrappers import Response
def connector_proxy_type_ahead_url() -> Any:
"""Returns the connector proxy type ahead url."""
return current_app.config["SPIFFWORKFLOW_BACKEND_CONNECTOR_PROXY_TYPE_AHEAD_URL"]
def type_ahead(category: str, prefix: str, limit: int) -> flask.wrappers.Response:
url = f"{connector_proxy_type_ahead_url()}/v1/type-ahead/{category}?prefix={prefix}&limit={limit}"
proxy_response = requests.get(url)
status = proxy_response.status_code
if status // 100 == 2:
response = proxy_response.text
else:
# supress pop up errors on the client
status = 200
response = "[]"
return Response(response, status=status, mimetype="application/json")

View File

@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react';
import React, { useEffect, useRef, useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import validator from '@rjsf/validator-ajv8';
@ -8,6 +8,7 @@ import {
Tabs,
Grid,
Column,
ComboBox,
Button,
ButtonSet,
// @ts-ignore
@ -22,6 +23,73 @@ import { modifyProcessIdentifierForPathParam } from '../helpers';
import { ProcessInstanceTask } from '../interfaces';
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
// TODO: move this somewhere else
function TypeAheadWidget({
id,
onChange,
options: { category, itemFormat },
}: {
id: string;
onChange: any;
options: any;
}) {
const pathForCategory = (inputText: string) => {
return `/connector-proxy/type-ahead/${category}?prefix=${inputText}&limit=100`;
};
const lastSearchTerm = useRef('');
const [items, setItems] = useState<any[]>([]);
const [selectedItem, setSelectedItem] = useState<any>(null);
const itemFormatRegex = /[^{}]+(?=})/g;
const itemFormatSubstitutions = itemFormat.match(itemFormatRegex);
const itemToString = (item: any) => {
if (!item) {
return null;
}
let str = itemFormat;
itemFormatSubstitutions.forEach((key: string) => {
str = str.replace(`{${key}}`, item[key]);
});
return str;
};
const handleTypeAheadResult = (result: any, inputText: string) => {
if (lastSearchTerm.current === inputText) {
setItems(result);
}
};
const typeAheadSearch = (inputText: string) => {
if (inputText) {
lastSearchTerm.current = inputText;
// TODO: check cache of prefixes -> results
HttpService.makeCallToBackend({
path: pathForCategory(inputText),
successCallback: (result: any) =>
handleTypeAheadResult(result, inputText),
});
}
};
return (
<ComboBox
onInputChange={typeAheadSearch}
onChange={(event: any) => {
setSelectedItem(event.selectedItem);
onChange(itemToString(event.selectedItem));
}}
id={id}
items={items}
itemToString={itemToString}
placeholder={`Start typing to search for ${category}...`}
titleText={`Type ahead search for ${category}`}
selectedItem={selectedItem}
/>
);
}
class UnexpectedHumanTaskType extends Error {
constructor(message: string) {
super(message);
@ -294,6 +362,8 @@ export default function TaskShow() {
return getFieldsWithDateValidations(jsonSchema, formData, errors);
};
const widgets = { typeAhead: TypeAheadWidget };
return (
<Grid fullWidth condensed>
<Column sm={4} md={5} lg={8}>
@ -303,6 +373,7 @@ export default function TaskShow() {
onSubmit={handleFormSubmit}
schema={jsonSchema}
uiSchema={formUiSchema}
widgets={widgets}
validator={validator}
onChange={updateFormData}
customValidate={customValidate}