Skip to content

Commit

Permalink
add search for the test list at frontend home (#1026)
Browse files Browse the repository at this point in the history
* add search for the test list at frontend home

* fixb BE search

* add description to search fields

* simplify typings

Co-authored-by: Sebastian Choren <[email protected]>
  • Loading branch information
cescoferraro and schoren authored Aug 11, 2022
1 parent 0802d28 commit dedbcf6
Show file tree
Hide file tree
Showing 14 changed files with 106 additions and 49 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ swagger/
# Common text editors
.vscode/
.idea/
server/vendor
5 changes: 5 additions & 0 deletions api/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ paths:
schema:
type: integer
default: 0
- in: query
name: query
description: "query to search tests, based on test name and description"
schema:
type: string
responses:
200:
description: successful operation
Expand Down
4 changes: 2 additions & 2 deletions server/http/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,13 +224,13 @@ func (c *controller) GetTestRuns(ctx context.Context, testID string, take, skip
return openapi.Response(200, c.mappers.Out.Runs(runs)), nil
}

func (c *controller) GetTests(ctx context.Context, take, skip int32) (openapi.ImplResponse, error) {
func (c *controller) GetTests(ctx context.Context, take, skip int32, query string) (openapi.ImplResponse, error) {
analytics.SendEvent("Test List", "test")
if take == 0 {
take = 20
}

tests, err := c.testDB.GetTests(ctx, take, skip)
tests, err := c.testDB.GetTests(ctx, take, skip, query)
if err != nil {
return handleDBError(err), err
}
Expand Down
2 changes: 1 addition & 1 deletion server/model/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ type TestRepository interface {
IDExists(context.Context, uuid.UUID) (bool, error)
GetLatestTestVersion(context.Context, uuid.UUID) (Test, error)
GetTestVersion(_ context.Context, _ uuid.UUID, verson int) (Test, error)
GetTests(_ context.Context, take, skip int32) ([]Test, error)
GetTests(_ context.Context, take, skip int32, query string) ([]Test, error)
}

type DefinitionRepository interface {
Expand Down
2 changes: 1 addition & 1 deletion server/openapi/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ type ApiApiServicer interface {
GetTestRun(context.Context, string, string) (ImplResponse, error)
GetTestRuns(context.Context, string, int32, int32) (ImplResponse, error)
GetTestVersionDefinitionFile(context.Context, string, int32) (ImplResponse, error)
GetTests(context.Context, int32, int32) (ImplResponse, error)
GetTests(context.Context, int32, int32, string) (ImplResponse, error)
ImportTestRun(context.Context, ExportedTestInformation) (ImplResponse, error)
RerunTestRun(context.Context, string, string) (ImplResponse, error)
RunTest(context.Context, string) (ImplResponse, error)
Expand Down
3 changes: 2 additions & 1 deletion server/openapi/api_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -452,7 +452,8 @@ func (c *ApiApiController) GetTests(w http.ResponseWriter, r *http.Request) {
c.errorHandler(w, r, &ParsingError{Err: err}, nil)
return
}
result, err := c.service.GetTests(r.Context(), takeParam, skipParam)
queryParam := query.Get("query")
result, err := c.service.GetTests(r.Context(), takeParam, skipParam, queryParam)
// If an error occurred, encode the error with the status code
if err != nil {
c.errorHandler(w, r, err, &result)
Expand Down
2 changes: 1 addition & 1 deletion server/openapi/impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

package openapi

//Implementation response defines an error code with the associated body
// Implementation response defines an error code with the associated body
type ImplResponse struct {
Code int
Body interface{}
Expand Down
4 changes: 2 additions & 2 deletions server/testdb/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ func (m *MockRepository) GetLatestTestVersion(_ context.Context, id uuid.UUID) (
return args.Get(0).(model.Test), args.Error(1)
}

func (m *MockRepository) GetTests(_ context.Context, take int32, skip int32) ([]model.Test, error) {
args := m.Called(take, skip)
func (m *MockRepository) GetTests(_ context.Context, take, skip int32, query string) ([]model.Test, error) {
args := m.Called(take, skip, query)
return args.Get(0).([]model.Test), args.Error(1)
}

Expand Down
26 changes: 19 additions & 7 deletions server/testdb/tests.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"database/sql"
"encoding/json"
"fmt"
"strings"
"time"

"github.com/google/uuid"
Expand Down Expand Up @@ -178,21 +179,32 @@ func (td *postgresDB) GetLatestTestVersion(ctx context.Context, id uuid.UUID) (m
return test, nil
}

func (td *postgresDB) GetTests(ctx context.Context, take, skip int32) ([]model.Test, error) {
stmt, err := td.db.Prepare(getTestSQL + `
func (td *postgresDB) GetTests(ctx context.Context, take, skip int32, query string) ([]model.Test, error) {
hasSearchQuery := query != ""
params := []any{take, skip}

sql := getTestSQL + `
INNER JOIN (
SELECT id as idx, max(version) as latest_version FROM tests GROUP BY idx
) as latestTests ON latestTests.idx = t.id
WHERE t.version = latestTests.latest_version
ORDER BY (t.test ->> 'CreatedAt')::timestamp DESC
LIMIT $1 OFFSET $2
`)
WHERE t.version = latestTests.latest_version `
if hasSearchQuery {
params = append(params, "%"+strings.ReplaceAll(query, " ", "%")+"%")
sql += ` AND (
(t.test ->> 'Name') ilike $3
OR (t.test ->> 'Description') ilike $3
)`
}

sql += ` ORDER BY (t.test ->> 'CreatedAt')::timestamp DESC LIMIT $1 OFFSET $2`

stmt, err := td.db.Prepare(sql)
if err != nil {
return nil, err
}
defer stmt.Close()

rows, err := stmt.QueryContext(ctx, take, skip)
rows, err := stmt.QueryContext(ctx, params...)
if err != nil {
return nil, err
}
Expand Down
57 changes: 40 additions & 17 deletions server/testdb/tests_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,22 +92,45 @@ func TestGetTests(t *testing.T) {
db, clean := getDB()
defer clean()

createTestWithName(t, db, "1")
createTestWithName(t, db, "2")
createTestWithName(t, db, "3")

actual, err := db.GetTests(context.TODO(), 20, 0)
require.NoError(t, err)
assert.Len(t, actual, 3)

// test order
assert.Equal(t, "3", actual[0].Name)
assert.Equal(t, "2", actual[1].Name)
assert.Equal(t, "1", actual[2].Name)

actual, err = db.GetTests(context.TODO(), 20, 10)
require.NoError(t, err)
assert.Len(t, actual, 0)
createTestWithName(t, db, "one")
createTestWithName(t, db, "two")
createTestWithName(t, db, "three")

t.Run("Order", func(t *testing.T) {
actual, err := db.GetTests(context.TODO(), 20, 0, "")
require.NoError(t, err)
assert.Len(t, actual, 3)

// test order
assert.Equal(t, "three", actual[0].Name)
assert.Equal(t, "two", actual[1].Name)
assert.Equal(t, "one", actual[2].Name)
})

t.Run("Pagination", func(t *testing.T) {
actual, err := db.GetTests(context.TODO(), 20, 10, "")
require.NoError(t, err)
assert.Len(t, actual, 0)
})

t.Run("SearchByName", func(t *testing.T) {
_, _ = db.CreateTest(context.TODO(), model.Test{Name: "VerySpecificName"})
actual, err := db.GetTests(context.TODO(), 10, 0, "specif")
require.NoError(t, err)
assert.Len(t, actual, 1)

assert.Equal(t, "VerySpecificName", actual[0].Name)
})

t.Run("SearchByDescription", func(t *testing.T) {
_, _ = db.CreateTest(context.TODO(), model.Test{Description: "VeryUniqueText"})

actual, err := db.GetTests(context.TODO(), 10, 0, "nique")
require.NoError(t, err)
assert.Len(t, actual, 1)

assert.Equal(t, "VeryUniqueText", actual[0].Description)
})
}

func TestGetTestsWithMultipleVersions(t *testing.T) {
Expand All @@ -126,7 +149,7 @@ func TestGetTestsWithMultipleVersions(t *testing.T) {
_, err = db.UpdateTest(context.TODO(), test2)
require.NoError(t, err)

tests, err := db.GetTests(context.TODO(), 20, 0)
tests, err := db.GetTests(context.TODO(), 20, 0, "")
assert.NoError(t, err)
assert.Len(t, tests, 2)

Expand Down
14 changes: 12 additions & 2 deletions web/src/hooks/useInfiniteScroll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,20 @@ type TUseInfiniteScrollParams<P> = P & {
take?: number;
};

export interface InfiniteScrollModel<T> {
isLoading: boolean;
localPage: number;
loadMore: () => void;
hasMore: boolean;
isFetching: boolean;
refresh: () => void;
list: T[];
}

const useInfiniteScroll = <T, P>(
useGetDataListQuery: UseQuery<any>,
{take = 20, ...queryParams}: TUseInfiniteScrollParams<P>
) => {
): InfiniteScrollModel<T> => {
const [localPage, setLocalPage] = useState(0);
const [list, setList] = useState<T[]>([]);
const [lastCount, setLastCount] = useState(0);
Expand All @@ -34,7 +44,7 @@ const useInfiniteScroll = <T, P>(

setLastCount(currentList.length);
}
}, [currentList]);
}, [currentList, localPage]);

const refresh = useCallback(() => {
setLocalPage(1);
Expand Down
10 changes: 6 additions & 4 deletions web/src/pages/Home/HomeContent.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import TestList from './TestList';
import {useState} from 'react';
import SearchInput from '../../components/SearchInput';
import * as S from './Home.styled';
import HomeActions from './HomeActions';
import SearchInput from '../../components/SearchInput';
import TestList from './TestList';

const HomeContent: React.FC = () => {
const [query, setQuery] = useState<string>('');
return (
<S.Wrapper>
<S.TitleText>All Tests</S.TitleText>
<S.PageHeader>
<SearchInput onSearch={() => console.log('onSearch')} placeholder="Search test (Not implemented yet)" />
<SearchInput onSearch={value => setQuery(value)} placeholder="Search test" />
<HomeActions />
</S.PageHeader>
<TestList />
<TestList query={query} />
</S.Wrapper>
);
};
Expand Down
21 changes: 12 additions & 9 deletions web/src/pages/Home/TestList.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
import {useCallback} from 'react';
import {useNavigate} from 'react-router-dom';

import InfiniteScroll from 'components/InfiniteScroll';
import TestCard from 'components/TestCard';
import useInfiniteScroll from 'hooks/useInfiniteScroll';
import {useCallback} from 'react';
import {useNavigate} from 'react-router-dom';
import {useGetTestListQuery, useRunTestMutation} from 'redux/apis/TraceTest.api';
import HomeAnalyticsService from 'services/Analytics/HomeAnalytics.service';
import TestAnalyticsService from 'services/Analytics/TestAnalytics.service';
import {TTest} from 'types/Test.types';
import useInfiniteScroll from '../../hooks/useInfiniteScroll';
import {TTest} from '../../types/Test.types';
import * as S from './Home.styled';
import NoResults from './NoResults';
import {useMenuDeleteCallback} from './useMenuDeleteCallback';

const {onTestClick} = HomeAnalyticsService;

const TestList = () => {
interface IProps {
query: string;
}

const TestList = ({query}: IProps) => {
const {list, isLoading, loadMore, hasMore} = useInfiniteScroll<TTest, {query: string}>(useGetTestListQuery, {query});
const navigate = useNavigate();
const [runTest] = useRunTestMutation();
const {list: resultList, hasMore, loadMore, isLoading} = useInfiniteScroll<TTest, {}>(useGetTestListQuery, {});

const onClick = useCallback(
(testId: string) => {
Expand Down Expand Up @@ -45,11 +48,11 @@ const TestList = () => {
loadMore={loadMore}
isLoading={isLoading}
hasMore={hasMore}
shouldTrigger={Boolean(resultList.length)}
shouldTrigger={Boolean(list.length)}
emptyComponent={<NoResults />}
>
<S.TestListContainer data-cy="test-list">
{resultList?.map(test => (
{list?.map(test => (
<TestCard test={test} onClick={onClick} onDelete={onDelete} onRunTest={onRunTest} key={test.id} />
))}
</S.TestListContainer>
Expand Down
4 changes: 2 additions & 2 deletions web/src/redux/apis/TraceTest.api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ const TraceTestAPI = createApi({
{type: Tags.TEST, id: test?.id},
],
}),
getTestList: build.query<TTest[], {take?: number; skip?: number}>({
query: ({take = 25, skip = 0}) => `/tests?take=${take}&skip=${skip}`,
getTestList: build.query<TTest[], {take?: number; skip?: number; query?: string}>({
query: ({take = 25, skip = 0, query = ''}) => `/tests?take=${take}&skip=${skip}&query=${query}`,
providesTags: () => [{type: Tags.TEST, id: 'LIST'}],
transformResponse: (rawTestList: TTest[]) => rawTestList.map(rawTest => Test(rawTest)),
}),
Expand Down

0 comments on commit dedbcf6

Please sign in to comment.