Skip to content

Conversation

@Maria-Lordwill
Copy link
Collaborator

@Maria-Lordwill Maria-Lordwill commented Nov 11, 2025

Move workflows filters to the header by introducing Redux Toolkit slice, new filter select components, and replacing /templates/titles with /templates/titles-by-workflows and /templates/titles-by-tasks returning count

Replace legacy workflows actions with a Redux Toolkit slice, add header filter components (templates, starters, tasks, performers), and switch templates titles APIs to /templates/titles-by-workflows and /templates/titles-by-tasks with responses keyed by count; backend adds corresponding viewset actions, queries, and serializers, and updates tests.

📍Where to Start

Start with the workflows endpoints in TemplateViewSet in template.py, then follow the queries in queries.py and the frontend slice in slice.ts.

Changes since #70 opened

  • Converted CSS dimensions from pixel units to rem units across multiple components [af7f995]
  • Changed letter-spacing values from em units to rem units across modal, dropdown, and table components [af7f995]
  • Added CSS custom properties for color standardization and replaced literal color values with variable references [af7f995]
  • Added selector functions across Redux state slices for accessing workflow, task, template, account, authentication user, and group state [f9681f7]
  • Refactored workflow-related components to use dedicated Redux selectors instead of inline state access [f9681f7]
  • Updated Redux saga files to import selectors from correct modules and use new selector functions [f9681f7]
  • Changed FilterSelect component reset option rendering condition from truthy check to explicit null comparison and added aria-label attribute to clear button [f16d1a8]
  • Replaced direct Redux state selection with memoized selector functions in WorkflowsTable and WorkflowsLayout components [f16d1a8]
  • Improved ResizeObserver cleanup and lifecycle management in WorkflowsTable and WorkflowsLayout components [f16d1a8]
  • Refactored utility functions in helpers module to use modern patterns and reduce complexity [f16d1a8]
  • Renamed parameters in HighlightsFeed component to avoid variable shadowing [f16d1a8]
  • Removed lint and prettier disable directives across multiple files [f16d1a8]
  • Added keyboard navigation support to modal close action and password visibility toggle [59095af]
  • Associated input label with input field using unique identifier [59095af]
  • Set explicit button type to prevent form submission [59095af]
  • Refactored import statements and component props [59095af]
  • Integrated performersGroupsIdsFilter into the WorkflowsLayout component by importing and using getWorkflowPerformersGroupsIdsFilter selector, tracking the filter value with prevPerformersGroupsIdsFilterRef, including it in currentFiltersValuesRef and dependenciesRefs, adding it to the dependency array of the effect that triggers applyFilters, and introduced isFirstRenderRef to ensure updateCurrentPerformersCounters runs on the initial render regardless of filter changes [ec615ed]
  • Added getWorkflowPerformersGroupsIdsFilter selector to retrieve state.workflows.workflowsSettings.values.performersGroupIdsFilter from Redux state [ec615ed]
  • Removed .dropdown__active-value CSS rule from the Select component stylesheet [ec615ed]

📊 Macroscope summarized ec615ed. 43 files reviewed, 28 issues evaluated, 27 issues filtered, 1 comment posted

🗂️ Filtered Issues

backend/src/processes/queries.py — 0 comments posted, 1 evaluated, 1 filtered
  • line 1738: The type annotation user: User on line 1738 uses User which is not imported in backend/src/processes/queries.py. The file shows UserModel = get_user_model() at line 34, and TemplateTitlesByEventsQuery at line 1674 correctly uses user: UserModel. This inconsistency will cause a NameError: name 'User' is not defined when the module is loaded. [ Already posted ]
backend/src/processes/views/template.py — 0 comments posted, 3 evaluated, 3 filtered
  • line 154: The action method was renamed from titles to titles_by_workflows (as shown in code object 5's diff), but the permissions check at line 154 still references 'titles' instead of 'titles_by_workflows'. This causes the titles_by_workflows action to fall through to the default permissions case at line 180-186, which requires UserIsAdminOrAccountOwner() permission that wasn't required before. Non-admin users who could previously access this endpoint will now receive permission denied errors. [ Already posted ]
  • line 154: The get_permissions method at line 154 still references the old action name 'titles' instead of the new name 'titles_by_workflows'. Since action_serializer_classes was changed to rename 'titles' to 'titles_by_workflows', the permission check will never match for the titles_by_workflows action. This causes requests to titles_by_workflows to fall through to the default permissions (lines 180-186) which incorrectly requires UserIsAdminOrAccountOwner(), making the endpoint more restrictive than intended. [ Already posted ]
  • line 154: The get_permissions() method at line 154 checks for action name 'titles', but the action was renamed to 'titles_by_workflows' (line 488). This causes the titles_by_workflows action to fall through to the default permissions at lines 180-186, which incorrectly requires UserIsAdminOrAccountOwner() permission instead of the intended less restrictive permissions. Users who should have access to this endpoint will receive permission denied errors. [ Already posted ]
frontend/src/public/components/TuneViewModal/TuneViewModal.tsx — 0 comments posted, 1 evaluated, 1 filtered
  • line 53: The useEffect dependency array on line 53 is missing savedFields. The effect uses savedFields to initialize selectedFields state, but if savedFields changes in Redux while isOpen, templateId, and templateTasks remain the same, the effect won't re-run and the component will display stale data. [ Out of scope ]
frontend/src/public/components/UI/Select/FilterSelect.tsx — 0 comments posted, 2 evaluated, 2 filtered
  • line 108: When groupedOptions is provided but flatGroupedOptions is not, allOptions falls back to options (line 108). However, the actual selectable options exist within groupedOptions.values().options. This means renderPlaceholder(allOptions) at line 374, the select all functionality at lines 247-250, and the areAllSelected check at lines 257-261 will operate on the wrong set of options, potentially causing incorrect counts, missing selections, or the select all feature to not work correctly with grouped options. [ Already posted ]
  • line 284: The condition changed from props.selectedOption && renderResetOption() to selectedOption !== null && renderResetOption(). In multiple select mode (isMultiple: true), selectedOption is typed as never and will be undefined at runtime. Since undefined !== null evaluates to true, renderResetOption() will now be called in multiple select mode when it wasn't before. If noValueLabel is provided, this will render an unnecessary reset option item in the dropdown for multi-select, which is likely unintended. [ Already posted ]
frontend/src/public/components/UI/Select/SelectMenu.tsx — 0 comments posted, 1 evaluated, 1 filtered
  • line 97: The CSS class radioStyles['select-menu__radio--checked'] on line 97 appears to reference a class that likely doesn't exist in RadioButton.css. The naming convention select-menu__radio--checked is inconsistent with the other radio style classes used (radio, radio__box, radio__title) which follow standard BEM naming. This would result in the checked state styling not being applied when withRadio is true and an item is selected. [ Low confidence ]
frontend/src/public/components/Workflows/WorkflowsGridPage/WorkflowsGridPage.tsx — 0 comments posted, 4 evaluated, 4 filtered
  • line 29: useCallback(debounce(debounceTime, onSearch), []) has an empty dependency array but captures onSearch and debounceTime. If onSearch prop changes, the debounced function will continue calling the stale callback. The dependency array should include onSearch and debounceTime, or use useRef to hold the latest callback. [ Out of scope ]
  • line 147: The key={item} prop on line 147 and 155 will be undefined for each item since the array contains empty slots. Even if the iteration bug is fixed, using the item value as a key when items are all null or identical values will cause React key warnings and potential rendering issues. Should use the index: map((_, index) => <WorkflowCardLoader key={index} />). [ Out of scope ]
  • line 147: Array(INIT_SKELETION_QUANTITY).map() does not iterate over empty slots created by Array(n). The callback is never executed, so loader will be an empty array and no skeleton loaders will render. Should use Array.from({ length: INIT_SKELETION_QUANTITY }) or Array(INIT_SKELETION_QUANTITY).fill(null).map(). [ Out of scope ]
  • line 155: Array(SCROLL_LOADERS_QUANTITY).map() has the same bug - empty slots are not iterated. The infinite scroll loader will never display skeleton cards. Should use Array.from({ length: SCROLL_LOADERS_QUANTITY }) or Array(SCROLL_LOADERS_QUANTITY).fill(null).map(). [ Out of scope ]
frontend/src/public/components/Workflows/WorkflowsTablePage/WorkflowsTable/WorkflowsTable.tsx — 0 comments posted, 3 evaluated, 3 filtered
  • line 99: The savedGlobalWidths and savedOptionalWidths are read from localStorage on every render (lines 99-104) rather than being memoized. When currentUser is initially null/undefined during auth loading, the localStorage key becomes workflow-column-widths-undefined-global, potentially loading incorrect cached data. Once auth completes and currentUser.id is available, the component re-reads localStorage with the correct key, but the initial render already used stale/wrong widths which are then used to initialize column widths in the useEffect at lines 378-393. [ Out of scope ]
  • line 112: The debounceOnSearch callback at line 112 has an empty dependency array [], but it captures onSearch from props. If the parent component passes a different onSearch function on re-render, the debounced callback will continue using the stale original reference, potentially calling an outdated handler. [ Low confidence ]
  • line 395: At line 395, createResizeHandler is called with currentUser?.id and templatesIdsFilter[0]. If currentUser is null/undefined (before auth state loads) or if templatesIdsFilter is empty, these will be undefined. This passes undefined to the resize handler which then saves column widths to localStorage with keys containing 'undefined', causing width data to be saved to wrong keys and lost when the user/template values become available. [ Already posted ]
frontend/src/public/components/Workflows/WorkflowsTablePage/WorkflowsTable/WorkflowsTableActions.tsx — 0 comments posted, 1 evaluated, 1 filtered
  • line 31: The isDisabled logic was changed from && to || operators, fundamentally altering when the tune view button is disabled. Previously: disabled when templatesIdsFilter.length !== 1 AND (status === Loaded || status === EmptyList). Now: disabled when templatesIdsFilter.length !== 1 OR status === LoadingList. This means during LoadingNextPage with multiple templates, the button is now enabled (was disabled), and during LoadingList with exactly 1 template, the button is now disabled (was enabled). If the old behavior was correct, this introduces a regression. [ Low confidence ]
frontend/src/public/components/icons/PerformerFilterIcon.tsx — 0 comments posted, 1 evaluated, 1 filtered
  • line 4: The component declares React.SVGAttributes<SVGElement> as its props type, which allows callers to pass any SVG attribute (like onClick, aria-label, style, etc.), but only className is destructured and passed to the <svg> element. All other props are silently ignored, which could lead to unexpected behavior if callers expect those props to be applied. [ Already posted ]
frontend/src/public/components/icons/StartRoundIcon.tsx — 0 comments posted, 1 evaluated, 1 filtered
  • line 6: The component now only extracts fill and className from props, discarding all other props. Previously, the component used ...rest spread to forward additional props (like onClick, aria-label, style, id, tabIndex, etc.) to the underlying <svg> element. Any code that passes additional props to StartRoundIcon will now silently ignore those props, breaking event handlers, accessibility attributes, and styling. [ Already posted ]
frontend/src/public/components/icons/TableViewIcon.tsx — 0 comments posted, 1 evaluated, 1 filtered
  • line 4: The component accepts React.SVGAttributes<SVGElement> as props but only destructures fill without spreading remaining props to the <svg> element. If callers pass other standard SVG attributes like className, style, onClick, or aria-label, they will be silently ignored, causing unexpected behavior. [ Low confidence ]
frontend/src/public/layout/WorkflowsLayout/PerformerFilterSelect.tsx — 0 comments posted, 2 evaluated, 2 filtered
  • line 57: The useMemo dependency array [groups, performersCounters] on line 57 should include performersCountersMap since that variable is used inside the memoized callback. While this works due to the indirect dependency chain (when performersCounters changes, performersCountersMap is recalculated), React's hooks rules expect direct dependencies to be listed. [ Low confidence ]
  • line 79: The useMemo dependency array [activeUsers, performersCounters] on line 79 uses performersCountersMap inside the callback but lists performersCounters as the dependency instead of performersCountersMap. This is the same indirect dependency issue as the performersGroupOptions memoization. [ Already posted ]
frontend/src/public/layout/WorkflowsLayout/TaskFilterSelect.tsx — 1 comment posted, 2 evaluated, 1 filtered
  • line 52: The spread ...(count && { count }) on line 52 will not include count when its value is 0 because 0 is falsy. If count: 0 is a valid value that should be displayed (e.g., showing that a step has zero workflows), this value will be omitted from the options object. [ Already posted ]
frontend/src/public/layout/WorkflowsLayout/WorkflowsLayout.tsx — 0 comments posted, 3 evaluated, 3 filtered
  • line 92: The currentFiltersValuesRef is initialized with prop/selector values at line 92-100, but these values become stale immediately after initialization since refs don't update on re-renders. The synchronization effect at lines 271-289 updates this ref, but there's a timing window during the first render where currentFiltersValuesRef.current may have stale values if accessed by the useLayoutEffect at line 297 before the sync effect runs. [ Low confidence ]
  • line 234: The useEffect at line 230-234 that calls updateWorkflowsTemplateStepsCounters() had templatesIdsFilter removed from its dependency array (replaced with stepsIdsFilter). If step counters need to be recalculated when the template filter changes, this is now broken - changing templatesIdsFilter will no longer trigger updateWorkflowsTemplateStepsCounters(). [ Already posted ]
  • line 311: Using changedFiltersRef.current.size as a dependency in useLayoutEffect (line 311) is unreliable. React's dependency array comparison does not track ref mutations - refs don't trigger re-renders. While this may work incidentally when other state changes cause re-renders, if changedFiltersRef.current is modified without an accompanying state change that causes a re-render, this effect won't execute. This could leave the prev*Ref values stale, causing checkFilterDependenciesChanged to return incorrect results in subsequent renders. [ Already posted ]
frontend/src/public/layout/WorkflowsLayout/utils.ts — 0 comments posted, 1 evaluated, 1 filtered
  • line 52: When selectedOption is undefined (because no option matches filterIds[0]), getUserFullName(selectedOption) returns an empty string '' instead of a meaningful fallback message. This results in an empty/blank placeholder in the UI, unlike the ERenderPlaceholderType.Task/Template branches which return a fallback message containing 'sorting.unknown-filter-value'. [ Already posted ]
frontend/src/public/redux/workflows/slice.ts — 0 comments posted, 1 evaluated, 1 filtered
  • line 106: The isObjectChanged function in helpers.ts calls isObjectEmpty, but isObjectEmpty was removed from that file (shown in the diff). This will cause a runtime ReferenceError when checkFiltersChanged is called, which happens whenever any workflow filter is updated via updateWorkflowsFilterValue (e.g., setFilterStatus, setFilterTemplate, etc.). [ Low confidence ]

@Maria-Lordwill Maria-Lordwill added the Frontend Web client changes request label Nov 12, 2025
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Issue on line in frontend/src/public/components/UI/Select/FilterSelect.tsx:202:

isSelectAll can drift from props.selectedOptions, so the Select All checkbox stays checked and triggers resetFilter even when not all are selected. Consider deriving both the checked state and click handler from areAllSelected only to keep UI and behavior in sync.

🚀 Reply to ask Macroscope to explain or update this suggestion.

👍 Helpful? React to give us feedback.

…e table from a 'starters' filter to plain text
…maticapp/pneumaticworkflow into frontend/workflows/43469__move_filters_to_header
…GET /templates/titles-by-tasks and in the response rename the property workflows_count to count
export type TGetTemplatesTitlesByTasksResponse = ITemplateTitleBaseWithCount[];

export function getTemplatesTitlesByTasks(completionStatus: ETaskListCompletionStatus) {
const {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Destructuring api.urls assumes config exists. If getBrowserConfigEnv() returns {}, it throws. Consider guarding with a default or optional chaining before reading urls.

Suggested change
const {
const config = getBrowserConfigEnv();
const baseUrl = config?.api?.urls?.templatesTitlesByTasks ?? '';
const query = getTemplatesTitlesQueryString(completionStatus);
const url = `${baseUrl}${query}`;

🚀 Reply to ask Macroscope to explain or update this suggestion.

👍 Helpful? React to give us feedback.

…ET /templates/titles-by-workflows and in the response rename the property workflows_count to count
- for the 'FilterSelect' component, add the ability to disable it
- in 'workflowTable' component: replace the header in the 'performers' column in the table from a 'performers' filter to plain text
- in the performers filter at the top: added the icon and added displaying text on the badge
@Maria-Lordwill Maria-Lordwill force-pushed the frontend/workflows/43469__move_filters_to_header branch from 10114d7 to eaf976f Compare December 9, 2025 06:31
const getFilteredValues = () => {
const normalizedSearchText = searchText.toLowerCase();

if (!groupedOptions) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getFilteredValues ignores flatGroupedOptions: when groupedOptions is absent it uses options instead of allOptions, which desyncs the list from the "Select all" and placeholder. Consider returning/filtering allOptions here.

Suggested change
if (!groupedOptions) {
if (!groupedOptions) {
if (!searchText) {
return allOptions;
}
return getFilteredOptions(allOptions, normalizedSearchText);
}

🚀 Reply to ask Macroscope to explain or update this suggestion.

👍 Helpful? React to give us feedback.

case EHighlightsActions.SetTemplatesTitles:
return { ...state, templatesTitles: action.payload, isTemplatesTitlesLoading: false };
case EHighlightsActions.LoadHighlights:
return { ...state };
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reducers return new objects with no changes. Suggest returning state (including in default) to preserve referential equality and avoid unnecessary re-renders.

Suggested change
return { ...state };
return state;

🚀 Reply to ask Macroscope to explain or update this suggestion.

👍 Helpful? React to give us feedback.

updateWorkflowsTemplateStepsCounters();
}
}, [statusFilter, templatesIdsFilter, performersIdsFilter, workflowStartersIdsFilter]);
}, [statusFilter, performersIdsFilter, workflowStartersIdsFilter]);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useEffect that calls updateWorkflowsTemplateStepsCounters() omits templatesIdsFilter and stepsIdsFilter from its dependencies, so counters won’t refresh when templates/steps change. Consider adding both to the dependency array.

Suggested change
}, [statusFilter, performersIdsFilter, workflowStartersIdsFilter]);
}, [statusFilter, performersIdsFilter, workflowStartersIdsFilter, templatesIdsFilter, stepsIdsFilter]);

🚀 Reply to ask Macroscope to explain or update this suggestion.

👍 Helpful? React to give us feedback.


def __init__(
self,
user: User,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

user: User isn’t defined in this module (you use UserModel = get_user_model() above and other queries type as UserModel). This will raise NameError at import. Consider changing the annotation to UserModel for consistency.

Suggested change
user: User,
user: UserModel,

🚀 Reply to ask Macroscope to explain or update this suggestion.

👍 Helpful? React to give us feedback.

if (changedFiltersRef.current.size > 0) {
changedFiltersRef.current = new Set();
}
}, [changedFiltersRef.current.size]);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useLayoutEffect depends on changedFiltersRef.current.size, but mutating a ref doesn’t trigger re-renders, so the effect can miss updates. Consider keying the effect off the actual filter values (it already early-returns if nothing changed) so refs sync reliably.

Suggested change
}, [changedFiltersRef.current.size]);
}, [statusFilter, templatesIdsFilter, stepsIdsFilter, workflowStartersIdsFilter, performersIdsFilter, sorting]);

🚀 Reply to ask Macroscope to explain or update this suggestion.

👍 Helpful? React to give us feedback.

{props.selectedOption && renderResetOption()}
{rendeSelectAllOption()}
{renderSelectAllOption()}
{foundValues.map((option) => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isTitle options render as selectable items. Consider treating isTitle: true as a non-interactive header: render with the title style, disable the checkbox, skip handleChange, and set toggle={false} so clicks don’t select these items.

🚀 Reply to ask Macroscope to explain or update this suggestion.

👍 Helpful? React to give us feedback.

renderPlaceholder,
} = props;

const allOptions = flatGroupedOptions || options;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

allOptions uses options when groupedOptions is provided without flatGroupedOptions, so the placeholder, Select All, and “all selected” checks operate on the wrong set. Consider deriving allOptions by flattening groupedOptions when present.

Suggested change
const allOptions = flatGroupedOptions || options;
const allOptions = flatGroupedOptions || (groupedOptions ? Array.from(groupedOptions.values()).flatMap(g => g.options) : options);

🚀 Reply to ask Macroscope to explain or update this suggestion.

👍 Helpful? React to give us feedback.

@Maria-Lordwill Maria-Lordwill force-pushed the frontend/workflows/43469__move_filters_to_header branch from f16d1a8 to 56e45de Compare December 15, 2025 05:25
<>
{props.selectedOption && renderResetOption()}
{rendeSelectAllOption()}
{selectedOption !== null && renderResetOption()}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

renderResetOption() now runs in multi-select because selectedOption !== null is true when selectedOption is undefined. Consider guarding by select mode (and nullish) so the reset option only shows for single-select.

Suggested change
{selectedOption !== null && renderResetOption()}
{!isMultiple && selectedOption != null && renderResetOption()}

🚀 Reply to ask Macroscope to explain or update this suggestion.

👍 Helpful? React to give us feedback.

user: User,
with_tasks_in_progress: Optional[bool] = None,
workflows_status: Optional[WorkflowStatus] = None,
status: Optional[WorkflowStatus] = None,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

status is typed as Optional[WorkflowStatus], but the serializer passes WorkflowApiStatus strings. This mismatch can cause a KeyError in WorkflowApiStatus.MAP[status]. Consider updating the type hint to Optional[WorkflowApiStatus] to reflect actual usage.

Suggested change
status: Optional[WorkflowStatus] = None,
status: Optional[WorkflowApiStatus] = None,

🚀 Reply to ask Macroscope to explain or update this suggestion.

👍 Helpful? React to give us feedback.

…rkflows update when selecting a group; also removed the unused CSS class dropdown__active-value
return new Map(
selectedTemplates.map((template) => [
template.id,
{
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getRenderPlaceholder returns selectedOption?.name for ERenderPlaceholderType.Task, but name is a React element in flatGroupedOptions. This may render as [object Object]. Consider using a string field (e.g., selectedOption.searchByText or the raw step name) for the placeholder instead, or document that name must be a string here.

🚀 Reply to ask Macroscope to explain or update this suggestion.

👍 Helpful? React to give us feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Frontend Web client changes request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants