Skip to content
This repository was archived by the owner on Mar 25, 2021. It is now read-only.

feat: Add useTopics model hook and query for topics listing #136

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
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
13 changes: 9 additions & 4 deletions client/Bootstrap/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,22 @@ import { init } from 'i18n';
import { ApolloProvider } from '@apollo/client';

import { apolloClient } from 'Bootstrap/GraphQLClient';
import { ConfigFeatureFlagProvider, FeatureFlag } from 'Contexts';
import { Home } from 'Panels/Home';
import { LoggingProvider } from 'Contexts';
import {
ConfigFeatureFlagProvider,
FeatureFlag,
LoggingProvider,
} from 'Contexts';
import { Home, Topics } from 'Panels';

init(); //Bootstrap i18next support
ReactDOM.render(
<ApolloProvider client={apolloClient}>
<ConfigFeatureFlagProvider>
<LoggingProvider>
<FeatureFlag flag={'client.Pages.PlaceholderHome'}>
<Home />
<Home>
<Topics />
Copy link
Contributor

Choose a reason for hiding this comment

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

Needs a router, not hard coding like this.

</Home>
</FeatureFlag>
</LoggingProvider>
</ConfigFeatureFlagProvider>
Expand Down
2 changes: 1 addition & 1 deletion client/Contexts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ It is expected that when used in production Context Provider(s) will be used by

## Test approach

Elements should be tested in a functional manor. See [Test Driven Development](../../docs/Test.md#style-of-test).
Elements should be tested in a functional manner. See [Test Driven Development](../../docs/Test.md#style-of-test).

## Expected files

Expand Down
2 changes: 1 addition & 1 deletion client/Elements/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

## Test approach

Bootstrap should be tested in a behavioural manor. See [Behavioural Driven Development](../../docs/Test.md#style-of-test).
Bootstrap should be tested in a behavioural manner. See [Behavioural Driven Development](../../docs/Test.md#style-of-test).

## Expected files

Expand Down
2 changes: 1 addition & 1 deletion client/Groups/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

## Test approach

Bootstrap should be tested in a behavioural manor. See [Behavioural Driven Development](../../docs/Test.md#style-of-test).
Bootstrap should be tested in a behavioural manner. See [Behavioural Driven Development](../../docs/Test.md#style-of-test).

## Expected files

Expand Down
2 changes: 1 addition & 1 deletion client/Hooks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This directory contains all re-usable custom [React Hooks](https://reactjs.org/d

## Test approach

Elements should be tested in a functional manor. See [Test Driven Development](../../docs/Test.md#style-of-test).
Elements should be tested in a functional manner. See [Test Driven Development](../../docs/Test.md#style-of-test).

## Expected files

Expand Down
2 changes: 1 addition & 1 deletion client/Panels/Home/Home.steps.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ When('it is rendered with no version', () => {
showVersionSet = false;
});

Then('it should display the expected text', () => {
Then('it should display the expected text', async () => {
Copy link
Member

Choose a reason for hiding this comment

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

Wont block the PR on it, but was this intended?

const { getByText, queryByText } = renderResult;
expect(getByText('Welcome to the Strimzi UI')).toBeInTheDocument();
const versionString = `Version: ${coreConfigFromContext.client.about.version}`;
Expand Down
2 changes: 1 addition & 1 deletion client/Panels/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ A `Panel` component represents a top level element of a UI - the primary/signifi

## Test approach

Bootstrap should be tested in a behavioural manor. See [Behavioural Driven Development](../../docs/Test.md#style-of-test).
Bootstrap should be tested in a behavioural manner. See [Behavioural Driven Development](../../docs/Test.md#style-of-test).

## Expected files

Expand Down
38 changes: 38 additions & 0 deletions client/Panels/Topics/Model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright Strimzi authors.
* License: Apache License 2.0 (see the file LICENSE or http://apache.org/licenses/LICENSE-2.0.html).
*/
import { ChangeEvent, useState, useCallback } from 'react';
import { GET_TOPICS } from 'Queries/Topics';
import { useQuery } from '@apollo/client';
import { useTopicsModelType, topicsType } from './useTopicsModel.types';
import debounce from 'lodash.debounce';

const onChangeEvent = (
evt: ChangeEvent<HTMLInputElement>,
Copy link
Contributor

Choose a reason for hiding this comment

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

Don't use html events here, it breaks the MVVC abstraction. Use scalar types or add an abstraction.

callWithValue: (value: string) => void
) => callWithValue(evt.target.value);

export const useTopicsModel = (): useTopicsModelType => {
const [filter, setTopicsFilter] = useState();
const debouncedUpdateTopicsFilter = useCallback(
debounce(setTopicsFilter, 500),
[]
);
const { data, loading, error } = useQuery<topicsType>(GET_TOPICS, {
Copy link
Contributor

Choose a reason for hiding this comment

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

Types start with capital letters.

variables: { filter },
});

const model = {
filter,
topics: data ? data.topics : [],
error,
loading,
};

return {
model,
updateTopicsFilter: (evt) =>
onChangeEvent(evt, debouncedUpdateTopicsFilter),
};
};
3 changes: 3 additions & 0 deletions client/Panels/Topics/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Topics

The `Topics` panel retrieves and displays the list of Kafka topics for a cluster.
15 changes: 15 additions & 0 deletions client/Panels/Topics/Topics.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@

# Copyright Strimzi authors.
# License: Apache License 2.0 (see the file LICENSE or http://apache.org/licenses/LICENSE-2.0.html).
Feature: Topics panel

Scenario: Seeing all available topics:
Given a Topics panel component
When it is rendered
Then I see all topics

Scenario: Viewing a specific topic (when it exists in the cluster):
Given a Topics panel component
When it is rendered
And I filter for topic 'testtopic1'
Then I see topic 'testtopic1' in the topic list
87 changes: 87 additions & 0 deletions client/Panels/Topics/Topics.steps.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Copyright Strimzi authors.
* License: Apache License 2.0 (see the file LICENSE or http://apache.org/licenses/LICENSE-2.0.html).
*/
import {
And,
Given,
When,
Then,
Fusion,
Before,
After,
} from 'jest-cucumber-fusion';
import {
fireEvent,
render,
RenderResult,
waitFor,
} from '@testing-library/react';
import { Topics } from '.';
import React, { ReactElement } from 'react';
import { withApolloProviderReturning } from 'utils/test';
import {
mockGetTopicsRequests,
mockGetTopicsResponses,
} from './useTopicsModel.assets';

let renderResult: RenderResult;
let component: ReactElement;

Before(() => {
jest.useFakeTimers();
});

After(() => {
jest.useRealTimers();
});

Given('a Topics panel component', () => {
component = <Topics />;
});

When('it is rendered', () => {
renderResult = render(
withApolloProviderReturning(
[
mockGetTopicsRequests.successRequest,
mockGetTopicsRequests.successFilteredRequest,
],
component
)
);
jest.runAllTimers();
});

And(/^I filter for topic '(.+)'$/, async (filter) => {
fireEvent.change(renderResult.getByPlaceholderText('filter'), {
target: { value: filter },
});
await waitFor(() => {
Copy link
Member

Choose a reason for hiding this comment

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

My personal preference would be that rather than using mock timers, we use the apollo helpers, eg

await apolloMockResponse(); // tick for data

Copy link
Contributor

Choose a reason for hiding this comment

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

Agreed.

jest.runAllTimers();
});
});

Then('I see all topics', async () => {
const { findByText } = renderResult;
expect(
await findByText(JSON.stringify(mockGetTopicsResponses.successResponse), {
exact: false,
})
).toBeInTheDocument();
});

Then(/^I see topic '(.+)' in the topic list$/, async (topicName) => {
const { findByText } = renderResult;
expect(
await findByText(
JSON.stringify(mockGetTopicsResponses.successFilteredResponse),
{ exact: false }
)
).toBeInTheDocument();
expect(
await findByText(topicName as string, { exact: false })
).toBeInTheDocument();
});

Fusion('Topics.feature');
30 changes: 30 additions & 0 deletions client/Panels/Topics/View.carbon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright Strimzi authors.
* License: Apache License 2.0 (see the file LICENSE or http://apache.org/licenses/LICENSE-2.0.html).
*/
import React, { FunctionComponent } from 'react';
import { useTopicsModel } from './Model';

/**
* Placeholder Topics panel FC.
*/
const Topics: FunctionComponent = ({ children }) => {
const { model, updateTopicsFilter } = useTopicsModel();

let topics: JSX.Element;
if (model.loading) {
topics = <p>Loading...</p>;
} else {
topics = <p>{`Topics retrieved: ${JSON.stringify(model.topics)}`}</p>;
}

return (
<div className='topics'>
<input placeholder='filter' onChange={updateTopicsFilter} />
{topics}
{children}
</div>
);
};

export { Topics };
5 changes: 5 additions & 0 deletions client/Panels/Topics/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/*
* Copyright Strimzi authors.
* License: Apache License 2.0 (see the file LICENSE or http://apache.org/licenses/LICENSE-2.0.html).
*/
export * from './View.carbon';
39 changes: 39 additions & 0 deletions client/Panels/Topics/useTopicsModel.assets.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright Strimzi authors.
* License: Apache License 2.0 (see the file LICENSE or http://apache.org/licenses/LICENSE-2.0.html).
*/
import { generateMockDataResponseForGQLRequest } from 'utils/test/withApollo/withApollo.util';
Copy link
Member

Choose a reason for hiding this comment

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

import { generateMockDataResponseForGQLRequest } from 'utils/test' ?

import { topicType } from './useTopicsModel.types';
import { GET_TOPICS } from 'Queries/Topics';

const successResponse: topicType[] = [
{ name: 'testtopic1', partitions: 1, replicas: 1 },
{ name: 'testtopic2', partitions: 2, replicas: 2 },
{ name: 'testtopic3', partitions: 3, replicas: 3 },
];

const successFilteredResponse: topicType[] = [
{ name: 'testtopic1', partitions: 1, replicas: 1 },
];

export const mockGetTopicsResponses = {
successResponse,
successFilteredResponse,
};

const successRequest = generateMockDataResponseForGQLRequest(
GET_TOPICS,
{ topics: successResponse },
{ variables: { filter: undefined } }
);

const successFilteredRequest = generateMockDataResponseForGQLRequest(
GET_TOPICS,
{ topics: successFilteredResponse },
{ variables: { filter: 'testtopic1' } }
);

export const mockGetTopicsRequests = {
successRequest,
successFilteredRequest,
};
45 changes: 45 additions & 0 deletions client/Panels/Topics/useTopicsModel.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright Strimzi authors.
* License: Apache License 2.0 (see the file LICENSE or http://apache.org/licenses/LICENSE-2.0.html).
*/
import React from 'react';
import { renderHook, act } from '@testing-library/react-hooks';
import { withApolloProviderReturning } from 'utils/test';
import { useTopicsModel } from './Model';
import {
mockGetTopicsRequests,
mockGetTopicsResponses,
} from './useTopicsModel.assets';

describe('`useTopicsModel` hook', () => {
beforeEach(() => {
jest.useFakeTimers();
});

afterEach(() => {
jest.useRealTimers();
});

it('returns the expected query content', async () => {
const { result, rerender } = renderHook(useTopicsModel, {
wrapper: ({ children }) =>
withApolloProviderReturning(
[mockGetTopicsRequests.successRequest],
<React.Fragment>{children}</React.Fragment>
),
});

expect(result.current.model.loading).toBe(true);
expect(result.current.model.topics).toEqual([]);

await act(async () => {
jest.runAllTimers();
});
rerender();

expect(result.current.model.topics).toEqual(
mockGetTopicsResponses.successResponse
);
expect(result.current.model.loading).toBe(false);
});
});
27 changes: 27 additions & 0 deletions client/Panels/Topics/useTopicsModel.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright Strimzi authors.
* License: Apache License 2.0 (see the file LICENSE or http://apache.org/licenses/LICENSE-2.0.html).
*/
import { ApolloError } from '@apollo/client';
import { ChangeEvent } from 'react';

/** the shape of the object returned by the useTopicsModel hook */
export type useTopicsModelType = {
model: {
filter: string | undefined;
topics: topicType[];
error: ApolloError | undefined;
loading: boolean;
};
updateTopicsFilter: (evt: ChangeEvent<HTMLInputElement>) => void;
};

export type topicsType = {
topics: topicType[];
};

export type topicType = {
name: string;
partitions: number;
replicas: number;
};
Copy link
Contributor

Choose a reason for hiding this comment

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

Use the types generated by the Rama's PR here.

6 changes: 6 additions & 0 deletions client/Panels/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/*
* Copyright Strimzi authors.
* License: Apache License 2.0 (see the file LICENSE or http://apache.org/licenses/LICENSE-2.0.html).
*/
export * from './Home';
export * from './Topics';
Loading