mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-10-29 17:49:14 +00:00
Fix #10174: Display Synonym in the Glossary Term List and include Syn…onyms in Glossary Search; Fix #10738: Search Filters needs to be consistent across entities. Example Topic and Container; Fix #10789: Search experience: improve weight on direct match for tableName and columnName (#10807)
* Fix #10174: Display Synonym in the Glossary Term List and include Synonyms in Glossary Search; Fix #10738: Search Filters needs to be consistent across entities. Example Topic and Container; Fix #10789: Search experience: improve weight on direct match for tableName and columnName * Fix #10174: Display Synonym in the Glossary Term List and include Synonyms in Glossary Search; Fix #10738: Search Filters needs to be consistent across entities. Example Topic and Container; Fix #10789: Search experience: improve weight on direct match for tableName and columnName * Fix #10174: Display Synonym in the Glossary Term List and include Synonyms in Glossary Search; Fix #10738: Search Filters needs to be consistent across entities. Example Topic and Container; Fix #10789: Search experience: improve weight on direct match for tableName and columnName * feat: implement filters for topic containers and glossary * fix: jest tests * fix: cypress tests --------- Co-authored-by: karanh37 <karanh37@gmail.com> Co-authored-by: karanh37 <33024356+karanh37@users.noreply.github.com>
This commit is contained in:
parent
e53a8c5859
commit
744cac26c4
@ -51,8 +51,7 @@ public class TopicIndex implements ElasticSearchIndex {
|
||||
doc.put("service_suggest", serviceSuggest);
|
||||
doc.put("entityType", Entity.TOPIC);
|
||||
doc.put("serviceType", topic.getServiceType());
|
||||
doc.put("schemaText", topic.getMessageSchema() != null ? topic.getMessageSchema().getSchemaText() : null);
|
||||
doc.put("schemaType", topic.getMessageSchema() != null ? topic.getMessageSchema().getSchemaType() : null);
|
||||
doc.put("messageSchema", topic.getMessageSchema() != null ? topic.getMessageSchema() : null);
|
||||
return doc;
|
||||
}
|
||||
|
||||
|
||||
@ -415,7 +415,10 @@ public class SearchResource {
|
||||
.field(FIELD_DISPLAY_NAME, 15.0f)
|
||||
.field(FIELD_DISPLAY_NAME_NGRAM)
|
||||
.field(FIELD_NAME, 15.0f)
|
||||
.field(DISPLAY_NAME_KEYWORD, 25.0f)
|
||||
.field(NAME_KEYWORD, 25.0f)
|
||||
.field(FIELD_DESCRIPTION, 1.0f)
|
||||
.field("columns.name.keyword", 10.0f)
|
||||
.field("columns.name", 2.0f)
|
||||
.field("columns.name.ngram")
|
||||
.field("columns.displayName", 2.0f)
|
||||
@ -466,6 +469,8 @@ public class SearchResource {
|
||||
.field(FIELD_DISPLAY_NAME, 15.0f)
|
||||
.field(FIELD_DISPLAY_NAME_NGRAM)
|
||||
.field(FIELD_NAME, 15.0f)
|
||||
.field(DISPLAY_NAME_KEYWORD, 25.0f)
|
||||
.field(NAME_KEYWORD, 25.0f)
|
||||
.field(FIELD_DESCRIPTION, 1.0f)
|
||||
.field("messageSchema.schemaFields.name", 2.0f)
|
||||
.field("messageSchema.schemaFields.description", 1.0f)
|
||||
@ -493,6 +498,8 @@ public class SearchResource {
|
||||
.field(FIELD_DISPLAY_NAME, 15.0f)
|
||||
.field(FIELD_DISPLAY_NAME_NGRAM)
|
||||
.field(FIELD_NAME, 15.0f)
|
||||
.field(DISPLAY_NAME_KEYWORD, 25.0f)
|
||||
.field(NAME_KEYWORD, 25.0f)
|
||||
.field(FIELD_DESCRIPTION, 1.0f)
|
||||
.field("charts.name", 2.0f)
|
||||
.field("charts.description", 1.0f)
|
||||
@ -523,6 +530,8 @@ public class SearchResource {
|
||||
.field(FIELD_DISPLAY_NAME, 15.0f)
|
||||
.field(FIELD_DISPLAY_NAME_NGRAM)
|
||||
.field(FIELD_NAME, 15.0f)
|
||||
.field(DISPLAY_NAME_KEYWORD, 25.0f)
|
||||
.field(NAME_KEYWORD, 25.0f)
|
||||
.field(DESCRIPTION, 1.0f)
|
||||
.field("tasks.name", 2.0f)
|
||||
.field("tasks.description", 1.0f)
|
||||
@ -551,6 +560,8 @@ public class SearchResource {
|
||||
.field(FIELD_DISPLAY_NAME, 15.0f)
|
||||
.field(FIELD_DISPLAY_NAME_NGRAM)
|
||||
.field(FIELD_NAME, 15.0f)
|
||||
.field(DISPLAY_NAME_KEYWORD, 25.0f)
|
||||
.field(NAME_KEYWORD, 25.0f)
|
||||
.field(DESCRIPTION, 1.0f)
|
||||
.field("mlFeatures.name", 2.0f)
|
||||
.field("mlFeatures.description", 1.0f)
|
||||
@ -580,7 +591,10 @@ public class SearchResource {
|
||||
.field(FIELD_DISPLAY_NAME_NGRAM)
|
||||
.field(FIELD_NAME, 15.0f)
|
||||
.field(FIELD_DESCRIPTION, 1.0f)
|
||||
.field(DISPLAY_NAME_KEYWORD, 25.0f)
|
||||
.field(NAME_KEYWORD, 25.0f)
|
||||
.field("dataModel.columns.name", 2.0f)
|
||||
.field("dataModel.columns.name.keyword", 10.0f)
|
||||
.field("dataModel.columns.name.ngram")
|
||||
.field("dataModel.columns.displayName", 2.0f)
|
||||
.field("dataModel.columns.displayName.ngram")
|
||||
@ -686,7 +700,16 @@ public class SearchResource {
|
||||
QueryStringQueryBuilder queryBuilder =
|
||||
QueryBuilders.queryStringQuery(query)
|
||||
.field(FIELD_NAME, 10.0f)
|
||||
.field(NAME_KEYWORD, 10.0f)
|
||||
.field(DISPLAY_NAME_KEYWORD, 10.0f)
|
||||
.field(FIELD_DISPLAY_NAME, 10.0f)
|
||||
.field(FIELD_DISPLAY_NAME_NGRAM)
|
||||
.field("synonyms", 5.0f)
|
||||
.field("synonyms.ngram")
|
||||
.field(DESCRIPTION, 3.0f)
|
||||
.field("glossary.name", 5.0f)
|
||||
.field("glossary.displayName", 5.0f)
|
||||
.field("glossary.displayName.ngram")
|
||||
.defaultOperator(Operator.AND)
|
||||
.fuzziness(Fuzziness.AUTO);
|
||||
|
||||
@ -699,8 +722,12 @@ public class SearchResource {
|
||||
hb.field(highlightGlossaryName);
|
||||
hb.preTags("<span class=\"text-highlighter\">");
|
||||
hb.postTags("</span>");
|
||||
|
||||
return searchBuilder(queryBuilder, hb, from, size);
|
||||
SearchSourceBuilder searchSourceBuilder =
|
||||
new SearchSourceBuilder().query(queryBuilder).highlighter(hb).from(from).size(size);
|
||||
searchSourceBuilder
|
||||
.aggregation(AggregationBuilders.terms("tags.tagFQN").field("tags.tagFQN").size(MAX_AGGREGATE_SIZE))
|
||||
.aggregation(AggregationBuilders.terms("glossary.name").field("glossary.name.keyword"));
|
||||
return searchSourceBuilder;
|
||||
}
|
||||
|
||||
private SearchSourceBuilder buildTagSearchBuilder(String query, int from, int size) {
|
||||
|
||||
@ -17,6 +17,11 @@
|
||||
"lowercase",
|
||||
"om_stemmer"
|
||||
]
|
||||
},
|
||||
"om_ngram": {
|
||||
"tokenizer": "ngram",
|
||||
"min_gram": 1,
|
||||
"max_gram": 2
|
||||
}
|
||||
},
|
||||
"filter": {
|
||||
@ -47,10 +52,15 @@
|
||||
},
|
||||
"displayName": {
|
||||
"type": "text",
|
||||
"analyzer": "om_analyzer",
|
||||
"fields": {
|
||||
"keyword": {
|
||||
"type": "keyword",
|
||||
"ignore_above": 256
|
||||
},
|
||||
"ngram": {
|
||||
"type": "text",
|
||||
"analyzer": "om_ngram"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -71,7 +81,17 @@
|
||||
"type": "text"
|
||||
},
|
||||
"synonyms": {
|
||||
"type": "text"
|
||||
"type": "text",
|
||||
"fields": {
|
||||
"keyword": {
|
||||
"type": "keyword",
|
||||
"ignore_above": 256
|
||||
},
|
||||
"ngram": {
|
||||
"type": "text",
|
||||
"analyzer": "om_ngram"
|
||||
}
|
||||
}
|
||||
},
|
||||
"glossary": {
|
||||
"properties": {
|
||||
@ -88,11 +108,29 @@
|
||||
"type": "keyword"
|
||||
},
|
||||
"name": {
|
||||
"type": "keyword",
|
||||
"type": "text",
|
||||
"fields": {
|
||||
"keyword": {
|
||||
"type": "keyword",
|
||||
"ignore_above": 256
|
||||
},
|
||||
"ngram": {
|
||||
"type": "text",
|
||||
"analyzer": "om_ngram"
|
||||
}
|
||||
}
|
||||
},
|
||||
"displayName": {
|
||||
"type": "text",
|
||||
"analyzer": "om_analyzer",
|
||||
"fields": {
|
||||
"keyword": {
|
||||
"type": "keyword",
|
||||
"ignore_above": 256
|
||||
},
|
||||
"ngram": {
|
||||
"type": "text",
|
||||
"analyzer": "om_ngram"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@ -76,11 +76,110 @@
|
||||
"href": {
|
||||
"type": "text"
|
||||
},
|
||||
"schemaText": {
|
||||
"type": "text"
|
||||
},
|
||||
"schemaType": {
|
||||
"type": "text"
|
||||
"messageSchema": {
|
||||
"properties": {
|
||||
"schemaText": {
|
||||
"type": "text"
|
||||
},
|
||||
"schemaType": {
|
||||
"type": "text"
|
||||
},
|
||||
"schemaFields": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "keyword",
|
||||
"normalizer": "lowercase_normalizer",
|
||||
"fields": {
|
||||
"keyword": {
|
||||
"type": "keyword",
|
||||
"ignore_above": 256
|
||||
}
|
||||
}
|
||||
},
|
||||
"dataType": {
|
||||
"type": "text"
|
||||
},
|
||||
"description": {
|
||||
"type": "text",
|
||||
"index_options": "docs",
|
||||
"analyzer": "om_analyzer",
|
||||
"norms": false
|
||||
},
|
||||
"fullyQualifiedName": {
|
||||
"type": "text"
|
||||
},
|
||||
"tags": {
|
||||
"properties": {
|
||||
"tagFQN": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"labelType": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"description": {
|
||||
"type": "text"
|
||||
},
|
||||
"source": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"state": {
|
||||
"type": "keyword"
|
||||
}
|
||||
}
|
||||
},
|
||||
"children": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "keyword",
|
||||
"fields": {
|
||||
"keyword": {
|
||||
"type": "keyword",
|
||||
"ignore_above": 36
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"name": {
|
||||
"type": "keyword",
|
||||
"fields": {
|
||||
"keyword": {
|
||||
"type": "keyword",
|
||||
"ignore_above": 256
|
||||
}
|
||||
}
|
||||
},
|
||||
"fullyQualifiedName": {
|
||||
"type": "text"
|
||||
},
|
||||
"description": {
|
||||
"type": "text"
|
||||
},
|
||||
"tags": {
|
||||
"properties": {
|
||||
"tagFQN": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"labelType": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"description": {
|
||||
"type": "text"
|
||||
},
|
||||
"source": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"state": {
|
||||
"type": "keyword"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"cleanupPolicies": {
|
||||
"type": "keyword"
|
||||
|
||||
@ -70,7 +70,7 @@ describe('Tags page should work', () => {
|
||||
|
||||
cy.get('.ant-table-thead > tr > .ant-table-cell')
|
||||
.eq(0)
|
||||
.contains('Name')
|
||||
.contains('Tag')
|
||||
.should('be.visible');
|
||||
cy.get('.ant-table-thead > tr > .ant-table-cell')
|
||||
.eq(1)
|
||||
|
||||
@ -19,6 +19,7 @@ import { Button, Card, Col, Row, Space, Tabs } from 'antd';
|
||||
import FacetFilter from 'components/common/facetfilter/FacetFilter';
|
||||
import SearchedData from 'components/searched-data/SearchedData';
|
||||
import { SORT_ORDER } from 'enums/common.enum';
|
||||
import { EntityType } from 'enums/entity.enum';
|
||||
import unique from 'fork-ts-checker-webpack-plugin/lib/utils/array/unique';
|
||||
import {
|
||||
isEmpty,
|
||||
@ -221,6 +222,10 @@ const Explore: React.FC<ExploreProps> = ({
|
||||
});
|
||||
};
|
||||
|
||||
const showFilters = useMemo(() => {
|
||||
return entityDetails?.entityType !== EntityType.TAG ?? true;
|
||||
}, [entityDetails]);
|
||||
|
||||
useEffect(() => {
|
||||
const escapeKeyHandler = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape') {
|
||||
@ -323,14 +328,17 @@ const Explore: React.FC<ExploreProps> = ({
|
||||
<Row gutter={[8, 0]} wrap={false}>
|
||||
<Col className="searched-data-container" flex="auto">
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={24}>
|
||||
<ExploreQuickFilters
|
||||
fields={selectedQuickFilters}
|
||||
index={searchIndex}
|
||||
onAdvanceSearch={() => toggleModal(true)}
|
||||
onFieldValueSelect={handleQuickFiltersValueSelect}
|
||||
/>
|
||||
</Col>
|
||||
{showFilters && (
|
||||
<Col span={24}>
|
||||
<ExploreQuickFilters
|
||||
fields={selectedQuickFilters}
|
||||
index={searchIndex}
|
||||
onAdvanceSearch={() => toggleModal(true)}
|
||||
onFieldValueSelect={handleQuickFiltersValueSelect}
|
||||
/>
|
||||
</Col>
|
||||
)}
|
||||
|
||||
{sqlQuery && (
|
||||
<Col span={24}>
|
||||
<AppliedFilterText
|
||||
|
||||
@ -305,7 +305,7 @@ const Appbar: React.FC = (): JSX.Element => {
|
||||
});
|
||||
} else {
|
||||
// Outside Explore page
|
||||
history.push(getExplorePath({ search: value }));
|
||||
history.push(getExplorePath({ tab: 'tables', search: value }));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -54,7 +54,7 @@ const FacetFilter: React.FC<FacetFilterProps> = ({
|
||||
|
||||
const sortedTiersList = {
|
||||
...tier,
|
||||
buckets: getSortedTierBucketList(tier.buckets),
|
||||
buckets: tier ? getSortedTierBucketList(tier.buckets) : [],
|
||||
};
|
||||
|
||||
return Object.entries({ ...restProps, 'tier.tagFQN': sortedTiersList })
|
||||
|
||||
@ -21,6 +21,8 @@ const aggregationKeyToTitleMap: Record<string, string> = {
|
||||
'tags.tagFQN': 'Tag',
|
||||
'service.name.keyword': 'Service Name',
|
||||
entityType: 'Entity Type',
|
||||
'messageSchema.schemaFields.name': 'Schema Fields',
|
||||
'glossary.name': 'Glossary',
|
||||
};
|
||||
|
||||
const aggregationKeyOrdering: Record<string, number> = {
|
||||
|
||||
@ -125,7 +125,7 @@ const NavBar = ({
|
||||
{ value: SearchIndex.PIPELINE, label: t('label.pipeline') },
|
||||
{ value: SearchIndex.MLMODEL, label: t('label.ml-model') },
|
||||
{ value: SearchIndex.CONTAINER, label: t('label.container') },
|
||||
{ value: SearchIndex.GLOSSARY, label: t('label.glossary-term') },
|
||||
{ value: SearchIndex.GLOSSARY, label: t('label.glossary') },
|
||||
{ value: SearchIndex.TAG, label: t('label.tag') },
|
||||
],
|
||||
[]
|
||||
@ -142,6 +142,7 @@ const NavBar = ({
|
||||
defaultActiveFirstOption
|
||||
className="global-search-select"
|
||||
listHeight={300}
|
||||
popupClassName="global-search-select-menu"
|
||||
value={searchCriteria}
|
||||
onChange={updateSearchCriteria}>
|
||||
{globalSearchOptions.map(({ value, label }) => (
|
||||
@ -289,7 +290,7 @@ const NavBar = ({
|
||||
to={{
|
||||
pathname: ROUTES.TAGS,
|
||||
}}>
|
||||
{t('label.tag-plural')}
|
||||
{t('label.classification')}
|
||||
</NavLink>
|
||||
),
|
||||
},
|
||||
|
||||
@ -74,6 +74,27 @@ export const PIPELINE_DROPDOWN_ITEMS = [
|
||||
},
|
||||
];
|
||||
|
||||
export const TOPIC_DROPDOWN_ITEMS = [
|
||||
{
|
||||
label: t('label.schema-field'),
|
||||
key: 'messageSchema.schemaFields.name',
|
||||
},
|
||||
];
|
||||
|
||||
export const CONTAINER_DROPDOWN_ITEMS = [
|
||||
{
|
||||
label: t('label.column'),
|
||||
key: 'dataModel.columns.name',
|
||||
},
|
||||
];
|
||||
|
||||
export const GLOSSARY_DROPDOWN_ITEMS = [
|
||||
{
|
||||
label: t('label.owner'),
|
||||
key: 'owner.displayName',
|
||||
},
|
||||
];
|
||||
|
||||
export const ALL_DROPDOWN_ITEMS = [
|
||||
...COMMON_DROPDOWN_ITEMS,
|
||||
...TABLE_DROPDOWN_ITEMS,
|
||||
|
||||
@ -97,7 +97,7 @@ export const tabsInfo: { [K in ExploreSearchIndex]: ExploreTabInfo } = {
|
||||
path: 'containers',
|
||||
},
|
||||
[SearchIndex.GLOSSARY]: {
|
||||
label: t('label.glossary-term-plural'),
|
||||
label: t('label.glossary-plural'),
|
||||
sortingFields: entitySortingFields,
|
||||
sortField: INITIAL_SORT_FIELD,
|
||||
path: 'glossary',
|
||||
|
||||
@ -1145,7 +1145,7 @@
|
||||
"schedule-for-ingestion-description": "Scheduling can be set up at an hourly, daily, or weekly cadence. The timezone is in UTC.",
|
||||
"scheduled-run-every": "Scheduled to run every",
|
||||
"scopes-comma-separated": "Add the Scopes value, separated by commas",
|
||||
"search-for-entity-types": "Search for Tables, Topics, Dashboards, Pipelines and ML Models.",
|
||||
"search-for-entity-types": "Search for Tables, Topics, Dashboards, Pipelines, ML Models, Glossary and Tags.",
|
||||
"search-for-ingestion": "Search for ingestion",
|
||||
"select-column-name": "Select column name",
|
||||
"select-gcs-config-type": "Select GCS Config Type",
|
||||
|
||||
@ -421,7 +421,7 @@ describe('Test TagsPage page', () => {
|
||||
it('Table with respective header should be render', async () => {
|
||||
const { container } = render(<TagsPage />);
|
||||
const table = await findByTestId(container, 'table');
|
||||
const name = await findByText(container, 'label.name');
|
||||
const name = await findByText(container, 'label.tag');
|
||||
const description = await findByText(container, 'label.description');
|
||||
const actions = await findByText(container, 'label.action-plural');
|
||||
|
||||
|
||||
@ -722,7 +722,7 @@ const TagsPage = () => {
|
||||
() =>
|
||||
[
|
||||
{
|
||||
title: t('label.name'),
|
||||
title: t('label.tag'),
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
render: (_, record: Tag) => getEntityName(record),
|
||||
@ -918,8 +918,8 @@ const TagsPage = () => {
|
||||
setIsAddingTag((prevState) => !prevState);
|
||||
setErrorDataTag(undefined);
|
||||
}}>
|
||||
{t('label.add-new-entity', {
|
||||
entity: t('label.tag-lowercase'),
|
||||
{t('label.add-entity', {
|
||||
entity: t('label.tag'),
|
||||
})}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
|
||||
@ -23,3 +23,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ant-tabs-tab + .ant-tabs-tab {
|
||||
margin: 0 0 0 24px;
|
||||
}
|
||||
|
||||
@ -177,14 +177,14 @@ pre {
|
||||
.search-grey::placeholder {
|
||||
color: #6b7280;
|
||||
}
|
||||
.global-search-select {
|
||||
width: 140px;
|
||||
.global-search-select-menu {
|
||||
width: 110px !important;
|
||||
}
|
||||
.appbar-search .rc-virtual-list-holder {
|
||||
max-height: max-content;
|
||||
}
|
||||
.suggestions-menu {
|
||||
margin-left: 140px;
|
||||
margin-left: 60px;
|
||||
}
|
||||
|
||||
/* Breadcrumb */
|
||||
|
||||
@ -23,9 +23,12 @@ import { RenderSettings } from 'react-awesome-query-builder';
|
||||
import {
|
||||
ALL_DROPDOWN_ITEMS,
|
||||
COMMON_DROPDOWN_ITEMS,
|
||||
CONTAINER_DROPDOWN_ITEMS,
|
||||
DASHBOARD_DROPDOWN_ITEMS,
|
||||
GLOSSARY_DROPDOWN_ITEMS,
|
||||
PIPELINE_DROPDOWN_ITEMS,
|
||||
TABLE_DROPDOWN_ITEMS,
|
||||
TOPIC_DROPDOWN_ITEMS,
|
||||
} from '../constants/AdvancedSearch.constants';
|
||||
|
||||
import { AdvancedFields, EntityFields } from '../enums/AdvancedSearch.enum';
|
||||
@ -40,7 +43,7 @@ export const getDropDownItems = (index: string) => {
|
||||
return [...TABLE_DROPDOWN_ITEMS, ...COMMON_DROPDOWN_ITEMS];
|
||||
|
||||
case SearchIndex.TOPIC:
|
||||
return [...COMMON_DROPDOWN_ITEMS];
|
||||
return [...TOPIC_DROPDOWN_ITEMS, ...COMMON_DROPDOWN_ITEMS];
|
||||
|
||||
case SearchIndex.DASHBOARD:
|
||||
return [...DASHBOARD_DROPDOWN_ITEMS, ...COMMON_DROPDOWN_ITEMS];
|
||||
@ -52,6 +55,10 @@ export const getDropDownItems = (index: string) => {
|
||||
return [
|
||||
...COMMON_DROPDOWN_ITEMS.filter((item) => item.key !== 'service_type'),
|
||||
];
|
||||
case SearchIndex.CONTAINER:
|
||||
return [...CONTAINER_DROPDOWN_ITEMS, ...COMMON_DROPDOWN_ITEMS];
|
||||
case SearchIndex.GLOSSARY:
|
||||
return [...GLOSSARY_DROPDOWN_ITEMS];
|
||||
|
||||
default:
|
||||
return [];
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user