Skip to content

Commit

Permalink
feat(KaotoIOgh-60): Extend Catalog API properties table
Browse files Browse the repository at this point in the history
  • Loading branch information
mkralik3 committed Sep 5, 2023
1 parent 0e088ad commit 79f04ac
Show file tree
Hide file tree
Showing 10 changed files with 303 additions and 111 deletions.
4 changes: 2 additions & 2 deletions packages/ui/src/camel-utils/camel-to-table.adapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ describe('camelComponentToTable', () => {
});
it('should return a apis IPropertiesTable with the correct values', () => {
const table = camelComponentApisToTable({ apis: componentDef.apis!, apiProperties: componentDef.apiProperties! });
expect(table.type).toEqual(PropertiesTableType.Simple);
expect(table.type).toEqual(PropertiesTableType.Tree);
expect(table.headers).toContain(PropertiesHeaders.Name);
expect(table.headers).toContain(PropertiesHeaders.Description);
expect(table.headers).toContain(PropertiesHeaders.Type);
Expand Down Expand Up @@ -248,7 +248,7 @@ describe('camelComponentToTable', () => {
{ apis: componentDef.apis!, apiProperties: componentDef.apiProperties! },
{ filterKey: 'description', filterValue: 'whatever' },
);
expect(table.type).toEqual(PropertiesTableType.Simple);
expect(table.type).toEqual(PropertiesTableType.Tree);
expect(table.headers).toContain(PropertiesHeaders.Name);
expect(table.headers).toContain(PropertiesHeaders.Description);
expect(table.headers).toContain(PropertiesHeaders.Type);
Expand Down
20 changes: 15 additions & 5 deletions packages/ui/src/camel-utils/camel-to-table.adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
} from '../components/PropertiesModal';
import {
ICamelComponentApi,
ICamelComponentApiKind,
ICamelComponentApiProperty,
ICamelComponentHeader,
ICamelComponentProperty,
Expand Down Expand Up @@ -109,28 +110,37 @@ export const camelComponentApisToTable = (
propertiesRows.push({
name: propertyName,
description: property.description,
type: property.type,
rowAdditionalInfo: {},
type: getClassNameOnly(property.javaType),
rowAdditionalInfo: {
required: property.required,
autowired: property.autowired,
enum: property.enum,
apiKind: ICamelComponentApiKind.PARAM
},
});
}
methodsRows.push({
name: methodName,
description: method.description,
type: '',
children: propertiesRows,
rowAdditionalInfo: {},
rowAdditionalInfo: {
apiKind: ICamelComponentApiKind.METHOD
},
});
}
apisRows.push({
name: apiName,
description: api.description,
type: getApiType(api.consumerOnly, api.producerOnly),
children: methodsRows,
rowAdditionalInfo: {},
rowAdditionalInfo: {
apiKind: ICamelComponentApiKind.API
},
});
}
return {
type: PropertiesTableType.Simple,
type: PropertiesTableType.Tree,
headers: [PropertiesHeaders.Name, PropertiesHeaders.Description, PropertiesHeaders.Type],
rows: apisRows,
};
Expand Down
32 changes: 27 additions & 5 deletions packages/ui/src/components/PropertiesModal/PropertiesModal.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
### PropertiesModal
## PropertiesModal

This folder contains `PropertiesModal` component which defines how the properties detail modal with the table is rendered. The table is rendered according to `IPropertiesTable` object. `PropertiesModal` component decides which columns are shown according to `CatalogKind` enum (from the input tile) and use `camel-to-table.adapter.ts` util to get corresponding `IPropertiesTable` model for render.
This folder contains `PropertiesModal` component which defines how the properties detail modal with tabs and tables is rendered. The `PropertiesModal` component decides which tabs should be shown according to `CatalogKind` enum (from the input tile). For a particular `CatalogKind`, it uses a particular transform function from `camel-to-tabs.adapter.ts` util to get an array of `IPropertiesTab` for rendering. It passes all that info to the `PropertiesTabs` component which is responsible for rendering tables. The `PropertiesTabs` component renders all tabs from the input array of `IPropertiesTab` and for each tab, it renders all tables which are stored in `IPropertiesTab.tables`. According to a type of table, `Simple` or `Tree` table is rendered.

That transformation functions in `camel-to-tabs.adapter.ts` contain a "definition" of tabs for each `CatalogKind` and according to that, it calls particular functions from `camel-to-table.adapter.ts` util to get all `IPropertiesTable` which are related to that `CatalogKind`.

To add a new column, extend `PropertiesTable.models.ts`, update `camel-to-table.adapter.ts` if the column is needed also for that definition, and update the table in `PropertiesModal`.
To add a new type of catalog definition, extend the switch in `PropertiesModal` which will cover that new type case.
That functions in `camel-to-table.adapter.ts` contain a "definition" of tables.


### How to update table to add a new column

To add a new column, extend models in `PropertiesTable.models.ts` (`PropertiesHeaders` and `IPropertiesRow`), update `camel-to-table.adapter.ts` if the column is needed for a particular definition, and when the data in the cell contins special formation, add new case into `PropertiesTableCommon.tsx`

__Make sure__ that orders of headers in the `IPropertiesTable.headers` match with the orders of element in a particular row `IPropertiesRow`

Expand All @@ -21,6 +27,22 @@ e.g.
required: "xyz",
}],
}
```

### How to add brand new Catalog type

```
To add a new type of catalog definition, extend the switch in `PropertiesModal` which will cover that new type case. After that create a tab transformation function into `camel-to-tabs.adapter.ts` where define which tabs will be rendered and which tables will be contained. If you need a new table type, add the table transformation function into `camel-to-table.adapter.ts`

### How to add metadata information into table for existing cell

If you need to pass some metadata information, e.g. for formatting existing cells (e.g. if Required==true => add Required as suffix of text), extend `IPropertiesRowAdditionalInfo` object about new information. That information will be available in `PropertiesTableCommon.tsx` where you can define what should be done according to that data.

### Index

- `PropertiesModal` - modal component
- `PropertiesTabs` - tabs component
- `PropertiesTableCommon` - the functions render headers row or data cells row. They contain cases when cell data needs some special formatting according to row metadata (`IPropertiesRow.rowAdditionalInfo`). The functions are common in both, Simple and Tree tables.
- `PropertiesTableSimple` - simple table which render data by rows
- `PropertiesTableTree` - tree table which render data by rows and childrens
- `camel-to-tabs` - functions which define how many tabs will be rendered for a particular catalog type, how they will look and which tables will be contained
- `camel-to-table` - functions which define what data will be in the table according to properties type
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { ICamelComponentApiKind } from '../../models';

export const enum PropertiesTableType {
Simple,
Tree,
Expand All @@ -12,13 +14,20 @@ export const enum PropertiesHeaders {
Example = 'example',
}

/**
* Metadata which is not rendered as a separate cell but according to which, some formatting is applied
*/
export interface IPropertiesRowAdditionalInfo {
required?: boolean;
group?: string;
autowired?: boolean;
enum?: string[];
apiKind?: ICamelComponentApiKind;
}

/**
* Row for table with cells
*/
export interface IPropertiesRow {
property?: string;
name: string;
Expand All @@ -30,13 +39,19 @@ export interface IPropertiesRow {
children?: IPropertiesRow[];
}

/**
* Whole table data for rendering
*/
export type IPropertiesTable = {
type: PropertiesTableType;
headers: PropertiesHeaders[];
rows: IPropertiesRow[];
caption?: string;
};

/**
* Whole tab data for rendering
*/
export interface IPropertiesTab {
rootName: string;
tables: IPropertiesTable[];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ describe('Component tile', () => {
description: 'Client api',
methods: {
send: {
description: 'Client send',
description: 'Send ediMessage to trading partner',
signatures: [''],
},
},
Expand All @@ -83,7 +83,26 @@ describe('Component tile', () => {
},
},
},
apiProperties: {},
apiProperties: {
client: {
methods: {
send: {
properties: {
as2From: {
index: 0,
kind: 'parameter',
displayName: 'As2 From',
group: 'producer',
required: true,
type: 'String',
javaType: 'java.lang.String',
description: 'AS2 name of sender',
},
},
},
},
},
},
},
};

Expand Down Expand Up @@ -161,17 +180,73 @@ describe('Component tile', () => {

//tab 3
expect(screen.getByTestId('tab-3')).toHaveTextContent('APIs (2)');
//headers
// //headers
expect(screen.getByTestId('tab-3-table-0-header-name')).toHaveTextContent('name');
expect(screen.getByTestId('tab-3-table-0-header-description')).toHaveTextContent('description');
expect(screen.getByTestId('tab-3-table-0-header-type')).toHaveTextContent('type');
// rows
// // rows
expect(screen.getByTestId('tab-3-table-0-row-0-cell-apiKind')).toHaveTextContent('Api');
expect(screen.getByTestId('tab-3-table-0-row-0-cell-name')).toHaveTextContent('client');
expect(screen.getByTestId('tab-3-table-0-row-0-cell-description')).toHaveTextContent('Client api');
expect(screen.getByTestId('tab-3-table-0-row-0-cell-type')).toHaveTextContent('Both');
expect(screen.getByTestId('tab-3-table-0-row-1-cell-name')).toHaveTextContent('client2');
expect(screen.getByTestId('tab-3-table-0-row-1-cell-description')).toHaveTextContent('Client2 api');
expect(screen.getByTestId('tab-3-table-0-row-1-cell-type')).toHaveTextContent('Producer');

expect(screen.getByTestId('tab-3-table-0-row-1-cell-apiKind')).toHaveTextContent('Method');
expect(screen.getByTestId('tab-3-table-0-row-1-cell-name')).toHaveTextContent('send');
expect(screen.getByTestId('tab-3-table-0-row-1-cell-description')).toHaveTextContent(
'Send ediMessage to trading partner',
);
expect(screen.getByTestId('tab-3-table-0-row-1-cell-type')).toHaveTextContent('');

expect(screen.getByTestId('tab-3-table-0-row-2-cell-apiKind')).toHaveTextContent('Param');
expect(screen.getByTestId('tab-3-table-0-row-2-cell-name')).toHaveTextContent('as2From');
expect(screen.getByTestId('tab-3-table-0-row-2-cell-description')).toHaveTextContent('Required AS2 name of sender');
expect(screen.getByTestId('tab-3-table-0-row-2-cell-type')).toHaveTextContent('String');

expect(screen.getByTestId('tab-3-table-0-row-3-cell-apiKind')).toHaveTextContent('Api');
expect(screen.getByTestId('tab-3-table-0-row-3-cell-name')).toHaveTextContent('client2');
expect(screen.getByTestId('tab-3-table-0-row-3-cell-description')).toHaveTextContent('Client2 api');
expect(screen.getByTestId('tab-3-table-0-row-3-cell-type')).toHaveTextContent('Producer');
});

it('switchs between tabs and apis', async () => {
// modal uses React portals so baseElement needs to be used here
render(<PropertiesModal tile={tile} isModalOpen={true} onClose={jest.fn()} />);
// switch to API tab
expect(screen.getByTestId('tab-0-table-0-properties-modal-table-caption')).toBeVisible();
expect(screen.getByTestId('tab-1-table-0-properties-modal-table-caption')).not.toBeVisible();
expect(screen.getByTestId('tab-2-table-0-properties-modal-table-caption')).not.toBeVisible();
expect(screen.getByTestId('tab-3-table-0-properties-modal-table-caption')).not.toBeVisible();
expect(screen.getByTestId('tab-3')).toHaveAttribute('aria-selected', 'false');
screen.getByTestId('tab-3').click();
await new Promise(process.nextTick);
expect(screen.getByTestId('tab-0-table-0-properties-modal-table-caption')).not.toBeVisible();
expect(screen.getByTestId('tab-1-table-0-properties-modal-table-caption')).not.toBeVisible();
expect(screen.getByTestId('tab-2-table-0-properties-modal-table-caption')).not.toBeVisible();
expect(screen.getByTestId('tab-3-table-0-properties-modal-table-caption')).toBeVisible();
expect(screen.getByTestId('tab-3')).toHaveAttribute('aria-selected', 'true');

// expand api
expect(screen.getByLabelText('Expand row 0')).toHaveAttribute('aria-expanded', 'false');
screen.getByLabelText('Expand row 0').click();
await new Promise(process.nextTick);
expect(screen.getByLabelText('Collapse row 0')).toHaveAttribute('aria-expanded', 'true');

// expand method
expect(screen.getByLabelText('Expand row 1')).toHaveAttribute('aria-expanded', 'false');
screen.getByLabelText('Expand row 1').click();
await new Promise(process.nextTick);
expect(screen.getByLabelText('Collapse row 1')).toHaveAttribute('aria-expanded', 'true');

// close api, method is invisible but should still be expanded
screen.getByLabelText('Collapse row 0').click();
await new Promise(process.nextTick);
expect(screen.getByLabelText('Expand row 0')).toHaveAttribute('aria-expanded', 'false');
expect(screen.getByLabelText('Collapse row 1')).not.toBeVisible();
expect(screen.getByLabelText('Collapse row 1')).toHaveAttribute('aria-expanded', 'true');
screen.getByLabelText('Expand row 0').click();
await new Promise(process.nextTick);
expect(screen.getByLabelText('Collapse row 1')).toBeVisible();
expect(screen.getByLabelText('Collapse row 1')).toHaveAttribute('aria-expanded', 'true');
});
});

Expand Down
4 changes: 2 additions & 2 deletions packages/ui/src/components/PropertiesModal/PropertiesTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,15 @@ export const PropertiesTabs: FunctionComponent<IPropertiesTabsProps> = (props) =
key={table_index}
table={table}
rootDataTestId={'tab-' + tab_index + '-table-' + table_index}
></PropertiesTableSimple>
/>
);
case PropertiesTableType.Tree:
return (
<PropertiesTableTree
key={table_index}
table={table}
rootDataTestId={'tab-' + tab_index + '-table-' + table_index}
></PropertiesTableTree>
/>
);
}
})}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { List, ListItem } from '@patternfly/react-core';
import { Td, Th } from '@patternfly/react-table';
import { ReactNode } from 'react';
import { IPropertiesRow, PropertiesHeaders } from '../PropertiesModal.models';

/**
* Returns table headers as array of <Th>
*/
export const renderHeaders = (propertiesHeaders: PropertiesHeaders[], rootDataTestId: string): ReactNode[] => {
return propertiesHeaders.map((header) => (
<Th data-testid={rootDataTestId + '-header-' + header} key={header}>
{header}
</Th>
));
};

/**
* Returns table row cells as array of <Td>
*/
export const renderRowData = (
propertiesHeaders: PropertiesHeaders[],
row: IPropertiesRow,
rootDataTestId: string,
row_index: number,
): ReactNode[] => {
const dataTestIdCell = rootDataTestId + '-cell-';

return propertiesHeaders.map((header) => (
<Td data-testid={dataTestIdCell + header} key={row_index + header} dataLabel={header} modifier="wrap">
{
//suffix required for description cell if needed
header == PropertiesHeaders.Description && row.rowAdditionalInfo.required ? (
<span data-label="required">Required </span>
) : (
''
)
}
{
//suffix autowired for description cell if needed
header == PropertiesHeaders.Description && row.rowAdditionalInfo.autowired ? (
<span data-label="autowired">Autowired </span>
) : (
''
)
}
{<span>{row[header]?.toString()}</span>}
{
//prefix with group for name cell if needed
header == PropertiesHeaders.Name && row.rowAdditionalInfo.group ? (
<span data-label="group"> ({row.rowAdditionalInfo.group})</span>
) : (
''
)
}
{
//prefix with enum for description cell if needed
header == PropertiesHeaders.Description && row.rowAdditionalInfo.enum ? (
<>
<p data-label="enum">Enum values:</p>
<List>
{row.rowAdditionalInfo.enum.map((item, enum_index) => (
<ListItem data-testid={dataTestIdCell + header + '-enum-' + enum_index} key={item}>
{item}
</ListItem>
))}
</List>
</>
) : (
''
)
}
</Td>
));
};
Loading

0 comments on commit 79f04ac

Please sign in to comment.