mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-09-08 08:31:37 +00:00
ui: clean-up in data insight page (#13193)
* ui: clean-up in data insight page * updated datePickerMenu with type * added option to allow/disallowed Custom Range * added icon and some utils change * refactor cost analysis page * translation * DI enum * updated di interface file * removed duplicated code * updated date picker * export the getEntryFormattedValue function * miner fix * created dataInsight provider and move all the props in to provider, created classBase file * translation sync * fixed failing unit test * fixed failing unite test
This commit is contained in:
parent
40b3048b35
commit
48a892cefd
@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
|
||||
<path d="M15.7917 6.30722C15.7748 6.35294 15.7724 6.40267 15.7849 6.44954C15.7974 6.4964 15.824 6.53808 15.8612 6.56882C18.7628 8.96162 21.002 12.1368 21.494 15.9312C21.8156 18.42 20.8892 20.7696 18.4244 21.6504C17.8292 21.8632 16.7844 21.9704 15.29 21.972C13.298 21.9752 11.3052 21.9784 9.31165 21.9816C7.57085 21.9832 6.36125 21.86 5.68285 21.612C3.14365 20.6832 2.36125 18.2712 2.72365 15.684C3.23005 12.0984 5.47885 8.90642 8.24125 6.65282C8.30419 6.60121 8.34939 6.53129 8.37049 6.45292C8.39159 6.37455 8.38752 6.29169 8.35885 6.21602L7.12525 2.92322C7.08415 2.8128 7.07031 2.69407 7.08492 2.57718C7.09953 2.46029 7.14215 2.34872 7.20914 2.25201C7.27613 2.1553 7.36549 2.07634 7.46958 2.02187C7.57367 1.96739 7.68939 1.93904 7.80685 1.93922L10.0052 1.93682C10.0685 1.93683 10.1294 1.91281 10.1756 1.86962C11.454 0.656822 12.738 0.656822 14.0276 1.86962C14.0739 1.91281 14.1348 1.93683 14.198 1.93682H16.3076C16.4336 1.93696 16.5576 1.96759 16.6691 2.02609C16.7807 2.08459 16.8764 2.16923 16.9481 2.27276C17.0198 2.3763 17.0653 2.49566 17.0809 2.62063C17.0964 2.74561 17.0814 2.87249 17.0372 2.99042L15.7917 6.30722ZM13.8092 3.42482C13.1996 3.41522 13.1396 2.87762 12.7148 2.64002C11.3804 1.88882 11.27 3.42482 10.2716 3.43442C9.87645 3.43602 9.49725 3.43682 9.13405 3.43682C9.11052 3.4368 9.08737 3.44253 9.06655 3.45353C9.04573 3.46453 9.02786 3.48047 9.01444 3.50001C9.00102 3.51955 8.99245 3.54211 8.98946 3.56579C8.98647 3.58946 8.98914 3.61355 8.99725 3.63602L9.78925 5.76722C9.81058 5.82293 9.84814 5.87086 9.89698 5.90471C9.94582 5.93856 10.0037 5.95673 10.0628 5.95682H14.1932C14.2315 5.95673 14.2687 5.94496 14.3 5.92308C14.3314 5.9012 14.3552 5.87026 14.3684 5.83442L15.2204 3.56642C15.2524 3.48162 15.2236 3.43922 15.134 3.43922C14.6812 3.43602 14.2396 3.43122 13.8092 3.42482ZM14.9732 7.74962C14.7812 7.55122 14.6012 7.45282 14.4332 7.45442C12.8844 7.45762 11.3284 7.45842 9.76525 7.45682C9.69059 7.45685 9.61719 7.48216 9.55645 7.52882C7.57885 9.09042 6.07565 10.9392 5.04685 13.0752C3.75805 15.7488 3.29245 20.4672 7.65565 20.4744C10.2748 20.4792 12.8949 20.4792 15.5157 20.4744C16.6276 20.4712 17.4084 20.396 17.858 20.2488C19.9796 19.5504 20.294 17.5224 19.8956 15.468C19.2356 12.072 17.2436 10.1016 14.9732 7.74962Z" fill="currentColor"/>
|
||||
<path d="M15.0974 15.4637C15.0974 15.8333 15 16.176 14.8051 16.4918C14.6169 16.8009 14.3347 17.0563 13.9584 17.2579C13.5888 17.4528 13.1486 17.5603 12.6379 17.5805V18.3969H11.9928V17.5704C11.2804 17.5099 10.7059 17.2982 10.2691 16.9353C9.83228 16.5657 9.60044 16.0651 9.57356 15.4334H11.4081C11.4484 15.8366 11.6433 16.0953 11.9928 16.2096V14.6169C11.4686 14.4825 11.0486 14.3515 10.7328 14.2238C10.4236 14.0961 10.1515 13.8912 9.91628 13.6089C9.68108 13.3267 9.56348 12.9403 9.56348 12.4497C9.56348 11.8382 9.7886 11.3477 10.2388 10.9781C10.6958 10.6085 11.2804 10.4001 11.9928 10.3531V9.53662H12.6379V10.3531C13.3435 10.4069 13.9012 10.6118 14.3112 10.968C14.7211 11.3241 14.9496 11.8181 14.9966 12.4497H13.152C13.1116 12.0869 12.9403 11.8517 12.6379 11.7441V13.3065C13.1956 13.4611 13.6257 13.5989 13.9281 13.7198C14.2305 13.8408 14.4993 14.0424 14.7345 14.3246C14.9764 14.6001 15.0974 14.9798 15.0974 15.4637ZM11.388 12.3691C11.388 12.5371 11.4384 12.6782 11.5392 12.7925C11.6467 12.9067 11.7979 13.0075 11.9928 13.0949V11.6937C11.8046 11.7273 11.6568 11.8013 11.5492 11.9155C11.4417 12.023 11.388 12.1742 11.388 12.3691ZM12.6379 16.2398C12.8395 16.2062 12.9974 16.1256 13.1116 15.9979C13.2326 15.8702 13.2931 15.7123 13.2931 15.5241C13.2931 15.3494 13.236 15.2083 13.1217 15.1008C13.0142 14.9865 12.8529 14.8891 12.6379 14.8085V16.2398Z" fill="currentColor"/>
|
||||
</svg>
|
After Width: | Height: | Size: 3.6 KiB |
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2023 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 { Col, Row } from 'antd';
|
||||
import React from 'react';
|
||||
import { useDataInsightProvider } from '../../../pages/DataInsightPage/DataInsightProvider';
|
||||
import DailyActiveUsersChart from '../DailyActiveUsersChart';
|
||||
import PageViewsByEntitiesChart from '../PageViewsByEntitiesChart';
|
||||
import TopActiveUsers from '../TopActiveUsers';
|
||||
import TopViewEntities from '../TopViewEntities';
|
||||
|
||||
const AppAnalyticsTab = () => {
|
||||
const { chartFilter, selectedDaysFilter } = useDataInsightProvider();
|
||||
|
||||
return (
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={24}>
|
||||
<TopViewEntities chartFilter={chartFilter} />
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<PageViewsByEntitiesChart
|
||||
chartFilter={chartFilter}
|
||||
selectedDays={selectedDaysFilter}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<DailyActiveUsersChart
|
||||
chartFilter={chartFilter}
|
||||
selectedDays={selectedDaysFilter}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<TopActiveUsers chartFilter={chartFilter} />
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
|
||||
export default AppAnalyticsTab;
|
@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Copyright 2023 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 { Col, Row } from 'antd';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { DataInsightChartType } from '../../../generated/dataInsight/dataInsightChartResult';
|
||||
import { useDataInsightProvider } from '../../../pages/DataInsightPage/DataInsightProvider';
|
||||
import Loader from '../../Loader/Loader';
|
||||
import DescriptionInsight from '../DescriptionInsight';
|
||||
import OwnerInsight from '../OwnerInsight';
|
||||
import TierInsight from '../TierInsight';
|
||||
import TotalEntityInsight from '../TotalEntityInsight';
|
||||
|
||||
const DataAssetsTab = () => {
|
||||
const {
|
||||
chartFilter,
|
||||
selectedDaysFilter,
|
||||
kpi,
|
||||
tierTag: tier,
|
||||
} = useDataInsightProvider();
|
||||
const { t } = useTranslation();
|
||||
const { descriptionKpi, ownerKpi } = useMemo(() => {
|
||||
return {
|
||||
descriptionKpi: kpi.data.find(
|
||||
(value) =>
|
||||
value.dataInsightChart.name ===
|
||||
DataInsightChartType.PercentageOfEntitiesWithDescriptionByType
|
||||
),
|
||||
ownerKpi: kpi.data.find(
|
||||
(value) =>
|
||||
value.dataInsightChart.name ===
|
||||
DataInsightChartType.PercentageOfEntitiesWithOwnerByType
|
||||
),
|
||||
};
|
||||
}, [kpi]);
|
||||
|
||||
if (kpi.isLoading) {
|
||||
return <Loader />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={24}>
|
||||
<TotalEntityInsight
|
||||
chartFilter={chartFilter}
|
||||
selectedDays={selectedDaysFilter}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<DescriptionInsight
|
||||
chartFilter={chartFilter}
|
||||
dataInsightChartName={
|
||||
DataInsightChartType.PercentageOfEntitiesWithDescriptionByType
|
||||
}
|
||||
header={t('label.data-insight-description-summary-type', {
|
||||
type: t('label.data-asset'),
|
||||
})}
|
||||
kpi={descriptionKpi}
|
||||
selectedDays={selectedDaysFilter}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<OwnerInsight
|
||||
chartFilter={chartFilter}
|
||||
dataInsightChartName={
|
||||
DataInsightChartType.PercentageOfEntitiesWithOwnerByType
|
||||
}
|
||||
header={t('label.data-insight-owner-summary-type', {
|
||||
type: t('label.data-asset'),
|
||||
})}
|
||||
kpi={ownerKpi}
|
||||
selectedDays={selectedDaysFilter}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<DescriptionInsight
|
||||
chartFilter={chartFilter}
|
||||
dataInsightChartName={
|
||||
DataInsightChartType.PercentageOfServicesWithDescription
|
||||
}
|
||||
header={t('label.data-insight-description-summary-type', {
|
||||
type: t('label.service'),
|
||||
})}
|
||||
kpi={descriptionKpi}
|
||||
selectedDays={selectedDaysFilter}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<OwnerInsight
|
||||
chartFilter={chartFilter}
|
||||
dataInsightChartName={
|
||||
DataInsightChartType.PercentageOfServicesWithOwner
|
||||
}
|
||||
header={t('label.data-insight-owner-summary-type', {
|
||||
type: t('label.service'),
|
||||
})}
|
||||
kpi={ownerKpi}
|
||||
selectedDays={selectedDaysFilter}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<TierInsight
|
||||
chartFilter={chartFilter}
|
||||
selectedDays={selectedDaysFilter}
|
||||
tierTags={tier}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
|
||||
export default DataAssetsTab;
|
@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2023 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 { Kpi } from '../../../generated/dataInsight/kpi/kpi';
|
||||
import { Tag } from '../../../generated/entity/classification/tag';
|
||||
import { ChartFilter } from '../../../interface/data-insight.interface';
|
||||
|
||||
export interface DataAssetsTabProps {
|
||||
chartFilter: ChartFilter;
|
||||
selectedDaysFilter: number;
|
||||
kpiList: Kpi[];
|
||||
tier: { tags: Tag[]; isLoading: boolean };
|
||||
}
|
@ -15,9 +15,11 @@ import { CloseCircleOutlined } from '@ant-design/icons';
|
||||
import { Button, DatePicker, Dropdown, MenuProps, Space } from 'antd';
|
||||
import { RangePickerProps } from 'antd/lib/date-picker';
|
||||
import { isUndefined } from 'lodash';
|
||||
import { DateFilterType } from 'Models';
|
||||
import { MenuInfo } from 'rc-menu/lib/interface';
|
||||
import React, { useState } from 'react';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { ReactComponent as DropdownIcon } from '../../assets/svg/DropDown.svg';
|
||||
import { DateRangeObject } from '../../components/ProfilerDashboard/component/TestSummary';
|
||||
import {
|
||||
@ -37,21 +39,56 @@ import './DatePickerMenu.style.less';
|
||||
interface DatePickerMenuProps {
|
||||
showSelectedCustomRange?: boolean;
|
||||
handleDateRangeChange: (value: DateRangeObject, days?: number) => void;
|
||||
options?: DateFilterType;
|
||||
defaultValue?: string;
|
||||
allowCustomRange?: boolean;
|
||||
}
|
||||
|
||||
function DatePickerMenu({
|
||||
showSelectedCustomRange,
|
||||
handleDateRangeChange,
|
||||
options,
|
||||
defaultValue,
|
||||
allowCustomRange = true,
|
||||
}: DatePickerMenuProps) {
|
||||
const { menuOptions, defaultOptions } = useMemo(() => {
|
||||
let defaultOptions = DEFAULT_SELECTED_RANGE;
|
||||
|
||||
if (defaultValue) {
|
||||
if (options && !isUndefined(options[defaultValue]?.title)) {
|
||||
defaultOptions = {
|
||||
title: options[defaultValue].title,
|
||||
key: defaultValue,
|
||||
days: options[defaultValue].days,
|
||||
};
|
||||
} else if (
|
||||
!isUndefined(
|
||||
PROFILER_FILTER_RANGE[defaultValue as keyof DateFilterType]?.title
|
||||
)
|
||||
) {
|
||||
defaultOptions = {
|
||||
title: PROFILER_FILTER_RANGE[defaultValue].title,
|
||||
key: defaultValue,
|
||||
days: PROFILER_FILTER_RANGE[defaultValue].days,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
menuOptions: options ?? PROFILER_FILTER_RANGE,
|
||||
defaultOptions,
|
||||
};
|
||||
}, [options]);
|
||||
|
||||
const { t } = useTranslation();
|
||||
// State to display the label for selected range value
|
||||
const [selectedTimeRange, setSelectedTimeRange] = useState<string>(
|
||||
DEFAULT_SELECTED_RANGE.title
|
||||
defaultOptions.title
|
||||
);
|
||||
// state to determine the selected value to highlight in the dropdown
|
||||
const [selectedTimeRangeKey, setSelectedTimeRangeKey] = useState<
|
||||
keyof typeof PROFILER_FILTER_RANGE
|
||||
>(DEFAULT_SELECTED_RANGE.key as keyof typeof PROFILER_FILTER_RANGE);
|
||||
const [selectedTimeRangeKey, setSelectedTimeRangeKey] = useState<string>(
|
||||
defaultOptions.key
|
||||
);
|
||||
|
||||
const [isMenuOpen, setIsMenuOpen] = useState<boolean>(false);
|
||||
|
||||
@ -73,62 +110,61 @@ function DatePickerMenu({
|
||||
);
|
||||
|
||||
setSelectedTimeRange(selectedRangeLabel);
|
||||
setSelectedTimeRangeKey(
|
||||
'customRange' as keyof typeof PROFILER_FILTER_RANGE
|
||||
);
|
||||
setSelectedTimeRangeKey('customRange');
|
||||
setIsMenuOpen(false);
|
||||
handleDateRangeChange({ startTs, endTs }, daysCount);
|
||||
}
|
||||
};
|
||||
|
||||
const handleOptionClick = ({ key }: MenuInfo) => {
|
||||
const filterRange =
|
||||
PROFILER_FILTER_RANGE[key as keyof typeof PROFILER_FILTER_RANGE];
|
||||
const filterRange = menuOptions[key];
|
||||
if (isUndefined(filterRange)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedNumberOfDays = filterRange.days;
|
||||
const keyString = key as keyof typeof PROFILER_FILTER_RANGE;
|
||||
const startTs = getEpochMillisForPastDays(selectedNumberOfDays);
|
||||
|
||||
const endTs = getCurrentMillis();
|
||||
|
||||
setSelectedTimeRange(PROFILER_FILTER_RANGE[keyString].title);
|
||||
setSelectedTimeRangeKey(keyString);
|
||||
setSelectedTimeRange(menuOptions[key].title);
|
||||
setSelectedTimeRangeKey(key);
|
||||
setIsMenuOpen(false);
|
||||
|
||||
handleDateRangeChange({ startTs, endTs }, selectedNumberOfDays);
|
||||
};
|
||||
|
||||
const getMenuItems = () => {
|
||||
const items: MenuProps['items'] = Object.entries(PROFILER_FILTER_RANGE).map(
|
||||
const items: MenuProps['items'] = Object.entries(menuOptions).map(
|
||||
([key, value]) => ({
|
||||
label: value.title,
|
||||
key,
|
||||
})
|
||||
);
|
||||
items.push({
|
||||
label: t('label.custom-range'),
|
||||
key: 'customRange',
|
||||
children: [
|
||||
{
|
||||
label: (
|
||||
<DatePicker.RangePicker
|
||||
bordered={false}
|
||||
clearIcon={<CloseCircleOutlined />}
|
||||
format={(value) => value.utc().format('YYYY-MM-DD')}
|
||||
open={isMenuOpen}
|
||||
placement="bottomRight"
|
||||
suffixIcon={null}
|
||||
onChange={handleCustomDateChange}
|
||||
/>
|
||||
),
|
||||
key: 'datePicker',
|
||||
},
|
||||
],
|
||||
popupClassName: 'date-picker-sub-menu-popup',
|
||||
});
|
||||
{
|
||||
allowCustomRange &&
|
||||
items.push({
|
||||
label: t('label.custom-range'),
|
||||
key: 'customRange',
|
||||
children: [
|
||||
{
|
||||
label: (
|
||||
<DatePicker.RangePicker
|
||||
bordered={false}
|
||||
clearIcon={<CloseCircleOutlined />}
|
||||
format={(value) => value.utc().format('YYYY-MM-DD')}
|
||||
open={isMenuOpen}
|
||||
placement="bottomRight"
|
||||
suffixIcon={null}
|
||||
onChange={handleCustomDateChange}
|
||||
/>
|
||||
),
|
||||
key: 'datePicker',
|
||||
},
|
||||
],
|
||||
popupClassName: 'date-picker-sub-menu-popup',
|
||||
});
|
||||
}
|
||||
|
||||
return items;
|
||||
};
|
||||
|
@ -19,6 +19,7 @@ import { ReactComponent as DomainsIcon } from '../assets/svg/ic-domain.svg';
|
||||
import { ReactComponent as QualityIcon } from '../assets/svg/ic-quality-v1.svg';
|
||||
import { ReactComponent as SettingsIcon } from '../assets/svg/ic-settings-v1.svg';
|
||||
import { ReactComponent as InsightsIcon } from '../assets/svg/lampcharge.svg';
|
||||
import { getDataInsightPathWithFqn } from '../utils/DataInsightUtils';
|
||||
import { ROUTES } from './constants';
|
||||
|
||||
export const SIDEBAR_LIST = [
|
||||
@ -39,7 +40,7 @@ export const SIDEBAR_LIST = [
|
||||
{
|
||||
key: ROUTES.DATA_INSIGHT,
|
||||
label: i18next.t('label.insight-plural'),
|
||||
redirect_url: ROUTES.DATA_INSIGHT,
|
||||
redirect_url: getDataInsightPathWithFqn(),
|
||||
icon: InsightsIcon,
|
||||
dataTestId: 'app-bar-item-data-insight',
|
||||
},
|
||||
|
@ -12,7 +12,7 @@
|
||||
*/
|
||||
|
||||
import { t } from 'i18next';
|
||||
import { StepperStepType } from 'Models';
|
||||
import { DateFilterType, StepperStepType } from 'Models';
|
||||
import { CSMode } from '../enums/codemirror.enum';
|
||||
import { DMLOperationType } from '../generated/api/data/createTableProfile';
|
||||
import {
|
||||
@ -71,7 +71,7 @@ export const PROFILER_METRIC = [
|
||||
'customMetricsProfile',
|
||||
];
|
||||
|
||||
export const PROFILER_FILTER_RANGE = {
|
||||
export const PROFILER_FILTER_RANGE: DateFilterType = {
|
||||
yesterday: {
|
||||
days: 1,
|
||||
title: t('label.yesterday'),
|
||||
|
@ -0,0 +1,15 @@
|
||||
/*
|
||||
* Copyright 2023 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 DataInsightIndex {
|
||||
AGGREGATED_COST_ANALYSIS_REPORT_DATA = 'aggregated_cost_analysis_report_data_index',
|
||||
}
|
@ -12,15 +12,21 @@
|
||||
*/
|
||||
|
||||
import { TooltipProps } from 'recharts';
|
||||
import { DataInsightIndex } from '../enums/DataInsight.enum';
|
||||
import { ReportData } from '../generated/analytics/reportData';
|
||||
import { DataReportIndex } from '../generated/dataInsight/dataInsightChart';
|
||||
import { DataInsightChartType } from '../generated/dataInsight/dataInsightChartResult';
|
||||
import { KpiResult, KpiTargetType } from '../generated/dataInsight/kpi/kpi';
|
||||
import { KeysOfUnion } from './search.interface';
|
||||
|
||||
export interface ChartAggregateParam {
|
||||
dataInsightChartName: DataInsightChartType;
|
||||
dataReportIndex: DataReportIndex;
|
||||
startTs: number;
|
||||
endTs: number;
|
||||
startTs?: number;
|
||||
endTs?: number;
|
||||
from?: number;
|
||||
size?: number;
|
||||
queryFilter?: string;
|
||||
tier?: string;
|
||||
team?: string;
|
||||
}
|
||||
@ -36,6 +42,7 @@ export interface DataInsightChartTooltipProps extends TooltipProps<any, any> {
|
||||
isPercentage?: boolean;
|
||||
isTier?: boolean;
|
||||
kpiTooltipRecord?: Record<string, KpiTargetType>;
|
||||
valueFormatter?: (value: number | string) => string;
|
||||
}
|
||||
|
||||
export interface UIKpiResult extends KpiResult {
|
||||
@ -50,6 +57,7 @@ export enum DataInsightTabs {
|
||||
DATA_ASSETS = 'data-assets',
|
||||
APP_ANALYTICS = 'app-analytics',
|
||||
KPIS = 'kpi',
|
||||
COST_ANALYSIS = 'cost-analysis',
|
||||
}
|
||||
|
||||
export enum KpiDate {
|
||||
@ -62,3 +70,31 @@ export type KpiDates = {
|
||||
};
|
||||
|
||||
export type ChartValue = string | number | undefined;
|
||||
|
||||
export type AggregatedCostAnalysisReportDataSearchSource = ReportData; // extends EntityInterface
|
||||
|
||||
export type DataInsightSearchSourceMapping = {
|
||||
[DataInsightIndex.AGGREGATED_COST_ANALYSIS_REPORT_DATA]: AggregatedCostAnalysisReportDataSearchSource;
|
||||
};
|
||||
|
||||
export type DataInsightSearchRequest = {
|
||||
pageNumber?: number;
|
||||
pageSize?: number;
|
||||
searchIndex?: DataInsightIndex.AGGREGATED_COST_ANALYSIS_REPORT_DATA;
|
||||
query?: string;
|
||||
queryFilter?: Record<string, unknown>;
|
||||
postFilter?: Record<string, unknown>;
|
||||
sortField?: string;
|
||||
sortOrder?: string;
|
||||
includeDeleted?: boolean;
|
||||
trackTotalHits?: boolean;
|
||||
filters?: string;
|
||||
} & (
|
||||
| {
|
||||
fetchSource: true;
|
||||
includeFields?: KeysOfUnion<AggregatedCostAnalysisReportDataSearchSource>[];
|
||||
}
|
||||
| {
|
||||
fetchSource?: false;
|
||||
}
|
||||
);
|
||||
|
@ -11,6 +11,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { DataInsightIndex } from '../enums/DataInsight.enum';
|
||||
import { SearchIndex } from '../enums/search.enum';
|
||||
import { Tag } from '../generated/entity/classification/tag';
|
||||
import { Container } from '../generated/entity/data/container';
|
||||
@ -39,6 +40,7 @@ import { User } from '../generated/entity/teams/user';
|
||||
import { TestCase } from '../generated/tests/testCase';
|
||||
import { TestSuite } from '../generated/tests/testSuite';
|
||||
import { TagLabel } from '../generated/type/tagLabel';
|
||||
import { AggregatedCostAnalysisReportDataSearchSource } from './data-insight.interface';
|
||||
|
||||
/**
|
||||
* The `keyof` operator, when applied to a union type, expands to the keys are common for
|
||||
@ -252,7 +254,7 @@ export type SuggestRequest<
|
||||
}
|
||||
);
|
||||
|
||||
export interface SearchHitBody<SI extends SearchIndex, T> {
|
||||
export interface SearchHitBody<SI extends SearchIndex | DataInsightIndex, T> {
|
||||
_index: SI;
|
||||
_type?: string;
|
||||
_id?: string;
|
||||
@ -296,6 +298,22 @@ export interface SearchResponse<
|
||||
|
||||
export type Aggregations = Record<string, { buckets: Bucket[] }>;
|
||||
|
||||
export type DataInsightSearchResponse = {
|
||||
took?: number;
|
||||
timed_out?: boolean;
|
||||
hits: {
|
||||
total: {
|
||||
value: number;
|
||||
relation?: string;
|
||||
};
|
||||
hits: SearchHitBody<
|
||||
DataInsightIndex,
|
||||
AggregatedCostAnalysisReportDataSearchSource
|
||||
>[];
|
||||
};
|
||||
aggregations: Aggregations;
|
||||
};
|
||||
|
||||
/**
|
||||
* Because we are using an older version of typescript-eslint, defining
|
||||
* ```ts
|
||||
|
@ -261,6 +261,8 @@ declare module 'Models' {
|
||||
| Mlmodel
|
||||
| Container;
|
||||
|
||||
export type DateFilterType = Record<string, { days: number; title: string }>;
|
||||
|
||||
export type TagFilterOptions = {
|
||||
text: string;
|
||||
value: string;
|
||||
|
@ -4,6 +4,7 @@
|
||||
"accept": "Akzeptieren",
|
||||
"accept-suggestion": "Vorschlag akzeptieren",
|
||||
"access": "Zugriff",
|
||||
"accessed": "Accessed",
|
||||
"account": "Konto",
|
||||
"account-email": "Konto-E-Mail",
|
||||
"account-name": "Kontoname",
|
||||
@ -104,6 +105,7 @@
|
||||
"back-to-login-lowercase": "Zurück zur Anmeldeseite",
|
||||
"basic-configuration": "Grundkonfiguration",
|
||||
"batch-size": "Batchgröße",
|
||||
"before-number-of-day-plural": "Before {{numberOfDays}} days",
|
||||
"beta": "Beta",
|
||||
"bot": "Bot",
|
||||
"bot-detail": "Bot-Details",
|
||||
@ -182,6 +184,7 @@
|
||||
"conversation-plural": "Konversationen",
|
||||
"copied": "Kopiert",
|
||||
"copy": "Kopieren",
|
||||
"cost-analysis": "Cost Analysis",
|
||||
"count": "Anzahl",
|
||||
"create": "Erstellen",
|
||||
"create-entity": "{{entity}} erstellen",
|
||||
@ -530,6 +533,7 @@
|
||||
"kpi-name": "KPI-Name",
|
||||
"kpi-title": "Schlüsselindikatoren (KPI)",
|
||||
"kpi-uppercase": "KPI",
|
||||
"kpi-uppercase-plural": "KPIs",
|
||||
"language": "Sprache",
|
||||
"last": "Letzte",
|
||||
"last-error": "Letzter Fehler",
|
||||
@ -905,6 +909,7 @@
|
||||
"show-or-hide-advanced-config": "{{showAdv}} Erweiterte Konfiguration anzeigen/ausblenden",
|
||||
"sign-in-with-sso": "Mit {{sso}} anmelden",
|
||||
"size": "Größe",
|
||||
"size-evolution-graph": "Size Evolution Graph",
|
||||
"skew": "Verzerrung",
|
||||
"skipped": "Übersprungen",
|
||||
"slack": "Slack",
|
||||
@ -1483,6 +1488,7 @@
|
||||
"service-with-space-not-allowed": "Dienstname mit Leerzeichen ist nicht zulässig",
|
||||
"session-expired": "Ihre Sitzung ist abgelaufen! Bitte melden Sie sich erneut an, um auf OpenMetadata zuzugreifen.",
|
||||
"setup-custom-property": "OpenMetadata unterstützt benutzerdefinierte Eigenschaften in der Tabellenentität. Erstellen Sie eine benutzerdefinierte Eigenschaft, indem Sie einen eindeutigen Eigenschaftsnamen hinzufügen. Der Name muss mit einem Kleinbuchstaben beginnen, wie im camelCase-Format bevorzugt. Großbuchstaben und Zahlen können im Feldnamen enthalten sein; Leerzeichen, Unterstriche und Punkte werden jedoch nicht unterstützt. Wählen Sie den bevorzugten Eigenschaftstyp aus den angebotenen Optionen aus. Beschreiben Sie Ihre benutzerdefinierte Eigenschaft, um Ihrem Team mehr Informationen zu geben.",
|
||||
"size-evolution-description": "Size evolution of assets in organization.",
|
||||
"soft-delete-message-for-entity": "Durch das Soft-Löschen wird {{entity}} deaktiviert. Dadurch werden alle Entdeckungs-, Lese- oder Schreibvorgänge auf {{entity}} deaktiviert.",
|
||||
"something-went-wrong": "Etwas ist schiefgelaufen",
|
||||
"special-character-not-allowed": "Sonderzeichen sind nicht erlaubt.",
|
||||
|
@ -4,6 +4,7 @@
|
||||
"accept": "Accept",
|
||||
"accept-suggestion": "Accept Suggestion",
|
||||
"access": "Access",
|
||||
"accessed": "Accessed",
|
||||
"account": "Account",
|
||||
"account-email": "Account email",
|
||||
"account-name": "Account Name",
|
||||
@ -104,6 +105,7 @@
|
||||
"back-to-login-lowercase": "back to login",
|
||||
"basic-configuration": "Basic Configuration",
|
||||
"batch-size": "Batch Size",
|
||||
"before-number-of-day-plural": "Before {{numberOfDays}} days",
|
||||
"beta": "Beta",
|
||||
"bot": "Bot",
|
||||
"bot-detail": "Bot detail",
|
||||
@ -182,6 +184,7 @@
|
||||
"conversation-plural": "Conversations",
|
||||
"copied": "Copied",
|
||||
"copy": "Copy",
|
||||
"cost-analysis": "Cost Analysis",
|
||||
"count": "Count",
|
||||
"create": "Create",
|
||||
"create-entity": "Create {{entity}}",
|
||||
@ -530,6 +533,7 @@
|
||||
"kpi-name": "KPI Name",
|
||||
"kpi-title": "Key Performance Indicators (KPI)",
|
||||
"kpi-uppercase": "KPI",
|
||||
"kpi-uppercase-plural": "KPIs",
|
||||
"language": "Language",
|
||||
"last": "Last",
|
||||
"last-error": "Last error",
|
||||
@ -905,6 +909,7 @@
|
||||
"show-or-hide-advanced-config": "{{showAdv}} Advanced Config",
|
||||
"sign-in-with-sso": "Sign in with {{sso}}",
|
||||
"size": "Size",
|
||||
"size-evolution-graph": "Size Evolution Graph",
|
||||
"skew": "Skew",
|
||||
"skipped": "Skipped",
|
||||
"slack": "Slack",
|
||||
@ -1483,6 +1488,7 @@
|
||||
"service-with-space-not-allowed": "Service name with spaces are not allowed",
|
||||
"session-expired": "Your session has timed out! Please sign in again to access OpenMetadata.",
|
||||
"setup-custom-property": "OpenMetadata supports custom properties in the Table entity. Create a custom property by adding a unique property name. The name must start with a lowercase letter, as preferred in the camelCase format. Uppercase letters and numbers can be included in the field name; but spaces, underscores, and dots are not supported. Select the preferred property Type from among the options provided. Describe your custom property to provide more information to your team.",
|
||||
"size-evolution-description": "Size evolution of assets in organization.",
|
||||
"soft-delete-message-for-entity": "Soft deleting will deactivate the {{entity}}. This will disable any discovery, read or write operations on {{entity}}.",
|
||||
"something-went-wrong": "Something went wrong",
|
||||
"special-character-not-allowed": "Special characters are not allowed.",
|
||||
|
@ -4,6 +4,7 @@
|
||||
"accept": "Accept",
|
||||
"accept-suggestion": "Aceptar sugerencia",
|
||||
"access": "Acceso",
|
||||
"accessed": "Accessed",
|
||||
"account": "Cuenta",
|
||||
"account-email": "Correo electrónico de la cuenta",
|
||||
"account-name": "Account Name",
|
||||
@ -104,6 +105,7 @@
|
||||
"back-to-login-lowercase": "volver a iniciar sesión",
|
||||
"basic-configuration": "Configuración básica",
|
||||
"batch-size": "Tamaño del lote",
|
||||
"before-number-of-day-plural": "Before {{numberOfDays}} days",
|
||||
"beta": "Beta",
|
||||
"bot": "Bot",
|
||||
"bot-detail": "Detalles del bot",
|
||||
@ -182,6 +184,7 @@
|
||||
"conversation-plural": "Conversaciones",
|
||||
"copied": "Copied",
|
||||
"copy": "Copiar",
|
||||
"cost-analysis": "Cost Analysis",
|
||||
"count": "Conteo",
|
||||
"create": "Crear",
|
||||
"create-entity": "Crear {{entity}}",
|
||||
@ -530,6 +533,7 @@
|
||||
"kpi-name": "Nombre del KPI",
|
||||
"kpi-title": "Indicadores clave de rendimiento (KPI)",
|
||||
"kpi-uppercase": "KPI",
|
||||
"kpi-uppercase-plural": "KPIs",
|
||||
"language": "Idioma",
|
||||
"last": "Último",
|
||||
"last-error": "Último error",
|
||||
@ -905,6 +909,7 @@
|
||||
"show-or-hide-advanced-config": "{{showAdv}} Configuración Avanzada",
|
||||
"sign-in-with-sso": "Iniciar sesión con {{sso}}",
|
||||
"size": "Tamaño",
|
||||
"size-evolution-graph": "Size Evolution Graph",
|
||||
"skew": "Sesgo",
|
||||
"skipped": "Skipped",
|
||||
"slack": "Slack",
|
||||
@ -1483,6 +1488,7 @@
|
||||
"service-with-space-not-allowed": "No se permiten nombres de servicio con espacios",
|
||||
"session-expired": "¡Tu sesión ha caducado! Por favor, inicia sesión de nuevo para acceder a OpenMetadata.",
|
||||
"setup-custom-property": "OpenMetadata admite propiedades personalizadas en la entidad de Tabla. Crea una propiedad personalizada agregando un nombre de propiedad único. El nombre debe comenzar con una letra minúscula, como se prefiere en el formato camelCase. Las letras mayúsculas y los números pueden incluirse en el nombre del campo; pero no se admiten espacios, guiones bajos y puntos. Selecciona el tipo de propiedad preferido entre las opciones proporcionadas. Describe tu propiedad personalizada para proporcionar más información a tu equipo.",
|
||||
"size-evolution-description": "Size evolution of assets in organization.",
|
||||
"soft-delete-message-for-entity": "La eliminación suave desactivará la {{entity}}. Esto deshabilitará cualquier operación de descubrimiento, lectura o escritura en {{entity}}.",
|
||||
"something-went-wrong": "Algo salió mal",
|
||||
"special-character-not-allowed": "No se permiten caracteres especiales.",
|
||||
|
@ -4,6 +4,7 @@
|
||||
"accept": "Accepter",
|
||||
"accept-suggestion": "Accepter la Suggestion",
|
||||
"access": "Accès",
|
||||
"accessed": "Accessed",
|
||||
"account": "Compte",
|
||||
"account-email": "Compte email",
|
||||
"account-name": "Nom du Compte",
|
||||
@ -104,6 +105,7 @@
|
||||
"back-to-login-lowercase": "Retour à la Page de Connexion",
|
||||
"basic-configuration": "Configuration de Base",
|
||||
"batch-size": "Taille du Lot",
|
||||
"before-number-of-day-plural": "Before {{numberOfDays}} days",
|
||||
"beta": "Bêta",
|
||||
"bot": "Agent Numérique",
|
||||
"bot-detail": "Détail de l'Agent Numérique",
|
||||
@ -182,6 +184,7 @@
|
||||
"conversation-plural": "Conversations",
|
||||
"copied": "Copié",
|
||||
"copy": "Copier",
|
||||
"cost-analysis": "Cost Analysis",
|
||||
"count": "Décompte",
|
||||
"create": "Créer",
|
||||
"create-entity": "Créer {{entity}}",
|
||||
@ -530,6 +533,7 @@
|
||||
"kpi-name": "Nom des KPI",
|
||||
"kpi-title": "Indicateurs de Performance Clés (KPI)",
|
||||
"kpi-uppercase": "KPI",
|
||||
"kpi-uppercase-plural": "KPIs",
|
||||
"language": "Langage",
|
||||
"last": "Dernier·ère",
|
||||
"last-error": "Dernière erreur",
|
||||
@ -905,6 +909,7 @@
|
||||
"show-or-hide-advanced-config": "{{showAdv}} Configuration Avancée",
|
||||
"sign-in-with-sso": "Se Connecter avec {{sso}}",
|
||||
"size": "Taille",
|
||||
"size-evolution-graph": "Size Evolution Graph",
|
||||
"skew": "Déviation",
|
||||
"skipped": "Passé",
|
||||
"slack": "Slack",
|
||||
@ -1483,6 +1488,7 @@
|
||||
"service-with-space-not-allowed": "Nom de service avec des espaces ne sont pas autorisés",
|
||||
"session-expired": "Votre session a expiré! Veuillez vous connecter à nouveau pour accéder à OpenMetadata.",
|
||||
"setup-custom-property": "OpenMetadata permet la création de propriétés custom dans les entités table. Créez une propriété custom en ajoutant un nom de propriété unique. Le nom doit commencer par une lettre minuscule, comme préféré dans le format camelCase. Les lettres majuscules et les chiffres peuvent être inclus dans le nom du champ; mais les espaces, les tirets bas et les points ne sont pas pris en charge. Sélectionnez le Type de propriété préféré parmi les options fournies. Décrivez votre propriété personnalisée pour fournir plus d'informations à votre équipe.",
|
||||
"size-evolution-description": "Size evolution of assets in organization.",
|
||||
"soft-delete-message-for-entity": "La suppression logique désactivera le {{entity}}. Cela désactivera toute découverte, lecture ou écriture sur le {{entity}}.",
|
||||
"something-went-wrong": "Quelque chose s'est mal passé",
|
||||
"special-character-not-allowed": "Les caractères spéciaux ne sont pas autorisés",
|
||||
|
@ -4,6 +4,7 @@
|
||||
"accept": "Accept",
|
||||
"accept-suggestion": "提案を受け入れる",
|
||||
"access": "アクセス",
|
||||
"accessed": "Accessed",
|
||||
"account": "アカウント",
|
||||
"account-email": "アカウントのEmail",
|
||||
"account-name": "Account Name",
|
||||
@ -104,6 +105,7 @@
|
||||
"back-to-login-lowercase": "ログインに戻る",
|
||||
"basic-configuration": "Basic Configuration",
|
||||
"batch-size": "バッチサイズ",
|
||||
"before-number-of-day-plural": "Before {{numberOfDays}} days",
|
||||
"beta": "Beta",
|
||||
"bot": "ボット",
|
||||
"bot-detail": "ボットの詳細",
|
||||
@ -182,6 +184,7 @@
|
||||
"conversation-plural": "会話",
|
||||
"copied": "Copied",
|
||||
"copy": "Copy",
|
||||
"cost-analysis": "Cost Analysis",
|
||||
"count": "Count",
|
||||
"create": "作成",
|
||||
"create-entity": "{{entity}}を作成",
|
||||
@ -530,6 +533,7 @@
|
||||
"kpi-name": "KPI名",
|
||||
"kpi-title": "Key Performance Indicators (KPI)",
|
||||
"kpi-uppercase": "KPI",
|
||||
"kpi-uppercase-plural": "KPIs",
|
||||
"language": "言語",
|
||||
"last": "最新",
|
||||
"last-error": "最新のエラー",
|
||||
@ -905,6 +909,7 @@
|
||||
"show-or-hide-advanced-config": "{{showAdv}} 高度な設定",
|
||||
"sign-in-with-sso": "{{sso}}でサインインする",
|
||||
"size": "Size",
|
||||
"size-evolution-graph": "Size Evolution Graph",
|
||||
"skew": "Skew",
|
||||
"skipped": "Skipped",
|
||||
"slack": "Slack",
|
||||
@ -1483,6 +1488,7 @@
|
||||
"service-with-space-not-allowed": "サービス名に空白は使えません",
|
||||
"session-expired": "セッションがタイムアウトしました。OpenMetadataにアクセスするには再度サインインしてください。",
|
||||
"setup-custom-property": "OpenMetadataはテーブル要素のカスタムプロパティをサポートしています。カスタムプロパティを作成するにはユニークなプロパティ名を追加してください。プロパティ名は小文字のアルファベットから始め、かつキャメルケースであることが望ましいです。フィールド名に大文字と数字を含めることはできますが、スペースやアンダースコア、ドットはサポートされていません。提供されたオプションの中から適切なプロパティタイプを選択してください。カスタムプロパティの説明はチームにより多くの情報を提供します。",
|
||||
"size-evolution-description": "Size evolution of assets in organization.",
|
||||
"soft-delete-message-for-entity": "ソフトデリートは{{entity}}を非活性化します。これはデータの発見や、{{entity}}に対する読み込みや書き込みの操作を無効にします。",
|
||||
"something-went-wrong": "何らかの問題が発生しました",
|
||||
"special-character-not-allowed": "特殊文字は使用できません。",
|
||||
|
@ -4,6 +4,7 @@
|
||||
"accept": "Accept",
|
||||
"accept-suggestion": "Aceitar sugestão",
|
||||
"access": "Acesso",
|
||||
"accessed": "Accessed",
|
||||
"account": "Conta",
|
||||
"account-email": "E-mail da conta",
|
||||
"account-name": "Account Name",
|
||||
@ -104,6 +105,7 @@
|
||||
"back-to-login-lowercase": "voltar para login",
|
||||
"basic-configuration": "Configuração básica",
|
||||
"batch-size": "Tamanho do batch",
|
||||
"before-number-of-day-plural": "Before {{numberOfDays}} days",
|
||||
"beta": "Beta",
|
||||
"bot": "Bot",
|
||||
"bot-detail": "Detalhe do bot",
|
||||
@ -182,6 +184,7 @@
|
||||
"conversation-plural": "Conversas",
|
||||
"copied": "Copied",
|
||||
"copy": "Copy",
|
||||
"cost-analysis": "Cost Analysis",
|
||||
"count": "Contar",
|
||||
"create": "Criar",
|
||||
"create-entity": "Criar {{entity}}",
|
||||
@ -530,6 +533,7 @@
|
||||
"kpi-name": "Nome do KPI",
|
||||
"kpi-title": "Título do KPI",
|
||||
"kpi-uppercase": "KPI",
|
||||
"kpi-uppercase-plural": "KPIs",
|
||||
"language": "Idioma",
|
||||
"last": "Último",
|
||||
"last-error": "Último erro",
|
||||
@ -905,6 +909,7 @@
|
||||
"show-or-hide-advanced-config": "{{showAdv}} Configuração Avançada",
|
||||
"sign-in-with-sso": "Entrar com {{sso}}",
|
||||
"size": "Size",
|
||||
"size-evolution-graph": "Size Evolution Graph",
|
||||
"skew": "Inclinação",
|
||||
"skipped": "Skipped",
|
||||
"slack": "Slack",
|
||||
@ -1483,6 +1488,7 @@
|
||||
"service-with-space-not-allowed": "Nomes de serviços com espaços não são permitidos",
|
||||
"session-expired": "Sua sessão expirou! Por favor, faça login novamente para acessar o OpenMetadata.",
|
||||
"setup-custom-property": "O OpenMetadata suporta propriedades personalizadas na entidade Tabela. Crie uma propriedade personalizada adicionando um nome de propriedade exclusivo. O nome deve começar com uma letra minúscula, como preferido no formato camelCase. Letras maiúsculas e números podem ser incluídos no nome do campo; mas espaços, sublinhados e pontos não são suportados. Selecione o Tipo de propriedade preferido entre as opções fornecidas. Descreva sua propriedade personalizada para fornecer mais informações à sua equipe.",
|
||||
"size-evolution-description": "Size evolution of assets in organization.",
|
||||
"soft-delete-message-for-entity": "O soft delete desativará a {{entity}}. Isso desabilitará quaisquer operações de descoberta, leitura ou gravação na {{entity}}.",
|
||||
"something-went-wrong": "Algo deu errado",
|
||||
"special-character-not-allowed": "Caracteres especiais não são permitidos.",
|
||||
|
@ -4,6 +4,7 @@
|
||||
"accept": "Принять",
|
||||
"accept-suggestion": "Согласовать предложение",
|
||||
"access": "Доступ",
|
||||
"accessed": "Accessed",
|
||||
"account": "Аккаунт",
|
||||
"account-email": "Адрес электронной почты",
|
||||
"account-name": "Наименование аккаунта",
|
||||
@ -104,6 +105,7 @@
|
||||
"back-to-login-lowercase": "Вернуться на страницу входа",
|
||||
"basic-configuration": "Базовая конфигурация",
|
||||
"batch-size": "Размер пакета",
|
||||
"before-number-of-day-plural": "Before {{numberOfDays}} days",
|
||||
"beta": "Бета",
|
||||
"bot": "Бот",
|
||||
"bot-detail": "Детали бота",
|
||||
@ -182,6 +184,7 @@
|
||||
"conversation-plural": "Обсуждения",
|
||||
"copied": "Скопировано",
|
||||
"copy": "Скопировать",
|
||||
"cost-analysis": "Cost Analysis",
|
||||
"count": "Количество",
|
||||
"create": "Создать",
|
||||
"create-entity": "Создать {{entity}}",
|
||||
@ -530,6 +533,7 @@
|
||||
"kpi-name": "Наименование KPI",
|
||||
"kpi-title": "Ключевой показатель эффективности (KPI)",
|
||||
"kpi-uppercase": "KPI",
|
||||
"kpi-uppercase-plural": "KPIs",
|
||||
"language": "Язык",
|
||||
"last": "Последний",
|
||||
"last-error": "Последняя ошибка",
|
||||
@ -905,6 +909,7 @@
|
||||
"show-or-hide-advanced-config": "{{showAdv}} Расширенная конфигурация",
|
||||
"sign-in-with-sso": "Войдите с помощью {{sso}}",
|
||||
"size": "Размер",
|
||||
"size-evolution-graph": "Size Evolution Graph",
|
||||
"skew": "Перекос",
|
||||
"skipped": "Пропущено",
|
||||
"slack": "Slack",
|
||||
@ -1483,6 +1488,7 @@
|
||||
"service-with-space-not-allowed": "Имя службы с пробелами не допускается",
|
||||
"session-expired": "Время вашей сессии истекло! Пожалуйста, войдите снова, чтобы получить доступ к OpenMetadata.",
|
||||
"setup-custom-property": "OpenMetadata поддерживает настраиваемые свойства в табличном объекте. Создайте пользовательское свойство, добавив уникальное имя свойства. Имя должно начинаться со строчной буквы, что является предпочтительным в формате camelCase. В имя поля можно включать прописные буквы и цифры; но пробелы, символы подчеркивания и точки не поддерживаются. Выберите предпочтительный тип свойства из предложенных вариантов. Опишите свое пользовательское свойство, чтобы предоставить больше информации вашей команде.",
|
||||
"size-evolution-description": "Size evolution of assets in organization.",
|
||||
"soft-delete-message-for-entity": "Мягкое удаление деактивирует {{entity}}. Это отключит любые операции обнаружения, чтения или записи для {{entity}}.",
|
||||
"something-went-wrong": "Что-то пошло не так",
|
||||
"special-character-not-allowed": "Спецсимволы не допустимы.",
|
||||
|
@ -4,6 +4,7 @@
|
||||
"accept": "接受",
|
||||
"accept-suggestion": "接受建议",
|
||||
"access": "访问",
|
||||
"accessed": "Accessed",
|
||||
"account": "帐号",
|
||||
"account-email": "帐号邮箱",
|
||||
"account-name": "帐号名称",
|
||||
@ -104,6 +105,7 @@
|
||||
"back-to-login-lowercase": "返回登录",
|
||||
"basic-configuration": "基本配置",
|
||||
"batch-size": "批大小",
|
||||
"before-number-of-day-plural": "Before {{numberOfDays}} days",
|
||||
"beta": "Beta",
|
||||
"bot": "机器人",
|
||||
"bot-detail": "机器人详情",
|
||||
@ -182,6 +184,7 @@
|
||||
"conversation-plural": "对话",
|
||||
"copied": "已复制",
|
||||
"copy": "复制",
|
||||
"cost-analysis": "Cost Analysis",
|
||||
"count": "计数",
|
||||
"create": "新建",
|
||||
"create-entity": "新建{{entity}}",
|
||||
@ -530,6 +533,7 @@
|
||||
"kpi-name": "KPI 名称",
|
||||
"kpi-title": "关键绩效指标 (KPI)",
|
||||
"kpi-uppercase": "KPI",
|
||||
"kpi-uppercase-plural": "KPIs",
|
||||
"language": "语言",
|
||||
"last": "最近",
|
||||
"last-error": "最近错误",
|
||||
@ -905,6 +909,7 @@
|
||||
"show-or-hide-advanced-config": "{{showAdv}}高级配置",
|
||||
"sign-in-with-sso": "使用 {{sso}} 单点登录",
|
||||
"size": "大小",
|
||||
"size-evolution-graph": "Size Evolution Graph",
|
||||
"skew": "偏态",
|
||||
"skipped": "已跳过",
|
||||
"slack": "Slack",
|
||||
@ -1483,6 +1488,7 @@
|
||||
"service-with-space-not-allowed": "服务名称不允许使用空格",
|
||||
"session-expired": "您的会话已超时,请重新登录!",
|
||||
"setup-custom-property": "OpenMetadata 支持数据表中的自定义属性。通过添加唯一的属性名称来创建自定义属性。名称必须以小写字母开头,以 camelCase 驼峰格式为首选。字段名称可以包含大写字母和数字,但不支持空格、下划线和标点符号。从提供的选项中指定一个属性类型,并通过描述您的自定义属性向团队提供更多信息。",
|
||||
"size-evolution-description": "Size evolution of assets in organization.",
|
||||
"soft-delete-message-for-entity": "软删除将停用{{entity}},这将禁用{{entity}}上的任何发现、读取或写入操作",
|
||||
"something-went-wrong": "出现了一些问题",
|
||||
"special-character-not-allowed": "不允许使用特殊字符",
|
||||
|
@ -11,7 +11,15 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { SearchDropdownOption } from '../../components/SearchDropdown/SearchDropdown.interface';
|
||||
import { ReactNode } from 'react';
|
||||
import { DateRangeObject } from '../../components/ProfilerDashboard/component/TestSummary';
|
||||
import {
|
||||
SearchDropdownOption,
|
||||
SearchDropdownProps,
|
||||
} from '../../components/SearchDropdown/SearchDropdown.interface';
|
||||
import { Kpi } from '../../generated/dataInsight/kpi/kpi';
|
||||
import { Tag } from '../../generated/entity/classification/tag';
|
||||
import { ChartFilter } from '../../interface/data-insight.interface';
|
||||
|
||||
export type TeamStateType = {
|
||||
defaultOptions: SearchDropdownOption[];
|
||||
@ -19,3 +27,23 @@ export type TeamStateType = {
|
||||
options: SearchDropdownOption[];
|
||||
};
|
||||
export type TierStateType = Omit<TeamStateType, 'defaultOptions'>;
|
||||
|
||||
export interface DataInsightProviderProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export interface DataInsightContextType {
|
||||
teamFilter: Omit<SearchDropdownProps, 'label' | 'searchKey'>;
|
||||
tierFilter: Omit<SearchDropdownProps, 'label' | 'searchKey'>;
|
||||
selectedDaysFilter: number;
|
||||
chartFilter: ChartFilter;
|
||||
onChartFilterChange: (value: DateRangeObject, days?: number) => void;
|
||||
kpi: {
|
||||
isLoading: boolean;
|
||||
data: Kpi[];
|
||||
};
|
||||
tierTag: {
|
||||
tags: Tag[];
|
||||
isLoading: boolean;
|
||||
};
|
||||
}
|
||||
|
@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright 2023 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 { ReactComponent as AppAnalyticsIcon } from '../../assets/svg/app-analytics.svg';
|
||||
import { ReactComponent as DataAssetsIcon } from '../../assets/svg/data-asset.svg';
|
||||
import { ReactComponent as KPIIcon } from '../../assets/svg/kpi.svg';
|
||||
import AppAnalyticsTab from '../../components/DataInsightDetail/AppAnalyticsTab/AppAnalyticsTab.component';
|
||||
import DataAssetsTab from '../../components/DataInsightDetail/DataAssetsTab/DataAssetsTab.component';
|
||||
import { DataInsightTabs } from '../../interface/data-insight.interface';
|
||||
import { getDataInsightPathWithFqn } from '../../utils/DataInsightUtils';
|
||||
import i18n from '../../utils/i18next/LocalUtil';
|
||||
import KPIList from './KPIList';
|
||||
|
||||
type LeftSideBarType = {
|
||||
key: DataInsightTabs;
|
||||
label: string;
|
||||
icon: SvgComponent;
|
||||
iconProps: React.SVGProps<SVGSVGElement>;
|
||||
};
|
||||
|
||||
class DataInsightClassBase {
|
||||
public getLeftSideBar(): LeftSideBarType[] {
|
||||
return [
|
||||
{
|
||||
key: DataInsightTabs.DATA_ASSETS,
|
||||
label: i18n.t('label.data-asset-plural'),
|
||||
icon: AppAnalyticsIcon,
|
||||
iconProps: {
|
||||
className: 'side-panel-icons',
|
||||
},
|
||||
},
|
||||
{
|
||||
key: DataInsightTabs.APP_ANALYTICS,
|
||||
label: i18n.t('label.app-analytic-plural'),
|
||||
icon: DataAssetsIcon,
|
||||
iconProps: {
|
||||
className: 'side-panel-icons',
|
||||
},
|
||||
},
|
||||
{
|
||||
key: DataInsightTabs.KPIS,
|
||||
label: i18n.t('label.kpi-uppercase-plural'),
|
||||
icon: KPIIcon,
|
||||
iconProps: {
|
||||
className: 'side-panel-icons',
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
public getDataInsightTab() {
|
||||
return [
|
||||
{
|
||||
key: DataInsightTabs.DATA_ASSETS,
|
||||
path: getDataInsightPathWithFqn(DataInsightTabs.DATA_ASSETS),
|
||||
component: DataAssetsTab,
|
||||
},
|
||||
{
|
||||
key: DataInsightTabs.APP_ANALYTICS,
|
||||
path: getDataInsightPathWithFqn(DataInsightTabs.APP_ANALYTICS),
|
||||
component: AppAnalyticsTab,
|
||||
},
|
||||
{
|
||||
key: DataInsightTabs.KPIS,
|
||||
path: getDataInsightPathWithFqn(DataInsightTabs.KPIS),
|
||||
component: KPIList,
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
const dataInsightClassBase = new DataInsightClassBase();
|
||||
|
||||
export default dataInsightClassBase;
|
||||
export { DataInsightClassBase };
|
@ -0,0 +1,143 @@
|
||||
/*
|
||||
* Copyright 2023 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 { Button, Col, Row, Space, Typography } from 'antd';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useHistory, useParams } from 'react-router-dom';
|
||||
import DataInsightSummary from '../../../components/DataInsightDetail/DataInsightSummary';
|
||||
import KPIChart from '../../../components/DataInsightDetail/KPIChart';
|
||||
import DatePickerMenu from '../../../components/DatePickerMenu/DatePickerMenu.component';
|
||||
import { usePermissionProvider } from '../../../components/PermissionProvider/PermissionProvider';
|
||||
import { ResourceEntity } from '../../../components/PermissionProvider/PermissionProvider.interface';
|
||||
import SearchDropdown from '../../../components/SearchDropdown/SearchDropdown';
|
||||
import { ROUTES } from '../../../constants/constants';
|
||||
import { Operation } from '../../../generated/entity/policies/policy';
|
||||
import { DataInsightTabs } from '../../../interface/data-insight.interface';
|
||||
import { getOptionalDataInsightTabFlag } from '../../../utils/DataInsightUtils';
|
||||
import { formatDate } from '../../../utils/date-time/DateTimeUtils';
|
||||
import { checkPermission } from '../../../utils/PermissionsUtils';
|
||||
import { useDataInsightProvider } from '../DataInsightProvider';
|
||||
import { DataInsightHeaderProps } from './DataInsightHeader.interface';
|
||||
|
||||
const DataInsightHeader = ({ onScrollToChart }: DataInsightHeaderProps) => {
|
||||
const {
|
||||
teamFilter: team,
|
||||
tierFilter: tier,
|
||||
chartFilter,
|
||||
onChartFilterChange,
|
||||
kpi,
|
||||
} = useDataInsightProvider();
|
||||
|
||||
const { tab } = useParams<{ tab: DataInsightTabs }>();
|
||||
const history = useHistory();
|
||||
const { t } = useTranslation();
|
||||
const { permissions } = usePermissionProvider();
|
||||
|
||||
const { showDataInsightSummary, showKpiChart } =
|
||||
getOptionalDataInsightTabFlag(tab);
|
||||
|
||||
const viewKPIPermission = useMemo(
|
||||
() => checkPermission(Operation.ViewAll, ResourceEntity.KPI, permissions),
|
||||
[permissions]
|
||||
);
|
||||
|
||||
const createKPIPermission = useMemo(
|
||||
() => checkPermission(Operation.Create, ResourceEntity.KPI, permissions),
|
||||
[permissions]
|
||||
);
|
||||
|
||||
const handleAddKPI = () => {
|
||||
history.push(ROUTES.ADD_KPI);
|
||||
};
|
||||
|
||||
return (
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={24}>
|
||||
<Space className="w-full justify-between items-start">
|
||||
<div data-testid="data-insight-header">
|
||||
<Typography.Title level={5}>
|
||||
{t('label.data-insight-plural')}
|
||||
</Typography.Title>
|
||||
<Typography.Text className="data-insight-label-text">
|
||||
{t('message.data-insight-subtitle')}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
|
||||
{createKPIPermission && (
|
||||
<Button
|
||||
data-testid="add-kpi-btn"
|
||||
type="primary"
|
||||
onClick={handleAddKPI}>
|
||||
{t('label.add-entity', {
|
||||
entity: t('label.kpi-uppercase'),
|
||||
})}
|
||||
</Button>
|
||||
)}
|
||||
</Space>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Space className="w-full justify-between align-center">
|
||||
<Space className="w-full" size={16}>
|
||||
<SearchDropdown
|
||||
label={t('label.team')}
|
||||
searchKey="teams"
|
||||
{...team}
|
||||
/>
|
||||
|
||||
<SearchDropdown
|
||||
label={t('label.tier')}
|
||||
searchKey="tier"
|
||||
{...tier}
|
||||
/>
|
||||
</Space>
|
||||
<Space>
|
||||
<Typography className="data-insight-label-text text-xs">
|
||||
{`${formatDate(chartFilter.startTs)} - ${formatDate(
|
||||
chartFilter.endTs
|
||||
)}`}
|
||||
</Typography>
|
||||
<DatePickerMenu
|
||||
handleDateRangeChange={onChartFilterChange}
|
||||
showSelectedCustomRange={false}
|
||||
/>
|
||||
</Space>
|
||||
</Space>
|
||||
</Col>
|
||||
|
||||
{/* Do not show summary for KPIs */}
|
||||
{showDataInsightSummary && (
|
||||
<Col span={24}>
|
||||
<DataInsightSummary
|
||||
chartFilter={chartFilter}
|
||||
onScrollToChart={onScrollToChart}
|
||||
/>
|
||||
</Col>
|
||||
)}
|
||||
|
||||
{/* Do not show KPIChart for app analytics */}
|
||||
{showKpiChart && (
|
||||
<Col span={24}>
|
||||
<KPIChart
|
||||
chartFilter={chartFilter}
|
||||
createKPIPermission={createKPIPermission}
|
||||
isKpiLoading={kpi.isLoading}
|
||||
kpiList={kpi.data}
|
||||
viewKPIPermission={viewKPIPermission}
|
||||
/>
|
||||
</Col>
|
||||
)}
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
|
||||
export default DataInsightHeader;
|
@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2023 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 { DataInsightChartType } from '../../../generated/dataInsight/dataInsightChartResult';
|
||||
|
||||
export interface DataInsightHeaderProps {
|
||||
onScrollToChart: (chartType: DataInsightChartType) => void;
|
||||
}
|
@ -32,7 +32,7 @@ describe('Test Data insight left panel', () => {
|
||||
|
||||
const dataAssets = await screen.findByText('label.data-asset-plural');
|
||||
const appAnalytics = await screen.findByText('label.app-analytic-plural');
|
||||
const kpi = await screen.findByText('label.kpi-uppercases');
|
||||
const kpi = await screen.findByText('label.kpi-uppercase-plural');
|
||||
|
||||
expect(menuContainer).toBeInTheDocument();
|
||||
expect(dataAssets).toBeInTheDocument();
|
||||
@ -44,7 +44,7 @@ describe('Test Data insight left panel', () => {
|
||||
render(<DataInsightLeftPanel />);
|
||||
|
||||
const appAnalytics = await screen.findByText('label.app-analytic-plural');
|
||||
const kpi = await screen.findByText('label.kpi-uppercases');
|
||||
const kpi = await screen.findByText('label.kpi-uppercase-plural');
|
||||
|
||||
expect(appAnalytics).toBeInTheDocument();
|
||||
expect(kpi).toBeInTheDocument();
|
@ -12,42 +12,34 @@
|
||||
*/
|
||||
|
||||
import { Menu, MenuProps } from 'antd';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useHistory, useParams } from 'react-router-dom';
|
||||
import { ReactComponent as AppAnalyticsIcon } from '../../assets/svg/app-analytics.svg';
|
||||
import { ReactComponent as DataAssetsIcon } from '../../assets/svg/data-asset.svg';
|
||||
import { ReactComponent as KPIIcon } from '../../assets/svg/kpi.svg';
|
||||
import LeftPanelCard from '../../components/common/LeftPanelCard/LeftPanelCard';
|
||||
import { DataInsightTabs } from '../../interface/data-insight.interface';
|
||||
import { getDataInsightPathWithFqn } from '../../utils/DataInsightUtils';
|
||||
import LeftPanelCard from '../../../components/common/LeftPanelCard/LeftPanelCard';
|
||||
import { DataInsightTabs } from '../../../interface/data-insight.interface';
|
||||
import { getDataInsightPathWithFqn } from '../../../utils/DataInsightUtils';
|
||||
import DataInsightClassBase from '../DataInsightClassBase';
|
||||
|
||||
const DataInsightLeftPanel = () => {
|
||||
const { tab } = useParams<{ tab: DataInsightTabs }>();
|
||||
|
||||
const history = useHistory();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const menuItems: MenuProps['items'] = [
|
||||
{
|
||||
key: DataInsightTabs.DATA_ASSETS,
|
||||
label: t('label.data-asset-plural'),
|
||||
icon: <AppAnalyticsIcon className="side-panel-icons" />,
|
||||
},
|
||||
{
|
||||
key: DataInsightTabs.APP_ANALYTICS,
|
||||
label: t('label.app-analytic-plural'),
|
||||
icon: <DataAssetsIcon className="side-panel-icons" />,
|
||||
},
|
||||
{
|
||||
key: DataInsightTabs.KPIS,
|
||||
label: `${t('label.kpi-uppercase')}s`,
|
||||
icon: <KPIIcon className="side-panel-icons" />,
|
||||
},
|
||||
];
|
||||
const menuItems: MenuProps['items'] = useMemo(() => {
|
||||
const data = DataInsightClassBase.getLeftSideBar();
|
||||
|
||||
return data.map((value) => {
|
||||
const SvgIcon = value.icon;
|
||||
|
||||
return {
|
||||
key: value.key,
|
||||
label: value.label,
|
||||
icon: <SvgIcon {...value.iconProps} />,
|
||||
};
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleMenuClick: MenuProps['onClick'] = (e) => {
|
||||
history.push(getDataInsightPathWithFqn(e.key));
|
||||
history.push(getDataInsightPathWithFqn(e.key as DataInsightTabs));
|
||||
};
|
||||
|
||||
return (
|
@ -11,77 +11,48 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Button, Col, Row, Space, Typography } from 'antd';
|
||||
import { Col, Row } from 'antd';
|
||||
import { t } from 'i18next';
|
||||
import { isEmpty, isEqual } from 'lodash';
|
||||
import React, { useEffect, useLayoutEffect, useMemo, useState } from 'react';
|
||||
import { ListItem } from 'react-awesome-query-builder';
|
||||
import { useHistory, useParams } from 'react-router-dom';
|
||||
import React, { useLayoutEffect, useMemo, useState } from 'react';
|
||||
import {
|
||||
Redirect,
|
||||
Route,
|
||||
Switch,
|
||||
useHistory,
|
||||
useParams,
|
||||
} from 'react-router-dom';
|
||||
import ErrorPlaceHolder from '../../components/common/error-with-placeholder/ErrorPlaceHolder';
|
||||
import PageLayoutV1 from '../../components/containers/PageLayoutV1';
|
||||
import DailyActiveUsersChart from '../../components/DataInsightDetail/DailyActiveUsersChart';
|
||||
import DataInsightSummary from '../../components/DataInsightDetail/DataInsightSummary';
|
||||
import DescriptionInsight from '../../components/DataInsightDetail/DescriptionInsight';
|
||||
import KPIChart from '../../components/DataInsightDetail/KPIChart';
|
||||
import OwnerInsight from '../../components/DataInsightDetail/OwnerInsight';
|
||||
import PageViewsByEntitiesChart from '../../components/DataInsightDetail/PageViewsByEntitiesChart';
|
||||
import TierInsight from '../../components/DataInsightDetail/TierInsight';
|
||||
import TopActiveUsers from '../../components/DataInsightDetail/TopActiveUsers';
|
||||
import TopViewEntities from '../../components/DataInsightDetail/TopViewEntities';
|
||||
import TotalEntityInsight from '../../components/DataInsightDetail/TotalEntityInsight';
|
||||
import DatePickerMenu from '../../components/DatePickerMenu/DatePickerMenu.component';
|
||||
import { usePermissionProvider } from '../../components/PermissionProvider/PermissionProvider';
|
||||
import { ResourceEntity } from '../../components/PermissionProvider/PermissionProvider.interface';
|
||||
import { DateRangeObject } from '../../components/ProfilerDashboard/component/TestSummary';
|
||||
import SearchDropdown from '../../components/SearchDropdown/SearchDropdown';
|
||||
import { SearchDropdownOption } from '../../components/SearchDropdown/SearchDropdown.interface';
|
||||
import { autocomplete } from '../../constants/AdvancedSearch.constants';
|
||||
import { PAGE_SIZE, ROUTES } from '../../constants/constants';
|
||||
import {
|
||||
ENTITIES_CHARTS,
|
||||
INITIAL_CHART_FILTER,
|
||||
} from '../../constants/DataInsight.constants';
|
||||
import {
|
||||
DEFAULT_RANGE_DATA,
|
||||
DEFAULT_SELECTED_RANGE,
|
||||
} from '../../constants/profiler.constant';
|
||||
import { EntityFields } from '../../enums/AdvancedSearch.enum';
|
||||
import { ROUTES } from '../../constants/constants';
|
||||
import { ENTITIES_CHARTS } from '../../constants/DataInsight.constants';
|
||||
import { ERROR_PLACEHOLDER_TYPE } from '../../enums/common.enum';
|
||||
import { SearchIndex } from '../../enums/search.enum';
|
||||
import { DataInsightChartType } from '../../generated/dataInsight/dataInsightChartResult';
|
||||
import { Kpi } from '../../generated/dataInsight/kpi/kpi';
|
||||
import { Tag } from '../../generated/entity/classification/tag';
|
||||
import { Operation } from '../../generated/entity/policies/policy';
|
||||
import {
|
||||
ChartFilter,
|
||||
DataInsightTabs,
|
||||
} from '../../interface/data-insight.interface';
|
||||
import { getListKPIs } from '../../rest/KpiAPI';
|
||||
import { searchQuery } from '../../rest/searchAPI';
|
||||
import { getTags } from '../../rest/tagAPI';
|
||||
import {
|
||||
getDataInsightPathWithFqn,
|
||||
getTeamFilter,
|
||||
} from '../../utils/DataInsightUtils';
|
||||
import { formatDate } from '../../utils/date-time/DateTimeUtils';
|
||||
import { getEntityName } from '../../utils/EntityUtils';
|
||||
import { DataInsightTabs } from '../../interface/data-insight.interface';
|
||||
import { getDataInsightPathWithFqn } from '../../utils/DataInsightUtils';
|
||||
import { checkPermission } from '../../utils/PermissionsUtils';
|
||||
import { TeamStateType, TierStateType } from './DataInsight.interface';
|
||||
import './DataInsight.less';
|
||||
import DataInsightLeftPanel from './DataInsightLeftPanel';
|
||||
import KPIList from './KPIList';
|
||||
|
||||
const fetchTeamSuggestions = autocomplete({
|
||||
searchIndex: SearchIndex.TEAM,
|
||||
entitySearchIndex: SearchIndex.TEAM,
|
||||
entityField: EntityFields.OWNER,
|
||||
});
|
||||
import DataInsightClassBase from './DataInsightClassBase';
|
||||
import DataInsightHeader from './DataInsightHeader/DataInsightHeader.component';
|
||||
import DataInsightLeftPanel from './DataInsightLeftPanel/DataInsightLeftPanel';
|
||||
import DataInsightProvider from './DataInsightProvider';
|
||||
|
||||
const DataInsightPage = () => {
|
||||
const { tab } = useParams<{ tab: DataInsightTabs }>();
|
||||
|
||||
const { permissions } = usePermissionProvider();
|
||||
const history = useHistory();
|
||||
const isHeaderVisible = useMemo(
|
||||
() =>
|
||||
[
|
||||
DataInsightTabs.DATA_ASSETS,
|
||||
DataInsightTabs.KPIS,
|
||||
DataInsightTabs.APP_ANALYTICS,
|
||||
].includes(tab),
|
||||
[tab]
|
||||
);
|
||||
|
||||
const viewDataInsightChartPermission = useMemo(
|
||||
() =>
|
||||
@ -98,206 +69,8 @@ const DataInsightPage = () => {
|
||||
[permissions]
|
||||
);
|
||||
|
||||
const createKPIPermission = useMemo(
|
||||
() => checkPermission(Operation.Create, ResourceEntity.KPI, permissions),
|
||||
[permissions]
|
||||
);
|
||||
|
||||
const [teamsOptions, setTeamOptions] = useState<TeamStateType>({
|
||||
defaultOptions: [],
|
||||
selectedOptions: [],
|
||||
options: [],
|
||||
});
|
||||
const [tierOptions, setTierOptions] = useState<TierStateType>({
|
||||
selectedOptions: [],
|
||||
options: [],
|
||||
});
|
||||
|
||||
const [activeTab, setActiveTab] = useState(DataInsightTabs.DATA_ASSETS);
|
||||
const [chartFilter, setChartFilter] =
|
||||
useState<ChartFilter>(INITIAL_CHART_FILTER);
|
||||
const [kpiList, setKpiList] = useState<Array<Kpi>>([]);
|
||||
const [isKpiLoading, setIsKpiLoading] = useState(false);
|
||||
const [selectedDaysFilter, setSelectedDaysFilter] = useState(
|
||||
DEFAULT_SELECTED_RANGE.days
|
||||
);
|
||||
const [dateRangeObject, setDateRangeObject] =
|
||||
useState<DateRangeObject>(DEFAULT_RANGE_DATA);
|
||||
const [tier, setTier] = useState<{ tags: Tag[]; isLoading: boolean }>({
|
||||
tags: [],
|
||||
isLoading: true,
|
||||
});
|
||||
|
||||
const [selectedChart, setSelectedChart] = useState<DataInsightChartType>();
|
||||
|
||||
const defaultTierOptions = useMemo(() => {
|
||||
return tier.tags.map((op) => ({
|
||||
key: op.fullyQualifiedName ?? op.name,
|
||||
label: getEntityName(op),
|
||||
}));
|
||||
}, [tier]);
|
||||
|
||||
const { descriptionKpi, ownerKpi } = useMemo(() => {
|
||||
return {
|
||||
descriptionKpi: kpiList.find(
|
||||
(kpi) =>
|
||||
kpi.dataInsightChart.name ===
|
||||
DataInsightChartType.PercentageOfEntitiesWithDescriptionByType
|
||||
),
|
||||
ownerKpi: kpiList.find(
|
||||
(kpi) =>
|
||||
kpi.dataInsightChart.name ===
|
||||
DataInsightChartType.PercentageOfEntitiesWithOwnerByType
|
||||
),
|
||||
};
|
||||
}, [kpiList]);
|
||||
|
||||
const handleTierChange = (tiers: SearchDropdownOption[] = []) => {
|
||||
setTierOptions((prev) => ({ ...prev, selectedOptions: tiers }));
|
||||
setChartFilter((previous) => ({
|
||||
...previous,
|
||||
tier: tiers.length ? tiers.map((tier) => tier.key).join(',') : undefined,
|
||||
}));
|
||||
};
|
||||
|
||||
const handleDateRangeChange = (
|
||||
value: DateRangeObject,
|
||||
daysValue?: number
|
||||
) => {
|
||||
if (!isEqual(value, dateRangeObject)) {
|
||||
setDateRangeObject(value);
|
||||
setSelectedDaysFilter(daysValue ?? 0);
|
||||
setChartFilter((previous) => ({
|
||||
...previous,
|
||||
startTs: value.startTs,
|
||||
endTs: value.endTs,
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
const handleTeamChange = (teams: SearchDropdownOption[] = []) => {
|
||||
setTeamOptions((prev) => ({
|
||||
...prev,
|
||||
selectedOptions: teams,
|
||||
}));
|
||||
setChartFilter((previous) => ({
|
||||
...previous,
|
||||
team: teams.length ? teams.map((team) => team.key).join(',') : undefined,
|
||||
}));
|
||||
};
|
||||
|
||||
const handleTeamSearch = async (query: string) => {
|
||||
if (fetchTeamSuggestions && !isEmpty(query)) {
|
||||
try {
|
||||
const response = await fetchTeamSuggestions(query, PAGE_SIZE);
|
||||
setTeamOptions((prev) => ({
|
||||
...prev,
|
||||
options: getTeamFilter(response.values as ListItem[]),
|
||||
}));
|
||||
} catch (_error) {
|
||||
// we will not show the toast error message for suggestion API
|
||||
}
|
||||
} else {
|
||||
setTeamOptions((prev) => ({
|
||||
...prev,
|
||||
options: prev.defaultOptions,
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
const handleTierSearch = async (query: string) => {
|
||||
if (query) {
|
||||
setTierOptions((prev) => ({
|
||||
...prev,
|
||||
options: prev.options.filter(
|
||||
(value) =>
|
||||
value.label
|
||||
.toLocaleLowerCase()
|
||||
.includes(query.toLocaleLowerCase()) ||
|
||||
value.key.toLocaleLowerCase().includes(query.toLocaleLowerCase())
|
||||
),
|
||||
}));
|
||||
} else {
|
||||
setTierOptions((prev) => ({
|
||||
...prev,
|
||||
options: defaultTierOptions,
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
const fetchDefaultTeamOptions = async () => {
|
||||
if (teamsOptions.defaultOptions.length) {
|
||||
setTeamOptions((prev) => ({
|
||||
...prev,
|
||||
options: prev.defaultOptions,
|
||||
}));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await searchQuery({
|
||||
searchIndex: SearchIndex.TEAM,
|
||||
query: '*',
|
||||
pageSize: PAGE_SIZE,
|
||||
});
|
||||
const hits = response.hits.hits;
|
||||
const teamFilterOptions = hits.map((hit) => {
|
||||
const source = hit._source;
|
||||
|
||||
return { key: source.name, label: source.displayName ?? source.name };
|
||||
});
|
||||
setTeamOptions((prev) => ({
|
||||
...prev,
|
||||
defaultOptions: teamFilterOptions,
|
||||
options: teamFilterOptions,
|
||||
}));
|
||||
} catch (_error) {
|
||||
// we will not show the toast error message for search API
|
||||
}
|
||||
};
|
||||
|
||||
const getTierTag = async () => {
|
||||
setTier((prev) => ({ ...prev, isLoading: true }));
|
||||
try {
|
||||
const { data } = await getTags({
|
||||
parent: 'Tier',
|
||||
});
|
||||
|
||||
setTier((prev) => ({ ...prev, tags: data }));
|
||||
setTierOptions((prev) => ({
|
||||
...prev,
|
||||
options: data.map((op) => ({
|
||||
key: op.fullyQualifiedName ?? op.name,
|
||||
label: getEntityName(op),
|
||||
})),
|
||||
}));
|
||||
} catch (error) {
|
||||
// error
|
||||
} finally {
|
||||
setTier((prev) => ({ ...prev, isLoading: false }));
|
||||
}
|
||||
};
|
||||
|
||||
const fetchDefaultTierOptions = () => {
|
||||
setTierOptions((prev) => ({
|
||||
...prev,
|
||||
options: defaultTierOptions,
|
||||
}));
|
||||
};
|
||||
|
||||
const fetchKpiList = async () => {
|
||||
setIsKpiLoading(true);
|
||||
try {
|
||||
const response = await getListKPIs({ fields: 'dataInsightChart' });
|
||||
setKpiList(response.data);
|
||||
} catch (_err) {
|
||||
setKpiList([]);
|
||||
} finally {
|
||||
setIsKpiLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleScrollToChart = (chartType: DataInsightChartType) => {
|
||||
if (ENTITIES_CHARTS.includes(chartType)) {
|
||||
history.push(getDataInsightPathWithFqn(DataInsightTabs.DATA_ASSETS));
|
||||
@ -307,10 +80,6 @@ const DataInsightPage = () => {
|
||||
setSelectedChart(chartType);
|
||||
};
|
||||
|
||||
const handleAddKPI = () => {
|
||||
history.push(ROUTES.ADD_KPI);
|
||||
};
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (selectedChart) {
|
||||
const element = document.getElementById(selectedChart);
|
||||
@ -321,234 +90,66 @@ const DataInsightPage = () => {
|
||||
}
|
||||
}, [selectedChart]);
|
||||
|
||||
useEffect(() => {
|
||||
getTierTag();
|
||||
fetchDefaultTeamOptions();
|
||||
fetchKpiList();
|
||||
}, []);
|
||||
const { noDataInsightPermission, noKPIPermission, dataInsightTabs } =
|
||||
useMemo(() => {
|
||||
const data = {
|
||||
noDataInsightPermission:
|
||||
!viewDataInsightChartPermission &&
|
||||
(tab === DataInsightTabs.APP_ANALYTICS ||
|
||||
tab === DataInsightTabs.DATA_ASSETS),
|
||||
noKPIPermission: !viewKPIPermission && tab === DataInsightTabs.KPIS,
|
||||
dataInsightTabs: DataInsightClassBase.getDataInsightTab(),
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setChartFilter(INITIAL_CHART_FILTER);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setActiveTab(tab ?? DataInsightTabs.DATA_ASSETS);
|
||||
}, [tab]);
|
||||
return data;
|
||||
}, [viewDataInsightChartPermission, viewKPIPermission, tab]);
|
||||
|
||||
if (!viewDataInsightChartPermission && !viewKPIPermission) {
|
||||
return <ErrorPlaceHolder type={ERROR_PLACEHOLDER_TYPE.PERMISSION} />;
|
||||
}
|
||||
|
||||
const getTabContent = () => {
|
||||
const noDataInsightPermission =
|
||||
!viewDataInsightChartPermission &&
|
||||
(activeTab === DataInsightTabs.APP_ANALYTICS ||
|
||||
activeTab === DataInsightTabs.DATA_ASSETS);
|
||||
|
||||
const noKPIPermission =
|
||||
!viewKPIPermission && activeTab === DataInsightTabs.KPIS;
|
||||
|
||||
if (noDataInsightPermission || noKPIPermission) {
|
||||
return (
|
||||
<Row align="middle" className="w-full h-full" justify="center">
|
||||
<Col span={24}>
|
||||
<ErrorPlaceHolder type={ERROR_PLACEHOLDER_TYPE.PERMISSION} />
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
if (noDataInsightPermission || noKPIPermission) {
|
||||
return (
|
||||
<Row
|
||||
className="page-container"
|
||||
data-testid="data-insight-container"
|
||||
gutter={[16, 16]}>
|
||||
<Row align="middle" className="w-full h-full" justify="center">
|
||||
<Col span={24}>
|
||||
<Space className="w-full justify-between items-start">
|
||||
<div data-testid="data-insight-header">
|
||||
<Typography.Title level={5}>
|
||||
{t('label.data-insight-plural')}
|
||||
</Typography.Title>
|
||||
<Typography.Text className="data-insight-label-text">
|
||||
{t('message.data-insight-subtitle')}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
|
||||
{createKPIPermission && (
|
||||
<Button
|
||||
data-testid="add-kpi-btn"
|
||||
type="primary"
|
||||
onClick={handleAddKPI}>
|
||||
{t('label.add-entity', {
|
||||
entity: t('label.kpi-uppercase'),
|
||||
})}
|
||||
</Button>
|
||||
)}
|
||||
</Space>
|
||||
<ErrorPlaceHolder type={ERROR_PLACEHOLDER_TYPE.PERMISSION} />
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Space className="w-full justify-between align-center">
|
||||
<Space className="w-full" size={16}>
|
||||
<SearchDropdown
|
||||
label={t('label.team')}
|
||||
options={teamsOptions.options}
|
||||
searchKey="teams"
|
||||
selectedKeys={teamsOptions.selectedOptions}
|
||||
onChange={handleTeamChange}
|
||||
onGetInitialOptions={fetchDefaultTeamOptions}
|
||||
onSearch={handleTeamSearch}
|
||||
/>
|
||||
|
||||
<SearchDropdown
|
||||
label={t('label.tier')}
|
||||
options={tierOptions.options}
|
||||
searchKey="tier"
|
||||
selectedKeys={tierOptions.selectedOptions}
|
||||
onChange={handleTierChange}
|
||||
onGetInitialOptions={fetchDefaultTierOptions}
|
||||
onSearch={handleTierSearch}
|
||||
/>
|
||||
</Space>
|
||||
<Space>
|
||||
<Typography className="data-insight-label-text text-xs">
|
||||
{`${formatDate(chartFilter.startTs)} - ${formatDate(
|
||||
chartFilter.endTs
|
||||
)}`}
|
||||
</Typography>
|
||||
<DatePickerMenu
|
||||
handleDateRangeChange={handleDateRangeChange}
|
||||
showSelectedCustomRange={false}
|
||||
/>
|
||||
</Space>
|
||||
</Space>
|
||||
</Col>
|
||||
|
||||
{/* Do not show summary for KPIs */}
|
||||
{tab !== DataInsightTabs.KPIS && (
|
||||
<Col span={24}>
|
||||
<DataInsightSummary
|
||||
chartFilter={chartFilter}
|
||||
onScrollToChart={handleScrollToChart}
|
||||
/>
|
||||
</Col>
|
||||
)}
|
||||
|
||||
{/* Do not show KPIChart for app analytics */}
|
||||
{tab !== DataInsightTabs.APP_ANALYTICS && (
|
||||
<Col span={24}>
|
||||
<KPIChart
|
||||
chartFilter={chartFilter}
|
||||
createKPIPermission={createKPIPermission}
|
||||
isKpiLoading={isKpiLoading}
|
||||
kpiList={kpiList}
|
||||
viewKPIPermission={viewKPIPermission}
|
||||
/>
|
||||
</Col>
|
||||
)}
|
||||
{activeTab === DataInsightTabs.DATA_ASSETS && (
|
||||
<>
|
||||
<Col span={24}>
|
||||
<TotalEntityInsight
|
||||
chartFilter={chartFilter}
|
||||
selectedDays={selectedDaysFilter}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<DescriptionInsight
|
||||
chartFilter={chartFilter}
|
||||
dataInsightChartName={
|
||||
DataInsightChartType.PercentageOfEntitiesWithDescriptionByType
|
||||
}
|
||||
header={t('label.data-insight-description-summary-type', {
|
||||
type: t('label.data-asset'),
|
||||
})}
|
||||
kpi={descriptionKpi}
|
||||
selectedDays={selectedDaysFilter}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<OwnerInsight
|
||||
chartFilter={chartFilter}
|
||||
dataInsightChartName={
|
||||
DataInsightChartType.PercentageOfEntitiesWithOwnerByType
|
||||
}
|
||||
header={t('label.data-insight-owner-summary-type', {
|
||||
type: t('label.data-asset'),
|
||||
})}
|
||||
kpi={ownerKpi}
|
||||
selectedDays={selectedDaysFilter}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<DescriptionInsight
|
||||
chartFilter={chartFilter}
|
||||
dataInsightChartName={
|
||||
DataInsightChartType.PercentageOfServicesWithDescription
|
||||
}
|
||||
header={t('label.data-insight-description-summary-type', {
|
||||
type: t('label.service'),
|
||||
})}
|
||||
kpi={descriptionKpi}
|
||||
selectedDays={selectedDaysFilter}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<OwnerInsight
|
||||
chartFilter={chartFilter}
|
||||
dataInsightChartName={
|
||||
DataInsightChartType.PercentageOfServicesWithOwner
|
||||
}
|
||||
header={t('label.data-insight-owner-summary-type', {
|
||||
type: t('label.service'),
|
||||
})}
|
||||
kpi={ownerKpi}
|
||||
selectedDays={selectedDaysFilter}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<TierInsight
|
||||
chartFilter={chartFilter}
|
||||
selectedDays={selectedDaysFilter}
|
||||
tierTags={tier}
|
||||
/>
|
||||
</Col>
|
||||
</>
|
||||
)}
|
||||
{activeTab === DataInsightTabs.APP_ANALYTICS && (
|
||||
<>
|
||||
<Col span={24}>
|
||||
<TopViewEntities chartFilter={chartFilter} />
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<PageViewsByEntitiesChart
|
||||
chartFilter={chartFilter}
|
||||
selectedDays={selectedDaysFilter}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<DailyActiveUsersChart
|
||||
chartFilter={chartFilter}
|
||||
selectedDays={selectedDaysFilter}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<TopActiveUsers chartFilter={chartFilter} />
|
||||
</Col>
|
||||
</>
|
||||
)}
|
||||
|
||||
{activeTab === DataInsightTabs.KPIS && (
|
||||
<KPIList viewKPIPermission={viewKPIPermission} />
|
||||
)}
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<PageLayoutV1
|
||||
leftPanel={<DataInsightLeftPanel />}
|
||||
pageTitle={t('label.data-insight')}>
|
||||
{getTabContent()}
|
||||
<DataInsightProvider>
|
||||
<Row
|
||||
className="page-container"
|
||||
data-testid="data-insight-container"
|
||||
gutter={[16, 16]}>
|
||||
{isHeaderVisible && (
|
||||
<Col span={24}>
|
||||
<DataInsightHeader onScrollToChart={handleScrollToChart} />
|
||||
</Col>
|
||||
)}
|
||||
<Col span={24}>
|
||||
<Switch>
|
||||
{dataInsightTabs.map((tab) => (
|
||||
<Route
|
||||
exact
|
||||
component={tab.component}
|
||||
key={tab.key}
|
||||
path={tab.path}
|
||||
/>
|
||||
))}
|
||||
|
||||
<Route exact path={ROUTES.DATA_INSIGHT}>
|
||||
<Redirect to={getDataInsightPathWithFqn()} />
|
||||
</Route>
|
||||
</Switch>
|
||||
</Col>
|
||||
</Row>
|
||||
</DataInsightProvider>
|
||||
</PageLayoutV1>
|
||||
);
|
||||
};
|
||||
|
@ -11,101 +11,56 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { act, render, screen } from '@testing-library/react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { DataInsightTabs } from '../../interface/data-insight.interface';
|
||||
import DataInsightPage from './DataInsightPage.component';
|
||||
|
||||
let activeTab = DataInsightTabs.DATA_ASSETS;
|
||||
const activeTab = DataInsightTabs.DATA_ASSETS;
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
Switch: jest.fn().mockImplementation(({ children }) => <div>{children}</div>),
|
||||
Route: jest
|
||||
.fn()
|
||||
.mockImplementation(({ children }) => (
|
||||
<div data-testid="route">{children}</div>
|
||||
)),
|
||||
useHistory: jest.fn().mockReturnValue({ push: jest.fn() }),
|
||||
useParams: jest.fn().mockImplementation(() => ({ tab: activeTab })),
|
||||
}));
|
||||
|
||||
jest.mock('../../components/containers/PageLayoutV1', () =>
|
||||
jest.fn().mockImplementation(({ children }) => <>{children}</>)
|
||||
);
|
||||
jest.mock('../../components/MyData/LeftSidebar/LeftSidebar.component', () =>
|
||||
jest.fn().mockReturnValue(<p>Sidebar</p>)
|
||||
);
|
||||
|
||||
jest.mock('../../components/DataInsightDetail/DataInsightSummary', () =>
|
||||
jest
|
||||
.fn()
|
||||
.mockReturnValue(
|
||||
<div data-testid="data-insight-summary">DataInsight Summary</div>
|
||||
)
|
||||
);
|
||||
|
||||
jest.mock('../../components/DataInsightDetail/DescriptionInsight', () =>
|
||||
jest
|
||||
.fn()
|
||||
.mockReturnValue(
|
||||
<div data-testid="description-insight">DescriptionInsight</div>
|
||||
)
|
||||
);
|
||||
|
||||
jest.mock('../../components/DataInsightDetail/OwnerInsight', () =>
|
||||
jest.fn().mockReturnValue(<div data-testid="owner-insight">OwnerInsight</div>)
|
||||
);
|
||||
|
||||
jest.mock('../../components/DataInsightDetail/TierInsight', () =>
|
||||
jest.fn().mockReturnValue(<div data-testid="tier-insight">TierInsight</div>)
|
||||
);
|
||||
|
||||
jest.mock('../../components/DataInsightDetail/TopActiveUsers', () =>
|
||||
jest
|
||||
.fn()
|
||||
.mockReturnValue(<div data-testid="top-active-user">TopActiveUsers</div>)
|
||||
);
|
||||
|
||||
jest.mock('../../components/DataInsightDetail/TopViewEntities', () =>
|
||||
jest
|
||||
.fn()
|
||||
.mockReturnValue(
|
||||
<div data-testid="top-viewed-entities">TopViewEntities</div>
|
||||
)
|
||||
);
|
||||
|
||||
jest.mock('../../components/DataInsightDetail/TotalEntityInsight', () =>
|
||||
jest
|
||||
.fn()
|
||||
.mockReturnValue(
|
||||
<div data-testid="total-entity-insight">TotalEntityInsight</div>
|
||||
)
|
||||
);
|
||||
|
||||
jest.mock('../../utils/DataInsightUtils', () => ({
|
||||
getTeamFilter: jest.fn().mockReturnValue([]),
|
||||
}));
|
||||
|
||||
jest.mock('../../components/DataInsightDetail/DailyActiveUsersChart', () =>
|
||||
jest
|
||||
.fn()
|
||||
.mockReturnValue(
|
||||
<div data-testid="daily-active-users">DailyActiveUsersChart</div>
|
||||
)
|
||||
);
|
||||
jest.mock('../../components/DataInsightDetail/PageViewsByEntitiesChart', () =>
|
||||
jest
|
||||
.fn()
|
||||
.mockReturnValue(
|
||||
<div data-testid="entities-page-views">PageViewsByEntitiesChart</div>
|
||||
)
|
||||
);
|
||||
jest.mock('../../components/DataInsightDetail/KPIChart', () =>
|
||||
jest.fn().mockReturnValue(<div data-testid="kpi-chart">KPIChart</div>)
|
||||
jest.mock('./DataInsightProvider', () =>
|
||||
jest.fn().mockImplementation(({ children }) => <>{children}</>)
|
||||
);
|
||||
|
||||
jest.mock('./KPIList', () =>
|
||||
jest.fn().mockReturnValue(<div data-testid="kpi-list">KPI List</div>)
|
||||
);
|
||||
|
||||
jest.mock('./DataInsightLeftPanel', () =>
|
||||
jest.mock('./DataInsightLeftPanel/DataInsightLeftPanel', () =>
|
||||
jest.fn().mockReturnValue(<div data-testid="left-panel">Left panel</div>)
|
||||
);
|
||||
jest.mock('./DataInsightHeader/DataInsightHeader.component', () =>
|
||||
jest.fn().mockReturnValue(<div>DataInsightHeader.component</div>)
|
||||
);
|
||||
jest.mock(
|
||||
'../../components/DataInsightDetail/DataAssetsTab/DataAssetsTab.component',
|
||||
() => jest.fn().mockReturnValue(<div>DataAssetsTab.component</div>)
|
||||
);
|
||||
const mockComponent = () => <div>dataAssetsComponent</div>;
|
||||
jest.mock('./DataInsightClassBase', () => ({
|
||||
getDataInsightTab: jest.fn().mockReturnValue([
|
||||
{
|
||||
key: 'data-assets',
|
||||
path: '/data-insights/data-assets',
|
||||
component: mockComponent,
|
||||
},
|
||||
]),
|
||||
}));
|
||||
|
||||
jest.mock('../../components/PermissionProvider/PermissionProvider', () => ({
|
||||
usePermissionProvider: jest.fn().mockReturnValue({
|
||||
@ -122,47 +77,13 @@ jest.mock('../../components/PermissionProvider/PermissionProvider', () => ({
|
||||
|
||||
describe('Test DataInsightPage Component', () => {
|
||||
it('Should render all child elements', async () => {
|
||||
render(<DataInsightPage />);
|
||||
render(<DataInsightPage />, { wrapper: MemoryRouter });
|
||||
|
||||
const container = screen.getByTestId('data-insight-container');
|
||||
const insightSummary = screen.getByTestId('data-insight-summary');
|
||||
const descriptionInsight = screen.getAllByTestId('description-insight');
|
||||
const ownerInsight = screen.getAllByTestId('owner-insight');
|
||||
const tierInsight = screen.getByTestId('tier-insight');
|
||||
|
||||
const totalEntityInsight = screen.getByTestId('total-entity-insight');
|
||||
|
||||
expect(container).toBeInTheDocument();
|
||||
|
||||
expect(insightSummary).toBeInTheDocument();
|
||||
expect(descriptionInsight).toHaveLength(2);
|
||||
expect(ownerInsight).toHaveLength(2);
|
||||
expect(tierInsight).toBeInTheDocument();
|
||||
|
||||
expect(totalEntityInsight).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Should not render the KPI chart for app analytics', async () => {
|
||||
activeTab = DataInsightTabs.APP_ANALYTICS;
|
||||
|
||||
await act(async () => {
|
||||
render(<DataInsightPage />, { wrapper: MemoryRouter });
|
||||
});
|
||||
|
||||
const kpiChart = screen.queryByTestId('kpi-chart');
|
||||
|
||||
expect(kpiChart).toBeNull();
|
||||
});
|
||||
|
||||
it('Should not render the insights summary for KPIs', async () => {
|
||||
activeTab = DataInsightTabs.KPIS;
|
||||
|
||||
await act(async () => {
|
||||
render(<DataInsightPage />, { wrapper: MemoryRouter });
|
||||
});
|
||||
|
||||
const dataInsightsSummary = screen.queryByTestId('data-insight-summary');
|
||||
|
||||
expect(dataInsightsSummary).toBeNull();
|
||||
expect(
|
||||
await screen.findByText('DataInsightHeader.component')
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
await screen.findByTestId('data-insight-container')
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
@ -0,0 +1,293 @@
|
||||
/*
|
||||
* Copyright 2023 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 { isEmpty, isEqual } from 'lodash';
|
||||
import React, {
|
||||
createContext,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { ListItem } from 'react-awesome-query-builder';
|
||||
import Loader from '../../components/Loader/Loader';
|
||||
import { DateRangeObject } from '../../components/ProfilerDashboard/component/TestSummary';
|
||||
import { SearchDropdownOption } from '../../components/SearchDropdown/SearchDropdown.interface';
|
||||
import { autocomplete } from '../../constants/AdvancedSearch.constants';
|
||||
import { PAGE_SIZE } from '../../constants/constants';
|
||||
import { INITIAL_CHART_FILTER } from '../../constants/DataInsight.constants';
|
||||
import {
|
||||
DEFAULT_RANGE_DATA,
|
||||
DEFAULT_SELECTED_RANGE,
|
||||
} from '../../constants/profiler.constant';
|
||||
import { EntityFields } from '../../enums/AdvancedSearch.enum';
|
||||
import { SearchIndex } from '../../enums/search.enum';
|
||||
import { Kpi } from '../../generated/dataInsight/kpi/kpi';
|
||||
import { Tag } from '../../generated/entity/classification/tag';
|
||||
import { ChartFilter } from '../../interface/data-insight.interface';
|
||||
import { getListKPIs } from '../../rest/KpiAPI';
|
||||
import { searchQuery } from '../../rest/searchAPI';
|
||||
import { getTags } from '../../rest/tagAPI';
|
||||
import { getTeamFilter } from '../../utils/DataInsightUtils';
|
||||
import { getEntityName } from '../../utils/EntityUtils';
|
||||
import {
|
||||
DataInsightContextType,
|
||||
DataInsightProviderProps,
|
||||
TeamStateType,
|
||||
TierStateType,
|
||||
} from './DataInsight.interface';
|
||||
|
||||
export const DataInsightContext = createContext<DataInsightContextType>(
|
||||
{} as DataInsightContextType
|
||||
);
|
||||
const fetchTeamSuggestions = autocomplete({
|
||||
searchIndex: SearchIndex.TEAM,
|
||||
entitySearchIndex: SearchIndex.TEAM,
|
||||
entityField: EntityFields.OWNER,
|
||||
});
|
||||
|
||||
const DataInsightProvider = ({ children }: DataInsightProviderProps) => {
|
||||
const [teamsOptions, setTeamOptions] = useState<TeamStateType>({
|
||||
defaultOptions: [],
|
||||
selectedOptions: [],
|
||||
options: [],
|
||||
});
|
||||
const [tierOptions, setTierOptions] = useState<TierStateType>({
|
||||
selectedOptions: [],
|
||||
options: [],
|
||||
});
|
||||
|
||||
const [chartFilter, setChartFilter] =
|
||||
useState<ChartFilter>(INITIAL_CHART_FILTER);
|
||||
const [kpiList, setKpiList] = useState<Array<Kpi>>([]);
|
||||
const [isKpiLoading, setIsKpiLoading] = useState(true);
|
||||
const [selectedDaysFilter, setSelectedDaysFilter] = useState(
|
||||
DEFAULT_SELECTED_RANGE.days
|
||||
);
|
||||
const [dateRangeObject, setDateRangeObject] =
|
||||
useState<DateRangeObject>(DEFAULT_RANGE_DATA);
|
||||
const [tier, setTier] = useState<{ tags: Tag[]; isLoading: boolean }>({
|
||||
tags: [],
|
||||
isLoading: true,
|
||||
});
|
||||
|
||||
const defaultTierOptions = useMemo(() => {
|
||||
return tier.tags.map((op) => ({
|
||||
key: op.fullyQualifiedName ?? op.name,
|
||||
label: getEntityName(op),
|
||||
}));
|
||||
}, [tier]);
|
||||
|
||||
const handleTierChange = (tiers: SearchDropdownOption[] = []) => {
|
||||
setTierOptions((prev) => ({ ...prev, selectedOptions: tiers }));
|
||||
setChartFilter((previous) => ({
|
||||
...previous,
|
||||
tier: tiers.length ? tiers.map((tier) => tier.key).join(',') : undefined,
|
||||
}));
|
||||
};
|
||||
|
||||
const handleDateRangeChange = (
|
||||
value: DateRangeObject,
|
||||
daysValue?: number
|
||||
) => {
|
||||
if (!isEqual(value, dateRangeObject)) {
|
||||
setDateRangeObject(value);
|
||||
setSelectedDaysFilter(daysValue ?? 0);
|
||||
setChartFilter((previous) => ({
|
||||
...previous,
|
||||
startTs: value.startTs,
|
||||
endTs: value.endTs,
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
const handleTeamChange = (teams: SearchDropdownOption[] = []) => {
|
||||
setTeamOptions((prev) => ({
|
||||
...prev,
|
||||
selectedOptions: teams,
|
||||
}));
|
||||
setChartFilter((previous) => ({
|
||||
...previous,
|
||||
team: teams.length ? teams.map((team) => team.key).join(',') : undefined,
|
||||
}));
|
||||
};
|
||||
|
||||
const handleTeamSearch = async (query: string) => {
|
||||
if (fetchTeamSuggestions && !isEmpty(query)) {
|
||||
try {
|
||||
const response = await fetchTeamSuggestions(query, PAGE_SIZE);
|
||||
setTeamOptions((prev) => ({
|
||||
...prev,
|
||||
options: getTeamFilter(response.values as ListItem[]),
|
||||
}));
|
||||
} catch (_error) {
|
||||
// we will not show the toast error message for suggestion API
|
||||
}
|
||||
} else {
|
||||
setTeamOptions((prev) => ({
|
||||
...prev,
|
||||
options: prev.defaultOptions,
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
const handleTierSearch = async (query: string) => {
|
||||
if (query) {
|
||||
setTierOptions((prev) => ({
|
||||
...prev,
|
||||
options: prev.options.filter(
|
||||
(value) =>
|
||||
value.label
|
||||
.toLocaleLowerCase()
|
||||
.includes(query.toLocaleLowerCase()) ||
|
||||
value.key.toLocaleLowerCase().includes(query.toLocaleLowerCase())
|
||||
),
|
||||
}));
|
||||
} else {
|
||||
setTierOptions((prev) => ({
|
||||
...prev,
|
||||
options: defaultTierOptions,
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
const fetchDefaultTeamOptions = async () => {
|
||||
if (teamsOptions.defaultOptions.length) {
|
||||
setTeamOptions((prev) => ({
|
||||
...prev,
|
||||
options: prev.defaultOptions,
|
||||
}));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await searchQuery({
|
||||
searchIndex: SearchIndex.TEAM,
|
||||
query: '*',
|
||||
pageSize: PAGE_SIZE,
|
||||
});
|
||||
const hits = response.hits.hits;
|
||||
const teamFilterOptions = hits.map((hit) => {
|
||||
const source = hit._source;
|
||||
|
||||
return { key: source.name, label: source.displayName ?? source.name };
|
||||
});
|
||||
setTeamOptions((prev) => ({
|
||||
...prev,
|
||||
defaultOptions: teamFilterOptions,
|
||||
options: teamFilterOptions,
|
||||
}));
|
||||
} catch (_error) {
|
||||
// we will not show the toast error message for search API
|
||||
}
|
||||
};
|
||||
|
||||
const getTierTag = async () => {
|
||||
setTier((prev) => ({ ...prev, isLoading: true }));
|
||||
try {
|
||||
const { data } = await getTags({
|
||||
parent: 'Tier',
|
||||
});
|
||||
|
||||
setTier((prev) => ({ ...prev, tags: data }));
|
||||
setTierOptions((prev) => ({
|
||||
...prev,
|
||||
options: data.map((op) => ({
|
||||
key: op.fullyQualifiedName ?? op.name,
|
||||
label: getEntityName(op),
|
||||
})),
|
||||
}));
|
||||
} catch (error) {
|
||||
// error
|
||||
} finally {
|
||||
setTier((prev) => ({ ...prev, isLoading: false }));
|
||||
}
|
||||
};
|
||||
|
||||
const fetchDefaultTierOptions = () => {
|
||||
setTierOptions((prev) => ({
|
||||
...prev,
|
||||
options: defaultTierOptions,
|
||||
}));
|
||||
};
|
||||
|
||||
const fetchKpiList = async () => {
|
||||
setIsKpiLoading(true);
|
||||
try {
|
||||
const response = await getListKPIs({ fields: 'dataInsightChart' });
|
||||
setKpiList(response.data);
|
||||
} catch (_err) {
|
||||
setKpiList([]);
|
||||
} finally {
|
||||
setIsKpiLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const dataInsightHeaderProps = useMemo(
|
||||
() => ({
|
||||
chartFilter: chartFilter,
|
||||
selectedDaysFilter,
|
||||
onChartFilterChange: handleDateRangeChange,
|
||||
kpi: {
|
||||
isLoading: isKpiLoading,
|
||||
data: kpiList,
|
||||
},
|
||||
teamFilter: {
|
||||
options: teamsOptions.options,
|
||||
selectedKeys: teamsOptions.selectedOptions,
|
||||
onChange: handleTeamChange,
|
||||
onGetInitialOptions: fetchDefaultTeamOptions,
|
||||
onSearch: handleTeamSearch,
|
||||
},
|
||||
tierFilter: {
|
||||
options: tierOptions.options,
|
||||
selectedKeys: tierOptions.selectedOptions,
|
||||
onChange: handleTierChange,
|
||||
onGetInitialOptions: fetchDefaultTierOptions,
|
||||
onSearch: handleTierSearch,
|
||||
},
|
||||
tierTag: tier,
|
||||
}),
|
||||
[
|
||||
handleTeamSearch,
|
||||
chartFilter,
|
||||
isKpiLoading,
|
||||
kpiList,
|
||||
handleDateRangeChange,
|
||||
handleTierSearch,
|
||||
fetchDefaultTierOptions,
|
||||
handleTierChange,
|
||||
tierOptions,
|
||||
fetchDefaultTeamOptions,
|
||||
handleTeamChange,
|
||||
teamsOptions,
|
||||
]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
getTierTag();
|
||||
fetchDefaultTeamOptions();
|
||||
fetchKpiList();
|
||||
setChartFilter(INITIAL_CHART_FILTER);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<DataInsightContext.Provider value={dataInsightHeaderProps}>
|
||||
{isKpiLoading ? <Loader /> : children}
|
||||
</DataInsightContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useDataInsightProvider = () => useContext(DataInsightContext);
|
||||
|
||||
export default DataInsightProvider;
|
@ -62,7 +62,7 @@ jest.mock('../../rest/KpiAPI', () => ({
|
||||
|
||||
describe('KPI list component', () => {
|
||||
it('Should render the kpi list', async () => {
|
||||
render(<KPIList viewKPIPermission />, { wrapper: MemoryRouter });
|
||||
render(<KPIList />, { wrapper: MemoryRouter });
|
||||
|
||||
const container = await screen.findByTestId('kpi-table');
|
||||
const descriptionKPI = await screen.findByText('Description KPI');
|
||||
@ -77,7 +77,7 @@ describe('KPI list component', () => {
|
||||
it('Action button should work', async () => {
|
||||
const KPI = KPI_DATA[0];
|
||||
|
||||
render(<KPIList viewKPIPermission />, { wrapper: MemoryRouter });
|
||||
render(<KPIList />, { wrapper: MemoryRouter });
|
||||
|
||||
const editButton = await screen.findByTestId(
|
||||
`edit-action-${KPI.displayName}`
|
||||
|
@ -25,6 +25,8 @@ import { PagingHandlerParams } from '../../components/common/next-previous/NextP
|
||||
import RichTextEditorPreviewer from '../../components/common/rich-text-editor/RichTextEditorPreviewer';
|
||||
import Table from '../../components/common/Table/Table';
|
||||
import { EmptyGraphPlaceholder } from '../../components/DataInsightDetail/EmptyGraphPlaceholder';
|
||||
import { usePermissionProvider } from '../../components/PermissionProvider/PermissionProvider';
|
||||
import { ResourceEntity } from '../../components/PermissionProvider/PermissionProvider.interface';
|
||||
import {
|
||||
getKpiPath,
|
||||
INITIAL_PAGING_VALUE,
|
||||
@ -34,17 +36,24 @@ import {
|
||||
import { ERROR_PLACEHOLDER_TYPE } from '../../enums/common.enum';
|
||||
import { EntityType } from '../../enums/entity.enum';
|
||||
import { Kpi, KpiTargetType } from '../../generated/dataInsight/kpi/kpi';
|
||||
import { Operation } from '../../generated/entity/policies/policy';
|
||||
import { Paging } from '../../generated/type/paging';
|
||||
import { useAuth } from '../../hooks/authHooks';
|
||||
import { getListKPIs } from '../../rest/KpiAPI';
|
||||
import { formatDateTime } from '../../utils/date-time/DateTimeUtils';
|
||||
import { getEntityName } from '../../utils/EntityUtils';
|
||||
import { checkPermission } from '../../utils/PermissionsUtils';
|
||||
import SVGIcons, { Icons } from '../../utils/SvgUtils';
|
||||
|
||||
const KPIList = ({ viewKPIPermission }: { viewKPIPermission: boolean }) => {
|
||||
const KPIList = () => {
|
||||
const history = useHistory();
|
||||
const { isAdminUser } = useAuth();
|
||||
const { t } = useTranslation();
|
||||
const { permissions } = usePermissionProvider();
|
||||
const viewKPIPermission = useMemo(
|
||||
() => checkPermission(Operation.ViewAll, ResourceEntity.KPI, permissions),
|
||||
[permissions]
|
||||
);
|
||||
const [kpiList, setKpiList] = useState<Array<Kpi>>([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [kpiPage, setKpiPage] = useState(INITIAL_PAGING_VALUE);
|
||||
|
@ -70,6 +70,7 @@ jest.mock('../../components/common/ResizablePanels/ResizablePanels', () =>
|
||||
);
|
||||
|
||||
jest.mock('../../utils/DataInsightUtils', () => ({
|
||||
...jest.requireActual('../../utils/DataInsightUtils'),
|
||||
getKpiTargetValueByMetricType: jest.fn().mockReturnValue(10),
|
||||
getDisabledDates: jest.fn().mockReturnValue(true),
|
||||
}));
|
||||
|
@ -55,6 +55,7 @@ import { Kpi } from '../../generated/dataInsight/kpi/kpi';
|
||||
import { getListDataInsightCharts } from '../../rest/DataInsightAPI';
|
||||
import { getListKPIs, postKPI } from '../../rest/KpiAPI';
|
||||
import {
|
||||
getDataInsightPathWithFqn,
|
||||
getDisabledDates,
|
||||
getKpiTargetValueByMetricType,
|
||||
} from '../../utils/DataInsightUtils';
|
||||
@ -67,7 +68,7 @@ const { Option } = Select;
|
||||
const breadcrumb = [
|
||||
{
|
||||
name: t('label.data-insight'),
|
||||
url: ROUTES.DATA_INSIGHT,
|
||||
url: getDataInsightPathWithFqn(),
|
||||
},
|
||||
{
|
||||
name: t('label.kpi-list'),
|
||||
|
@ -56,6 +56,7 @@ jest.mock('../../hooks/authHooks', () => ({
|
||||
}));
|
||||
|
||||
jest.mock('../../utils/DataInsightUtils', () => ({
|
||||
...jest.requireActual('../../utils/DataInsightUtils'),
|
||||
getKpiTargetValueByMetricType: jest.fn().mockReturnValue(10),
|
||||
getDisabledDates: jest.fn().mockReturnValue(true),
|
||||
}));
|
||||
|
@ -45,6 +45,7 @@ import { useAuth } from '../../hooks/authHooks';
|
||||
import { getChartById } from '../../rest/DataInsightAPI';
|
||||
import { getKPIByName, patchKPI } from '../../rest/KpiAPI';
|
||||
import {
|
||||
getDataInsightPathWithFqn,
|
||||
getDisabledDates,
|
||||
getKpiTargetValueByMetricType,
|
||||
} from '../../utils/DataInsightUtils';
|
||||
@ -74,7 +75,7 @@ const EditKPIPage = () => {
|
||||
() => [
|
||||
{
|
||||
name: t('label.data-insight'),
|
||||
url: ROUTES.DATA_INSIGHT,
|
||||
url: getDataInsightPathWithFqn(),
|
||||
},
|
||||
{
|
||||
name: t('label.kpi-list'),
|
||||
|
@ -14,8 +14,10 @@
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { isArray, isNil } from 'lodash';
|
||||
import { SearchIndex } from '../enums/search.enum';
|
||||
import { DataInsightSearchRequest } from '../interface/data-insight.interface';
|
||||
import {
|
||||
Aggregations,
|
||||
DataInsightSearchResponse,
|
||||
KeysOfUnion,
|
||||
RawSuggestResponse,
|
||||
SearchIndexSearchSourceMapping,
|
||||
@ -234,6 +236,61 @@ export const searchQuery = async <
|
||||
return formatSearchQueryResponse(res.data);
|
||||
};
|
||||
|
||||
/**
|
||||
* Access point for the Search API.
|
||||
* Executes a request to /search/query, returning a formatted response.
|
||||
*
|
||||
* @param req Request object
|
||||
*/
|
||||
export const searchQueryDataInsight = async (
|
||||
req: DataInsightSearchRequest
|
||||
): Promise<DataInsightSearchResponse> => {
|
||||
const {
|
||||
query,
|
||||
pageNumber = 1,
|
||||
pageSize = 10,
|
||||
queryFilter,
|
||||
sortField,
|
||||
sortOrder,
|
||||
searchIndex,
|
||||
includeDeleted,
|
||||
trackTotalHits,
|
||||
postFilter,
|
||||
fetchSource,
|
||||
filters,
|
||||
} = req;
|
||||
|
||||
const queryWithSlash = getQueryWithSlash(query || '');
|
||||
|
||||
const apiQuery = query
|
||||
? filters
|
||||
? `${queryWithSlash} AND `
|
||||
: queryWithSlash
|
||||
: '';
|
||||
|
||||
const apiUrl = `/search/query?q=${apiQuery}${filters ?? ''}`;
|
||||
|
||||
const res = await APIClient.get(apiUrl, {
|
||||
params: {
|
||||
index: searchIndex,
|
||||
from: (pageNumber - 1) * pageSize,
|
||||
size: pageSize,
|
||||
deleted: includeDeleted,
|
||||
query_filter: JSON.stringify(queryFilter),
|
||||
post_filter: JSON.stringify(postFilter),
|
||||
sort_field: sortField,
|
||||
sort_order: sortOrder,
|
||||
track_total_hits: trackTotalHits,
|
||||
fetch_source: fetchSource,
|
||||
include_source_fields: req.fetchSource ? req.includeFields : undefined,
|
||||
},
|
||||
});
|
||||
|
||||
return formatSearchQueryResponse(
|
||||
res.data
|
||||
) as unknown as DataInsightSearchResponse;
|
||||
};
|
||||
|
||||
/**
|
||||
* Formats a response from {@link rawSuggestQuery}
|
||||
*
|
||||
|
@ -25,6 +25,7 @@ import {
|
||||
omit,
|
||||
round,
|
||||
sortBy,
|
||||
startCase,
|
||||
sumBy,
|
||||
toNumber,
|
||||
} from 'lodash';
|
||||
@ -33,15 +34,15 @@ import React from 'react';
|
||||
import { ListItem } from 'react-awesome-query-builder';
|
||||
import { LegendProps, Surface } from 'recharts';
|
||||
import { SearchDropdownOption } from '../components/SearchDropdown/SearchDropdown.interface';
|
||||
import {
|
||||
ENTITIES_SUMMARY_LIST,
|
||||
WEB_SUMMARY_LIST,
|
||||
} from '../constants/DataInsight.constants';
|
||||
import {
|
||||
GRAYED_OUT_COLOR,
|
||||
PLACEHOLDER_ROUTE_TAB,
|
||||
ROUTES,
|
||||
} from '../constants/constants';
|
||||
import {
|
||||
ENTITIES_SUMMARY_LIST,
|
||||
WEB_SUMMARY_LIST,
|
||||
} from '../constants/DataInsight.constants';
|
||||
import { KpiTargetType } from '../generated/api/dataInsight/kpi/createKpiRequest';
|
||||
import {
|
||||
DataInsightChartResult,
|
||||
@ -53,6 +54,7 @@ import { TotalEntitiesByTier } from '../generated/dataInsight/type/totalEntities
|
||||
import {
|
||||
ChartValue,
|
||||
DataInsightChartTooltipProps,
|
||||
DataInsightTabs,
|
||||
} from '../interface/data-insight.interface';
|
||||
import { pluralize } from './CommonUtils';
|
||||
import { customFormatDateTime, formatDate } from './date-time/DateTimeUtils';
|
||||
@ -67,7 +69,8 @@ const checkIsPercentageGraph = (dataInsightChartType: DataInsightChartType) =>
|
||||
|
||||
export const renderLegend = (
|
||||
legendData: LegendProps,
|
||||
activeKeys = [] as string[]
|
||||
activeKeys = [] as string[],
|
||||
valueFormatter?: (value: string) => string
|
||||
) => {
|
||||
const { payload = [] } = legendData;
|
||||
|
||||
@ -101,7 +104,7 @@ export const renderLegend = (
|
||||
/>
|
||||
</Surface>
|
||||
<span style={{ color: isActive ? 'inherit' : GRAYED_OUT_COLOR }}>
|
||||
{entry.value}
|
||||
{valueFormatter ? valueFormatter(entry.value) : entry.value}
|
||||
</span>
|
||||
</li>
|
||||
);
|
||||
@ -110,7 +113,7 @@ export const renderLegend = (
|
||||
);
|
||||
};
|
||||
|
||||
const getEntryFormattedValue = (
|
||||
export const getEntryFormattedValue = (
|
||||
value: number | string | undefined,
|
||||
dataKey: number | string | undefined,
|
||||
kpiTooltipRecord: DataInsightChartTooltipProps['kpiTooltipRecord'],
|
||||
@ -138,7 +141,7 @@ const getEntryFormattedValue = (
|
||||
} else if (isInteger(value)) {
|
||||
return `${value}${suffix}`;
|
||||
} else {
|
||||
return `${value.toFixed(2)}${suffix}`;
|
||||
return `${round(value, 2)}${suffix}`;
|
||||
}
|
||||
} else {
|
||||
return '';
|
||||
@ -146,7 +149,13 @@ const getEntryFormattedValue = (
|
||||
};
|
||||
|
||||
export const CustomTooltip = (props: DataInsightChartTooltipProps) => {
|
||||
const { active, payload = [], isPercentage, kpiTooltipRecord } = props;
|
||||
const {
|
||||
active,
|
||||
payload = [],
|
||||
isPercentage,
|
||||
valueFormatter,
|
||||
kpiTooltipRecord,
|
||||
} = props;
|
||||
|
||||
if (active && payload && payload.length) {
|
||||
const timestamp = formatDate(payload[0].payload.timestampValue || 0);
|
||||
@ -163,15 +172,17 @@ export const CustomTooltip = (props: DataInsightChartTooltipProps) => {
|
||||
<Surface className="mr-2" height={12} version="1.1" width={12}>
|
||||
<rect fill={entry.color} height="14" rx="2" width="14" />
|
||||
</Surface>
|
||||
{entry.dataKey}
|
||||
{startCase(entry.dataKey as string)}
|
||||
</span>
|
||||
<span className="font-medium">
|
||||
{getEntryFormattedValue(
|
||||
entry.value,
|
||||
entry.dataKey,
|
||||
kpiTooltipRecord,
|
||||
isPercentage
|
||||
)}
|
||||
{valueFormatter
|
||||
? valueFormatter(entry.value)
|
||||
: getEntryFormattedValue(
|
||||
entry.value,
|
||||
entry.dataKey,
|
||||
kpiTooltipRecord,
|
||||
isPercentage
|
||||
)}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
@ -656,5 +667,15 @@ export const getKpiResultFeedback = (day: number, isTargetMet: boolean) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const getDataInsightPathWithFqn = (fqn: string) =>
|
||||
ROUTES.DATA_INSIGHT_WITH_TAB.replace(PLACEHOLDER_ROUTE_TAB, fqn);
|
||||
export const getDataInsightPathWithFqn = (tab = DataInsightTabs.DATA_ASSETS) =>
|
||||
ROUTES.DATA_INSIGHT_WITH_TAB.replace(PLACEHOLDER_ROUTE_TAB, tab);
|
||||
|
||||
export const getOptionalDataInsightTabFlag = (tab: DataInsightTabs) => {
|
||||
return {
|
||||
showDataInsightSummary:
|
||||
tab === DataInsightTabs.APP_ANALYTICS ||
|
||||
tab === DataInsightTabs.DATA_ASSETS,
|
||||
showKpiChart:
|
||||
tab === DataInsightTabs.KPIS || tab === DataInsightTabs.DATA_ASSETS,
|
||||
};
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user