Unverified Commit 98d559c7 authored by Daniel's avatar Daniel Committed by GitHub

Refactor of Search Results and Search List Item (#21)

- Refactor of search results and related types.
-- `SearchListResult`  - > `Resource`,
-- `SearchListItem` -> `ResourceListItem` and `TableListItem`
- Modified search APIs to support different resources such as tables, users, and dashboards.
parent ba1e4c68
...@@ -60,6 +60,7 @@ def popular_tables() -> Response: ...@@ -60,6 +60,7 @@ def popular_tables() -> Response:
'database': db, 'database': db,
'description': result.get('table_description'), 'description': result.get('table_description'),
'key': '{0}://{1}.{2}/{3}'.format(db, cluster, schema_name, table_name), 'key': '{0}://{1}.{2}/{3}'.format(db, cluster, schema_name, table_name),
'type': 'table',
} }
try: try:
......
...@@ -98,6 +98,19 @@ def _create_url_with_field(*, search_term: str, page_index: int) -> str: ...@@ -98,6 +98,19 @@ def _create_url_with_field(*, search_term: str, page_index: int) -> str:
return url return url
# TODO - Implement these functions
def _search_tables(*, search_term: str, page_index: int) -> Dict[str, Any]:
return {}
def _search_dashboards(*, search_term: str, page_index: int) -> Dict[str, Any]:
return {}
def _search_people(*, search_term: str, page_index: int) -> Dict[str, Any]:
return {}
@action_logging @action_logging
def _search(*, search_term: str, page_index: int) -> Dict[str, Any]: def _search(*, search_term: str, page_index: int) -> Dict[str, Any]:
""" """
...@@ -109,12 +122,28 @@ def _search(*, search_term: str, page_index: int) -> Dict[str, Any]: ...@@ -109,12 +122,28 @@ def _search(*, search_term: str, page_index: int) -> Dict[str, Any]:
TODO: Define an interface for envoy_client TODO: Define an interface for envoy_client
""" """
results_dict = { def _map_table_result(result: Dict) -> Dict:
return {
'type': 'table',
'key': result.get('key', None),
'name': result.get('name', None),
'cluster': result.get('cluster', None),
'description': result.get('description', None),
'database': result.get('database', None),
'schema_name': result.get('schema_name', None),
'last_updated': result.get('last_updated', None),
}
tables = {
'page_index': int(page_index),
'results': [], 'results': [],
'search_term': search_term,
'total_results': 0, 'total_results': 0,
'page_index': int(page_index), }
results_dict = {
'search_term': search_term,
'msg': '', 'msg': '',
'tables': tables,
} }
try: try:
...@@ -139,20 +168,9 @@ def _search(*, search_term: str, page_index: int) -> Dict[str, Any]: ...@@ -139,20 +168,9 @@ def _search(*, search_term: str, page_index: int) -> Dict[str, Any]:
if status_code == HTTPStatus.OK: if status_code == HTTPStatus.OK:
results_dict['msg'] = 'Success' results_dict['msg'] = 'Success'
results_dict['total_results'] = response.json().get('total_results')
# Filter and parse the response dictionary from the search service
params = [
'key',
'name',
'cluster',
'description',
'database',
'schema_name',
'last_updated',
]
results = response.json().get('results') results = response.json().get('results')
results_dict['results'] = [{key: result.get(key, None) for key in params} for result in results] tables['results'] = [_map_table_result(result) for result in results]
tables['total_results'] = response.json().get('total_results')
else: else:
message = 'Encountered error: Search request failed' message = 'Encountered error: Search request failed'
results_dict['msg'] = message results_dict['msg'] = message
......
import * as React from 'react';
import { Link } from 'react-router-dom';
// TODO: Use css-modules instead of 'import'
import './styles.scss';
interface SearchListItemProps {
title?: string;
subtitle?: string;
lastUpdated?: any; // TODO: Investigate typescript Date error when set as 'number'
params?: SearchListItemParams;
schema?: string;
table?: string;
db?: string;
cluster?: string;
}
interface SearchListItemParams {
source?: string;
index?: number;
}
const SearchListItem: React.SFC<SearchListItemProps> = ({ title, subtitle, lastUpdated, params, db, schema, table, cluster }) => {
/* TODO: We have to fix a bug with this feature. Commented out support.
const createLastUpdatedTimestamp = () => {
if (lastUpdated) {
const dateTokens = new Date(lastUpdated).toDateString().split(' ');
return (
<label>
{`${dateTokens[1].toUpperCase()} ${dateTokens[2]}`}
</label>
)
}
return null;
}*/
return (
<li className="list-group-item search-list-item">
<Link to={`/table_detail/${cluster}/${db}/${schema}/${table}?index=${params.index}&source=${params.source}`}>
<img className="icon icon-color icon-database" />
<div className="resultInfo">
<span className="title truncated">{ title }</span>
<span className="subtitle truncated">{ subtitle }</span>
</div>
{ /*createLastUpdatedTimestamp()*/ }
<img className="icon icon-right" />
</Link>
</li>
);
};
SearchListItem.defaultProps = {
title: '',
subtitle: '',
params: {},
};
export default SearchListItem;
import * as React from 'react'; import * as React from 'react';
import SearchListItem from './SearchListItem'; import ResourceListItem from '../../common/ResourceListItem';
import { SearchListResult } from '../types'; import { Resource } from "../../common/ResourceListItem/types";
interface SearchListProps { interface SearchListProps {
results?: SearchListResult[]; results?: Resource[];
params?: SearchListParams; params?: SearchListParams;
} }
...@@ -13,22 +14,15 @@ interface SearchListParams { ...@@ -13,22 +14,15 @@ interface SearchListParams {
} }
const SearchList: React.SFC<SearchListProps> = ({ results, params }) => { const SearchList: React.SFC<SearchListProps> = ({ results, params }) => {
const { source , paginationStartIndex } = params; const { source, paginationStartIndex } = params;
const resultMap = results.map((entry, i) =>
<SearchListItem
key={ entry.key }
params={{source, index: paginationStartIndex + i}}
title={`${entry.schema_name}.${entry.name}`}
subtitle={ entry.description }
lastUpdated = { entry.last_updated }
schema={ entry.schema_name }
cluster={ entry.cluster }
table={ entry.name }
db={ entry.database }
/>);
return ( return (
<ul className="list-group"> <ul className="list-group">
{ resultMap } {
results.map((resource, index) => {
const logging = { source, index: paginationStartIndex + index };
return <ResourceListItem item={ resource } logging={ logging } key={ index } />;
})
}
</ul> </ul>
); );
}; };
......
...@@ -6,21 +6,27 @@ import Pagination from 'react-js-pagination'; ...@@ -6,21 +6,27 @@ import Pagination from 'react-js-pagination';
import SearchBar from './SearchBar'; import SearchBar from './SearchBar';
import SearchList from './SearchList'; import SearchList from './SearchList';
import InfoButton from '../common/InfoButton'; import InfoButton from '../common/InfoButton';
import { SearchListResult } from './types'; import { TableResource } from "../common/ResourceListItem/types";
import { ExecuteSearchRequest } from '../../ducks/search/reducer'; import { ExecuteSearchRequest } from '../../ducks/search/reducer';
import { GetPopularTablesRequest } from '../../ducks/popularTables/reducer'; import { GetPopularTablesRequest } from '../../ducks/popularTables/reducer';
// TODO: Use css-modules instead of 'import' // TODO: Use css-modules instead of 'import'
import './styles.scss'; import './styles.scss';
import {
DashboardSearchResults,
TableSearchResults,
UserSearchResults
} from "../../ducks/search/types";
const RESULTS_PER_PAGE = 10; const RESULTS_PER_PAGE = 10;
export interface StateFromProps { export interface StateFromProps {
pageIndex: number;
popularTables: SearchListResult[];
searchResults: SearchListResult[];
searchTerm: string; searchTerm: string;
totalResults: number; popularTables: TableResource[];
tables: TableSearchResults;
dashboards: DashboardSearchResults
users: UserSearchResults;
} }
export interface DispatchFromProps { export interface DispatchFromProps {
...@@ -39,11 +45,23 @@ class SearchPage extends React.Component<SearchPageProps, SearchPageState> { ...@@ -39,11 +45,23 @@ class SearchPage extends React.Component<SearchPageProps, SearchPageState> {
public static defaultProps: SearchPageProps = { public static defaultProps: SearchPageProps = {
executeSearch: () => undefined, executeSearch: () => undefined,
getPopularTables: () => undefined, getPopularTables: () => undefined,
searchResults: [],
searchTerm: '', searchTerm: '',
pageIndex: 0,
popularTables: [], popularTables: [],
totalResults: 0, dashboards: {
page_index: 0,
results: [],
total_results: 0,
},
tables: {
page_index: 0,
results: [],
total_results: 0,
},
users: {
page_index: 0,
results: [],
total_results: 0,
}
}; };
constructor(props) { constructor(props) {
...@@ -66,15 +84,17 @@ class SearchPage extends React.Component<SearchPageProps, SearchPageState> { ...@@ -66,15 +84,17 @@ class SearchPage extends React.Component<SearchPageProps, SearchPageState> {
} }
createErrorMessage() { createErrorMessage() {
const { pageIndex, searchResults, searchTerm, totalResults } = this.props; const items = this.props.tables;
if (totalResults === 0 && searchTerm.length > 0) { const { page_index, total_results } = items;
const { searchTerm } = this.props;
if (total_results === 0 && searchTerm.length > 0) {
return ( return (
<label> <label>
Your search - <i>{ searchTerm }</i> - did not match any tables. Your search - <i>{ searchTerm }</i> - did not match any tables.
</label> </label>
) )
} }
if (totalResults > 0 && (RESULTS_PER_PAGE * pageIndex) + 1 > totalResults) { if (total_results > 0 && (RESULTS_PER_PAGE * page_index) + 1 > total_results) {
return ( return (
<label> <label>
Page index out of bounds for available matches. Page index out of bounds for available matches.
...@@ -110,24 +130,29 @@ class SearchPage extends React.Component<SearchPageProps, SearchPageState> { ...@@ -110,24 +130,29 @@ class SearchPage extends React.Component<SearchPageProps, SearchPageState> {
) )
} }
const { pageIndex, popularTables, searchResults, searchTerm, totalResults } = this.props; const items = this.props.tables;
const showResultsList = searchResults.length > 0 || popularTables.length > 0; const { page_index, results, total_results } = items;
const { popularTables } = this.props;
const showResultsList = results.length > 0 || popularTables.length > 0;
if (showResultsList) { if (showResultsList) {
const startIndex = (RESULTS_PER_PAGE * pageIndex) + 1; const startIndex = (RESULTS_PER_PAGE * page_index) + 1;
const endIndex = RESULTS_PER_PAGE * ( pageIndex + 1); const endIndex = RESULTS_PER_PAGE * ( page_index + 1);
const hasSearchResults = totalResults > 0; let listTitle = `${startIndex}-${Math.min(endIndex, total_results)} of ${total_results} results`;
const listTitle = hasSearchResults ? let infoText = "Ordered by the relevance of matches within a resource's metadata, as well as overall usage.";
`${startIndex}-${Math.min(endIndex, totalResults)} of ${totalResults} results` :
'Popular Tables';
const infoText = hasSearchResults ?
"Ordered by the relevance of matches within a resource's metadata, as well as overall usage." :
"These are some of the most commonly accessed tables within your organization.";
const searchListParams = { const searchListParams = {
source: hasSearchResults ? 'search_results' : 'popular_tables', source: 'search_results',
paginationStartIndex: RESULTS_PER_PAGE * pageIndex paginationStartIndex: RESULTS_PER_PAGE * page_index
}; };
const showPopularTables = total_results < 1;
if (showPopularTables) {
listTitle = 'Popular Tables';
infoText = "These are some of the most commonly accessed tables within your organization.";
searchListParams.source = 'popular_tables';
}
return ( return (
<div className="col-xs-12"> <div className="col-xs-12">
<div className="search-list-container"> <div className="search-list-container">
...@@ -135,15 +160,15 @@ class SearchPage extends React.Component<SearchPageProps, SearchPageState> { ...@@ -135,15 +160,15 @@ class SearchPage extends React.Component<SearchPageProps, SearchPageState> {
<label> { listTitle } </label> <label> { listTitle } </label>
<InfoButton infoText={ infoText }/> <InfoButton infoText={ infoText }/>
</div> </div>
<SearchList results={ hasSearchResults ? searchResults : popularTables } params={ searchListParams }/> <SearchList results={ showPopularTables ? popularTables : results } params={ searchListParams }/>
</div> </div>
<div className="search-pagination-component"> <div className="search-pagination-component">
{ {
totalResults > RESULTS_PER_PAGE && total_results > RESULTS_PER_PAGE &&
<Pagination <Pagination
activePage={ pageIndex + 1 } activePage={ page_index + 1 }
itemsCountPerPage={ RESULTS_PER_PAGE } itemsCountPerPage={ RESULTS_PER_PAGE }
totalItemsCount={ totalResults } totalItemsCount={ total_results }
pageRangeDisplayed={ 10 } pageRangeDisplayed={ 10 }
onChange={ this.handlePageChange } onChange={ this.handlePageChange }
/> />
......
export interface SearchListResult {
database: string;
cluster: string;
description: string;
key: string;
last_updated: number;
name: string;
schema_name: string;
}
import * as React from 'react';
import { Link } from 'react-router-dom';
import { LoggingParams, TableResource} from '../types';
import './styles.scss';
interface TableListItemProps {
item: TableResource;
logging: LoggingParams;
}
class TableListItem extends React.Component<TableListItemProps, {}> {
constructor(props) {
super(props);
}
/* TODO: We have to fix a bug with this feature. Commented out support.
const createLastUpdatedTimestamp = () => {
if (lastUpdated) {
const dateTokens = new Date(lastUpdated).toDateString().split(' ');
return (
<label>
{`${dateTokens[1].toUpperCase()} ${dateTokens[2]}`}
</label>
)
}
return null;
}*/
getLink = () => {
const { item, logging } = this.props;
return `/table_detail/${item.cluster}/${item.database}/${item.schema_name}/${item.name}`
+ `?index=${logging.index}&source=${logging.source}`;
};
render() {
const { item } = this.props;
return (
<li className="list-group-item search-list-item">
<Link to={ this.getLink() }>
<img className="icon icon-color icon-database" />
<div className="resultInfo">
<span className="title truncated">{ `${item.schema_name}.${item.name} `}</span>
<span className="subtitle truncated">{ item.description }</span>
</div>
{ /*createLastUpdatedTimestamp()*/ }
<img className="icon icon-right" />
</Link>
</li>
);
}
}
export default TableListItem;
import * as React from 'react'
import { LoggingParams, Resource, ResourceType, TableResource } from './types';
import TableListItem from './TableListItem';
interface ListItemProps {
logging: LoggingParams;
item: Resource;
}
export default class ResourceListItem extends React.Component<ListItemProps, {}> {
constructor(props) {
super(props);
}
render() {
switch(this.props.item.type) {
case ResourceType.table:
return (<TableListItem item={ this.props.item as TableResource } logging={ this.props.logging } />);
// case ListItemType.user:
// case ListItemType.dashboard:
default:
return (null);
}
}
}
export enum ResourceType {
table = "table",
user = "user",
dashboard = "dashboard",
}
export interface Resource {
type: ResourceType;
}
export interface TableResource extends Resource {
type: ResourceType.table;
database: string;
cluster: string;
description: string;
key: string;
last_updated: number;
name: string;
schema_name: string;
}
// Placeholder until the schema is defined.
export interface UserResource extends Resource {
type: ResourceType.user;
first_name: string;
last_name: string;
email: string;
}
// Placeholder until the schema is defined.
export interface DashboardResource extends Resource {
type: ResourceType.dashboard;
title: string;
}
export interface LoggingParams {
source: string;
index: number;
}
...@@ -2,18 +2,18 @@ import { connect } from 'react-redux'; ...@@ -2,18 +2,18 @@ import { connect } from 'react-redux';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
import { GlobalState } from "../../ducks/rootReducer"; import { GlobalState } from "../../ducks/rootReducer";
import { executeSearch, ExecuteSearchRequest } from '../../ducks/search/reducer'; import { executeSearch } from '../../ducks/search/reducer';
import { getPopularTables, GetPopularTablesRequest } from '../../ducks/popularTables/reducer'; import { getPopularTables } from '../../ducks/popularTables/reducer';
import SearchPage, { DispatchFromProps, StateFromProps } from '../../components/SearchPage'; import SearchPage, { DispatchFromProps, StateFromProps } from '../../components/SearchPage';
export const mapStateToProps = (state: GlobalState) => { export const mapStateToProps = (state: GlobalState) => {
return { return {
pageIndex: state.search.pageIndex,
popularTables: state.popularTables,
searchResults: state.search.searchResults,
searchTerm: state.search.searchTerm, searchTerm: state.search.searchTerm,
totalResults: state.search.totalResults, popularTables: state.popularTables,
tables: state.search.tables,
users: state.search.users,
dashboards: state.search.dashboards,
}; };
}; };
......
import axios from 'axios';
function transformSearchResults(data) {
return {
pageIndex: data.page_index,
searchResults: data.results,
searchTerm: data.search_term,
totalResults: data.total_results,
};
}
export function searchExecuteSearch(action) {
const { term, pageIndex } = action;
return axios.get(`/api/search/v0/?query=${term}&page_index=${pageIndex}`)
.then(response => transformSearchResults(response.data))
.catch((error) => transformSearchResults(error.response.data));
}
import { SearchListResult } from '../../components/SearchPage/types'; import { TableResource } from "../../components/common/ResourceListItem/types";
/* getPopularTables */ /* getPopularTables */
export enum GetPopularTables { export enum GetPopularTables {
...@@ -13,7 +13,7 @@ export interface GetPopularTablesRequest { ...@@ -13,7 +13,7 @@ export interface GetPopularTablesRequest {
interface GetPopularTablesResponse { interface GetPopularTablesResponse {
type: GetPopularTables.SUCCESS | GetPopularTables.FAILURE; type: GetPopularTables.SUCCESS | GetPopularTables.FAILURE;
payload: SearchListResult[]; payload: TableResource[];
} }
export function getPopularTables(): GetPopularTablesRequest { export function getPopularTables(): GetPopularTablesRequest {
...@@ -23,7 +23,7 @@ export function getPopularTables(): GetPopularTablesRequest { ...@@ -23,7 +23,7 @@ export function getPopularTables(): GetPopularTablesRequest {
export type PopularTablesReducerAction = GetPopularTablesRequest | GetPopularTablesResponse; export type PopularTablesReducerAction = GetPopularTablesRequest | GetPopularTablesResponse;
export type PopularTablesReducerState = SearchListResult[]; export type PopularTablesReducerState = TableResource[];
const initialState: PopularTablesReducerState = []; const initialState: PopularTablesReducerState = [];
......
import axios, { AxiosResponse, AxiosError } from 'axios';
import {
DashboardSearchResults,
TableSearchResults,
UserSearchResults,
} from '../types';
import { SearchReducerState } from "../reducer";
interface SearchResponse {
msg: string;
status_code: number;
search_term: string;
dashboards: DashboardSearchResults;
tables: TableSearchResults;
users: UserSearchResults;
}
function transformSearchResults(data: SearchResponse): SearchReducerState {
return {
searchTerm: data.search_term,
dashboards: data.dashboards,
tables: data.tables,
users: data.users,
};
}
export function searchExecuteSearch(action) {
const { term, pageIndex } = action;
return axios.get(`/api/search/v0/?query=${term}&page_index=${pageIndex}`)
.then((response: AxiosResponse<SearchResponse>)=> transformSearchResults(response.data))
.catch((error: AxiosError) => transformSearchResults(error.response.data));
}
import { SearchListResult } from '../../components/SearchPage/types'; import {
DashboardSearchResults,
TableSearchResults,
UserSearchResults,
} from './types';
/* executeSearch */ /* executeSearch */
export enum ExecuteSearch { export enum ExecuteSearch {
...@@ -30,17 +34,29 @@ export function executeSearch(term: string, pageIndex: number): ExecuteSearchReq ...@@ -30,17 +34,29 @@ export function executeSearch(term: string, pageIndex: number): ExecuteSearchReq
export type SearchReducerAction = ExecuteSearchRequest | ExecuteSearchResponse; export type SearchReducerAction = ExecuteSearchRequest | ExecuteSearchResponse;
export interface SearchReducerState { export interface SearchReducerState {
pageIndex: number;
searchResults: SearchListResult[];
searchTerm: string; searchTerm: string;
totalResults: number; dashboards: DashboardSearchResults;
tables: TableSearchResults;
users: UserSearchResults;
} }
const initialState: SearchReducerState = { const initialState: SearchReducerState = {
pageIndex: 0,
searchResults: [],
searchTerm: '', searchTerm: '',
totalResults: 0, dashboards: {
page_index: 0,
results: [],
total_results: 0,
},
tables: {
page_index: 0,
results: [],
total_results: 0,
},
users: {
page_index: 0,
results: [],
total_results: 0,
},
}; };
export default function reducer(state: SearchReducerState = initialState, action: SearchReducerAction): SearchReducerState { export default function reducer(state: SearchReducerState = initialState, action: SearchReducerAction): SearchReducerState {
......
...@@ -8,7 +8,7 @@ import { ...@@ -8,7 +8,7 @@ import {
import { import {
searchExecuteSearch, searchExecuteSearch,
} from '../api/search/v0'; } from './api/v0';
export function* executeSearchWorker(action: ExecuteSearchRequest): SagaIterator { export function* executeSearchWorker(action: ExecuteSearchRequest): SagaIterator {
......
import { Resource, DashboardResource, TableResource, UserResource } from "../../components/common/ResourceListItem/types";
interface SearchResults<T extends Resource> {
page_index: number;
total_results: number;
results: T[];
}
export type DashboardSearchResults = SearchResults<DashboardResource>;
export type TableSearchResults = SearchResults<TableResource>;
export type UserSearchResults = SearchResults<UserResource>;
import axios, { AxiosResponse, AxiosError } from 'axios'; import axios, { AxiosResponse, AxiosError } from 'axios';
import { CurrentUser } from './types'; import { CurrentUser } from '../types';
export function getCurrentUser() { export function getCurrentUser() {
return axios.get(`/api/current_user`) return axios.get(`/api/current_user`)
......
...@@ -2,7 +2,7 @@ import { SagaIterator } from 'redux-saga'; ...@@ -2,7 +2,7 @@ import { SagaIterator } from 'redux-saga';
import { call, put, takeEvery } from 'redux-saga/effects'; import { call, put, takeEvery } from 'redux-saga/effects';
import { GetCurrentUser } from './reducer'; import { GetCurrentUser } from './reducer';
import { getCurrentUser } from './api'; import { getCurrentUser } from './api/v0';
export function* getUserWorker(): SagaIterator { export function* getUserWorker(): SagaIterator {
try { try {
......
...@@ -25,11 +25,12 @@ class MetadataTest(unittest.TestCase): ...@@ -25,11 +25,12 @@ class MetadataTest(unittest.TestCase):
self.expected_parsed_popular_tables = [ self.expected_parsed_popular_tables = [
{ {
'name': 'test_table', 'name': 'test_table',
'schema_name': 'test_schema',
'cluster': 'test_cluster', 'cluster': 'test_cluster',
'database': 'test_db', 'database': 'test_db',
'description': 'This is a test', 'description': 'This is a test',
'key': 'test_db://test_cluster.test_schema/test_table', 'key': 'test_db://test_cluster.test_schema/test_table',
'schema_name': 'test_schema',
'type': 'table',
} }
] ]
self.mock_metadata = { self.mock_metadata = {
......
...@@ -34,6 +34,7 @@ class SearchTest(unittest.TestCase): ...@@ -34,6 +34,7 @@ class SearchTest(unittest.TestCase):
} }
self.expected_parsed_search_results = [ self.expected_parsed_search_results = [
{ {
'type': 'table',
'cluster': 'test_cluster', 'cluster': 'test_cluster',
'database': 'test_db', 'database': 'test_db',
'description': 'This is a test', 'description': 'This is a test',
...@@ -81,8 +82,10 @@ class SearchTest(unittest.TestCase): ...@@ -81,8 +82,10 @@ class SearchTest(unittest.TestCase):
response = test.get('/api/search/v0/', query_string=dict(query='test', page_index='0')) response = test.get('/api/search/v0/', query_string=dict(query='test', page_index='0'))
data = json.loads(response.data) data = json.loads(response.data)
self.assertEqual(response.status_code, HTTPStatus.OK) self.assertEqual(response.status_code, HTTPStatus.OK)
self.assertEqual(data.get('total_results'), self.mock_search_results.get('total_results'))
self.assertCountEqual(data.get('results'), self.expected_parsed_search_results) tables = data.get('tables')
self.assertEqual(tables.get('total_results'), self.mock_search_results.get('total_results'))
self.assertCountEqual(tables.get('results'), self.expected_parsed_search_results)
@responses.activate @responses.activate
def test_search_fail_on_non_200_response(self) -> None: def test_search_fail_on_non_200_response(self) -> None:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment