Type ahead widget (#205)
This commit is contained in:
parent
aa0a4680af
commit
10e665ac48
|
@ -1,5 +1,6 @@
|
|||
pyrightconfig.json
|
||||
.idea/
|
||||
t
|
||||
*~
|
||||
.dccache
|
||||
*~
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
|
@ -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}
|
||||
|
|
Loading…
Reference in New Issue