Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions public/locales/de-DE/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@
},
"processes": {
"title": "Prozessdefinitionen",
"filter-placeholder": "Name / Schlüssel suchen",
"version": "Version",
"change-definition": "Definition wechseln",
"definition-id": "Definitions-ID",
Expand Down
1 change: 1 addition & 0 deletions public/locales/en-US/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@
},
"processes": {
"title": "Process Definitions",
"filter-placeholder": "Search Name / Key",
"version": "Version",
"change-definition": "Change Definition",
"definition-id": "Definition ID",
Expand Down
1 change: 1 addition & 0 deletions public/locales/es-ES/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@
},
"processes": {
"title": "Definiciones de procesos",
"filter-placeholder": "Buscar nombre / clave",
"version": "Versión",
"change-definition": "Cambiar definición",
"definition-id": "ID de definición",
Expand Down
1 change: 1 addition & 0 deletions public/locales/fr-FR/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@
},
"processes": {
"title": "Définitions de processus",
"filter-placeholder": "Chercher nom / clé",
"version": "Version",
"change-definition": "Changer de définition",
"definition-id": "ID de définition",
Expand Down
1 change: 1 addition & 0 deletions public/locales/nl-NL/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@
},
"processes": {
"title": "Procesdefinities",
"filter-placeholder": "Naam / sleutel zoeken",
"version": "Versie",
"change-definition": "Definitie wijzigen",
"definition-id": "Definitie-ID",
Expand Down
39 changes: 35 additions & 4 deletions src/api/resources/process_definition.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,38 @@
import { GET, GET_TEXT, POST } from '../helper.jsx'

export const get_process_definitions = (state) =>
GET('/process-definition/statistics', state, state.api.process.definition.list)
import { GET, GET_TEXT, POST, RESPONSE_STATE } from '../helper.jsx';

export const get_process_definitions = async (state, queryString = '') => {
if (!queryString) {
return GET('/process-definition/statistics', state, state.api.process.definition.list);
}
const defsSignal = { value: null };
const statsSignal = { value: null };

await Promise.all([
GET(`/process-definition?${queryString}`, state, defsSignal),
GET('/process-definition/statistics', state, statsSignal)
]);
if (defsSignal.value.status === RESPONSE_STATE.ERROR || statsSignal.value.status === RESPONSE_STATE.ERROR) {
state.api.process.definition.list.value = {
status: RESPONSE_STATE.ERROR,
error: defsSignal.value.error || statsSignal.value.error
};
return;
}

const filteredDefs = defsSignal.value.data;
const allStats = statsSignal.value.data;

const validDefinitionIds = new Set(filteredDefs.map(def => def.id));

const finalFilteredStats = allStats.filter(stat =>
validDefinitionIds.has(stat.id || stat.definition?.id)
);

state.api.process.definition.list.value = {
status: RESPONSE_STATE.SUCCESS,
data: finalFilteredStats
};
};

export const get_process_definition_statistics_with_incidents = (state, id) =>
GET(`/process-definition/${id}/statistics?incidents=true`, state, state.api.process.definition.statistics)
Expand Down
15 changes: 15 additions & 0 deletions src/assets/icons.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -169,3 +169,18 @@ export const arrows_pointing_out = () =>
(<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="size-6">
<path strokeLinecap="round" strokeLinejoin="round" d="M3.75 3.75v4.5m0-4.5h4.5m-4.5 0L9 9M3.75 20.25v-4.5m0 4.5h4.5m-4.5 0L9 15M20.25 3.75h-4.5m4.5 0v4.5m0-4.5L15 9m5.25 11.25h-4.5m4.5 0v-4.5m0 4.5L15 15" />
</svg>)

export const save = () => (
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
className="size-6">
<path stroke-linecap="round" stroke-linejoin="round"
d="M20.25 6.375c0 2.278-3.694 4.125-8.25 4.125S3.75 8.653 3.75 6.375m16.5 0c0-2.278-3.694-4.125-8.25-4.125S3.75 4.097 3.75 6.375m16.5 0v11.25c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125V6.375m16.5 0v3.75m-16.5-3.75v3.75m16.5 0v3.75C20.25 16.153 16.556 18 12 18s-8.25-1.847-8.25-4.125v-3.75m16.5 0c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125" />
</svg>
);

export const x_mark = () => (
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={2} stroke="currentColor"
style={{ width: '12px', height: '12px' }}>
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18 18 6M6 6l12 12" />
</svg>
);
263 changes: 263 additions & 0 deletions src/components/AdvancedFilter.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
import { h } from 'preact';

Check warning on line 1 in src/components/AdvancedFilter.jsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this unused import of 'h'.

See more on https://sonarcloud.io/project/issues?id=operaton_web-apps&issues=AZ3YuYTK8dludof5nSXo&open=AZ3YuYTK8dludof5nSXo&pullRequest=57
import { useState, useEffect, useRef, useContext } from 'preact/hooks';
import { AppState } from '../state.js'; // 👈 Import your context
import '../css/components.css';
import engine_rest from "../api/engine_rest.jsx";
import * as Icons from '../assets/icons.jsx';
import { chevron_down, link_out } from "../assets/icons.jsx";

const AVAILABLE_FIELDS = ['Name', 'Key', 'State', 'Tenant ID'];
const AVAILABLE_OPERATORS = ['like', '=', '!='];

export function AdvancedFilter() {
const state = useContext(AppState);

const [activeFilters, setActiveFilters] = useState([]);
const [currentStep, setCurrentStep] = useState('FIELD');
const [draftFilter, setDraftFilter] = useState({ field: null, operator: null });
const [inputValue, setInputValue] = useState('');
const [isDropdownOpen, setIsDropdownOpen] = useState(false);

// New state for the Saved Queries dropdown
const [isSavedQueriesMenuOpen, setIsSavedQueriesMenuOpen] = useState(false);
const [savedQueriesList, setSavedQueriesList] = useState([]);

const inputRef = useRef(null);

// Trigger backend request whenever activeFilters change
useEffect(() => {
if (!state) return;

const params = new URLSearchParams();

params.append('firstResult', '0');
params.append('maxResults', '50');
params.append('sortBy', 'name');
params.append('sortOrder', 'asc');

activeFilters.forEach(filter => {
const { field, operator, value } = filter;
if (!value) return;

let apiKey = '';
let apiValue = value;

if (field === 'Name') {
apiKey = operator === 'like' ? 'nameLike' : 'name';
apiValue = operator === 'like' ? `%${value}%` : value;
} else if (field === 'Key') {
apiKey = operator === 'like' ? 'keyLike' : 'key';
apiValue = operator === 'like' ? `%${value}%` : value;
}

if (apiKey) params.append(apiKey, apiValue);
});

const queryString = params.toString();

console.log("Sending Request to Backend with URL query:", queryString);

void engine_rest.process_definition.list(state, queryString);

Check failure on line 60 in src/components/AdvancedFilter.jsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this use of the "void" operator.

See more on https://sonarcloud.io/project/issues?id=operaton_web-apps&issues=AZ3YuYTK8dludof5nSXp&open=AZ3YuYTK8dludof5nSXp&pullRequest=57

}, [activeFilters, state]);

const saveToLocalStorage = () => {
if (activeFilters.length === 0) {
alert("Cannot save an empty query!");
return;
}

let existingSaved = JSON.parse(localStorage.getItem('savedQueries') || '[]');

// Migration: If the old format was just a single array of objects, wrap it
if (existingSaved.length > 0 && !Array.isArray(existingSaved[0])) {
existingSaved = [existingSaved];
}

// Prevent saving exact duplicates
const newQueryStr = JSON.stringify(activeFilters);
const isDuplicate = existingSaved.some(q => JSON.stringify(q) === newQueryStr);

if (!isDuplicate) {

Check warning on line 81 in src/components/AdvancedFilter.jsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Unexpected negated condition.

See more on https://sonarcloud.io/project/issues?id=operaton_web-apps&issues=AZ3YuYTK8dludof5nSXq&open=AZ3YuYTK8dludof5nSXq&pullRequest=57
existingSaved.push(activeFilters);
localStorage.setItem('savedQueries', JSON.stringify(existingSaved));
alert('Query saved to local storage!');
} else {
alert('This query is already saved!');
}
};

const toggleSavedQueriesMenu = () => {
if (!isSavedQueriesMenuOpen) {
let saved = JSON.parse(localStorage.getItem('savedQueries') || '[]');
if (saved.length > 0 && !Array.isArray(saved[0])) {
saved = [saved]; // Migration catch for rendering
}
setSavedQueriesList(saved);
}
setIsSavedQueriesMenuOpen(!isSavedQueriesMenuOpen);
};

const applySavedQuery = (query) => {
setActiveFilters(query);
setIsSavedQueriesMenuOpen(false);
};

const resetInputState = () => {
setCurrentStep('FIELD');
setDraftFilter({ field: null, operator: null });
setInputValue('');
setIsDropdownOpen(false);
};

const handleInputFocus = () => {
if (currentStep !== 'VALUE') {
setIsDropdownOpen(true);
}
};

const handleInputChange = (e) => {
const val = e.target.value;
setInputValue(val);

if (val.trim() === '') {
resetInputState();
setIsDropdownOpen(true);
}
};

const handleKeyDown = (e) => {
if (e.key === 'Enter' && currentStep === 'VALUE') {
const prefix = `${draftFilter.field} ${draftFilter.operator} `;
if (inputValue.startsWith(prefix)) {
const extractedValue = inputValue.substring(prefix.length).trim();

if (extractedValue) {
setActiveFilters([...activeFilters, { ...draftFilter, value: extractedValue }]);
resetInputState();
}
}
}

if (e.key === 'Backspace' && inputValue === '') {
if (activeFilters.length > 0 && currentStep === 'FIELD') {
const newFilters = [...activeFilters];
newFilters.pop();
setActiveFilters(newFilters);
}
}
};

const handleOptionSelect = (option) => {
if (currentStep === 'FIELD') {
setDraftFilter({ field: option, operator: null });
setCurrentStep('OPERATOR');
setInputValue(`${option} `);
} else if (currentStep === 'OPERATOR') {
setDraftFilter(prev => ({ ...prev, operator: option }));
setCurrentStep('VALUE');
setInputValue(`${draftFilter.field} ${option} `);
setIsDropdownOpen(false);
}

setTimeout(() => inputRef.current?.focus(), 0);
};

const removeFilter = (indexToRemove) => {
setActiveFilters(activeFilters.filter((_, index) => index !== indexToRemove));
};

const dropdownOptions = currentStep === 'FIELD' ? AVAILABLE_FIELDS : AVAILABLE_OPERATORS;

return (
<div className="filter-bar">
<div className="pills-container">
{activeFilters.map((filter, index) => (
<div key={index} className="filter-pill">

Check warning on line 176 in src/components/AdvancedFilter.jsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Do not use Array index in keys

See more on https://sonarcloud.io/project/issues?id=operaton_web-apps&issues=AZ3YuYTK8dludof5nSXr&open=AZ3YuYTK8dludof5nSXr&pullRequest=57
<span className="remove-btn" onClick={() => removeFilter(index)}>×</span>

Check warning on line 177 in src/components/AdvancedFilter.jsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Avoid non-native interactive elements. If using native HTML is not possible, add an appropriate role and support for tabbing, mouse, keyboard, and touch inputs to an interactive content element.

See more on https://sonarcloud.io/project/issues?id=operaton_web-apps&issues=AZ3YuYTK8dludof5nSXs&open=AZ3YuYTK8dludof5nSXs&pullRequest=57

Check warning on line 177 in src/components/AdvancedFilter.jsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Visible, non-interactive elements with click handlers must have at least one keyboard listener.

See more on https://sonarcloud.io/project/issues?id=operaton_web-apps&issues=AZ3YuYTK8dludof5nSXt&open=AZ3YuYTK8dludof5nSXt&pullRequest=57
{filter.field} {filter.operator} {filter.value}
</div>
))}
</div>

<div className="input-wrapper">
<input
ref={inputRef}
type="text"
value={inputValue}
placeholder={activeFilters.length === 0 && currentStep === 'FIELD' ? "Add criteria" : ""}
className={currentStep !== 'FIELD' && inputValue ? 'active-typing' : ''}
onFocus={handleInputFocus}
onBlur={() => setTimeout(() => setIsDropdownOpen(false), 150)}
onInput={handleInputChange}
onKeyDown={handleKeyDown}
autocomplete="off"
/>

{isDropdownOpen && (
<ul className="dropdown-menu visible">
{dropdownOptions.map(option => (
<li
key={option}
onMouseDown={(e) => {
e.preventDefault();
handleOptionSelect(option);
}}
>

Check warning on line 206 in src/components/AdvancedFilter.jsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Non-interactive elements should not be assigned mouse or keyboard event listeners.

See more on https://sonarcloud.io/project/issues?id=operaton_web-apps&issues=AZ3YuYTK8dludof5nSXu&open=AZ3YuYTK8dludof5nSXu&pullRequest=57
{option}
</li>
))}
</ul>
)}
</div>

<div className="actions">
<span className="count">{activeFilters.length}</span>
<span className="action-btn" title="Copy Link"><Icons.link_out /></span>

Check warning on line 216 in src/components/AdvancedFilter.jsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Imported JSX component link_out must be in PascalCase

See more on https://sonarcloud.io/project/issues?id=operaton_web-apps&issues=AZ3YuYTK8dludof5nSXv&open=AZ3YuYTK8dludof5nSXv&pullRequest=57
<span className="action-btn" onClick={saveToLocalStorage} title="Save Query"><Icons.save /></span>

Check warning on line 217 in src/components/AdvancedFilter.jsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Visible, non-interactive elements with click handlers must have at least one keyboard listener.

See more on https://sonarcloud.io/project/issues?id=operaton_web-apps&issues=AZ3YuYTK8dludof5nSXx&open=AZ3YuYTK8dludof5nSXx&pullRequest=57

Check warning on line 217 in src/components/AdvancedFilter.jsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Avoid non-native interactive elements. If using native HTML is not possible, add an appropriate role and support for tabbing, mouse, keyboard, and touch inputs to an interactive content element.

See more on https://sonarcloud.io/project/issues?id=operaton_web-apps&issues=AZ3YuYTK8dludof5nSXw&open=AZ3YuYTK8dludof5nSXw&pullRequest=57

Check warning on line 217 in src/components/AdvancedFilter.jsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Imported JSX component save must be in PascalCase

See more on https://sonarcloud.io/project/issues?id=operaton_web-apps&issues=AZ3YuYTK8dludof5nSXy&open=AZ3YuYTK8dludof5nSXy&pullRequest=57

<div style={{ position: 'relative', display: 'flex', alignItems: 'center' }}>
<span
className="action-btn"
title="Load Saved Query"
onClick={toggleSavedQueriesMenu}
style={{ fontSize: '12px', marginLeft: '-6px' }}
>

Check warning on line 225 in src/components/AdvancedFilter.jsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Visible, non-interactive elements with click handlers must have at least one keyboard listener.

See more on https://sonarcloud.io/project/issues?id=operaton_web-apps&issues=AZ3YuYTK8dludof5nSX0&open=AZ3YuYTK8dludof5nSX0&pullRequest=57

Check warning on line 225 in src/components/AdvancedFilter.jsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Avoid non-native interactive elements. If using native HTML is not possible, add an appropriate role and support for tabbing, mouse, keyboard, and touch inputs to an interactive content element.

See more on https://sonarcloud.io/project/issues?id=operaton_web-apps&issues=AZ3YuYTK8dludof5nSXz&open=AZ3YuYTK8dludof5nSXz&pullRequest=57
<Icons.chevron_down />

Check warning on line 226 in src/components/AdvancedFilter.jsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Imported JSX component chevron_down must be in PascalCase

See more on https://sonarcloud.io/project/issues?id=operaton_web-apps&issues=AZ3YuYTK8dludof5nSX1&open=AZ3YuYTK8dludof5nSX1&pullRequest=57
</span>

{isSavedQueriesMenuOpen && (
<ul
className="dropdown-menu visible"
style={{
right: 0,
left: 'auto',
minWidth: '220px',
maxHeight: '300px',
overflowY: 'auto'
}}
>
{savedQueriesList.length === 0 ? (
<li style={{ color: '#999', cursor: 'default' }}>No saved queries yet.</li>
) : (
savedQueriesList.map((query, index) => (
<li
key={index}

Check warning on line 245 in src/components/AdvancedFilter.jsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Do not use Array index in keys

See more on https://sonarcloud.io/project/issues?id=operaton_web-apps&issues=AZ3YuYTK8dludof5nSX3&open=AZ3YuYTK8dludof5nSX3&pullRequest=57
onMouseDown={(e) => {
e.preventDefault();
applySavedQuery(query);
}}
style={{ borderBottom: '1px solid #eee' }}
>

Check warning on line 251 in src/components/AdvancedFilter.jsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Non-interactive elements should not be assigned mouse or keyboard event listeners.

See more on https://sonarcloud.io/project/issues?id=operaton_web-apps&issues=AZ3YuYTK8dludof5nSX2&open=AZ3YuYTK8dludof5nSX2&pullRequest=57
{/* Render the saved query beautifully as a text string */}
{query.map(f => `${f.field} ${f.operator} "${f.value}"`).join(' AND ')}
</li>
))
)}
</ul>
)}
</div>
</div>
</div>
);
}
Loading