mirror of
https://github.com/status-im/spiff-arena.git
synced 2025-02-25 07:55:16 +00:00
added ability to filter process instances by process initiator
This commit is contained in:
parent
ee9e307d21
commit
ee650e6039
10
spiffworkflow-backend/bin/get_routes
Executable file
10
spiffworkflow-backend/bin/get_routes
Executable file
@ -0,0 +1,10 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
function error_handler() {
|
||||||
|
>&2 echo "Exited with BAD EXIT CODE '${2}' in ${0} script at line: ${1}."
|
||||||
|
exit "$2"
|
||||||
|
}
|
||||||
|
trap 'error_handler ${LINENO} $?' ERR
|
||||||
|
set -o errtrace -o errexit -o nounset -o pipefail
|
||||||
|
|
||||||
|
grep -E '^ +\/' src/spiffworkflow_backend/api.yml | sort
|
@ -1414,11 +1414,34 @@ paths:
|
|||||||
items:
|
items:
|
||||||
$ref: "#/components/schemas/Task"
|
$ref: "#/components/schemas/Task"
|
||||||
|
|
||||||
|
/users/search:
|
||||||
|
parameters:
|
||||||
|
- name: username_prefix
|
||||||
|
in: query
|
||||||
|
required: true
|
||||||
|
description: The prefix of the user
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- Users
|
||||||
|
operationId: spiffworkflow_backend.routes.users_controller.user_search
|
||||||
|
summary: Returns a list of users that the search param
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: list of users
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/User"
|
||||||
|
|
||||||
/user-groups/for-current-user:
|
/user-groups/for-current-user:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- Process Instances
|
- User Groups
|
||||||
operationId: spiffworkflow_backend.routes.process_api_blueprint.user_group_list_for_current_user
|
operationId: spiffworkflow_backend.routes.users_controller.user_group_list_for_current_user
|
||||||
summary: Group identifiers for current logged in user
|
summary: Group identifiers for current logged in user
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
"""User."""
|
"""User."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
import jwt
|
import jwt
|
||||||
import marshmallow
|
import marshmallow
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
@ -16,15 +18,18 @@ class UserNotFoundError(Exception):
|
|||||||
"""UserNotFoundError."""
|
"""UserNotFoundError."""
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
class UserModel(SpiffworkflowBaseDBModel):
|
class UserModel(SpiffworkflowBaseDBModel):
|
||||||
"""UserModel."""
|
"""UserModel."""
|
||||||
|
|
||||||
__tablename__ = "user"
|
__tablename__ = "user"
|
||||||
__table_args__ = (db.UniqueConstraint("service", "service_id", name="service_key"),)
|
__table_args__ = (db.UniqueConstraint("service", "service_id", name="service_key"),)
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
|
||||||
username = db.Column(
|
id: int = db.Column(db.Integer, primary_key=True)
|
||||||
|
username: str = db.Column(
|
||||||
db.String(255), nullable=False, unique=True
|
db.String(255), nullable=False, unique=True
|
||||||
) # should always be a unique value
|
)
|
||||||
|
|
||||||
service = db.Column(
|
service = db.Column(
|
||||||
db.String(255), nullable=False, unique=False
|
db.String(255), nullable=False, unique=False
|
||||||
) # not 'openid' -- google, aws
|
) # not 'openid' -- google, aws
|
||||||
|
@ -71,14 +71,6 @@ def permissions_check(body: Dict[str, Dict[str, list[str]]]) -> flask.wrappers.R
|
|||||||
return make_response(jsonify({"results": response_dict}), 200)
|
return make_response(jsonify({"results": response_dict}), 200)
|
||||||
|
|
||||||
|
|
||||||
def user_group_list_for_current_user() -> flask.wrappers.Response:
|
|
||||||
"""User_group_list_for_current_user."""
|
|
||||||
groups = g.user.groups
|
|
||||||
# TODO: filter out the default group and have a way to know what is the default group
|
|
||||||
group_identifiers = [i.identifier for i in groups if i.identifier != "everybody"]
|
|
||||||
return make_response(jsonify(sorted(group_identifiers)), 200)
|
|
||||||
|
|
||||||
|
|
||||||
def process_list() -> Any:
|
def process_list() -> Any:
|
||||||
"""Returns a list of all known processes.
|
"""Returns a list of all known processes.
|
||||||
|
|
||||||
|
@ -0,0 +1,26 @@
|
|||||||
|
"""users_controller."""
|
||||||
|
|
||||||
|
import flask
|
||||||
|
from spiffworkflow_backend.models.user import UserModel
|
||||||
|
from flask import make_response
|
||||||
|
from flask import jsonify
|
||||||
|
from flask import g
|
||||||
|
|
||||||
|
|
||||||
|
def user_search(username_prefix: str) -> flask.wrappers.Response:
|
||||||
|
"""User_search."""
|
||||||
|
found_users = UserModel.query.filter(UserModel.username.like(f"{username_prefix}%")).all() # type: ignore
|
||||||
|
|
||||||
|
response_json = {
|
||||||
|
"users": found_users,
|
||||||
|
"username_prefix": username_prefix,
|
||||||
|
}
|
||||||
|
return make_response(jsonify(response_json), 200)
|
||||||
|
|
||||||
|
|
||||||
|
def user_group_list_for_current_user() -> flask.wrappers.Response:
|
||||||
|
"""User_group_list_for_current_user."""
|
||||||
|
groups = g.user.groups
|
||||||
|
# TODO: filter out the default group and have a way to know what is the default group
|
||||||
|
group_identifiers = [i.identifier for i in groups if i.identifier != "everybody"]
|
||||||
|
return make_response(jsonify(sorted(group_identifiers)), 200)
|
@ -0,0 +1,41 @@
|
|||||||
|
"""test_users_controller."""
|
||||||
|
|
||||||
|
from tests.spiffworkflow_backend.helpers.base_test import BaseTest
|
||||||
|
from spiffworkflow_backend.models.user import UserModel
|
||||||
|
from flask.testing import FlaskClient
|
||||||
|
from flask.app import Flask
|
||||||
|
|
||||||
|
|
||||||
|
class TestUsersController(BaseTest):
|
||||||
|
"""TestUsersController."""
|
||||||
|
|
||||||
|
def test_user_search_returns_a_user(
|
||||||
|
self,
|
||||||
|
app: Flask,
|
||||||
|
client: FlaskClient,
|
||||||
|
with_db_and_bpmn_file_cleanup: None,
|
||||||
|
with_super_admin_user: UserModel,
|
||||||
|
) -> None:
|
||||||
|
"""Test_user_search_returns_a_user."""
|
||||||
|
self.find_or_create_user(username="aa")
|
||||||
|
self.find_or_create_user(username="ab")
|
||||||
|
self.find_or_create_user(username="abc")
|
||||||
|
self.find_or_create_user(username="ac")
|
||||||
|
|
||||||
|
self._assert_search_has_count(client, with_super_admin_user, 'aa', 1)
|
||||||
|
self._assert_search_has_count(client, with_super_admin_user, 'ab', 2)
|
||||||
|
self._assert_search_has_count(client, with_super_admin_user, 'ac', 1)
|
||||||
|
self._assert_search_has_count(client, with_super_admin_user, 'ad', 0)
|
||||||
|
self._assert_search_has_count(client, with_super_admin_user, 'a', 4)
|
||||||
|
|
||||||
|
def _assert_search_has_count(self, client: FlaskClient, with_super_admin_user: UserModel, username_prefix: str, expected_count: int) -> None:
|
||||||
|
"""_assert_search_has_count."""
|
||||||
|
response = client.get(
|
||||||
|
f"/v1.0/users/search?username_prefix={username_prefix}",
|
||||||
|
headers=self.logged_in_headers(with_super_admin_user),
|
||||||
|
)
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.json
|
||||||
|
assert response.json['users'] is not None
|
||||||
|
assert response.json['username_prefix'] == username_prefix
|
||||||
|
assert len(response.json['users']) == expected_count
|
@ -1,4 +1,4 @@
|
|||||||
import { useContext, useEffect, useMemo, useState } from 'react';
|
import { useContext, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
Link,
|
Link,
|
||||||
useNavigate,
|
useNavigate,
|
||||||
@ -60,6 +60,7 @@ import {
|
|||||||
ReportColumnForEditing,
|
ReportColumnForEditing,
|
||||||
ReportMetadata,
|
ReportMetadata,
|
||||||
ReportFilter,
|
ReportFilter,
|
||||||
|
User,
|
||||||
} from '../interfaces';
|
} from '../interfaces';
|
||||||
import ProcessModelSearch from './ProcessModelSearch';
|
import ProcessModelSearch from './ProcessModelSearch';
|
||||||
import ProcessInstanceReportSearch from './ProcessInstanceReportSearch';
|
import ProcessInstanceReportSearch from './ProcessInstanceReportSearch';
|
||||||
@ -134,10 +135,14 @@ export default function ProcessInstanceListTable({
|
|||||||
|
|
||||||
const [errorObject, setErrorObject] = (useContext as any)(ErrorContext);
|
const [errorObject, setErrorObject] = (useContext as any)(ErrorContext);
|
||||||
|
|
||||||
const processInstancePathPrefix =
|
const processInstanceListPathPrefix =
|
||||||
variant === 'all'
|
variant === 'all'
|
||||||
? '/admin/process-instances/all'
|
? '/admin/process-instances/all'
|
||||||
: '/admin/process-instances/for-me';
|
: '/admin/process-instances/for-me';
|
||||||
|
const processInstanceShowPathPrefix =
|
||||||
|
variant === 'all'
|
||||||
|
? '/admin/process-instances'
|
||||||
|
: '/admin/process-instances/for-me';
|
||||||
|
|
||||||
const [processStatusAllOptions, setProcessStatusAllOptions] = useState<any[]>(
|
const [processStatusAllOptions, setProcessStatusAllOptions] = useState<any[]>(
|
||||||
[]
|
[]
|
||||||
@ -164,6 +169,12 @@ export default function ProcessInstanceListTable({
|
|||||||
useState<ReportColumnForEditing | null>(null);
|
useState<ReportColumnForEditing | null>(null);
|
||||||
const [reportColumnFormMode, setReportColumnFormMode] = useState<string>('');
|
const [reportColumnFormMode, setReportColumnFormMode] = useState<string>('');
|
||||||
|
|
||||||
|
const [processInstanceInitiatorOptions, setProcessInstanceInitiatorOptions] =
|
||||||
|
useState<string[]>([]);
|
||||||
|
const [processInitiatorSelection, setProcessInitiatorSelection] =
|
||||||
|
useState<User | null>(null);
|
||||||
|
const lastRequestedInitatorSearchTerm = useRef<string>();
|
||||||
|
|
||||||
const dateParametersToAlwaysFilterBy: dateParameters = useMemo(() => {
|
const dateParametersToAlwaysFilterBy: dateParameters = useMemo(() => {
|
||||||
return {
|
return {
|
||||||
start_from: [setStartFromDate, setStartFromTime],
|
start_from: [setStartFromDate, setStartFromTime],
|
||||||
@ -529,7 +540,7 @@ export default function ProcessInstanceListTable({
|
|||||||
|
|
||||||
setErrorObject(null);
|
setErrorObject(null);
|
||||||
setProcessInstanceReportJustSaved(null);
|
setProcessInstanceReportJustSaved(null);
|
||||||
navigate(`${processInstancePathPrefix}?${queryParamString}`);
|
navigate(`${processInstanceListPathPrefix}?${queryParamString}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const dateComponent = (
|
const dateComponent = (
|
||||||
@ -628,7 +639,7 @@ export default function ProcessInstanceListTable({
|
|||||||
|
|
||||||
setErrorObject(null);
|
setErrorObject(null);
|
||||||
setProcessInstanceReportJustSaved(mode || null);
|
setProcessInstanceReportJustSaved(mode || null);
|
||||||
navigate(`${processInstancePathPrefix}${queryParamString}`);
|
navigate(`${processInstanceListPathPrefix}${queryParamString}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const reportColumns = () => {
|
const reportColumns = () => {
|
||||||
@ -976,6 +987,22 @@ export default function ProcessInstanceListTable({
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleProcessInstanceInitiatorSearchResult = (result: any) => {
|
||||||
|
if (lastRequestedInitatorSearchTerm.current === result.username_prefix) {
|
||||||
|
setProcessInstanceInitiatorOptions(result.users);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const searchForProcessInitiator = (inputText: string) => {
|
||||||
|
if (inputText) {
|
||||||
|
lastRequestedInitatorSearchTerm.current = inputText;
|
||||||
|
HttpService.makeCallToBackend({
|
||||||
|
path: `/users/search?username_prefix=${inputText}`,
|
||||||
|
successCallback: handleProcessInstanceInitiatorSearchResult,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const filterOptions = () => {
|
const filterOptions = () => {
|
||||||
if (!showFilterOptions) {
|
if (!showFilterOptions) {
|
||||||
return null;
|
return null;
|
||||||
@ -1008,7 +1035,27 @@ export default function ProcessInstanceListTable({
|
|||||||
selectedItem={processModelSelection}
|
selectedItem={processModelSelection}
|
||||||
/>
|
/>
|
||||||
</Column>
|
</Column>
|
||||||
<Column md={8}>{processStatusSearch()}</Column>
|
<Column md={4}>
|
||||||
|
<ComboBox
|
||||||
|
onInputChange={searchForProcessInitiator}
|
||||||
|
onChange={(event: any) => {
|
||||||
|
setProcessInitiatorSelection(event.selectedItem);
|
||||||
|
}}
|
||||||
|
id="process-instance-initiator-search"
|
||||||
|
data-qa="process-instance-initiator-search"
|
||||||
|
items={processInstanceInitiatorOptions}
|
||||||
|
itemToString={(processInstanceInitatorOption: User) => {
|
||||||
|
if (processInstanceInitatorOption) {
|
||||||
|
return processInstanceInitatorOption.username;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}}
|
||||||
|
placeholder="Process Initiator"
|
||||||
|
titleText="PROC"
|
||||||
|
selectedItem={processInitiatorSelection}
|
||||||
|
/>
|
||||||
|
</Column>
|
||||||
|
<Column md={4}>{processStatusSearch()}</Column>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid fullWidth className="with-bottom-margin">
|
<Grid fullWidth className="with-bottom-margin">
|
||||||
<Column md={4}>
|
<Column md={4}>
|
||||||
@ -1114,7 +1161,7 @@ export default function ProcessInstanceListTable({
|
|||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
data-qa="process-instance-show-link"
|
data-qa="process-instance-show-link"
|
||||||
to={`${processInstancePathPrefix}/${modifiedProcessModelId}/${id}`}
|
to={`${processInstanceShowPathPrefix}/${modifiedProcessModelId}/${id}`}
|
||||||
title={`View process instance ${id}`}
|
title={`View process instance ${id}`}
|
||||||
>
|
>
|
||||||
<span data-qa="paginated-entity-id">{id}</span>
|
<span data-qa="paginated-entity-id">{id}</span>
|
||||||
|
@ -1,3 +1,8 @@
|
|||||||
|
export interface User {
|
||||||
|
id: number;
|
||||||
|
username: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Secret {
|
export interface Secret {
|
||||||
id: number;
|
id: number;
|
||||||
key: string;
|
key: string;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user