mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-11-25 07:04:43 +00:00
✨ [Feature] : Advance Search (#5427)
Co-authored-by: Vivek Ratnavel Subramanian <vivekratnavel90@gmail.com>
This commit is contained in:
parent
5537db2ab4
commit
f5dbae14da
@ -42,7 +42,7 @@ export const SEARCH_ENTITY_TOPIC = {
|
||||
|
||||
export const SEARCH_ENTITY_DASHBOARD = {
|
||||
dashboard_1: {
|
||||
term: 'Sales Dashboard',
|
||||
term: 'Slack Dashboard',
|
||||
entity: MYDATA_SUMMARY_OPTIONS.dashboards,
|
||||
},
|
||||
dashboard_2: {
|
||||
@ -52,9 +52,9 @@ export const SEARCH_ENTITY_DASHBOARD = {
|
||||
};
|
||||
|
||||
export const SEARCH_ENTITY_PIPELINE = {
|
||||
pipeline_1: { term: 'Hive ETL', entity: MYDATA_SUMMARY_OPTIONS.pipelines },
|
||||
pipeline_1: { term: 'Snowflake ETL', entity: MYDATA_SUMMARY_OPTIONS.pipelines },
|
||||
pipeline_2: {
|
||||
term: 'Snowflake ETL',
|
||||
term: 'Hive ETL',
|
||||
entity: MYDATA_SUMMARY_OPTIONS.pipelines,
|
||||
},
|
||||
pipeline_3: {
|
||||
|
||||
@ -32,7 +32,7 @@ describe('Entity Details Page', () => {
|
||||
// click on the 1st result and go to manage tab in entity details page
|
||||
cy.wait(500);
|
||||
cy.get('[data-testid="table-link"]').first().should('be.visible').click();
|
||||
cy.get('[data-testid="Manage"]').scrollIntoView().click();
|
||||
cy.get('[data-testid="Manage"]').click();
|
||||
|
||||
// check for delete section and delete button is available or not
|
||||
cy.get('[data-testid="danger-zone"]').scrollIntoView().should('be.visible');
|
||||
|
||||
@ -132,9 +132,12 @@ export const getSuggestedTeams = (term: string): Promise<AxiosResponse> => {
|
||||
export const getUserSuggestions: Function = (
|
||||
term: string
|
||||
): Promise<AxiosResponse> => {
|
||||
return APIClient.get(
|
||||
`/search/suggest?q=${term}&index=${SearchIndex.USER},${SearchIndex.TEAM}`
|
||||
);
|
||||
const params = {
|
||||
q: term,
|
||||
index: `${SearchIndex.USER},${SearchIndex.TEAM}`,
|
||||
};
|
||||
|
||||
return APIClient.get(`/search/suggest`, { params });
|
||||
};
|
||||
|
||||
export const getSearchedUsers = (
|
||||
@ -193,3 +196,13 @@ export const deleteEntity: Function = (
|
||||
|
||||
return APIClient.delete(path);
|
||||
};
|
||||
|
||||
export const getAdvancedFieldOptions = (
|
||||
q: string,
|
||||
index: string,
|
||||
field: string | undefined
|
||||
): Promise<AxiosResponse> => {
|
||||
const params = { index, field, q };
|
||||
|
||||
return APIClient.get(`/search/suggest`, { params });
|
||||
};
|
||||
|
||||
@ -184,6 +184,10 @@ const ActivityThreadPanel: FC<ActivityThreadPanelProp> = ({
|
||||
fetchMoreThread(isInView as boolean, paging, isThreadLoading);
|
||||
}, [paging, isThreadLoading, isInView]);
|
||||
|
||||
useEffect(() => {
|
||||
document.body.style.overflow = 'hidden';
|
||||
}, []);
|
||||
|
||||
return ReactDOM.createPortal(
|
||||
<div className={classNames('tw-h-full', className)}>
|
||||
<FeedPanelOverlay
|
||||
|
||||
@ -0,0 +1,164 @@
|
||||
/*
|
||||
* Copyright 2021 Collate
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { fireEvent, render } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { AdvanceField } from '../Explore/explore.interface';
|
||||
import AdvancedField from './AdvancedField';
|
||||
|
||||
const mockData = {
|
||||
'metadata-suggest': [
|
||||
{
|
||||
text: 'clou',
|
||||
offset: 0,
|
||||
length: 4,
|
||||
options: [
|
||||
{
|
||||
text: 'Cloud_Infra',
|
||||
_index: 'team_search_index',
|
||||
_type: '_doc',
|
||||
_id: '267a4dd4-df64-400d-b4d1-3925d6f23885',
|
||||
_score: 10,
|
||||
_source: {
|
||||
suggest: [
|
||||
{
|
||||
input: 'Cloud_Infra',
|
||||
weight: 5,
|
||||
},
|
||||
{
|
||||
input: 'Cloud_Infra',
|
||||
weight: 10,
|
||||
},
|
||||
],
|
||||
deleted: false,
|
||||
// eslint-disable-next-line @typescript-eslint/camelcase
|
||||
team_id: '267a4dd4-df64-400d-b4d1-3925d6f23885',
|
||||
name: 'Cloud_Infra',
|
||||
// eslint-disable-next-line @typescript-eslint/camelcase
|
||||
display_name: 'Cloud_Infra',
|
||||
// eslint-disable-next-line @typescript-eslint/camelcase
|
||||
entity_type: 'team',
|
||||
users: [],
|
||||
owns: [],
|
||||
// eslint-disable-next-line @typescript-eslint/camelcase
|
||||
default_roles: [],
|
||||
// eslint-disable-next-line @typescript-eslint/camelcase
|
||||
last_updated_timestamp: 1654838173854,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
jest.mock('../../axiosAPIs/miscAPI', () => ({
|
||||
getAdvancedFieldOptions: jest
|
||||
.fn()
|
||||
.mockImplementation(() => Promise.resolve()),
|
||||
getUserSuggestions: jest
|
||||
.fn()
|
||||
.mockImplementation(() => Promise.resolve({ data: { suggest: mockData } })),
|
||||
}));
|
||||
|
||||
const index = 'table_search_index';
|
||||
const field = { key: 'owner.name', value: undefined } as AdvanceField;
|
||||
const onFieldRemove = jest.fn();
|
||||
const onFieldValueSelect = jest.fn();
|
||||
|
||||
const mockProps = {
|
||||
index,
|
||||
field,
|
||||
onFieldRemove,
|
||||
onFieldValueSelect,
|
||||
};
|
||||
|
||||
describe('Test AdvancedField Component', () => {
|
||||
it('Should render advancedfield component', async () => {
|
||||
const { findByTestId } = render(<AdvancedField {...mockProps} />);
|
||||
|
||||
const label = await findByTestId('field-label');
|
||||
|
||||
expect(label).toBeInTheDocument();
|
||||
|
||||
expect(label).toHaveTextContent('Owner:');
|
||||
|
||||
const searchSelect = await findByTestId('field-select');
|
||||
|
||||
expect(searchSelect).toBeInTheDocument();
|
||||
|
||||
const removeButton = await findByTestId('field-remove-button');
|
||||
|
||||
expect(removeButton).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Should call remove method on click of remove button', async () => {
|
||||
const { findByTestId } = render(<AdvancedField {...mockProps} />);
|
||||
|
||||
const label = await findByTestId('field-label');
|
||||
|
||||
expect(label).toBeInTheDocument();
|
||||
|
||||
expect(label).toHaveTextContent('Owner:');
|
||||
|
||||
const searchSelect = await findByTestId('field-select');
|
||||
|
||||
expect(searchSelect).toBeInTheDocument();
|
||||
|
||||
const removeButton = await findByTestId('field-remove-button');
|
||||
|
||||
expect(removeButton).toBeInTheDocument();
|
||||
|
||||
fireEvent.click(removeButton);
|
||||
|
||||
expect(onFieldRemove).toHaveBeenCalledWith(field.key);
|
||||
});
|
||||
|
||||
it('Should call select method on click of option', async () => {
|
||||
const { findByTestId, findByRole, findAllByTestId } = render(
|
||||
<AdvancedField {...mockProps} />
|
||||
);
|
||||
|
||||
const label = await findByTestId('field-label');
|
||||
|
||||
expect(label).toBeInTheDocument();
|
||||
|
||||
expect(label).toHaveTextContent('Owner:');
|
||||
|
||||
const searchSelect = await findByTestId('field-select');
|
||||
|
||||
expect(searchSelect).toBeInTheDocument();
|
||||
|
||||
const removeButton = await findByTestId('field-remove-button');
|
||||
|
||||
expect(removeButton).toBeInTheDocument();
|
||||
|
||||
const searchInput = await findByRole('combobox');
|
||||
|
||||
expect(searchInput).toBeInTheDocument();
|
||||
|
||||
fireEvent.change(searchInput, { target: { value: 'cloud' } });
|
||||
|
||||
const fieldOptions = await findAllByTestId('field-option');
|
||||
|
||||
expect(fieldOptions).toHaveLength(
|
||||
mockData['metadata-suggest'][0].options.length
|
||||
);
|
||||
|
||||
fireEvent.click(fieldOptions[0]);
|
||||
|
||||
expect(onFieldValueSelect).toHaveBeenCalledWith({
|
||||
key: 'owner.name',
|
||||
value: 'Cloud_Infra',
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,183 @@
|
||||
/*
|
||||
* Copyright 2021 Collate
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { Select } from 'antd';
|
||||
import { AxiosError, AxiosResponse } from 'axios';
|
||||
import { startCase } from 'lodash';
|
||||
import React, { FC, useState } from 'react';
|
||||
import {
|
||||
getAdvancedFieldOptions,
|
||||
getUserSuggestions,
|
||||
} from '../../axiosAPIs/miscAPI';
|
||||
import { MISC_FIELDS } from '../../constants/advanceSearch.constants';
|
||||
import {
|
||||
getAdvancedField,
|
||||
getItemLabel,
|
||||
} from '../../utils/AdvancedSearchUtils';
|
||||
import { showErrorToast } from '../../utils/ToastUtils';
|
||||
import { AdvanceField } from '../Explore/explore.interface';
|
||||
|
||||
interface Props {
|
||||
index: string;
|
||||
field: AdvanceField;
|
||||
onFieldRemove: (value: string) => void;
|
||||
onFieldValueSelect: (field: AdvanceField) => void;
|
||||
}
|
||||
|
||||
interface Option {
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface InputProps {
|
||||
options: Option[];
|
||||
value: string | undefined;
|
||||
handleChange: (value: string) => void;
|
||||
handleSearch: (value: string) => void;
|
||||
handleSelect: (value: string) => void;
|
||||
handleClear: () => void;
|
||||
}
|
||||
|
||||
const SearchInput = ({
|
||||
options,
|
||||
value,
|
||||
handleChange,
|
||||
handleSearch,
|
||||
handleSelect,
|
||||
handleClear,
|
||||
}: InputProps) => {
|
||||
const { Option } = Select;
|
||||
|
||||
const optionsElement = options.map((d) => (
|
||||
<Option data-testid="field-option" key={d.value}>
|
||||
{d.label}
|
||||
</Option>
|
||||
));
|
||||
|
||||
return (
|
||||
<Select
|
||||
allowClear
|
||||
showSearch
|
||||
bordered={false}
|
||||
className="ant-advaced-field-select"
|
||||
data-testid="field-select"
|
||||
defaultActiveFirstOption={false}
|
||||
dropdownClassName="ant-suggestion-dropdown"
|
||||
filterOption={false}
|
||||
notFoundContent={null}
|
||||
placeholder="Search to Select"
|
||||
showArrow={false}
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
onClear={handleClear}
|
||||
onSearch={handleSearch}
|
||||
onSelect={handleSelect}>
|
||||
{optionsElement}
|
||||
</Select>
|
||||
);
|
||||
};
|
||||
|
||||
const AdvancedField: FC<Props> = ({
|
||||
field,
|
||||
onFieldRemove,
|
||||
index,
|
||||
onFieldValueSelect,
|
||||
}) => {
|
||||
const advancedField = getAdvancedField(field.key);
|
||||
|
||||
const [options, setOptions] = useState<Option[]>([]);
|
||||
const [value, setValue] = useState<string | undefined>(field.value);
|
||||
|
||||
const fetchOptions = (query: string) => {
|
||||
if (!MISC_FIELDS.includes(field.key)) {
|
||||
getAdvancedFieldOptions(query, index, advancedField)
|
||||
.then((res: AxiosResponse) => {
|
||||
const suggestOptions =
|
||||
res.data.suggest['metadata-suggest'][0].options ?? [];
|
||||
const uniqueOptions = [
|
||||
// eslint-disable-next-line
|
||||
...new Set(suggestOptions.map((op: any) => op.text)),
|
||||
];
|
||||
setOptions(
|
||||
uniqueOptions.map((op: unknown) => ({
|
||||
label: op as string,
|
||||
value: op as string,
|
||||
}))
|
||||
);
|
||||
})
|
||||
.catch((err: AxiosError) => showErrorToast(err));
|
||||
} else {
|
||||
getUserSuggestions(query)
|
||||
.then((res: AxiosResponse) => {
|
||||
const suggestOptions =
|
||||
res.data.suggest['metadata-suggest'][0].options ?? [];
|
||||
const uniqueOptions = [
|
||||
// eslint-disable-next-line
|
||||
...new Set(suggestOptions.map((op: any) => op._source.name)),
|
||||
];
|
||||
setOptions(
|
||||
uniqueOptions.map((op: unknown) => ({
|
||||
label: op as string,
|
||||
value: op as string,
|
||||
}))
|
||||
);
|
||||
})
|
||||
.catch((err: AxiosError) => showErrorToast(err));
|
||||
}
|
||||
};
|
||||
|
||||
const handleSearch = (newValue: string) => {
|
||||
if (newValue) {
|
||||
fetchOptions(newValue);
|
||||
} else {
|
||||
setOptions([]);
|
||||
}
|
||||
};
|
||||
|
||||
const handleChange = (newValue: string) => {
|
||||
setValue(newValue);
|
||||
};
|
||||
|
||||
const handleOnSelect = (newValue: string) => {
|
||||
onFieldValueSelect({ ...field, value: newValue });
|
||||
};
|
||||
|
||||
const handleOnClear = () => {
|
||||
onFieldValueSelect({ ...field, value: undefined });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="tw-bg-white tw-border tw-border-main tw-rounded tw-p-1 tw-px-2 tw-flex tw-justify-between">
|
||||
<span className="tw-self-center" data-testid="field-label">
|
||||
{startCase(getItemLabel(field.key))}:
|
||||
</span>
|
||||
<SearchInput
|
||||
handleChange={handleChange}
|
||||
handleClear={handleOnClear}
|
||||
handleSearch={handleSearch}
|
||||
handleSelect={handleOnSelect}
|
||||
options={options}
|
||||
value={value}
|
||||
/>
|
||||
<span
|
||||
className="tw-cursor-pointer tw-self-center"
|
||||
data-testid="field-remove-button"
|
||||
onClick={() => onFieldRemove(field.key)}>
|
||||
<FontAwesomeIcon className="tw-text-primary" icon="times" />
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AdvancedField;
|
||||
@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2021 Collate
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { fireEvent, render } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { AdvanceField } from '../Explore/explore.interface';
|
||||
import AdvancedFields from './AdvancedFields';
|
||||
|
||||
jest.mock('./AdvancedField', () =>
|
||||
jest
|
||||
.fn()
|
||||
.mockReturnValue(<div data-testid="advanced-field">AdvancedField</div>)
|
||||
);
|
||||
|
||||
const index = 'table_search_index';
|
||||
const fields = [
|
||||
{ key: 'owner.name', value: undefined },
|
||||
{ key: 'column_names', value: undefined },
|
||||
] as AdvanceField[];
|
||||
|
||||
const onFieldRemove = jest.fn();
|
||||
const onClear = jest.fn();
|
||||
const onFieldValueSelect = jest.fn();
|
||||
|
||||
const mockProps = {
|
||||
index,
|
||||
fields,
|
||||
onFieldRemove,
|
||||
onClear,
|
||||
onFieldValueSelect,
|
||||
};
|
||||
|
||||
describe('Test AdvancedFields component', () => {
|
||||
it('Should render AdvancedFields component', async () => {
|
||||
const { findByTestId, findAllByTestId } = render(
|
||||
<AdvancedFields {...mockProps} />
|
||||
);
|
||||
|
||||
const fields = await findAllByTestId('advanced-field');
|
||||
const clearButton = await findByTestId('clear-all-button');
|
||||
|
||||
expect(fields).toHaveLength(fields.length);
|
||||
|
||||
expect(clearButton).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Should call onClear method on click of Clear All button', async () => {
|
||||
const { findByTestId, findAllByTestId } = render(
|
||||
<AdvancedFields {...mockProps} />
|
||||
);
|
||||
|
||||
const fields = await findAllByTestId('advanced-field');
|
||||
const clearButton = await findByTestId('clear-all-button');
|
||||
|
||||
expect(fields).toHaveLength(fields.length);
|
||||
|
||||
expect(clearButton).toBeInTheDocument();
|
||||
|
||||
fireEvent.click(clearButton);
|
||||
|
||||
expect(onClear).toBeCalled();
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2021 Collate
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { uniqueId } from 'lodash';
|
||||
import React, { FC } from 'react';
|
||||
import { AdvanceField } from '../Explore/explore.interface';
|
||||
import AdvancedField from './AdvancedField';
|
||||
|
||||
interface Props {
|
||||
index: string;
|
||||
fields: Array<AdvanceField>;
|
||||
onFieldRemove: (value: string) => void;
|
||||
onClear: () => void;
|
||||
onFieldValueSelect: (field: AdvanceField) => void;
|
||||
}
|
||||
|
||||
const AdvancedFields: FC<Props> = ({
|
||||
fields,
|
||||
onFieldRemove,
|
||||
onClear,
|
||||
index,
|
||||
onFieldValueSelect,
|
||||
}) => {
|
||||
return (
|
||||
<div className="tw-flex tw-gap-2 tw-mb-3">
|
||||
{fields.map((field) => (
|
||||
<AdvancedField
|
||||
field={field}
|
||||
index={index}
|
||||
key={uniqueId()}
|
||||
onFieldRemove={onFieldRemove}
|
||||
onFieldValueSelect={onFieldValueSelect}
|
||||
/>
|
||||
))}
|
||||
<span
|
||||
className="tw-text-primary tw-self-center tw-cursor-pointer"
|
||||
data-testid="clear-all-button"
|
||||
onClick={onClear}>
|
||||
Clear All
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AdvancedFields;
|
||||
@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Copyright 2021 Collate
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { fireEvent, render } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import {
|
||||
COMMON_DROPDOWN_ITEMS,
|
||||
TABLE_DROPDOWN_ITEMS,
|
||||
} from '../../constants/advanceSearch.constants';
|
||||
import { AdvanceField } from '../Explore/explore.interface';
|
||||
import AdvancedSearchDropDown from './AdvancedSearchDropDown';
|
||||
|
||||
const mockItems = [...COMMON_DROPDOWN_ITEMS, ...TABLE_DROPDOWN_ITEMS];
|
||||
|
||||
jest.mock('../../utils/AdvancedSearchUtils', () => ({
|
||||
getDropDownItems: jest
|
||||
.fn()
|
||||
.mockReturnValue([...COMMON_DROPDOWN_ITEMS, ...TABLE_DROPDOWN_ITEMS]),
|
||||
}));
|
||||
|
||||
const onSelect = jest.fn();
|
||||
const selectedItems = [] as AdvanceField[];
|
||||
const index = 'table_search_index';
|
||||
|
||||
const mockPorps = {
|
||||
selectedItems,
|
||||
index,
|
||||
onSelect,
|
||||
};
|
||||
|
||||
describe('Test AdvancedSearch DropDown Component', () => {
|
||||
it('Should render dropdown component', async () => {
|
||||
const { findByTestId, findAllByTestId } = render(
|
||||
<AdvancedSearchDropDown {...mockPorps} />
|
||||
);
|
||||
|
||||
const dropdownLabel = await findByTestId('dropdown-label');
|
||||
|
||||
expect(dropdownLabel).toBeInTheDocument();
|
||||
|
||||
fireEvent.click(dropdownLabel);
|
||||
|
||||
const dropdownMenu = await findByTestId('dropdown-menu');
|
||||
|
||||
expect(dropdownMenu).toBeInTheDocument();
|
||||
|
||||
const menuItems = await findAllByTestId('dropdown-menu-item');
|
||||
|
||||
expect(menuItems).toHaveLength(mockItems.length);
|
||||
});
|
||||
|
||||
it('Should call onSelect method on onClick option', async () => {
|
||||
const { findByTestId, findAllByTestId } = render(
|
||||
<AdvancedSearchDropDown {...mockPorps} />
|
||||
);
|
||||
|
||||
const dropdownLabel = await findByTestId('dropdown-label');
|
||||
|
||||
expect(dropdownLabel).toBeInTheDocument();
|
||||
|
||||
fireEvent.click(dropdownLabel);
|
||||
|
||||
const dropdownMenu = await findByTestId('dropdown-menu');
|
||||
|
||||
expect(dropdownMenu).toBeInTheDocument();
|
||||
|
||||
const menuItems = await findAllByTestId('dropdown-menu-item');
|
||||
|
||||
expect(menuItems).toHaveLength(mockItems.length);
|
||||
|
||||
fireEvent.click(menuItems[0]);
|
||||
|
||||
expect(onSelect).toHaveBeenCalledWith(mockItems[0].key);
|
||||
});
|
||||
|
||||
it('Selected option should be disabled', async () => {
|
||||
const { findByTestId, findAllByTestId } = render(
|
||||
<AdvancedSearchDropDown
|
||||
{...mockPorps}
|
||||
selectedItems={[{ key: mockItems[0].key, value: undefined }]}
|
||||
/>
|
||||
);
|
||||
|
||||
const dropdownLabel = await findByTestId('dropdown-label');
|
||||
|
||||
expect(dropdownLabel).toBeInTheDocument();
|
||||
|
||||
fireEvent.click(dropdownLabel);
|
||||
|
||||
const dropdownMenu = await findByTestId('dropdown-menu');
|
||||
|
||||
expect(dropdownMenu).toBeInTheDocument();
|
||||
|
||||
const menuItems = await findAllByTestId('dropdown-menu-item');
|
||||
|
||||
expect(menuItems).toHaveLength(mockItems.length);
|
||||
|
||||
expect(menuItems[0]).toHaveAttribute('aria-disabled', 'true');
|
||||
|
||||
fireEvent.click(menuItems[0]);
|
||||
|
||||
expect(onSelect).not.toHaveBeenCalledWith(mockItems[0].key);
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2021 Collate
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Dropdown, Menu } from 'antd';
|
||||
import React, { FC } from 'react';
|
||||
import { getDropDownItems } from '../../utils/AdvancedSearchUtils';
|
||||
import { normalLink } from '../../utils/styleconstant';
|
||||
import { dropdownIcon as DropdownIcon } from '../../utils/svgconstant';
|
||||
import { AdvanceField } from '../Explore/explore.interface';
|
||||
|
||||
interface Props {
|
||||
index: string;
|
||||
selectedItems: Array<AdvanceField>;
|
||||
onSelect: (filter: string) => void;
|
||||
}
|
||||
|
||||
const AdvancedSearchDropDown: FC<Props> = ({
|
||||
index,
|
||||
onSelect,
|
||||
selectedItems,
|
||||
}) => {
|
||||
const items = getDropDownItems(index).map((item) => ({
|
||||
...item,
|
||||
onClick: () => onSelect(item.key),
|
||||
disabled: selectedItems.some((i) => item.key === i.key),
|
||||
'data-testid': 'dropdown-menu-item',
|
||||
}));
|
||||
|
||||
const menu = <Menu data-testid="dropdown-menu" items={items} />;
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
className="tw-self-center tw-mr-2 tw-cursor-pointer"
|
||||
data-testid="dropdown"
|
||||
overlay={menu}
|
||||
trigger={['click']}>
|
||||
<div className="tw-text-primary" data-testid="dropdown-label">
|
||||
<span className="tw-mr-2">Advanced Search</span>
|
||||
<DropdownIcon style={{ color: normalLink, margin: '0px' }} />
|
||||
</div>
|
||||
</Dropdown>
|
||||
);
|
||||
};
|
||||
|
||||
export default AdvancedSearchDropDown;
|
||||
@ -36,7 +36,6 @@ import { useHistory, useLocation } from 'react-router-dom';
|
||||
import { Button } from '../../components/buttons/Button/Button';
|
||||
import ErrorPlaceHolderES from '../../components/common/error-with-placeholder/ErrorPlaceHolderES';
|
||||
import FacetFilter from '../../components/common/facetfilter/FacetFilter';
|
||||
import DropDownList from '../../components/dropdown/DropDownList';
|
||||
import SearchedData from '../../components/searched-data/SearchedData';
|
||||
import {
|
||||
getExplorePathWithSearch,
|
||||
@ -66,9 +65,11 @@ import {
|
||||
import { formatDataResponse } from '../../utils/APIUtils';
|
||||
import { getCountBadge } from '../../utils/CommonUtils';
|
||||
import { getFilterCount, getFilterString } from '../../utils/FilterUtils';
|
||||
import { dropdownIcon as DropDownIcon } from '../../utils/svgconstant';
|
||||
import AdvancedFields from '../AdvancedSearch/AdvancedFields';
|
||||
import AdvancedSearchDropDown from '../AdvancedSearch/AdvancedSearchDropDown';
|
||||
import PageLayout from '../containers/PageLayout';
|
||||
import { ExploreProps } from './explore.interface';
|
||||
import { AdvanceField, ExploreProps } from './explore.interface';
|
||||
import SortingDropDown from './SortingDropDown';
|
||||
|
||||
const Explore: React.FC<ExploreProps> = ({
|
||||
tabCounts,
|
||||
@ -105,18 +106,16 @@ const Explore: React.FC<ExploreProps> = ({
|
||||
...filterObject,
|
||||
...searchFilter,
|
||||
});
|
||||
|
||||
const [currentPage, setCurrentPage] = useState<number>(1);
|
||||
const [totalNumberOfValue, setTotalNumberOfValues] = useState<number>(0);
|
||||
const [aggregations, setAggregations] = useState<Array<AggregationType>>([]);
|
||||
const [searchTag, setSearchTag] = useState<string>(location.search);
|
||||
|
||||
const [fieldListVisible, setFieldListVisible] = useState<boolean>(false);
|
||||
const [sortField, setSortField] = useState<string>(sortValue);
|
||||
const [sortOrder, setSortOrder] = useState<string>(INITIAL_SORT_ORDER);
|
||||
const [searchIndex, setSearchIndex] = useState<string>(getCurrentIndex(tab));
|
||||
const [currentTab, setCurrentTab] = useState<number>(getCurrentTab(tab));
|
||||
const [fieldList, setFieldList] =
|
||||
useState<Array<{ name: string; value: string }>>(tableSortingFields);
|
||||
|
||||
const [isEntityLoading, setIsEntityLoading] = useState(true);
|
||||
const [isFilterSet, setIsFilterSet] = useState<boolean>(
|
||||
!isEmpty(initialFilter)
|
||||
@ -125,6 +124,43 @@ const Explore: React.FC<ExploreProps> = ({
|
||||
const isMounting = useRef(true);
|
||||
const forceSetAgg = useRef(false);
|
||||
const previsouIndex = usePrevious(searchIndex);
|
||||
const [fieldList, setFieldList] =
|
||||
useState<Array<{ name: string; value: string }>>(tableSortingFields);
|
||||
|
||||
const [selectedAdvancedFields, setSelectedAdvancedField] = useState<
|
||||
Array<AdvanceField>
|
||||
>([]);
|
||||
|
||||
const onAdvancedFieldSelect = (value: string) => {
|
||||
const flag = selectedAdvancedFields.some((field) => field.key === value);
|
||||
if (!flag) {
|
||||
setSelectedAdvancedField((pre) => [
|
||||
...pre,
|
||||
{ key: value, value: undefined },
|
||||
]);
|
||||
}
|
||||
};
|
||||
const onAdvancedFieldRemove = (value: string) => {
|
||||
setSelectedAdvancedField((pre) =>
|
||||
pre.filter((field) => field.key !== value)
|
||||
);
|
||||
};
|
||||
|
||||
const onAdvancedFieldClear = () => {
|
||||
setSelectedAdvancedField([]);
|
||||
};
|
||||
|
||||
const onAdvancedFieldValueSelect = (field: AdvanceField) => {
|
||||
setSelectedAdvancedField((pre) => {
|
||||
return pre.map((preField) => {
|
||||
if (preField.key === field.key) {
|
||||
return field;
|
||||
} else {
|
||||
return preField;
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const handleSelectedFilter = (
|
||||
checked: boolean,
|
||||
@ -160,11 +196,16 @@ const Explore: React.FC<ExploreProps> = ({
|
||||
handleFilterChange(filterData);
|
||||
};
|
||||
|
||||
const handleFieldDropDown = (value: string) => {
|
||||
setSortField(value);
|
||||
};
|
||||
|
||||
const handleShowDeleted = (checked: boolean) => {
|
||||
onShowDeleted(checked);
|
||||
};
|
||||
|
||||
const onClearFilterHandler = (type: string[], isForceClear = false) => {
|
||||
setSelectedAdvancedField([]);
|
||||
const updatedFilter = type.reduce((filterObj, type) => {
|
||||
return { ...filterObj, [type]: [] };
|
||||
}, {});
|
||||
@ -335,44 +376,26 @@ const Explore: React.FC<ExploreProps> = ({
|
||||
return facetFilters;
|
||||
};
|
||||
|
||||
const handleFieldDropDown = (
|
||||
_e: React.MouseEvent<HTMLElement, MouseEvent>,
|
||||
value?: string
|
||||
) => {
|
||||
setSortField(value || sortField);
|
||||
setFieldListVisible(false);
|
||||
};
|
||||
const handleOrder = (value: string) => {
|
||||
setSortOrder(value);
|
||||
};
|
||||
|
||||
const getSortingElements = () => {
|
||||
return (
|
||||
<div className="tw-flex tw-gap-2">
|
||||
<div className="tw-mt-4">
|
||||
<span className="tw-mr-2">Sort by:</span>
|
||||
<span className="tw-relative">
|
||||
<Button
|
||||
className="focus:tw-no-underline"
|
||||
data-testid="sortBy"
|
||||
size="custom"
|
||||
theme="primary"
|
||||
variant="link"
|
||||
onClick={() => setFieldListVisible((visible) => !visible)}>
|
||||
{fieldList.find((field) => field.value === sortField)?.name ||
|
||||
'Relevance'}
|
||||
<DropDownIcon />
|
||||
</Button>
|
||||
{fieldListVisible && (
|
||||
<DropDownList
|
||||
dropDownList={fieldList}
|
||||
value={sortField}
|
||||
onSelect={handleFieldDropDown}
|
||||
<div className="tw-flex">
|
||||
<AdvancedSearchDropDown
|
||||
index={searchIndex}
|
||||
selectedItems={selectedAdvancedFields}
|
||||
onSelect={onAdvancedFieldSelect}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="tw-mt-2 tw-flex tw-gap-2">
|
||||
|
||||
<SortingDropDown
|
||||
fieldList={fieldList}
|
||||
handleFieldDropDown={handleFieldDropDown}
|
||||
sortField={sortField}
|
||||
/>
|
||||
|
||||
<div className="tw-flex">
|
||||
{sortOrder === 'asc' ? (
|
||||
<button onClick={() => handleOrder('desc')}>
|
||||
<FontAwesomeIcon
|
||||
@ -486,6 +509,17 @@ const Explore: React.FC<ExploreProps> = ({
|
||||
}
|
||||
};
|
||||
|
||||
const handleAdvancedSearch = (advancedFields: AdvanceField[]) => {
|
||||
const advancedFilterObject: FilterObject = {};
|
||||
advancedFields.forEach((field) => {
|
||||
if (field.value) {
|
||||
advancedFilterObject[field.key] = [field.value];
|
||||
}
|
||||
});
|
||||
|
||||
handleFilterChange(advancedFilterObject);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
handleSearchText(searchQuery || emptyValue);
|
||||
setCurrentPage(1);
|
||||
@ -493,11 +527,8 @@ const Explore: React.FC<ExploreProps> = ({
|
||||
|
||||
useEffect(() => {
|
||||
setFieldList(tabsInfo[getCurrentTab(tab) - 1].sortingFields);
|
||||
setSortField(
|
||||
searchQuery
|
||||
? tabsInfo[getCurrentTab(tab) - 1].sortField
|
||||
: INITIAL_SORT_FIELD
|
||||
);
|
||||
// if search text is there then set sortfield as ''(Relevance)
|
||||
setSortField(searchText ? '' : tabsInfo[getCurrentTab(tab) - 1].sortField);
|
||||
setSortOrder(INITIAL_SORT_ORDER);
|
||||
setCurrentTab(getCurrentTab(tab));
|
||||
setSearchIndex(getCurrentIndex(tab));
|
||||
@ -525,7 +556,6 @@ const Explore: React.FC<ExploreProps> = ({
|
||||
useEffect(() => {
|
||||
forceSetAgg.current = true;
|
||||
if (!isMounting.current) {
|
||||
resetFilters();
|
||||
fetchTableData();
|
||||
}
|
||||
}, [searchText, searchIndex, showDeleted]);
|
||||
@ -593,6 +623,32 @@ const Explore: React.FC<ExploreProps> = ({
|
||||
}
|
||||
}, [filters]);
|
||||
|
||||
/**
|
||||
* on index change clear the filters
|
||||
*/
|
||||
useEffect(() => {
|
||||
setSelectedAdvancedField([]);
|
||||
}, [searchIndex]);
|
||||
|
||||
/**
|
||||
* if search query is there then make sortfield as empty (Relevance)
|
||||
* otherwise change it to INITIAL_SORT_FIELD (last_updated)
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (searchText) {
|
||||
setSortField('');
|
||||
} else {
|
||||
setSortField(INITIAL_SORT_FIELD);
|
||||
}
|
||||
}, [searchText]);
|
||||
|
||||
/**
|
||||
* on advance field change call handleAdvancedSearch methdod
|
||||
*/
|
||||
useEffect(() => {
|
||||
handleAdvancedSearch(selectedAdvancedFields);
|
||||
}, [selectedAdvancedFields]);
|
||||
|
||||
// alwyas Keep this useEffect at the end...
|
||||
useEffect(() => {
|
||||
isMounting.current = false;
|
||||
@ -614,12 +670,24 @@ const Explore: React.FC<ExploreProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
const advanceFieldCheck =
|
||||
!connectionError && Boolean(selectedAdvancedFields.length);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{!connectionError && getTabs()}
|
||||
<PageLayout
|
||||
leftPanel={Boolean(!error) && fetchLeftPanel()}
|
||||
rightPanel={Boolean(!error) && <></>}>
|
||||
{advanceFieldCheck && (
|
||||
<AdvancedFields
|
||||
fields={selectedAdvancedFields}
|
||||
index={searchIndex}
|
||||
onClear={onAdvancedFieldClear}
|
||||
onFieldRemove={onAdvancedFieldRemove}
|
||||
onFieldValueSelect={onAdvancedFieldValueSelect}
|
||||
/>
|
||||
)}
|
||||
{error ? (
|
||||
<ErrorPlaceHolderES errorMessage={error} type="error" />
|
||||
) : (
|
||||
|
||||
@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2021 Collate
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { fireEvent, render } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { tableSortingFields } from '../../constants/constants';
|
||||
import SortingDropDown from './SortingDropDown';
|
||||
|
||||
const handleFieldDropDown = jest.fn();
|
||||
const fieldList = tableSortingFields;
|
||||
const sortField = '';
|
||||
|
||||
const mockPorps = {
|
||||
fieldList,
|
||||
sortField,
|
||||
handleFieldDropDown,
|
||||
};
|
||||
|
||||
describe('Test Sorting DropDown Component', () => {
|
||||
it('Should render dropdown component', async () => {
|
||||
const { findByTestId, findAllByTestId } = render(
|
||||
<SortingDropDown {...mockPorps} />
|
||||
);
|
||||
|
||||
const dropdownLabel = await findByTestId('dropdown-label');
|
||||
|
||||
expect(dropdownLabel).toBeInTheDocument();
|
||||
|
||||
fireEvent.click(dropdownLabel);
|
||||
|
||||
const dropdownMenu = await findByTestId('dropdown-menu');
|
||||
|
||||
expect(dropdownMenu).toBeInTheDocument();
|
||||
|
||||
const menuItems = await findAllByTestId('dropdown-menu-item');
|
||||
|
||||
expect(menuItems).toHaveLength(fieldList.length);
|
||||
});
|
||||
|
||||
it('Should call onSelect method on onClick option', async () => {
|
||||
const { findByTestId, findAllByTestId } = render(
|
||||
<SortingDropDown {...mockPorps} />
|
||||
);
|
||||
|
||||
const dropdownLabel = await findByTestId('dropdown-label');
|
||||
|
||||
expect(dropdownLabel).toBeInTheDocument();
|
||||
|
||||
fireEvent.click(dropdownLabel);
|
||||
|
||||
const dropdownMenu = await findByTestId('dropdown-menu');
|
||||
|
||||
expect(dropdownMenu).toBeInTheDocument();
|
||||
|
||||
const menuItems = await findAllByTestId('dropdown-menu-item');
|
||||
|
||||
expect(menuItems).toHaveLength(fieldList.length);
|
||||
|
||||
fireEvent.click(menuItems[0]);
|
||||
|
||||
expect(handleFieldDropDown).toHaveBeenCalledWith('last_updated_timestamp');
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2021 Collate
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Dropdown, Menu } from 'antd';
|
||||
import React, { FC } from 'react';
|
||||
import { normalLink } from '../../utils/styleconstant';
|
||||
import { dropdownIcon as DropDownIcon } from '../../utils/svgconstant';
|
||||
|
||||
interface Props {
|
||||
sortField: string;
|
||||
fieldList: Array<{ name: string; value: string }>;
|
||||
handleFieldDropDown: (value: string) => void;
|
||||
}
|
||||
|
||||
const SortingDropDown: FC<Props> = ({
|
||||
fieldList,
|
||||
handleFieldDropDown,
|
||||
sortField,
|
||||
}) => {
|
||||
const items = fieldList.map((field) => ({
|
||||
label: field.name,
|
||||
key: field.value,
|
||||
onClick: () => handleFieldDropDown(field.value),
|
||||
'data-testid': 'dropdown-menu-item',
|
||||
}));
|
||||
|
||||
const menu = <Menu data-testid="dropdown-menu" items={items} />;
|
||||
|
||||
const label = fieldList.find((field) => field.value === sortField)?.name;
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
className="tw-self-center tw-mr-2 tw-cursor-pointer"
|
||||
data-testid="dropdown"
|
||||
overlay={menu}
|
||||
trigger={['click']}>
|
||||
<div className="tw-text-primary" data-testid="dropdown-label">
|
||||
<span className="tw-mr-2">{label}</span>
|
||||
<DropDownIcon style={{ color: normalLink, margin: '0px' }} />
|
||||
</div>
|
||||
</Dropdown>
|
||||
);
|
||||
};
|
||||
|
||||
export default SortingDropDown;
|
||||
@ -60,3 +60,8 @@ export interface ExploreProps {
|
||||
fetchData: (value: SearchDataFunctionType[]) => void;
|
||||
onShowDeleted: (checked: boolean) => void;
|
||||
}
|
||||
|
||||
export interface AdvanceField {
|
||||
key: string;
|
||||
value: string | undefined;
|
||||
}
|
||||
|
||||
@ -229,15 +229,16 @@ const Appbar: React.FC = (): JSX.Element => {
|
||||
const searchHandler = (value: string) => {
|
||||
setIsOpen(false);
|
||||
addToRecentSearched(value);
|
||||
history.push(
|
||||
getExplorePathWithSearch(
|
||||
history.push({
|
||||
pathname: getExplorePathWithSearch(
|
||||
value,
|
||||
// this is for if user is searching from another page
|
||||
location.pathname.startsWith(ROUTES.EXPLORE)
|
||||
? appState.explorePageTab
|
||||
: 'tables'
|
||||
)
|
||||
);
|
||||
),
|
||||
search: location.search,
|
||||
});
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
|
||||
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2021 Collate
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export const COMMON_DROPDOWN_ITEMS = [
|
||||
{
|
||||
label: 'Owner',
|
||||
key: 'owner.name',
|
||||
},
|
||||
{
|
||||
label: 'Tag',
|
||||
key: 'tags',
|
||||
},
|
||||
{
|
||||
label: 'Service',
|
||||
key: 'servicename',
|
||||
},
|
||||
];
|
||||
|
||||
export const TABLE_DROPDOWN_ITEMS = [
|
||||
{
|
||||
label: 'Column',
|
||||
key: 'column_names',
|
||||
},
|
||||
|
||||
{
|
||||
label: 'Schema',
|
||||
key: 'databaseschema',
|
||||
},
|
||||
{
|
||||
label: 'Database',
|
||||
key: 'database',
|
||||
},
|
||||
];
|
||||
|
||||
export const DASHBOARD_DROPDOWN_ITEMS = [
|
||||
{
|
||||
label: 'Chart',
|
||||
key: 'chart_names',
|
||||
},
|
||||
];
|
||||
|
||||
export const PIPELINE_DROPDOWN_ITEMS = [
|
||||
{
|
||||
label: 'Task',
|
||||
key: 'task_names',
|
||||
},
|
||||
];
|
||||
|
||||
export const ALL_DROPDOWN_ITEMS = [
|
||||
...COMMON_DROPDOWN_ITEMS,
|
||||
...TABLE_DROPDOWN_ITEMS,
|
||||
...DASHBOARD_DROPDOWN_ITEMS,
|
||||
...PIPELINE_DROPDOWN_ITEMS,
|
||||
];
|
||||
|
||||
export const MISC_FIELDS = ['owner.name', 'tags'];
|
||||
@ -116,15 +116,15 @@ export const tableSortingFields = [
|
||||
value: 'last_updated_timestamp',
|
||||
},
|
||||
{ name: 'Weekly Usage', value: 'weekly_stats' },
|
||||
// { name: 'Daily Usage', value: 'daily_stats' },
|
||||
// { name: 'Monthly Usage', value: 'monthly_stats' },
|
||||
{ name: 'Relevance', value: '' },
|
||||
];
|
||||
|
||||
export const topicSortingFields = [
|
||||
export const entitySortingFields = [
|
||||
{
|
||||
name: 'Last Updated',
|
||||
value: 'last_updated_timestamp',
|
||||
},
|
||||
{ name: 'Relevance', value: '' },
|
||||
];
|
||||
|
||||
export const sortingOrder = [
|
||||
|
||||
@ -16,7 +16,7 @@ import { AggregationType, Bucket, FilterObject } from 'Models';
|
||||
import { SearchIndex } from '../enums/search.enum';
|
||||
import { getFilterKey } from '../utils/FilterUtils';
|
||||
import { Icons } from '../utils/SvgUtils';
|
||||
import { tableSortingFields, tiers, topicSortingFields } from './constants';
|
||||
import { entitySortingFields, tableSortingFields, tiers } from './constants';
|
||||
|
||||
export const INITIAL_SORT_FIELD = 'last_updated_timestamp';
|
||||
export const INITIAL_SORT_ORDER = 'desc';
|
||||
@ -232,7 +232,7 @@ export const tabsInfo = [
|
||||
label: 'Tables',
|
||||
index: SearchIndex.TABLE,
|
||||
sortingFields: tableSortingFields,
|
||||
sortField: '',
|
||||
sortField: INITIAL_SORT_FIELD,
|
||||
tab: 1,
|
||||
path: 'tables',
|
||||
icon: Icons.TABLE_GREY,
|
||||
@ -241,8 +241,8 @@ export const tabsInfo = [
|
||||
{
|
||||
label: 'Topics',
|
||||
index: SearchIndex.TOPIC,
|
||||
sortingFields: topicSortingFields,
|
||||
sortField: '',
|
||||
sortingFields: entitySortingFields,
|
||||
sortField: INITIAL_SORT_FIELD,
|
||||
tab: 2,
|
||||
path: 'topics',
|
||||
icon: Icons.TOPIC_GREY,
|
||||
@ -251,8 +251,8 @@ export const tabsInfo = [
|
||||
{
|
||||
label: 'Dashboards',
|
||||
index: SearchIndex.DASHBOARD,
|
||||
sortingFields: topicSortingFields,
|
||||
sortField: '',
|
||||
sortingFields: entitySortingFields,
|
||||
sortField: INITIAL_SORT_FIELD,
|
||||
tab: 3,
|
||||
path: 'dashboards',
|
||||
icon: Icons.DASHBOARD_GREY,
|
||||
@ -261,8 +261,8 @@ export const tabsInfo = [
|
||||
{
|
||||
label: 'Pipelines',
|
||||
index: SearchIndex.PIPELINE,
|
||||
sortingFields: topicSortingFields,
|
||||
sortField: '',
|
||||
sortingFields: entitySortingFields,
|
||||
sortField: INITIAL_SORT_FIELD,
|
||||
tab: 4,
|
||||
path: 'pipelines',
|
||||
icon: Icons.PIPELINE_GREY,
|
||||
@ -271,8 +271,8 @@ export const tabsInfo = [
|
||||
{
|
||||
label: 'ML Models',
|
||||
index: SearchIndex.MLMODEL,
|
||||
sortingFields: topicSortingFields,
|
||||
sortField: '',
|
||||
sortingFields: entitySortingFields,
|
||||
sortField: INITIAL_SORT_FIELD,
|
||||
tab: 5,
|
||||
path: 'mlmodels',
|
||||
icon: '',
|
||||
|
||||
@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright 2021 Collate
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export enum AdvancedFields {
|
||||
COLUMN = 'column_suggest',
|
||||
DATABASE = 'database_suggest',
|
||||
SCHEMA = 'schema_suggest',
|
||||
SERVICE = 'service_suggest',
|
||||
CHART = 'chart_suggest',
|
||||
TASK = 'task_suggest',
|
||||
}
|
||||
@ -45,7 +45,6 @@ import {
|
||||
getQueryParam,
|
||||
getSearchFilter,
|
||||
INITIAL_FROM,
|
||||
INITIAL_SORT_FIELD,
|
||||
INITIAL_SORT_ORDER,
|
||||
tabsInfo,
|
||||
ZERO_SIZE,
|
||||
@ -81,9 +80,7 @@ const ExplorePage: FunctionComponent = () => {
|
||||
const [searchResult, setSearchResult] = useState<ExploreSearchData>();
|
||||
const [showDeleted, setShowDeleted] = useState(false);
|
||||
const [initialSortField] = useState<string>(
|
||||
searchQuery
|
||||
? tabsInfo[getCurrentTab(tab) - 1].sortField
|
||||
: INITIAL_SORT_FIELD
|
||||
tabsInfo[getCurrentTab(tab) - 1].sortField
|
||||
);
|
||||
|
||||
const handleSearchText = (text: string) => {
|
||||
|
||||
@ -992,3 +992,22 @@ code {
|
||||
.ant-card-extra {
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.ant-advaced-field-select {
|
||||
color: #7147e8;
|
||||
min-width: 130px;
|
||||
}
|
||||
.ant-suggestion-dropdown {
|
||||
min-width: 200px !important;
|
||||
}
|
||||
.ant-select-item-option-selected:not(.ant-select-item-option-disabled) {
|
||||
background-color: #dbd1f9;
|
||||
}
|
||||
|
||||
.ant-select-single:not(.ant-select-customize-input) .ant-select-selector {
|
||||
padding: 0px 4px;
|
||||
}
|
||||
|
||||
.ant-select-single .ant-select-selector .ant-select-selection-search {
|
||||
left: 4px;
|
||||
}
|
||||
|
||||
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2021 Collate
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { isUndefined } from 'lodash';
|
||||
import {
|
||||
ALL_DROPDOWN_ITEMS,
|
||||
COMMON_DROPDOWN_ITEMS,
|
||||
DASHBOARD_DROPDOWN_ITEMS,
|
||||
PIPELINE_DROPDOWN_ITEMS,
|
||||
TABLE_DROPDOWN_ITEMS,
|
||||
} from '../constants/advanceSearch.constants';
|
||||
import { AdvancedFields } from '../enums/AdvancedSearch.enum';
|
||||
import { SearchIndex } from '../enums/search.enum';
|
||||
|
||||
export const getDropDownItems = (index: string) => {
|
||||
switch (index) {
|
||||
case SearchIndex.TABLE:
|
||||
return [...TABLE_DROPDOWN_ITEMS, ...COMMON_DROPDOWN_ITEMS];
|
||||
|
||||
case SearchIndex.TOPIC:
|
||||
return [...COMMON_DROPDOWN_ITEMS];
|
||||
|
||||
case SearchIndex.DASHBOARD:
|
||||
return [...DASHBOARD_DROPDOWN_ITEMS, ...COMMON_DROPDOWN_ITEMS];
|
||||
|
||||
case SearchIndex.PIPELINE:
|
||||
return [...PIPELINE_DROPDOWN_ITEMS, ...COMMON_DROPDOWN_ITEMS];
|
||||
|
||||
case SearchIndex.MLMODEL:
|
||||
return [
|
||||
...COMMON_DROPDOWN_ITEMS.filter((item) => item.key !== 'service_type'),
|
||||
];
|
||||
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
export const getItemLabel = (key: string) => {
|
||||
const item = ALL_DROPDOWN_ITEMS.find((dItem) => dItem.key === key);
|
||||
|
||||
return !isUndefined(item) ? item.label : 'label';
|
||||
};
|
||||
|
||||
export const getAdvancedField = (field: string) => {
|
||||
switch (field) {
|
||||
case 'column_names':
|
||||
return AdvancedFields.COLUMN;
|
||||
|
||||
case 'databaseschema':
|
||||
return AdvancedFields.SCHEMA;
|
||||
|
||||
case 'database':
|
||||
return AdvancedFields.DATABASE;
|
||||
|
||||
case 'chart_names':
|
||||
return AdvancedFields.CHART;
|
||||
|
||||
case 'task_names':
|
||||
return AdvancedFields.TASK;
|
||||
|
||||
case 'servicename':
|
||||
return AdvancedFields.SERVICE;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
};
|
||||
@ -35,7 +35,7 @@ export const getSearchAPIQuery = (
|
||||
filters ? ` AND ${filters}` : ''
|
||||
}&from=${start}&size=${size}${onlyDeleted ? '&deleted=true' : ''}${
|
||||
sortField ? `&sort_field=${sortField}` : ''
|
||||
}${sortOrder ? `&sort_order=${sortOrder}` : ''}${
|
||||
}${sortOrder && sortField ? `&sort_order=${sortOrder}` : ''}${
|
||||
searchIndex ? `&index=${searchIndex}` : ''
|
||||
}${trackTotalHits ? '&track_total_hits=true' : ''}`;
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user