mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-08-26 18:06:03 +00:00
Fixed issue Update Right panel content for add test-suite workflow, and icon for test suite #7043 (#7069)
* Fixed issue Update Right panel content for add test-suite workflow, and icon for test suite #7043 * updated link redirection path for add test * fixed failing unit test
This commit is contained in:
parent
d438fbead8
commit
c681494e44
@ -0,0 +1,13 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13.9221 3.6123L10.9766 0.580214C10.9299 0.532086 10.8675 0.5 10.7896 0.5H3.74545C2.77922 0.5 2 1.30214 2 2.29679V13.7032C2 14.6979 2.77922 15.5 3.74545 15.5H12.2545C13.2208 15.5 14 14.6979 14 13.7032V3.80481C14 3.7246 13.9688 3.66043 13.9221 3.6123V3.6123ZM11.0545 1.46257C11.7558 2.18449 12.3636 2.81016 13.0649 3.53209H11.6C11.2883 3.53209 11.0545 3.2754 11.0545 2.97059V1.46257ZM12.2545 14.9385H3.74545C3.09091 14.9385 2.54545 14.393 2.54545 13.7032V2.28075C2.54545 1.60695 3.07532 1.04545 3.74545 1.04545H10.5091V2.95455C10.5091 3.58021 11.0078 4.07754 11.6 4.07754H13.4545V13.7032C13.4545 14.393 12.9091 14.9385 12.2545 14.9385Z" fill="currentColor" stroke="currentColor" stroke-width="0.35"/>
|
||||
<path d="M11.0012 5.15234H7.93723C7.81612 5.15234 7.71924 5.26345 7.71924 5.40234C7.71924 5.54123 7.81612 5.65234 7.93723 5.65234H11.0012C11.1224 5.65234 11.2192 5.54123 11.2192 5.40234C11.2192 5.26345 11.1224 5.15234 11.0012 5.15234Z" fill="currentColor" stroke="currentColor" stroke-width="0.4"/>
|
||||
<path d="M5.2107 3.08291H9.15355C9.3094 3.08291 9.43407 2.95457 9.43407 2.79414C9.43407 2.63371 9.3094 2.50537 9.15355 2.50537H5.2107C5.05485 2.50537 4.93018 2.63371 4.93018 2.79414C4.93018 2.95457 5.05485 3.08291 5.2107 3.08291V3.08291Z" fill="currentColor" stroke="currentColor" stroke-width="0.25"/>
|
||||
<path d="M5.93531 4.66335L5.0341 5.59106L4.55424 5.09709C4.47231 5.01275 4.34357 5.01275 4.26164 5.09709C4.17971 5.18142 4.17971 5.31395 4.26164 5.39829L4.88195 6.03684C4.96388 6.12118 5.09262 6.12118 5.17455 6.03684L6.2162 4.96456C6.29813 4.88022 6.29813 4.74769 6.2162 4.66335C6.13427 4.57901 6.00553 4.57901 5.93531 4.66335V4.66335Z" fill="currentColor" stroke="currentColor" stroke-width="0.25"/>
|
||||
<path d="M11.0012 7.57471H7.93723C7.81612 7.57471 7.71924 7.68582 7.71924 7.82471C7.71924 7.9636 7.81612 8.07471 7.93723 8.07471H11.0012C11.1224 8.07471 11.2192 7.9636 11.2192 7.82471C11.2192 7.68582 11.1224 7.57471 11.0012 7.57471Z" fill="currentColor" stroke="currentColor" stroke-width="0.4"/>
|
||||
<path d="M5.94582 7.05455L5.03916 7.98788L4.55639 7.49091C4.47396 7.40606 4.34444 7.40606 4.26201 7.49091C4.17959 7.57576 4.17959 7.70909 4.26201 7.79394L4.88608 8.43636C4.96851 8.52121 5.09803 8.52121 5.18046 8.43636L6.22842 7.35758C6.31084 7.27273 6.31084 7.13939 6.22842 7.05455C6.146 6.98182 6.01647 6.98182 5.94582 7.05455V7.05455Z" fill="currentColor" stroke="currentColor" stroke-width="0.25"/>
|
||||
<path d="M11.0012 9.99731H7.93723C7.81612 9.99731 7.71924 10.1084 7.71924 10.2473C7.71924 10.3862 7.81612 10.4973 7.93723 10.4973H11.0012C11.1224 10.4973 11.2192 10.3862 11.2192 10.2473C11.2192 10.1084 11.1224 9.99731 11.0012 9.99731Z" fill="currentColor" stroke="currentColor" stroke-width="0.4"/>
|
||||
<path d="M5.93531 9.56325L5.0341 10.491L4.55424 9.99699C4.47231 9.91265 4.34357 9.91265 4.26164 9.99699C4.17971 10.0813 4.17971 10.2139 4.26164 10.2982L4.88195 10.9367C4.96388 11.0211 5.09262 11.0211 5.17455 10.9367L6.2162 9.86446C6.29813 9.78012 6.29813 9.64759 6.2162 9.56325C6.14598 9.47892 6.01723 9.47892 5.93531 9.56325V9.56325Z" fill="currentColor" stroke="currentColor" stroke-width="0.25"/>
|
||||
<path d="M11.0012 12.4199H7.93723C7.81612 12.4199 7.71924 12.531 7.71924 12.6699C7.71924 12.8088 7.81612 12.9199 7.93723 12.9199H11.0012C11.1224 12.9199 11.2192 12.8088 11.2192 12.6699C11.2192 12.531 11.1224 12.4199 11.0012 12.4199Z" fill="currentColor" stroke="currentColor" stroke-width="0.4"/>
|
||||
<path d="M5.62387 12.0072L4.4925 13.1386C4.41443 13.2167 4.41443 13.3434 4.4925 13.4215C4.57056 13.4995 4.69727 13.4995 4.77534 13.4215L5.90671 12.2901C5.98477 12.212 5.98477 12.0853 5.90671 12.0072C5.82864 11.9292 5.70193 11.9292 5.62387 12.0072Z" fill="currentColor" stroke="currentColor" stroke-width="0.25"/>
|
||||
<path d="M5.90658 13.1385L4.77521 12.0071C4.69715 11.9291 4.57043 11.9291 4.49237 12.0071C4.4143 12.0852 4.4143 12.2119 4.49237 12.29L5.62374 13.4214C5.7018 13.4994 5.82852 13.4994 5.90658 13.4214C5.98465 13.3433 5.98465 13.2166 5.90658 13.1385Z" fill="currentColor" stroke="currentColor" stroke-width="0.25"/>
|
||||
</svg>
|
After Width: | Height: | Size: 4.0 KiB |
@ -55,7 +55,7 @@ import {
|
||||
import RightPanel from './components/RightPanel';
|
||||
import SelectTestSuite from './components/SelectTestSuite';
|
||||
import TestCaseForm from './components/TestCaseForm';
|
||||
import { INGESTION_DATA, TEST_FORM_DATA } from './rightPanelData';
|
||||
import { addTestSuiteRightPanel, INGESTION_DATA } from './rightPanelData';
|
||||
import TestSuiteIngestion from './TestSuiteIngestion';
|
||||
|
||||
const AddDataQualityTestV1: React.FC<AddDataQualityTestProps> = ({ table }) => {
|
||||
@ -126,7 +126,9 @@ const AddDataQualityTestV1: React.FC<AddDataQualityTestProps> = ({ table }) => {
|
||||
}, [table, entityTypeFQN, isColumnFqn]);
|
||||
|
||||
const handleViewTestSuiteClick = () => {
|
||||
history.push(getTestSuitePath(testSuiteData?.fullyQualifiedName || ''));
|
||||
history.push(
|
||||
getTestSuitePath(selectedTestSuite?.data?.fullyQualifiedName || '')
|
||||
);
|
||||
};
|
||||
|
||||
const handleAirflowStatusCheck = (): Promise<void> => {
|
||||
@ -226,7 +228,8 @@ const AddDataQualityTestV1: React.FC<AddDataQualityTestProps> = ({ table }) => {
|
||||
"{successName}"
|
||||
</span>
|
||||
<span>
|
||||
has been created successfully. and will be pickup in next run.
|
||||
has been created successfully. This will be picked up in the next
|
||||
run.
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
@ -268,7 +271,14 @@ const AddDataQualityTestV1: React.FC<AddDataQualityTestProps> = ({ table }) => {
|
||||
data={
|
||||
addIngestion
|
||||
? INGESTION_DATA
|
||||
: TEST_FORM_DATA[activeServiceStep - 1]
|
||||
: addTestSuiteRightPanel(
|
||||
activeServiceStep,
|
||||
selectedTestSuite?.isNewTestSuite,
|
||||
{
|
||||
testCase: testCaseData?.name || '',
|
||||
testSuite: testSuiteData?.name || '',
|
||||
}
|
||||
)
|
||||
}
|
||||
/>
|
||||
}>
|
||||
|
@ -13,7 +13,7 @@
|
||||
import { Col, Row, Typography } from 'antd';
|
||||
import { AxiosError } from 'axios';
|
||||
import { compare } from 'fast-json-patch';
|
||||
import { camelCase } from 'lodash';
|
||||
import { camelCase, isEmpty } from 'lodash';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { useHistory, useParams } from 'react-router-dom';
|
||||
import {
|
||||
@ -105,7 +105,9 @@ const TestSuiteIngestion: React.FC<TestSuiteIngestionProps> = ({
|
||||
const createIngestionPipeline = async (repeatFrequency: string) => {
|
||||
const ingestionPayload: CreateIngestionPipeline = {
|
||||
airflowConfig: {
|
||||
scheduleInterval: repeatFrequency,
|
||||
scheduleInterval: isEmpty(repeatFrequency)
|
||||
? undefined
|
||||
: repeatFrequency,
|
||||
},
|
||||
name: `${testSuite.name}_${PipelineType.TestSuite}`,
|
||||
pipelineType: PipelineType.TestSuite,
|
||||
@ -131,7 +133,9 @@ const TestSuiteIngestion: React.FC<TestSuiteIngestionProps> = ({
|
||||
...ingestionPipeline,
|
||||
airflowConfig: {
|
||||
...ingestionPipeline?.airflowConfig,
|
||||
scheduleInterval: repeatFrequency,
|
||||
scheduleInterval: isEmpty(repeatFrequency)
|
||||
? undefined
|
||||
: repeatFrequency,
|
||||
},
|
||||
};
|
||||
const jsonPatch = compare(
|
||||
|
@ -1,3 +1,4 @@
|
||||
/* eslint-disable max-len */
|
||||
/*
|
||||
* Copyright 2022 Collate
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -14,19 +15,49 @@
|
||||
export const TEST_FORM_DATA = [
|
||||
{
|
||||
title: 'Select/Create Test Suite',
|
||||
body: 'Select existing test suite or Create new Test Suite',
|
||||
body: 'To create a Table or Column level test for an entity, start by selecting the Test Suite. Select an existing test suite or create a new test suite.',
|
||||
},
|
||||
{
|
||||
title: 'Test Case',
|
||||
body: 'Fill up the relevant details and create test case',
|
||||
title: 'Create Test Case',
|
||||
body: 'To create a test case, add a unique name. Select a test type from the options provided. Fill in the details for the parameters that show up for a selected test type. Enter a description (optional).',
|
||||
},
|
||||
{
|
||||
title: 'Test case created successfully',
|
||||
body: 'Visit the newly created test case to take a look at the details. Ensure that you have Airflow set up correctly before heading to ingest metadata.',
|
||||
title: 'Test Case Created Successfully',
|
||||
body: '{testCase} has been created successfully. View the Test Suite to check the details of the new created test case. This will be picked up in the next run.',
|
||||
},
|
||||
{
|
||||
title: 'Test Suite & Test Case Created Successfully',
|
||||
body: '{testSuite} & {testCase} has been created successfully. In the next step, you can schedule to ingest metadata at the desired frequency. You can also view the Test Suite to check the details of the new created test case.',
|
||||
},
|
||||
];
|
||||
|
||||
export const INGESTION_DATA = {
|
||||
title: 'Schedule for Ingestion',
|
||||
body: 'Scheduling can be set up at an hourly, daily, or weekly cadence. The timezone is in UTC.',
|
||||
title: 'Scheduler for Tests',
|
||||
body: 'The data quality tests can be scheduled to run at the desired frequency. The timezone is in UTC.',
|
||||
};
|
||||
|
||||
export const addTestSuiteRightPanel = (
|
||||
step: number,
|
||||
isSuiteCreate?: boolean,
|
||||
data?: { testSuite: string; testCase: string }
|
||||
) => {
|
||||
let message = TEST_FORM_DATA[step - 1];
|
||||
|
||||
if (step === 3) {
|
||||
if (isSuiteCreate) {
|
||||
message = TEST_FORM_DATA[step];
|
||||
const updatedMessage = message.body
|
||||
.replace('{testSuite}', data?.testSuite || 'Test Suite')
|
||||
.replace('{testCase}', data?.testCase || 'Test Case');
|
||||
message.body = updatedMessage;
|
||||
} else {
|
||||
const updatedMessage = message.body.replace(
|
||||
'{testCase}',
|
||||
data?.testCase || 'Test Case'
|
||||
);
|
||||
message.body = updatedMessage;
|
||||
}
|
||||
}
|
||||
|
||||
return message;
|
||||
};
|
||||
|
@ -255,17 +255,6 @@ const DatasetDetails: React.FC<DatasetDetailsProps> = ({
|
||||
isProtected: false,
|
||||
position: 5,
|
||||
},
|
||||
{
|
||||
name: 'Data Quality',
|
||||
icon: {
|
||||
alt: 'data-quality',
|
||||
name: 'icon-quality',
|
||||
title: 'Data Quality',
|
||||
selectedName: '',
|
||||
},
|
||||
isProtected: false,
|
||||
position: 6,
|
||||
},
|
||||
{
|
||||
name: 'Lineage',
|
||||
icon: {
|
||||
|
@ -241,7 +241,6 @@ describe('Test MyDataDetailsPage page', () => {
|
||||
const sampleDataTab = await findByTestId(tabs, 'Sample Data');
|
||||
const queriesTab = await findByTestId(tabs, 'Queries');
|
||||
const profilerTab = await findByTestId(tabs, 'Profiler');
|
||||
const dataQualityTab = await findByTestId(tabs, 'Data Quality');
|
||||
const lineageTab = await findByTestId(tabs, 'Lineage');
|
||||
const dbtTab = queryByTestId(tabs, 'DBT');
|
||||
|
||||
@ -254,7 +253,6 @@ describe('Test MyDataDetailsPage page', () => {
|
||||
expect(sampleDataTab).toBeInTheDocument();
|
||||
expect(queriesTab).toBeInTheDocument();
|
||||
expect(profilerTab).toBeInTheDocument();
|
||||
expect(dataQualityTab).toBeInTheDocument();
|
||||
expect(lineageTab).toBeInTheDocument();
|
||||
expect(dbtTab).not.toBeInTheDocument();
|
||||
});
|
||||
|
@ -73,7 +73,7 @@ const MyAssetStats: FunctionComponent<MyAssetStatsProps> = ({
|
||||
dataTestId: 'mlmodels',
|
||||
},
|
||||
testSuite: {
|
||||
icon: Icons.TABLE_GREY,
|
||||
icon: Icons.TEST_SUITE,
|
||||
data: 'Test Suite',
|
||||
count: entityCounts.testSuiteCount,
|
||||
link: getSettingPath(
|
||||
|
@ -46,6 +46,7 @@ import {
|
||||
getPartialNameFromTableFQN,
|
||||
hasEditAccess,
|
||||
} from '../../utils/CommonUtils';
|
||||
import { getAddDataQualityTableTestPath } from '../../utils/RouterUtils';
|
||||
import { serviceTypeLogo } from '../../utils/ServiceUtils';
|
||||
import {
|
||||
generateEntityLink,
|
||||
@ -299,7 +300,12 @@ const ProfilerDashboard: React.FC<ProfilerDashboardProps> = ({
|
||||
|
||||
const handleAddTestClick = () => {
|
||||
history.push(
|
||||
getTableTabPath(table.fullyQualifiedName || '', 'data-quality')
|
||||
getAddDataQualityTableTestPath(
|
||||
isColumnView
|
||||
? ProfilerDashboardType.COLUMN
|
||||
: ProfilerDashboardType.TABLE,
|
||||
entityTypeFQN || ''
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -12,16 +12,26 @@
|
||||
*/
|
||||
|
||||
import { Card, Col, Row, Statistic } from 'antd';
|
||||
import { AxiosError } from 'axios';
|
||||
import { sortBy } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { getListTestCase } from '../../../axiosAPIs/testAPI';
|
||||
import { API_RES_MAX_SIZE } from '../../../constants/constants';
|
||||
import {
|
||||
INITIAL_COUNT_METRIC_VALUE,
|
||||
INITIAL_MATH_METRIC_VALUE,
|
||||
INITIAL_PROPORTION_METRIC_VALUE,
|
||||
INITIAL_SUM_METRIC_VALUE,
|
||||
INITIAL_TEST_RESULT_SUMMARY,
|
||||
} from '../../../constants/profiler.constant';
|
||||
import { getTableFQNFromColumnFQN } from '../../../utils/CommonUtils';
|
||||
import { updateTestResults } from '../../../utils/DataQualityAndProfilerUtils';
|
||||
import { generateEntityLink } from '../../../utils/TableUtils';
|
||||
import { showErrorToast } from '../../../utils/ToastUtils';
|
||||
import Ellipses from '../../common/Ellipses/Ellipses';
|
||||
import { TableTestsType } from '../../TableProfiler/TableProfiler.interface';
|
||||
import {
|
||||
MetricChartType,
|
||||
ProfilerTabProps,
|
||||
@ -34,6 +44,7 @@ const ProfilerTab: React.FC<ProfilerTabProps> = ({
|
||||
profilerData,
|
||||
tableProfile,
|
||||
}) => {
|
||||
const { entityTypeFQN } = useParams<Record<string, string>>();
|
||||
const [countMetrics, setCountMetrics] = useState<MetricChartType>(
|
||||
INITIAL_COUNT_METRIC_VALUE
|
||||
);
|
||||
@ -46,6 +57,10 @@ const ProfilerTab: React.FC<ProfilerTabProps> = ({
|
||||
const [sumMetrics, setSumMetrics] = useState<MetricChartType>(
|
||||
INITIAL_SUM_METRIC_VALUE
|
||||
);
|
||||
const [tableTests, setTableTests] = useState<TableTestsType>({
|
||||
tests: [],
|
||||
results: INITIAL_TEST_RESULT_SUMMARY,
|
||||
});
|
||||
|
||||
const tableState = useMemo(
|
||||
() => [
|
||||
@ -64,23 +79,24 @@ const ProfilerTab: React.FC<ProfilerTabProps> = ({
|
||||
],
|
||||
[tableProfile]
|
||||
);
|
||||
const testSummary = useMemo(
|
||||
() => [
|
||||
const testSummary = useMemo(() => {
|
||||
const { results } = tableTests;
|
||||
|
||||
return [
|
||||
{
|
||||
title: 'Success',
|
||||
value: 0,
|
||||
value: results.success,
|
||||
},
|
||||
{
|
||||
title: 'Aborted',
|
||||
value: 0,
|
||||
value: results.aborted,
|
||||
},
|
||||
{
|
||||
title: 'Failed',
|
||||
value: 0,
|
||||
value: results.failed,
|
||||
},
|
||||
],
|
||||
[]
|
||||
);
|
||||
];
|
||||
}, [tableTests]);
|
||||
|
||||
const createMetricsChartData = () => {
|
||||
const updateProfilerData = sortBy(profilerData, 'timestamp');
|
||||
@ -171,10 +187,38 @@ const ProfilerTab: React.FC<ProfilerTabProps> = ({
|
||||
}));
|
||||
};
|
||||
|
||||
const fetchAllTests = async () => {
|
||||
const tableFqn = getTableFQNFromColumnFQN(entityTypeFQN);
|
||||
try {
|
||||
const { data } = await getListTestCase({
|
||||
fields: 'testCaseResult',
|
||||
entityLink: generateEntityLink(tableFqn),
|
||||
limit: API_RES_MAX_SIZE,
|
||||
});
|
||||
const tableTests: TableTestsType = {
|
||||
tests: [],
|
||||
results: { ...INITIAL_TEST_RESULT_SUMMARY },
|
||||
};
|
||||
data.forEach((test) => {
|
||||
updateTestResults(
|
||||
tableTests.results,
|
||||
test.testCaseResult?.testCaseStatus || ''
|
||||
);
|
||||
});
|
||||
setTableTests(tableTests);
|
||||
} catch (error) {
|
||||
showErrorToast(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
createMetricsChartData();
|
||||
}, [profilerData]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchAllTests();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={8}>
|
||||
|
@ -84,7 +84,7 @@ const ProfilerDashboardPage = () => {
|
||||
};
|
||||
|
||||
const handleTestCaseUpdate = () => {
|
||||
fetchTestCases(generateEntityLink(entityTypeFQN));
|
||||
fetchTestCases(generateEntityLink(entityTypeFQN, isColumnView));
|
||||
};
|
||||
|
||||
const fetchTableEntity = async () => {
|
||||
|
@ -17,6 +17,7 @@ import React, { ReactNode } from 'react';
|
||||
import { ReactComponent as BotIcon } from '../../src/assets/svg/bot-profile.svg';
|
||||
import { ReactComponent as DashboardIcon } from '../../src/assets/svg/dashboard-grey.svg';
|
||||
import { ReactComponent as RolesIcon } from '../../src/assets/svg/icon-role-grey.svg';
|
||||
import { ReactComponent as TestSuite } from '../../src/assets/svg/icon-test-suite.svg';
|
||||
import { ReactComponent as MlModelIcon } from '../../src/assets/svg/mlmodal.svg';
|
||||
import { ReactComponent as PipelineIcon } from '../../src/assets/svg/pipeline-grey.svg';
|
||||
import { ReactComponent as PoliciesIcon } from '../../src/assets/svg/policies.svg';
|
||||
@ -162,7 +163,7 @@ export const getGlobalSettingsMenuWithPermission = (
|
||||
ResourceEntity.TEST_SUITE,
|
||||
permissions
|
||||
),
|
||||
icon: <TableIcon className="side-panel-icons" />,
|
||||
icon: <TestSuite className="side-panel-icons" />,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -118,6 +118,7 @@ import IconKey from '../assets/svg/icon-key.svg';
|
||||
import IconNotNull from '../assets/svg/icon-notnull.svg';
|
||||
import IconPlusPrimaryOutlined from '../assets/svg/icon-plus-primary-outlined.svg';
|
||||
import IconRoleGrey from '../assets/svg/icon-role-grey.svg';
|
||||
import IconTestSuite from '../assets/svg/icon-test-suite.svg';
|
||||
import IconTour from '../assets/svg/icon-tour.svg';
|
||||
import IconUnique from '../assets/svg/icon-unique.svg';
|
||||
import IconUp from '../assets/svg/icon-up.svg';
|
||||
@ -204,6 +205,7 @@ export const Icons = {
|
||||
SQL_BUILDER: 'icon-sql-builder',
|
||||
TEAMS: 'icon-teams',
|
||||
TEAMS_GREY: 'icon-teams-grey',
|
||||
TEST_SUITE: 'icon-test-suite',
|
||||
WORKFLOWS: 'icon-workflows',
|
||||
MENU: 'icon-menu',
|
||||
FEED: 'icon-feed',
|
||||
@ -1008,6 +1010,10 @@ const SVGIcons: FunctionComponent<Props> = ({
|
||||
case Icons.DELETE_COLORED:
|
||||
IconComponent = IconDeleteColored;
|
||||
|
||||
break;
|
||||
case Icons.TEST_SUITE:
|
||||
IconComponent = IconTestSuite;
|
||||
|
||||
break;
|
||||
default:
|
||||
IconComponent = null;
|
||||
|
Loading…
x
Reference in New Issue
Block a user