Skip to content
Draft
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
9 changes: 9 additions & 0 deletions DSL/Resql/services/POST/get-services-dependency-data.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
SELECT
service_id,
name,
current_state AS state,
is_common,
structure::json
FROM services
WHERE NOT deleted
ORDER BY name ASC;
18 changes: 14 additions & 4 deletions DSL/Resql/services/POST/get-services-list.sql
Original file line number Diff line number Diff line change
@@ -1,19 +1,29 @@
SELECT
id,
service_id,
name,
description,
examples,
entities,
current_state AS state,
ruuter_type AS type,
is_common,
slot,
CEIL((SELECT COUNT(DISTINCT service_id) FROM services WHERE NOT deleted AND (:is_common::TEXT = '' OR is_common = (:is_common::TEXT)::BOOLEAN) AND (:search IS NULL OR :search = '' OR LOWER(name) LIKE LOWER('%' || :search || '%'))) / :page_size::DECIMAL) AS total_pages
CEIL((SELECT COUNT(DISTINCT service_id) FROM services WHERE NOT deleted AND (:is_common::TEXT = '' OR is_common = (:is_common::TEXT)::BOOLEAN) AND (:search IS NULL OR :search = '' OR LOWER(name) LIKE LOWER('%' || :search || '%') OR LOWER(description) LIKE LOWER('%' || :search || '%'))) / :page_size::DECIMAL) AS total_pages
FROM services
WHERE NOT deleted
AND (:is_common::TEXT = '' OR is_common = (:is_common::TEXT)::BOOLEAN)
AND (:search IS NULL OR :search = '' OR LOWER(name) LIKE LOWER('%' || :search || '%'))
ORDER BY
AND (:search IS NULL OR :search = '' OR LOWER(name) LIKE LOWER('%' || :search || '%') OR LOWER(description) LIKE LOWER('%' || :search || '%'))
ORDER BY
CASE WHEN :search IS NOT NULL AND :search != '' AND LOWER(name) = LOWER(:search) THEN 0
WHEN :search IS NOT NULL AND :search != '' AND LOWER(name) LIKE LOWER(:search || '%') THEN 1
WHEN :search IS NOT NULL AND :search != '' AND LOWER(name) LIKE LOWER('%' || :search || '%') THEN 2
ELSE 3 END,
CASE WHEN :sorting = 'name asc' THEN name END ASC,
CASE WHEN :sorting = 'name desc' THEN name END DESC,
CASE WHEN :sorting = 'state asc' THEN current_state END ASC,
CASE WHEN :sorting = 'state desc' THEN current_state END DESC,
name ASC
CASE WHEN :sorting = 'id asc' THEN id END ASC,
CASE WHEN :sorting = 'id desc' THEN id END DESC,
id ASC
OFFSET ((GREATEST(:page, 1) - 1) * :page_size) LIMIT :page_size;
20 changes: 20 additions & 0 deletions DSL/Ruuter/services/GET/services-dependency-data.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
declaration:
call: declare
version: 0.1
description: "Returns all services with structure for dependency graph computation"
method: get
accepts: json
returns: json
namespace: service

get_services_dependency_data:
call: http.post
args:
url: "[#SERVICE_RESQL]/get-services-dependency-data"
result: results

return_ok:
status: 200
wrapper: false
return: ${results.response.body}
next: end
522 changes: 271 additions & 251 deletions GUI/package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions GUI/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@
"@types/d3-timer": "^3.0.2",
"@types/howler": "^2.2.11",
"@types/jsoneditor": "^9.9.6",
"@types/node": "^26.3.0",
"@types/react": "^18.2.0",
"@types/react-datepicker": "^4.8.0",
"@types/react-dom": "^18.2.0",
Expand Down
25 changes: 25 additions & 0 deletions GUI/src/components/DataTable/DataTable.scss
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,15 @@
position: relative;
}

&--sticky-header {
thead th {
position: sticky;
top: 0;
z-index: 2;
background-color: get-color(white);
}
}

td {
padding: 12px 24px 12px 16px;
border-bottom: 1px solid get-color(black-coral-2);
Expand Down Expand Up @@ -69,6 +78,18 @@
}
}

&__empty {
text-align: center;
padding: 32px 16px;
color: get-color(black-coral-11);
font-style: italic;
}

&__sub-row-cell {
padding: 0 !important;
border-bottom: 1px solid get-color(black-coral-2);
}

&__filter {
position: absolute;
top: 100%;
Expand Down Expand Up @@ -201,6 +222,10 @@
th {
color: var(--dark-text-primary);
}

&.data-table--sticky-header thead th {
background-color: var(--dark-bg-light);
}
&__pagination-wrapper {
background-color: var(--dark-bg-highlight);
}
Expand Down
76 changes: 76 additions & 0 deletions GUI/src/components/DataTable/Pagination.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import clsx from 'clsx';
import { FC, useId } from 'react';
import { useTranslation } from 'react-i18next';
import { MdOutlineEast, MdOutlineWest } from 'react-icons/md';
import { Link } from 'react-router-dom';

import './DataTable.scss';

type TablePaginationProps = {
pageIndex: number;
pageSize: number;
pageCount: number;
onPageChange: (pageIndex: number) => void;
onPageSizeChange: (pageSize: number) => void;
alwaysShow?: boolean;
};

const TablePagination: FC<TablePaginationProps> = ({
pageIndex,
pageSize,
pageCount,
onPageChange,
onPageSizeChange,
alwaysShow = false,
}) => {
const id = useId();
const { t } = useTranslation();
const safePageCount = Math.max(pageCount, 1);
const showPagination = alwaysShow || safePageCount * pageSize > pageSize;

if (!showPagination) return null;

return (
<div className="data-table__pagination-wrapper">
<div className="data-table__pagination">
<button className="previous" onClick={() => onPageChange(pageIndex - 1)} disabled={pageIndex <= 0}>
<MdOutlineWest />
</button>

Check warning on line 38 in GUI/src/components/DataTable/Pagination.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add an explicit "type" attribute to this button.

See more on https://sonarcloud.io/project/issues?id=buerokratt_Service-Module&issues=AaA7nQqKx1NQpUhckVQt&open=AaA7nQqKx1NQpUhckVQt&pullRequest=1156
<nav role="navigation" aria-label={t('global.paginationNavigation') ?? ''}>
<ul className="links">
{Array.from({ length: safePageCount }).map((_, index) => (
<li key={`${id}-${index}`} className={clsx({ active: pageIndex === index })}>
<Link
to={`?page=${index + 1}`}
onClick={(event) => {
event.preventDefault();
onPageChange(index);
}}
aria-label={t('global.gotoPage') + index}
aria-current={pageIndex === index}
>
{index + 1}
</Link>
</li>
))}
</ul>
</nav>
<button className="next" onClick={() => onPageChange(pageIndex + 1)} disabled={pageIndex >= safePageCount - 1}>
<MdOutlineEast />
</button>

Check warning on line 60 in GUI/src/components/DataTable/Pagination.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add an explicit "type" attribute to this button.

See more on https://sonarcloud.io/project/issues?id=buerokratt_Service-Module&issues=AaA7nQqKx1NQpUhckVQu&open=AaA7nQqKx1NQpUhckVQu&pullRequest=1156
</div>
<div className="data-table__page-size">
<label htmlFor={id}>{t('global.resultCount')}</label>
<select id={id} value={pageSize} onChange={(event) => onPageSizeChange(Number(event.target.value))}>
{[5, 10, 20, 30, 50].map((size) => (
<option key={size} value={size}>
{size}
</option>
))}
</select>
</div>
</div>
);
};

export default TablePagination;
142 changes: 58 additions & 84 deletions GUI/src/components/DataTable/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,11 @@ import {
} from '@tanstack/react-table';
import clsx from 'clsx';
import { Icon, Track } from 'components';
import React, { CSSProperties, FC, ReactNode, useId } from 'react';
import { useTranslation } from 'react-i18next';
import { MdExpandLess, MdExpandMore, MdOutlineEast, MdOutlineWest, MdUnfoldMore } from 'react-icons/md';
import { Link } from 'react-router-dom';
import React, { CSSProperties, FC, ReactNode } from 'react';
import { MdExpandLess, MdExpandMore, MdUnfoldMore } from 'react-icons/md';

import Filter from './Filter';
import TablePagination from './Pagination';
import './DataTable.scss';

type DataTableProps = {
Expand All @@ -48,6 +47,10 @@ type DataTableProps = {
meta?: TableMeta<any>;
withScrollWrapper?: boolean;
renderSubRow?: (row: Row<any>) => ReactNode;
renderBeforeRow?: (row: Row<any>, index: number) => ReactNode;
stickyHeader?: boolean;
hidePagination?: boolean;
emptyMessage?: string;
alwaysShowPagination?: boolean;
};

Expand All @@ -63,7 +66,8 @@ declare module '@tanstack/table-core' {

declare module '@tanstack/react-table' {
interface TableMeta<TData extends RowData> {
getRowStyles: (row: Row<TData>) => CSSProperties;
getRowStyles?: (row: Row<TData>) => CSSProperties;
getRowProps?: (row: Row<TData>) => Record<string, string>;
onRowClick?: (row: Row<TData>) => void;
}
}
Expand Down Expand Up @@ -98,10 +102,12 @@ const DataTable: FC<DataTableProps> = ({
meta,
withScrollWrapper = true,
renderSubRow,
renderBeforeRow,
stickyHeader = false,
hidePagination = false,
emptyMessage,
alwaysShowPagination = false,
}) => {
const id = useId();
const { t } = useTranslation();
const tablePagination = pagination ?? {
pageIndex: 0,
pageSize: 10,
Expand Down Expand Up @@ -151,7 +157,7 @@ const DataTable: FC<DataTableProps> = ({
className={`data-table${withScrollWrapper ? '__scrollWrapper' : ''}`}
style={withScrollWrapper ? undefined : { overflowX: 'hidden' }}
>
<table className="data-table">
<table className={clsx('data-table', stickyHeader && 'data-table--sticky-header')}>
{!disableHead && (
<thead>
{table.getHeaderGroups().map((headerGroup) => (
Expand All @@ -161,7 +167,7 @@ const DataTable: FC<DataTableProps> = ({
{header.isPlaceholder ? null : (
<Track gap={8}>
{sortable && header.column.getCanSort() && (
<button onClick={header.column.getToggleSortingHandler()}>
<button type="button" onClick={header.column.getToggleSortingHandler()}>
{{
asc: <Icon icon={<MdExpandMore fontSize={20} />} size="medium" />,
desc: <Icon icon={<MdExpandLess fontSize={20} />} size="medium" />,
Expand All @@ -182,84 +188,52 @@ const DataTable: FC<DataTableProps> = ({
)}
<tbody>
{tableBodyPrefix}
{table.getRowModel().rows.map((row) => (
<React.Fragment key={row.id}>
<tr style={table.options.meta?.getRowStyles(row)} onClick={() => table.options.meta?.onRowClick?.(row)}>
{row.getVisibleCells().map((cell) => (
<td key={cell.id} style={renderSubRow ? { borderBottom: 'none' } : undefined}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>
))}
</tr>
{renderSubRow && (
<tr>
<td
colSpan={row.getVisibleCells().length}
style={{ padding: '0 16px 8px 16px', borderBottom: '1px solid #D2D3D8' }}
{table.getRowModel().rows.length === 0 && emptyMessage ? (
<tr>
<td colSpan={columns.length} className="data-table__empty">
{emptyMessage}
</td>
</tr>
) : (
table.getRowModel().rows.map((row, index) => {
const subRowContent = renderSubRow?.(row);
const beforeRowContent = renderBeforeRow?.(row, index);
return (
<React.Fragment key={row.id}>
{beforeRowContent}
<tr
style={table.options.meta?.getRowStyles?.(row)}
{...table.options.meta?.getRowProps?.(row)}
onClick={() => table.options.meta?.onRowClick?.(row)}
>
{renderSubRow(row)}
</td>
</tr>
)}
</React.Fragment>
))}
{row.getVisibleCells().map((cell) => (
<td key={cell.id} style={subRowContent ? { borderBottom: 'none' } : undefined}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>
))}
</tr>
{subRowContent && (
<tr className="data-table__sub-row">
<td colSpan={row.getVisibleCells().length} className="data-table__sub-row-cell">
{subRowContent}
</td>
</tr>
)}
</React.Fragment>
);
})
)}
</tbody>
</table>
{tablePagination && (
<div className="data-table__pagination-wrapper">
{(alwaysShowPagination ||
table.getPageCount() * table.getState().pagination.pageSize > table.getState().pagination.pageSize) && (
<div className="data-table__pagination">
<button className="previous" onClick={() => table.previousPage()} disabled={!table.getCanPreviousPage()}>
<MdOutlineWest />
</button>
<nav role="navigation" aria-label={t('global.paginationNavigation') ?? ''}>
<ul className="links">
{Array.from({ length: table.getPageCount() }).map((_, index) => (
<li
key={`${id}-${index}`}
className={clsx({ active: table.getState().pagination.pageIndex === index })}
>
<Link
to={`?page=${index + 1}`}
onClick={() => table.setPageIndex(index)}
aria-label={t('global.gotoPage') + index}
aria-current={table.getState().pagination.pageIndex === index}
>
{index + 1}
</Link>
</li>
))}
</ul>
</nav>
<button
className="next"
onClick={() => {
table.nextPage();
}}
disabled={!table.getCanNextPage()}
>
<MdOutlineEast />
</button>
</div>
)}
<div className="data-table__page-size">
<label htmlFor={id}>{t('global.resultCount')}</label>
<select
id={id}
value={table.getState().pagination.pageSize}
onChange={(e) => {
table.setPageSize(Number(e.target.value));
}}
>
{[5, 10, 20, 30, 50].map((pageSize) => (
<option key={pageSize} value={pageSize}>
{pageSize}
</option>
))}
</select>
</div>
</div>
{tablePagination && !hidePagination && (
<TablePagination
pageIndex={table.getState().pagination.pageIndex}
pageSize={table.getState().pagination.pageSize}
pageCount={table.getPageCount()}
alwaysShow={alwaysShowPagination}
onPageChange={(pageIndex) => table.setPageIndex(pageIndex)}
onPageSizeChange={(pageSize) => table.setPageSize(pageSize)}
/>
)}
</div>
);
Expand Down
Loading