Skip to content

Commit

Permalink
fix: sidebar problems (#885)
Browse files Browse the repository at this point in the history
  • Loading branch information
jonaslagoni authored Jan 17, 2024
1 parent 4b14d93 commit 9f4219c
Show file tree
Hide file tree
Showing 4 changed files with 219 additions and 56 deletions.
85 changes: 29 additions & 56 deletions library/src/containers/Sidebar/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
PUBLISH_LABEL_DEFAULT_TEXT,
SUBSCRIBE_LABEL_DEFAULT_TEXT,
} from '../../constants';
import { TagObject, filterObjectsByTags } from '../../helpers/sidebar';

const SidebarContext = React.createContext<{
setShowSidebar: React.Dispatch<React.SetStateAction<boolean>>;
Expand Down Expand Up @@ -168,52 +169,11 @@ export const Sidebar: React.FunctionComponent = () => {
);
};

interface TagObject<T = any> {
name: string;
object: { tags?: () => Array<{ name: () => string }> };
data: T;
}

function filterObjectsByTags<T = any>(
tags: string[],
objects: Array<TagObject<T>>,
): { tagged: Map<string, TagObject[]>; untagged: TagObject[] } {
const taggedObjects = new Set<TagObject>();
const tagged = new Map<string, TagObject[]>();

tags.forEach(tag => {
const taggedForTag: TagObject[] = [];
objects.forEach(obj => {
const object = obj.object;
if (typeof object.tags !== 'function') {
return;
}

const objectTags = (object.tags() || []).map(t => t.name());
const hasTag = objectTags.includes(tag);
if (hasTag) {
taggedForTag.push(obj);
taggedObjects.add(obj);
}
});
tagged.set(tag, taggedForTag);
});

const untagged: TagObject[] = [];
objects.forEach(obj => {
if (!taggedObjects.has(obj)) {
untagged.push(obj);
}
});

return { tagged, untagged };
}

const ServersList: React.FunctionComponent = () => {
const sidebarConfig = useConfig().sidebar;
const asyncapi = useSpec();
const servers = asyncapi.servers().all();
const showServers = sidebarConfig?.showServers || 'byDefault';
const showServers = sidebarConfig?.showServers ?? 'byDefault';

if (showServers === 'byDefault') {
return (
Expand All @@ -227,7 +187,12 @@ const ServersList: React.FunctionComponent = () => {

let specTagNames: string[];
if (showServers === 'bySpecTags') {
specTagNames = (asyncapi.info().tags() || []).map(tag => tag.name());
specTagNames = (
asyncapi
.info()
.tags()
.all() ?? []
).map(tag => tag.name());
} else {
const serverTagNamesSet = new Set<string>();
servers.forEach(server => {
Expand All @@ -238,7 +203,7 @@ const ServersList: React.FunctionComponent = () => {

const serializedServers: TagObject[] = servers.map(server => ({
name: server.id(),
object: server,
tags: server.tags(),
data: {},
}));
const { tagged, untagged } = filterObjectsByTags(
Expand Down Expand Up @@ -273,7 +238,7 @@ const OperationsList: React.FunctionComponent = () => {
const sidebarConfig = useConfig().sidebar;
const asyncapi = useSpec();
const operations = asyncapi.operations().all();
const showOperations = sidebarConfig?.showOperations || 'byDefault';
const showOperations = sidebarConfig?.showOperations ?? 'byDefault';

const processedOperations: Array<TagObject<{
channelName: string;
Expand All @@ -287,22 +252,22 @@ const OperationsList: React.FunctionComponent = () => {
if (operation.isSend()) {
processedOperations.push({
name: `publish-${operation.id()}`,
object: operation,
tags: operation.tags(),
data: {
channelName: channelAddress || '',
channelName: channelAddress ?? '',
kind: 'publish',
summary: operation.summary() || '',
summary: operation.summary() ?? '',
},
});
}
if (operation.isReceive()) {
processedOperations.push({
name: `subscribe-${operation.id()}`,
object: operation,
tags: operation.tags(),
data: {
channelName: channelAddress || '',
channelName: channelAddress ?? '',
kind: 'subscribe',
summary: operation.summary() || '',
summary: operation.summary() ?? '',
},
});
}
Expand All @@ -320,11 +285,19 @@ const OperationsList: React.FunctionComponent = () => {

let operationTagNames: string[];
if (showOperations === 'bySpecTags') {
operationTagNames = (asyncapi.info().tags() || []).map(tag => tag.name());
operationTagNames = (
asyncapi
.info()
.tags()
.all() ?? []
).map(tag => tag.name());
} else {
const operationTagNamesSet = new Set<string>();
operations.forEach(operation => {
operation.tags().forEach(t => operationTagNamesSet.add(t.name()));
operation
.tags()
.all()
.forEach(t => operationTagNamesSet.add(t.name()));
});
operationTagNames = Array.from(operationTagNamesSet);
}
Expand Down Expand Up @@ -374,9 +347,9 @@ const OperationItem: React.FunctionComponent<OperationItemProps> = ({
const isPublish = kind === 'publish';
let label: string = '';
if (isPublish) {
label = config.publishLabel || PUBLISH_LABEL_DEFAULT_TEXT;
label = config.publishLabel ?? PUBLISH_LABEL_DEFAULT_TEXT;
} else {
label = config.subscribeLabel || SUBSCRIBE_LABEL_DEFAULT_TEXT;
label = config.subscribeLabel ?? SUBSCRIBE_LABEL_DEFAULT_TEXT;
}

return (
Expand All @@ -394,7 +367,7 @@ const OperationItem: React.FunctionComponent<OperationItemProps> = ({
>
{label}
</span>
<span className="break-all inline-block">{summary || channelName}</span>
<span className="break-all inline-block">{summary ?? channelName}</span>
</a>
</li>
);
Expand Down
97 changes: 97 additions & 0 deletions library/src/containers/Sidebar/__tests__/SideBar.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/**
* @jest-environment jsdom
*/

import React from 'react';
import { render } from '@testing-library/react';
import { Sidebar } from '../Sidebar';
import { ConfigContext, SpecificationContext } from '../../../contexts';
import asyncapi from '../../../__tests__/docs/v3/streetlights-kafka.json';
import { Parser } from '../../../helpers';
import { AsyncAPIDocumentInterface } from '@asyncapi/parser';
describe('Sidebar component', () => {
let parsed: AsyncAPIDocumentInterface;
beforeAll(async () => {
const parsedDoc = await Parser.parse(asyncapi, {});
expect(parsedDoc.error).toBeUndefined();
expect(parsedDoc.asyncapi).toBeDefined();
parsed = parsedDoc.asyncapi!;
});
test('should render sidebar with showOperations: byDefault', async () => {
render(
<ConfigContext.Provider
value={{ sidebar: { showOperations: 'byDefault' } }}
>
<SpecificationContext.Provider value={parsed}>
<Sidebar />
</SpecificationContext.Provider>
</ConfigContext.Provider>,
);
});
test('should render sidebar with showOperations: byOperationsTags', async () => {
render(
<ConfigContext.Provider
value={{ sidebar: { showOperations: 'byOperationsTags' } }}
>
<SpecificationContext.Provider value={parsed}>
<Sidebar />
</SpecificationContext.Provider>
</ConfigContext.Provider>,
);
});
test('should render sidebar with showOperations: bySpecTags', async () => {
render(
<ConfigContext.Provider
value={{ sidebar: { showOperations: 'bySpecTags' } }}
>
<SpecificationContext.Provider value={parsed}>
<Sidebar />
</SpecificationContext.Provider>
</ConfigContext.Provider>,
);
});
test('should render sidebar with showServers: byDefault', async () => {
render(
<ConfigContext.Provider value={{ sidebar: { showServers: 'byDefault' } }}>
<SpecificationContext.Provider value={parsed}>
<Sidebar />
</SpecificationContext.Provider>
</ConfigContext.Provider>,
);
});
test('should render sidebar with showServers: byServersTags', async () => {
render(
<ConfigContext.Provider
value={{ sidebar: { showServers: 'byServersTags' } }}
>
<SpecificationContext.Provider value={parsed}>
<Sidebar />
</SpecificationContext.Provider>
</ConfigContext.Provider>,
);
});
test('should render sidebar with showServers: bySpecTags', async () => {
render(
<ConfigContext.Provider
value={{ sidebar: { showServers: 'bySpecTags' } }}
>
<SpecificationContext.Provider value={parsed}>
<Sidebar />
</SpecificationContext.Provider>
</ConfigContext.Provider>,
);
});
test('should render with showOperations: byDefault, showServers: byDefault', async () => {
render(
<ConfigContext.Provider
value={{
sidebar: { showOperations: 'byDefault', showServers: 'byDefault' },
}}
>
<SpecificationContext.Provider value={parsed}>
<Sidebar />
</SpecificationContext.Provider>
</ConfigContext.Provider>,
);
});
});
47 changes: 47 additions & 0 deletions library/src/helpers/__tests__/sidebar.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { OperationInterface, TagV2, TagsV2 } from '@asyncapi/parser';
import { TagObject, filterObjectsByTags } from '../sidebar';

describe('sidebar', () => {
describe('.filterObjectsByTags', () => {
test('should handle empty objects and find nothing', () => {
const tagsToFind = ['test'];
const objects: Array<TagObject<OperationInterface>> = [];
const filteredTags = filterObjectsByTags(tagsToFind, objects);
expect(filteredTags.tagged.size).toEqual(0);
expect(filteredTags.untagged.length).toEqual(0);
});
test('should handle find one instance', () => {
const tagsToFind = ['test'];
const tagsToSearch = new TagsV2([new TagV2({ name: 'test' })]);
const objects: Array<TagObject<any>> = [
{ data: {}, name: '', tags: tagsToSearch },
];
const filteredTags = filterObjectsByTags(tagsToFind, objects);
expect(filteredTags.tagged.size).toEqual(1);
expect(filteredTags.untagged.length).toEqual(0);
});
test('should handle find multiple instances', () => {
const tagsToFind = ['test'];
const obj1 = {
data: {},
name: '',
tags: new TagsV2([new TagV2({ name: 'test' })]),
};
const obj2 = {
data: {},
name: '',
tags: new TagsV2([new TagV2({ name: 'none' })]),
};
const obj3 = {
data: {},
name: '',
tags: new TagsV2([new TagV2({ name: 'test' })]),
};
const objects: Array<TagObject<any>> = [obj1, obj2, obj3];
const filteredTags = filterObjectsByTags(tagsToFind, objects);
expect(filteredTags.tagged.size).toEqual(1);
expect(filteredTags.tagged.get('test')!.length).toEqual(2);
expect(filteredTags.untagged.length).toEqual(1);
});
});
});
46 changes: 46 additions & 0 deletions library/src/helpers/sidebar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { TagsInterface } from '@asyncapi/parser';

export interface TagObject<T = any> {
name: string;
tags: TagsInterface;
data: T;
}
export interface SortedReturnType {
tagged: Map<string, TagObject[]>;
untagged: TagObject[];
}

/**
* Filter an array of objects by certain tags
*/
export function filterObjectsByTags<T>(
tags: string[],
objects: Array<TagObject<T>>,
): SortedReturnType {
const taggedObjects = new Set<TagObject>();
const tagged = new Map<string, TagObject[]>();
tags.forEach(tag => {
const taggedForTag: TagObject[] = [];
objects.forEach(obj => {
const objTags = obj.tags;
const nameTags = (objTags.all() ?? []).map(t => t.name());
const hasTag = nameTags.includes(tag);
if (hasTag) {
taggedForTag.push(obj);
taggedObjects.add(obj);
}
});
if (taggedForTag.length > 0) {
tagged.set(tag, taggedForTag);
}
});

const untagged: TagObject[] = [];
objects.forEach(obj => {
if (!taggedObjects.has(obj)) {
untagged.push(obj);
}
});

return { tagged, untagged };
}

0 comments on commit 9f4219c

Please sign in to comment.