[Dataset Quality] Fix flaky tests for Dataset Quality Summary (#184640)

## Summary

As part of the PR, i have rewritten 4 of the test files for both
serverless and stateful for Dataset Quality Project

- `/dataset_quality/dataset_quality_summary.ts`
  - Closes - https://github.com/elastic/kibana/issues/178874
  - Closes - https://github.com/elastic/kibana/issues/178884
  - Closes - https://github.com/elastic/kibana/issues/186354
- `/dataset_quality/dataset_quality_table.ts`
- Closes - https://github.com/elastic/kibana/issues/183940 (Possibly,
not guaranteed)
  - Closes - https://github.com/elastic/kibana/issues/182353
- `/dataset_quality/dataset_quality_table_filters.ts`
  - Closes - https://github.com/elastic/kibana/issues/183861
  - Closes - https://github.com/elastic/kibana/issues/182320
  - Closes - https://github.com/elastic/kibana/issues/184852
- `/dataset_quality/dataset_quality_flyout.ts`
  - Closes - https://github.com/elastic/kibana/issues/184438
  - Closes - https://github.com/elastic/kibana/issues/183851
  - Closes - https://github.com/elastic/kibana/issues/183771
  - Closes - https://github.com/elastic/kibana/issues/183525
  - Closes - https://github.com/elastic/kibana/issues/183312
  - Closes - https://github.com/elastic/kibana/issues/183129
  - Closes - https://github.com/elastic/kibana/issues/182154
  
## Why are the tests re-written

- Most of the `it` were loading its own data, which add 2 problems, 1.
Makes our tests slower, 2. Data cleanup becomes challenging. Now the
tests simply load one master set of data and all the Functional tests
can be executed on that master data. This makes our tests leaner and
more functional.

- Every `it` resets the page state after the tests. Like when a `it`
blocks opens the Flyout, it should also close the flyout which was
missing. In order to refresh the page, the `navigate` API was used,
which is not good. Navigate API should only be used once to navigate to
the page in the starting and then refresh events should be used if a
refresh is required, or the action should be un-done in order to get the
same state as previous. For ex - Sorting make update the state of the
whole page. At the end of the sorting test, sorting should be reset.
With these changes `it` block now only focus on pure functional testing.
This means the `it` blocks can be moved around, skipped without
impacting other tests

- We had too much of generic tests, which could be combined into 1 `it`
block and be checked together. Idea to split 1 `it` block into another
is when we test for a completely different scenario. For eg - Writing a
single `it` for testing different columns of a table is much more
cleaner than multiple `it` for testing various columns of the same
table.

- Removed usage of `retry.try`. (Personal Opinion, please read it with a
pinch of salt) - The retry service seems like an escape hatch (read
workaround) when we don't have control over the rendering of UI
elements. Better alternative is to use `retry.tryforTime` as the last
resort.
Also the only time i found using the whole `retry` package was when we
use the `browser` package for getting URL value of refreshing page. I
cannot prove yet the problem with the `browser` package but somehow it
breaks the sync behaviour causing elements to be not available hence
requiring these retries.
I have removed `browser.refresh` completely from our code in favour of
better refresh handlers using DateTimePicker Refresh action


Linked Issued - https://github.com/elastic/kibana/issues/184145
This commit is contained in:
Achyut Jhunjhunwala 2024-06-20 16:50:33 +02:00 committed by GitHub
parent 153ec668e3
commit 103e619c0a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 1007 additions and 1602 deletions

View file

@ -22,13 +22,15 @@ export function FieldsList({
title,
fields,
actionsMenu: ActionsMenu,
dataTestSubj = `datasetQualityFlyoutFieldsList-${title.toLowerCase().split(' ').join('_')}`,
}: {
title: string;
fields: Array<{ fieldTitle: string; fieldValue: ReactNode; isLoading: boolean }>;
actionsMenu?: ReactNode;
dataTestSubj?: string;
}) {
return (
<EuiPanel hasBorder grow={false}>
<EuiPanel hasBorder grow={false} data-test-subj={dataTestSubj}>
<EuiFlexGroup justifyContent="spaceBetween">
<EuiTitle size="s">
<span>{title}</span>

View file

@ -61,6 +61,7 @@ export async function getDataStreamDetails({
await getDataStreamsStats({
esClient,
dataStreams: [dataStream],
sizeStatsAvailable,
})
).items[0]?.lastActivity
: undefined;

View file

@ -191,6 +191,7 @@ export function createDegradedFieldsRecord({
export const datasetNames = ['synth.1', 'synth.2', 'synth.3'];
export const defaultNamespace = 'default';
export const productionNamespace = 'production';
// Logs Data logic
const MESSAGE_LOG_LEVELS: MessageWithLevel[] = [

View file

@ -12,6 +12,7 @@ import {
datasetNames,
getInitialTestLogs,
getLogsForDataset,
productionNamespace,
} from './data';
const integrationActions = {
@ -32,22 +33,70 @@ export default function ({ getService, getPageObjects }: DatasetQualityFtrProvid
const retry = getService('retry');
const browser = getService('browser');
const to = '2024-01-01T12:00:00.000Z';
const apacheAccessDatasetName = 'apache.access';
const apacheAccessDatasetHumanName = 'Apache access logs';
const apacheIntegrationId = 'apache';
const apachePkg = {
name: 'apache',
version: '1.14.0',
};
describe('Dataset quality flyout', () => {
// FLAKY: https://github.com/elastic/kibana/issues/182154
// Added this sub describe block so that the existing flaky tests can be skipped and new ones can be added in the other describe block
describe.skip('Other dataset quality flyout tests', () => {
before(async () => {
await synthtrace.index(getInitialTestLogs({ to, count: 4 }));
await PageObjects.datasetQuality.navigateTo();
});
const bitbucketDatasetName = 'atlassian_bitbucket.audit';
const bitbucketDatasetHumanName = 'Bitbucket Audit Logs';
const bitbucketPkg = {
name: 'atlassian_bitbucket',
version: '1.14.0',
};
after(async () => {
await synthtrace.clean();
await PageObjects.observabilityLogsExplorer.removeInstalledPackages();
});
const degradedDatasetName = datasetNames[2];
it('opens the flyout for the right dataset', async () => {
describe('Flyout', () => {
before(async () => {
// Install Apache Integration and ingest logs for it
await PageObjects.observabilityLogsExplorer.installPackage(apachePkg);
// Install Bitbucket Integration (package which does not has Dashboards) and ingest logs for it
await PageObjects.observabilityLogsExplorer.installPackage(bitbucketPkg);
await synthtrace.index([
// Ingest basic logs
getInitialTestLogs({ to, count: 4 }),
// Ingest Degraded Logs
createDegradedFieldsRecord({
to: new Date().toISOString(),
count: 2,
dataset: degradedDatasetName,
}),
// Index 15 logs for `logs-apache.access` dataset
getLogsForDataset({
to: new Date().toISOString(),
count: 15,
dataset: apacheAccessDatasetName,
namespace: productionNamespace,
}),
// Index degraded docs for Apache integration
getLogsForDataset({
to: new Date().toISOString(),
count: 1,
dataset: apacheAccessDatasetName,
namespace: productionNamespace,
isMalformed: true,
}),
// Index logs for Bitbucket integration
getLogsForDataset({ to, count: 10, dataset: bitbucketDatasetName }),
]);
await PageObjects.datasetQuality.navigateTo();
});
after(async () => {
await PageObjects.observabilityLogsExplorer.uninstallPackage(apachePkg);
await PageObjects.observabilityLogsExplorer.uninstallPackage(bitbucketPkg);
await synthtrace.clean();
});
describe('open flyout', () => {
it('should open the flyout for the right dataset', async () => {
const testDatasetName = datasetNames[1];
await PageObjects.datasetQuality.openDatasetFlyout(testDatasetName);
@ -55,42 +104,12 @@ export default function ({ getService, getPageObjects }: DatasetQualityFtrProvid
await testSubjects.existOrFail(
PageObjects.datasetQuality.testSubjectSelectors.datasetQualityFlyoutTitle
);
});
it('shows the correct last activity', async () => {
const testDatasetName = datasetNames[0];
// Update last activity for the dataset
await PageObjects.datasetQuality.closeFlyout();
await synthtrace.index(
getLogsForDataset({ to: new Date().toISOString(), count: 1, dataset: testDatasetName })
);
await PageObjects.datasetQuality.refreshTable();
const cols = await PageObjects.datasetQuality.parseDatasetTable();
const datasetNameCol = cols['Data Set Name'];
const datasetNameColCellTexts = await datasetNameCol.getCellTexts();
const testDatasetRowIndex = datasetNameColCellTexts.findIndex(
(dName: string) => dName === testDatasetName
);
const lastActivityText = (await cols['Last Activity'].getCellTexts())[testDatasetRowIndex];
await PageObjects.datasetQuality.openDatasetFlyout(testDatasetName);
const lastActivityTextExists = await PageObjects.datasetQuality.doestTextExistInFlyout(
lastActivityText,
`[data-test-subj=${PageObjects.datasetQuality.testSubjectSelectors.datasetQualityFlyoutFieldValue}]`
);
expect(lastActivityTextExists).to.eql(true);
});
it('reflects the breakdown field state in url', async () => {
const testDatasetName = datasetNames[0];
await PageObjects.datasetQuality.openDatasetFlyout(testDatasetName);
await PageObjects.datasetQuality.openDatasetFlyout(degradedDatasetName);
const breakdownField = 'service.name';
await PageObjects.datasetQuality.selectBreakdownField(breakdownField);
@ -109,25 +128,25 @@ export default function ({ getService, getPageObjects }: DatasetQualityFtrProvid
const currentUrl = await browser.getCurrentUrl();
expect(currentUrl).to.not.contain('breakdownField');
});
await PageObjects.datasetQuality.closeFlyout();
});
});
it('shows the integration details', async () => {
const apacheAccessDatasetName = 'apache.access';
const apacheAccessDatasetHumanName = 'Apache access logs';
const apacheIntegrationId = 'apache';
describe('integrations', () => {
it('should hide the integration section for non integrations', async () => {
const testDatasetName = datasetNames[1];
await PageObjects.observabilityLogsExplorer.navigateTo();
await PageObjects.datasetQuality.openDatasetFlyout(testDatasetName);
// Add initial integrations
await PageObjects.observabilityLogsExplorer.setupInitialIntegrations();
// Index 10 logs for `logs-apache.access` dataset
await synthtrace.index(
getLogsForDataset({ to, count: 10, dataset: apacheAccessDatasetName })
await testSubjects.missingOrFail(
PageObjects.datasetQuality.testSubjectSelectors
.datasetQualityFlyoutFieldsListIntegrationDetails
);
await PageObjects.datasetQuality.navigateTo();
await PageObjects.datasetQuality.closeFlyout();
});
it('should shows the integration section for integrations', async () => {
await PageObjects.datasetQuality.openDatasetFlyout(apacheAccessDatasetHumanName);
const integrationNameElements = await PageObjects.datasetQuality.getFlyoutElementsByText(
@ -135,175 +154,17 @@ export default function ({ getService, getPageObjects }: DatasetQualityFtrProvid
apacheIntegrationId
);
await PageObjects.datasetQuality.closeFlyout();
await testSubjects.existOrFail(
PageObjects.datasetQuality.testSubjectSelectors
.datasetQualityFlyoutFieldsListIntegrationDetails
);
expect(integrationNameElements.length).to.eql(1);
await PageObjects.datasetQuality.closeFlyout();
});
it('goes to log explorer page when open button is clicked', async () => {
const testDatasetName = datasetNames[2];
await PageObjects.datasetQuality.openDatasetFlyout(testDatasetName);
await (await PageObjects.datasetQuality.getFlyoutLogsExplorerButton()).click();
// Confirm dataset selector text in observability logs explorer
const datasetSelectorText =
await PageObjects.observabilityLogsExplorer.getDataSourceSelectorButtonText();
expect(datasetSelectorText).to.eql(testDatasetName);
});
it('shows summary KPIs', async () => {
await PageObjects.datasetQuality.navigateTo();
const apacheAccessDatasetHumanName = 'Apache access logs';
await PageObjects.datasetQuality.openDatasetFlyout(apacheAccessDatasetHumanName);
const summary = await PageObjects.datasetQuality.parseFlyoutKpis();
expect(summary).to.eql({
docsCountTotal: '0',
size: '0.0 B',
services: '0',
hosts: '0',
degradedDocs: '0',
});
});
it('shows the updated KPIs', async () => {
const apacheAccessDatasetName = 'apache.access';
const apacheAccessDatasetHumanName = 'Apache access logs';
await PageObjects.datasetQuality.openDatasetFlyout(apacheAccessDatasetHumanName);
const summaryBefore = await PageObjects.datasetQuality.parseFlyoutKpis();
// Set time range to 3 days ago
const flyoutBodyContainer = await testSubjects.find(
PageObjects.datasetQuality.testSubjectSelectors.datasetQualityFlyoutBody
);
await PageObjects.datasetQuality.setDatePickerLastXUnits(flyoutBodyContainer, 3, 'd');
// Index 2 doc 2 days ago
const time2DaysAgo = Date.now() - 2 * 24 * 60 * 60 * 1000;
await synthtrace.index(
getLogsForDataset({
to: time2DaysAgo,
count: 2,
dataset: apacheAccessDatasetName,
isMalformed: false,
})
);
// Index 5 degraded docs 2 days ago
await synthtrace.index(
getLogsForDataset({
to: time2DaysAgo,
count: 5,
dataset: apacheAccessDatasetName,
isMalformed: true,
})
);
await PageObjects.datasetQuality.refreshFlyout();
const summaryAfter = await PageObjects.datasetQuality.parseFlyoutKpis();
expect(parseInt(summaryAfter.docsCountTotal, 10)).to.be.greaterThan(
parseInt(summaryBefore.docsCountTotal, 10)
);
expect(parseInt(summaryAfter.degradedDocs, 10)).to.be.greaterThan(
parseInt(summaryBefore.degradedDocs, 10)
);
expect(parseInt(summaryAfter.size, 10)).to.be.greaterThan(parseInt(summaryBefore.size, 10));
expect(parseInt(summaryAfter.services, 10)).to.be.greaterThan(
parseInt(summaryBefore.services, 10)
);
expect(parseInt(summaryAfter.hosts, 10)).to.be.greaterThan(
parseInt(summaryBefore.hosts, 10)
);
});
it('shows the right number of services', async () => {
const apacheAccessDatasetName = 'apache.access';
const apacheAccessDatasetHumanName = 'Apache access logs';
await PageObjects.datasetQuality.openDatasetFlyout(apacheAccessDatasetHumanName);
const summaryBefore = await PageObjects.datasetQuality.parseFlyoutKpis();
const testServices = ['test-srv-1', 'test-srv-2'];
// Index 2 docs with different services
const timeNow = Date.now();
await synthtrace.index(
getLogsForDataset({
to: timeNow,
count: 2,
dataset: apacheAccessDatasetName,
isMalformed: false,
services: testServices,
})
);
await PageObjects.datasetQuality.refreshFlyout();
const summaryAfter = await PageObjects.datasetQuality.parseFlyoutKpis();
expect(parseInt(summaryAfter.services, 10)).to.eql(
parseInt(summaryBefore.services, 10) + testServices.length
);
});
it('goes to log explorer for degraded docs when show all is clicked', async () => {
const apacheAccessDatasetName = 'apache.access';
const apacheAccessDatasetHumanName = 'Apache access logs';
await PageObjects.datasetQuality.openDatasetFlyout(apacheAccessDatasetHumanName);
const degradedDocsShowAllSelector = `${PageObjects.datasetQuality.testSubjectSelectors.datasetQualityFlyoutKpiLink}-${PageObjects.datasetQuality.texts.degradedDocs}`;
await testSubjects.click(degradedDocsShowAllSelector);
await browser.switchTab(1);
// Confirm dataset selector text in observability logs explorer
const datasetSelectorText =
await PageObjects.observabilityLogsExplorer.getDataSourceSelectorButtonText();
expect(datasetSelectorText).to.contain(apacheAccessDatasetName);
await browser.closeCurrentWindow();
await browser.switchTab(0);
});
// Blocked by https://github.com/elastic/kibana/issues/181705
it.skip('goes to infra hosts for hosts when show all is clicked', async () => {
const apacheAccessDatasetHumanName = 'Apache access logs';
await PageObjects.datasetQuality.openDatasetFlyout(apacheAccessDatasetHumanName);
const hostsShowAllSelector = `${PageObjects.datasetQuality.testSubjectSelectors.datasetQualityFlyoutKpiLink}-${PageObjects.datasetQuality.texts.hosts}`;
await testSubjects.click(hostsShowAllSelector);
await browser.switchTab(1);
// Confirm url contains metrics/hosts
await retry.tryForTime(5000, async () => {
const currentUrl = await browser.getCurrentUrl();
const parsedUrl = new URL(currentUrl);
expect(parsedUrl.pathname).to.contain('/app/metrics/hosts');
});
await browser.closeCurrentWindow();
await browser.switchTab(0);
});
it('Integration actions menu is present with correct actions', async () => {
const apacheAccessDatasetName = 'apache.access';
const apacheAccessDatasetHumanName = 'Apache access logs';
await PageObjects.observabilityLogsExplorer.navigateTo();
// Add initial integrations
await PageObjects.observabilityLogsExplorer.setupInitialIntegrations();
// Index 10 logs for `logs-apache.access` dataset
await synthtrace.index(
getLogsForDataset({ to, count: 10, dataset: apacheAccessDatasetName })
);
await PageObjects.datasetQuality.navigateTo();
it('should show the integration actions menu with correct actions', async () => {
await PageObjects.datasetQuality.openDatasetFlyout(apacheAccessDatasetHumanName);
await PageObjects.datasetQuality.openIntegrationActionsMenu();
@ -314,25 +175,10 @@ export default function ({ getService, getPageObjects }: DatasetQualityFtrProvid
);
expect(actions.length).to.eql(3);
await PageObjects.datasetQuality.closeFlyout();
});
it('Integration dashboard action hidden for integrations without dashboards', async () => {
const bitbucketDatasetName = 'atlassian_bitbucket.audit';
const bitbucketDatasetHumanName = 'Bitbucket Audit Logs';
await PageObjects.observabilityLogsExplorer.navigateTo();
// Add initial integrations
await PageObjects.observabilityLogsExplorer.installPackage({
name: 'atlassian_bitbucket',
version: '1.14.0',
});
// Index 10 logs for `atlassian_bitbucket.audit` dataset
await synthtrace.index(getLogsForDataset({ to, count: 10, dataset: bitbucketDatasetName }));
await PageObjects.datasetQuality.navigateTo();
it('should hide integration dashboard for integrations without dashboards', async () => {
await PageObjects.datasetQuality.openDatasetFlyout(bitbucketDatasetHumanName);
await PageObjects.datasetQuality.openIntegrationActionsMenu();
@ -341,25 +187,10 @@ export default function ({ getService, getPageObjects }: DatasetQualityFtrProvid
integrationActions.viewDashboards
)
);
await PageObjects.datasetQuality.closeFlyout();
});
it('Integration overview action should navigate to the integration overview page', async () => {
const bitbucketDatasetName = 'atlassian_bitbucket.audit';
const bitbucketDatasetHumanName = 'Bitbucket Audit Logs';
await PageObjects.observabilityLogsExplorer.navigateTo();
// Add initial integrations
await PageObjects.observabilityLogsExplorer.installPackage({
name: 'atlassian_bitbucket',
version: '1.14.0',
});
// Index 10 logs for `atlassian_bitbucket.audit` dataset
await synthtrace.index(getLogsForDataset({ to, count: 10, dataset: bitbucketDatasetName }));
await PageObjects.datasetQuality.navigateTo();
it('Should navigate to integration overview page on clicking integration overview action', async () => {
await PageObjects.datasetQuality.openDatasetFlyout(bitbucketDatasetHumanName);
await PageObjects.datasetQuality.openIntegrationActionsMenu();
@ -375,58 +206,31 @@ export default function ({ getService, getPageObjects }: DatasetQualityFtrProvid
expect(parsedUrl.pathname).to.contain('/app/integrations/detail/atlassian_bitbucket');
});
});
it('Integration template action should navigate to the index template page', async () => {
const apacheAccessDatasetName = 'apache.access';
const apacheAccessDatasetHumanName = 'Apache access logs';
await PageObjects.observabilityLogsExplorer.navigateTo();
// Add initial integrations
await PageObjects.observabilityLogsExplorer.setupInitialIntegrations();
// Index 10 logs for `logs-apache.access` dataset
await synthtrace.index(
getLogsForDataset({ to, count: 10, dataset: apacheAccessDatasetName })
);
await PageObjects.datasetQuality.navigateTo();
});
it('should navigate to index template page in clicking Integration template', async () => {
await PageObjects.datasetQuality.openDatasetFlyout(apacheAccessDatasetHumanName);
await PageObjects.datasetQuality.openIntegrationActionsMenu();
const action = await PageObjects.datasetQuality.getIntegrationActionButtonByAction(
integrationActions.template
);
await action.click();
await retry.tryForTime(5000, async () => {
const action = await PageObjects.datasetQuality.getIntegrationActionButtonByAction(
integrationActions.template
);
await action.click();
const currentUrl = await browser.getCurrentUrl();
const parsedUrl = new URL(currentUrl);
expect(parsedUrl.pathname).to.contain(
`/app/management/data/index_management/templates/logs-${apacheAccessDatasetName}`
);
});
await PageObjects.datasetQuality.navigateTo();
});
it('Integration dashboard action should navigate to the selected dashboard', async () => {
const apacheAccessDatasetName = 'apache.access';
const apacheAccessDatasetHumanName = 'Apache access logs';
await PageObjects.observabilityLogsExplorer.navigateTo();
// Add initial integrations
await PageObjects.observabilityLogsExplorer.setupInitialIntegrations();
// Index 10 logs for `logs-apache.access` dataset
await synthtrace.index(
getLogsForDataset({ to, count: 10, dataset: apacheAccessDatasetName })
);
await PageObjects.datasetQuality.navigateTo();
it('should navigate to the selected dashboard on clicking integration dashboard action ', async () => {
await PageObjects.datasetQuality.openDatasetFlyout(apacheAccessDatasetHumanName);
await PageObjects.datasetQuality.openIntegrationActionsMenu();
@ -445,148 +249,207 @@ export default function ({ getService, getPageObjects }: DatasetQualityFtrProvid
const breadcrumbText = await testSubjects.getVisibleText('breadcrumb last');
expect(breadcrumbText).to.eql(dashboardText);
await PageObjects.datasetQuality.navigateTo();
});
});
// The above describe block has some failing/flaky tests which will
// be fixed as part of the tech debt mentioned here
// https://github.com/elastic/kibana/issues/184145
// Until then, the below describe block is added to cover the tests for the
// newly added degraded Fields Table. This must be merged under the above
// describe block once the tech debt is fixed.
describe('Dataset quality flyout with degraded fields', () => {
const goodDatasetName = 'good';
const degradedDatasetName = 'degraded';
const today = new Date().toISOString();
describe('summary panel', () => {
it('should show summary KPIs', async () => {
await PageObjects.datasetQuality.openDatasetFlyout(apacheAccessDatasetHumanName);
describe('Degraded Fields Table with common data', () => {
before(async () => {
await synthtrace.index([
getLogsForDataset({
to: today,
count: 2,
dataset: goodDatasetName,
isMalformed: false,
}),
createDegradedFieldsRecord({
to: today,
count: 2,
dataset: degradedDatasetName,
}),
]);
await PageObjects.datasetQuality.navigateTo();
});
const { docsCountTotal, degradedDocs, services, hosts, size } =
await PageObjects.datasetQuality.parseFlyoutKpis();
expect(parseInt(docsCountTotal, 10)).to.be(226);
expect(parseInt(degradedDocs, 10)).to.be(1);
expect(parseInt(services, 10)).to.be(3);
expect(parseInt(hosts, 10)).to.be(52);
expect(parseInt(size, 10)).to.be.greaterThan(0);
after(async () => {
await synthtrace.clean();
});
await PageObjects.datasetQuality.closeFlyout();
});
});
it('shows the degraded fields table with no data when no degraded fields are present', async () => {
await PageObjects.datasetQuality.openDatasetFlyout(goodDatasetName);
describe('navigation', () => {
it('should go to log explorer page when the open in log explorer button is clicked', async () => {
const testDatasetName = datasetNames[2];
await PageObjects.datasetQuality.openDatasetFlyout(testDatasetName);
await testSubjects.existOrFail(
PageObjects.datasetQuality.testSubjectSelectors.datasetQualityFlyoutDegradedTableNoData
);
const logExplorerButton = await PageObjects.datasetQuality.getFlyoutLogsExplorerButton();
await PageObjects.datasetQuality.closeFlyout();
});
await logExplorerButton.click();
it('should load the degraded fields table with data', async () => {
await PageObjects.datasetQuality.openDatasetFlyout(degradedDatasetName);
// Confirm dataset selector text in observability logs explorer
const datasetSelectorText =
await PageObjects.observabilityLogsExplorer.getDataSourceSelectorButtonText();
expect(datasetSelectorText).to.eql(testDatasetName);
await testSubjects.existOrFail(
PageObjects.datasetQuality.testSubjectSelectors.datasetQualityFlyoutDegradedFieldTable
);
const rows =
await PageObjects.datasetQuality.getDatasetQualityFlyoutDegradedFieldTableRows();
expect(rows.length).to.eql(2);
await PageObjects.datasetQuality.closeFlyout();
});
it('should display Spark Plot for every row of degraded fields', async () => {
await PageObjects.datasetQuality.openDatasetFlyout(degradedDatasetName);
const rows =
await PageObjects.datasetQuality.getDatasetQualityFlyoutDegradedFieldTableRows();
const sparkPlots = await testSubjects.findAll(
PageObjects.datasetQuality.testSubjectSelectors.datasetQualitySparkPlot
);
expect(rows.length).to.be(sparkPlots.length);
await PageObjects.datasetQuality.closeFlyout();
});
it('should sort the table when the count table header is clicked', async () => {
await PageObjects.datasetQuality.openDatasetFlyout(degradedDatasetName);
const table = await PageObjects.datasetQuality.parseDegradedFieldTable();
const countColumn = table['Docs count'];
const cellTexts = await countColumn.getCellTexts();
await countColumn.sort('ascending');
const sortedCellTexts = await countColumn.getCellTexts();
expect(cellTexts.reverse()).to.eql(sortedCellTexts);
await PageObjects.datasetQuality.closeFlyout();
});
// Should bring back the test to the dataset quality page
await PageObjects.datasetQuality.navigateTo();
});
describe('Degraded Fields Table with data ingestion', () => {
before(async () => {
await synthtrace.index([
getLogsForDataset({
to: today,
count: 2,
dataset: goodDatasetName,
isMalformed: false,
}),
createDegradedFieldsRecord({
to: today,
count: 2,
dataset: degradedDatasetName,
}),
]);
await PageObjects.datasetQuality.navigateTo();
it('should go log explorer for degraded docs when the show all button is clicked', async () => {
await PageObjects.datasetQuality.openDatasetFlyout(apacheAccessDatasetHumanName);
const degradedDocsShowAllSelector = `${PageObjects.datasetQuality.testSubjectSelectors.datasetQualityFlyoutKpiLink}-${PageObjects.datasetQuality.texts.degradedDocs}`;
await testSubjects.click(degradedDocsShowAllSelector);
await browser.switchTab(1);
// Confirm dataset selector text in observability logs explorer
const datasetSelectorText =
await PageObjects.observabilityLogsExplorer.getDataSourceSelectorButtonText();
expect(datasetSelectorText).to.contain(apacheAccessDatasetName);
await browser.closeCurrentWindow();
await browser.switchTab(0);
await PageObjects.datasetQuality.closeFlyout();
});
// Blocked by https://github.com/elastic/kibana/issues/181705
// Its a test written ahead of its time.
it.skip('goes to infra hosts for hosts when show all is clicked', async () => {
await PageObjects.datasetQuality.openDatasetFlyout(apacheAccessDatasetHumanName);
const hostsShowAllSelector = `${PageObjects.datasetQuality.testSubjectSelectors.datasetQualityFlyoutKpiLink}-${PageObjects.datasetQuality.texts.hosts}`;
await testSubjects.click(hostsShowAllSelector);
await browser.switchTab(1);
// Confirm url contains metrics/hosts
await retry.tryForTime(5000, async () => {
const currentUrl = await browser.getCurrentUrl();
const parsedUrl = new URL(currentUrl);
expect(parsedUrl.pathname).to.contain('/app/metrics/hosts');
});
after(async () => {
await synthtrace.clean();
await browser.closeCurrentWindow();
await browser.switchTab(0);
await PageObjects.datasetQuality.closeFlyout();
});
});
describe('degraded fields table', () => {
it(' should show empty degraded fields table when no degraded fields are present', async () => {
await PageObjects.datasetQuality.openDatasetFlyout(datasetNames[0]);
await testSubjects.existOrFail(
PageObjects.datasetQuality.testSubjectSelectors.datasetQualityFlyoutDegradedTableNoData
);
await PageObjects.datasetQuality.closeFlyout();
});
it('should show the degraded fields table with data when present', async () => {
await PageObjects.datasetQuality.openDatasetFlyout(degradedDatasetName);
await testSubjects.existOrFail(
PageObjects.datasetQuality.testSubjectSelectors.datasetQualityFlyoutDegradedFieldTable
);
const rows =
await PageObjects.datasetQuality.getDatasetQualityFlyoutDegradedFieldTableRows();
expect(rows.length).to.eql(2);
await PageObjects.datasetQuality.closeFlyout();
});
it('should display Spark Plot for every row of degraded fields', async () => {
await PageObjects.datasetQuality.openDatasetFlyout(degradedDatasetName);
const rows =
await PageObjects.datasetQuality.getDatasetQualityFlyoutDegradedFieldTableRows();
const sparkPlots = await testSubjects.findAll(
PageObjects.datasetQuality.testSubjectSelectors.datasetQualitySparkPlot
);
expect(rows.length).to.be(sparkPlots.length);
await PageObjects.datasetQuality.closeFlyout();
});
it('should sort the table when the count table header is clicked', async () => {
await PageObjects.datasetQuality.openDatasetFlyout(degradedDatasetName);
const table = await PageObjects.datasetQuality.parseDegradedFieldTable();
const countColumn = table['Docs count'];
const cellTexts = await countColumn.getCellTexts();
await countColumn.sort('ascending');
const sortedCellTexts = await countColumn.getCellTexts();
expect(cellTexts.reverse()).to.eql(sortedCellTexts);
await PageObjects.datasetQuality.closeFlyout();
});
it('should update the URL when the table is sorted', async () => {
await PageObjects.datasetQuality.openDatasetFlyout(degradedDatasetName);
const table = await PageObjects.datasetQuality.parseDegradedFieldTable();
const countColumn = table['Docs count'];
await retry.tryForTime(5000, async () => {
const currentUrl = await browser.getCurrentUrl();
const parsedUrl = new URL(currentUrl);
const pageState = parsedUrl.searchParams.get('pageState');
expect(decodeURIComponent(pageState as string)).to.contain(
'sort:(direction:desc,field:count)'
);
});
it('should update the table when new data is ingested and the flyout is refreshed using the time selector', async () => {
await PageObjects.datasetQuality.openDatasetFlyout(degradedDatasetName);
countColumn.sort('ascending');
const table = await PageObjects.datasetQuality.parseDegradedFieldTable();
await retry.tryForTime(5000, async () => {
const currentUrl = await browser.getCurrentUrl();
const parsedUrl = new URL(currentUrl);
const pageState = parsedUrl.searchParams.get('pageState');
const countColumn = table['Docs count'];
const cellTexts = await countColumn.getCellTexts();
await synthtrace.index([
createDegradedFieldsRecord({
to: today,
count: 2,
dataset: degradedDatasetName,
}),
]);
await PageObjects.datasetQuality.refreshFlyout();
const updatedCellTexts = await countColumn.getCellTexts();
const singleValuePreviously = parseInt(cellTexts[0], 10);
const singleValueNow = parseInt(updatedCellTexts[0], 10);
expect(singleValueNow).to.be(singleValuePreviously * 2);
await PageObjects.datasetQuality.closeFlyout();
expect(decodeURIComponent(pageState as string)).to.contain(
'sort:(direction:asc,field:count)'
);
});
await PageObjects.datasetQuality.closeFlyout();
});
// This is the only test which ingest data during the test.
// This block tests the refresh behavior of the degraded fields table.
// Even though this test ingest data, it can also be freely moved inside
// this describe block, and it won't affect any of the existing tests
it('should update the table when new data is ingested and the flyout is refreshed using the time selector', async () => {
await PageObjects.datasetQuality.openDatasetFlyout(degradedDatasetName);
const table = await PageObjects.datasetQuality.parseDegradedFieldTable();
const countColumn = table['Docs count'];
const cellTexts = await countColumn.getCellTexts();
await synthtrace.index([
createDegradedFieldsRecord({
to: new Date().toISOString(),
count: 2,
dataset: degradedDatasetName,
}),
]);
await PageObjects.datasetQuality.refreshFlyout();
const updatedTable = await PageObjects.datasetQuality.parseDegradedFieldTable();
const updatedCountColumn = updatedTable['Docs count'];
const updatedCellTexts = await updatedCountColumn.getCellTexts();
const singleValuePreviously = parseInt(cellTexts[0], 10);
const singleValueNow = parseInt(updatedCellTexts[0], 10);
expect(singleValueNow).to.be.greaterThan(singleValuePreviously);
await PageObjects.datasetQuality.closeFlyout();
});
});
});

View file

@ -17,21 +17,48 @@ export default function ({ getService, getPageObjects }: DatasetQualityFtrProvid
'datasetQuality',
]);
const synthtrace = getService('logSynthtraceEsClient');
const browser = getService('browser');
const retry = getService('retry');
const to = '2024-01-01T12:00:00.000Z';
const ingestDataForSummary = async () => {
// Ingest documents for 3 type of datasets
return synthtrace.index([
// Ingest good data to all 3 datasets
getInitialTestLogs({ to, count: 4 }),
// Ingesting poor data to one dataset
getLogsForDataset({
to: Date.now(),
count: 1,
dataset: datasetNames[1],
isMalformed: true,
}),
// Ingesting degraded docs into another dataset by ingesting malformed 1st and then good data
getLogsForDataset({
to: Date.now(),
count: 1,
dataset: datasetNames[2],
isMalformed: true,
}),
getLogsForDataset({
to: Date.now(),
count: 10,
dataset: datasetNames[2],
isMalformed: false,
}),
]);
};
describe('Dataset quality summary', () => {
before(async () => {
await synthtrace.index(getInitialTestLogs({ to, count: 4 }));
await PageObjects.datasetQuality.navigateTo();
});
after(async () => {
afterEach(async () => {
await synthtrace.clean();
});
it('shows poor, degraded and good count', async () => {
it('shows poor, degraded and good count as 0 and all dataset as healthy', async () => {
await synthtrace.index(getInitialTestLogs({ to, count: 4 }));
await PageObjects.datasetQuality.refreshTable();
const summary = await PageObjects.datasetQuality.parseSummaryPanel();
expect(summary).to.eql({
datasetHealthPoor: '0',
@ -42,92 +69,21 @@ export default function ({ getService, getPageObjects }: DatasetQualityFtrProvid
});
});
it('updates the poor count when degraded docs are ingested', async () => {
// Index malformed document with current timestamp
await synthtrace.index(
getLogsForDataset({
to: Date.now(),
count: 1,
dataset: datasetNames[2],
isMalformed: true,
})
);
it('shows updated count for poor, degraded and good datasets, estimated size and updates active datasets', async () => {
await ingestDataForSummary();
await PageObjects.datasetQuality.refreshTable();
await browser.refresh();
await PageObjects.datasetQuality.waitUntilSummaryPanelLoaded();
await retry.try(async () => {
const summary = await PageObjects.datasetQuality.parseSummaryPanel();
const { estimatedData, ...restOfSummary } = summary;
expect(restOfSummary).to.eql({
datasetHealthPoor: '1',
datasetHealthDegraded: '0',
datasetHealthGood: '2',
activeDatasets: '1 of 3',
});
const summary = await PageObjects.datasetQuality.parseSummaryPanel();
const { estimatedData, ...restOfSummary } = summary;
expect(restOfSummary).to.eql({
datasetHealthPoor: '1',
datasetHealthDegraded: '1',
datasetHealthGood: '1',
activeDatasets: '2 of 3',
});
});
it('updates the degraded count when degraded docs are ingested', async () => {
// Index malformed document with current timestamp
await synthtrace.index(
getLogsForDataset({
to: Date.now(),
count: 1,
dataset: datasetNames[1],
isMalformed: true,
})
);
// Index healthy documents
await synthtrace.index(
getLogsForDataset({
to: Date.now(),
count: 10,
dataset: datasetNames[1],
isMalformed: false,
})
);
await browser.refresh();
await PageObjects.datasetQuality.waitUntilSummaryPanelLoaded();
await retry.try(async () => {
const { estimatedData, ...restOfSummary } =
await PageObjects.datasetQuality.parseSummaryPanel();
expect(restOfSummary).to.eql({
datasetHealthPoor: '1',
datasetHealthDegraded: '1',
datasetHealthGood: '1',
activeDatasets: '2 of 3',
});
});
});
it('updates active datasets and estimated data KPIs', async () => {
const { estimatedData: existingEstimatedData } =
await PageObjects.datasetQuality.parseSummaryPanel();
// Index document at current time to mark dataset as active
await synthtrace.index(
getLogsForDataset({
to: Date.now(),
count: 4,
dataset: datasetNames[0],
isMalformed: false,
})
);
await browser.refresh(); // Summary panel doesn't update reactively
await PageObjects.datasetQuality.waitUntilSummaryPanelLoaded();
await retry.try(async () => {
const { activeDatasets: updatedActiveDatasets, estimatedData: updatedEstimatedData } =
await PageObjects.datasetQuality.parseSummaryPanel();
expect(updatedActiveDatasets).to.eql('3 of 3');
expect(updatedEstimatedData).to.not.eql(existingEstimatedData);
});
const sizeInNumber = parseFloat(estimatedData.split(' ')[0]);
expect(sizeInNumber).to.be.greaterThan(0);
});
});
}

View file

@ -7,7 +7,13 @@
import expect from '@kbn/expect';
import { DatasetQualityFtrProviderContext } from './config';
import { datasetNames, defaultNamespace, getInitialTestLogs, getLogsForDataset } from './data';
import {
datasetNames,
defaultNamespace,
getInitialTestLogs,
getLogsForDataset,
productionNamespace,
} from './data';
export default function ({ getService, getPageObjects }: DatasetQualityFtrProviderContext) {
const PageObjects = getPageObjects([
@ -17,40 +23,78 @@ export default function ({ getService, getPageObjects }: DatasetQualityFtrProvid
'datasetQuality',
]);
const synthtrace = getService('logSynthtraceEsClient');
const testSubjects = getService('testSubjects');
const retry = getService('retry');
const to = '2024-01-01T12:00:00.000Z';
const apacheAccessDatasetName = 'apache.access';
const apacheAccessDatasetHumanName = 'Apache access logs';
const pkg = {
name: 'apache',
version: '1.14.0',
};
describe('Dataset quality table', () => {
before(async () => {
await synthtrace.index(getInitialTestLogs({ to, count: 4 }));
// Install Integration and ingest logs for it
await PageObjects.observabilityLogsExplorer.installPackage(pkg);
// Ingest basic logs
await synthtrace.index([
// Ingest basic logs
getInitialTestLogs({ to, count: 4 }),
// Ingest Degraded Logs
getLogsForDataset({
to: new Date().toISOString(),
count: 1,
dataset: datasetNames[2],
isMalformed: true,
}),
// Index 10 logs for `logs-apache.access` dataset
getLogsForDataset({
to,
count: 10,
dataset: apacheAccessDatasetName,
namespace: productionNamespace,
}),
]);
await PageObjects.datasetQuality.navigateTo();
});
after(async () => {
await synthtrace.clean();
await PageObjects.observabilityLogsExplorer.removeInstalledPackages();
await PageObjects.observabilityLogsExplorer.uninstallPackage(pkg);
});
it('shows the right number of rows in correct order', async () => {
it('shows sort by dataset name and show namespace', async () => {
const cols = await PageObjects.datasetQuality.parseDatasetTable();
const datasetNameCol = cols['Data Set Name'];
await datasetNameCol.sort('descending');
const datasetNameColCellTexts = await datasetNameCol.getCellTexts();
expect(datasetNameColCellTexts).to.eql([...datasetNames].reverse());
expect(datasetNameColCellTexts).to.eql(
[apacheAccessDatasetHumanName, ...datasetNames].reverse()
);
const namespaceCol = cols.Namespace;
const namespaceColCellTexts = await namespaceCol.getCellTexts();
expect(namespaceColCellTexts).to.eql([defaultNamespace, defaultNamespace, defaultNamespace]);
expect(namespaceColCellTexts).to.eql([
defaultNamespace,
defaultNamespace,
defaultNamespace,
productionNamespace,
]);
const degradedDocsCol = cols['Degraded Docs (%)'];
const degradedDocsColCellTexts = await degradedDocsCol.getCellTexts();
expect(degradedDocsColCellTexts).to.eql(['0%', '0%', '0%']);
// Cleaning the sort
await datasetNameCol.sort('ascending');
});
it('shows the last activity', async () => {
const cols = await PageObjects.datasetQuality.parseDatasetTable();
const lastActivityCol = cols['Last Activity'];
const lastActivityColCellTexts = await lastActivityCol.getCellTexts();
expect(lastActivityColCellTexts).to.eql([
const activityCells = await lastActivityCol.getCellTexts();
const lastActivityCell = activityCells[activityCells.length - 1];
const restActivityCells = activityCells.slice(0, -1);
// The first cell of lastActivity should have data
expect(lastActivityCell).to.not.eql(PageObjects.datasetQuality.texts.noActivityText);
// The rest of the rows must show no activity
expect(restActivityCells).to.eql([
PageObjects.datasetQuality.texts.noActivityText,
PageObjects.datasetQuality.texts.noActivityText,
PageObjects.datasetQuality.texts.noActivityText,
@ -59,111 +103,30 @@ export default function ({ getService, getPageObjects }: DatasetQualityFtrProvid
it('shows degraded docs percentage', async () => {
const cols = await PageObjects.datasetQuality.parseDatasetTable();
const datasetNameCol = cols['Data Set Name'];
await datasetNameCol.sort('ascending');
const degradedDocsCol = cols['Degraded Docs (%)'];
const degradedDocsColCellTexts = await degradedDocsCol.getCellTexts();
expect(degradedDocsColCellTexts).to.eql(['0%', '0%', '0%']);
// Index malformed document with current timestamp
await synthtrace.index(
getLogsForDataset({
to: Date.now(),
count: 1,
dataset: datasetNames[2],
isMalformed: true,
})
);
// Set time range to Last 5 minute
const filtersContainer = await testSubjects.find(
PageObjects.datasetQuality.testSubjectSelectors.datasetQualityFiltersContainer
);
await PageObjects.datasetQuality.setDatePickerLastXUnits(filtersContainer, 5, 'm');
const updatedDegradedDocsColCellTexts = await degradedDocsCol.getCellTexts();
expect(updatedDegradedDocsColCellTexts[2]).to.not.eql('0%');
expect(degradedDocsColCellTexts).to.eql(['0%', '0%', '0%', '100%']);
});
it('shows the updated size of the index', async () => {
const testDatasetIndex = 2;
it('shows the value in the size column', async () => {
const cols = await PageObjects.datasetQuality.parseDatasetTable();
const datasetNameCol = cols['Data Set Name'];
await datasetNameCol.sort('ascending');
const datasetNameColCellTexts = await datasetNameCol.getCellTexts();
const datasetToUpdateRowIndex = datasetNameColCellTexts.findIndex(
(dName: string) => dName === datasetNames[testDatasetIndex]
);
const sizeColCellTexts = await cols.Size.getCellTexts();
const beforeSize = sizeColCellTexts[datasetToUpdateRowIndex];
const sizeGreaterThanZero = sizeColCellTexts[3];
const sizeEqualToZero = sizeColCellTexts[2];
// Index documents with current timestamp
await synthtrace.index(
getLogsForDataset({
to: Date.now(),
count: 4,
dataset: datasetNames[testDatasetIndex],
isMalformed: false,
})
);
const colsAfterUpdate = await PageObjects.datasetQuality.parseDatasetTable();
// Assert that size has changed
await retry.tryForTime(15000, async () => {
// Refresh the table
await PageObjects.datasetQuality.refreshTable();
const updatedSizeColCellTexts = await colsAfterUpdate.Size.getCellTexts();
expect(updatedSizeColCellTexts[datasetToUpdateRowIndex]).to.not.eql(beforeSize);
});
});
it('sorts by dataset name', async () => {
// const header = await PageObjects.datasetQuality.getDatasetTableHeader('Data Set Name');
const cols = await PageObjects.datasetQuality.parseDatasetTable();
const datasetNameCol = cols['Data Set Name'];
// Sort ascending
await datasetNameCol.sort('ascending');
const cellTexts = await datasetNameCol.getCellTexts();
const datasetNamesAsc = [...datasetNames].sort();
expect(cellTexts).to.eql(datasetNamesAsc);
expect(sizeGreaterThanZero).to.not.eql('0.0 KB');
expect(sizeEqualToZero).to.eql('0.0 B');
});
it('shows dataset from integration', async () => {
const apacheAccessDatasetName = 'apache.access';
const apacheAccessDatasetHumanName = 'Apache access logs';
await PageObjects.observabilityLogsExplorer.navigateTo();
// Add initial integrations
await PageObjects.observabilityLogsExplorer.setupInitialIntegrations();
// Index 10 logs for `logs-apache.access` dataset
await synthtrace.index(
getLogsForDataset({ to, count: 10, dataset: apacheAccessDatasetName })
);
await PageObjects.datasetQuality.navigateTo();
const cols = await PageObjects.datasetQuality.parseDatasetTable();
const datasetNameCol = cols['Data Set Name'];
// Sort ascending
await datasetNameCol.sort('ascending');
const datasetNameColCellTexts = await datasetNameCol.getCellTexts();
const datasetNamesAsc = [...datasetNames, apacheAccessDatasetHumanName].sort();
// Assert there are 4 rows
expect(datasetNameColCellTexts.length).to.eql(4);
expect(datasetNameColCellTexts).to.eql(datasetNamesAsc);
expect(datasetNameColCellTexts[0]).to.eql(apacheAccessDatasetHumanName);
});
it('goes to log explorer page when opened', async () => {
@ -179,50 +142,12 @@ export default function ({ getService, getPageObjects }: DatasetQualityFtrProvid
const datasetSelectorText =
await PageObjects.observabilityLogsExplorer.getDataSourceSelectorButtonText();
expect(datasetSelectorText).to.eql(datasetName);
});
it('shows the last activity when in time range', async () => {
// Return to Dataset Quality Page
await PageObjects.datasetQuality.navigateTo();
const cols = await PageObjects.datasetQuality.parseDatasetTable();
const lastActivityCol = cols['Last Activity'];
const datasetNameCol = cols['Data Set Name'];
// Set time range to Last 1 minute
const filtersContainer = await testSubjects.find(
PageObjects.datasetQuality.testSubjectSelectors.datasetQualityFiltersContainer
);
await PageObjects.datasetQuality.setDatePickerLastXUnits(filtersContainer, 1, 's');
const lastActivityColCellTexts = await lastActivityCol.getCellTexts();
expect(lastActivityColCellTexts).to.eql([
PageObjects.datasetQuality.texts.noActivityText,
PageObjects.datasetQuality.texts.noActivityText,
PageObjects.datasetQuality.texts.noActivityText,
PageObjects.datasetQuality.texts.noActivityText,
]);
const datasetToUpdate = datasetNames[0];
await synthtrace.index(
getLogsForDataset({ to: new Date().toISOString(), count: 1, dataset: datasetToUpdate })
);
const datasetNameColCellTexts = await datasetNameCol.getCellTexts();
const datasetToUpdateRowIndex = datasetNameColCellTexts.findIndex(
(dName: string) => dName === datasetToUpdate
);
await PageObjects.datasetQuality.setDatePickerLastXUnits(filtersContainer, 1, 'h');
await retry.tryForTime(5000, async () => {
const updatedLastActivityColCellTexts = await lastActivityCol.getCellTexts();
expect(updatedLastActivityColCellTexts[datasetToUpdateRowIndex]).to.not.eql(
PageObjects.datasetQuality.texts.noActivityText
);
});
});
it('hides inactive datasets', async () => {
await PageObjects.datasetQuality.waitUntilTableLoaded();
// Get number of rows with Last Activity not equal to "No activity in the selected timeframe"
const cols = await PageObjects.datasetQuality.parseDatasetTable();
const lastActivityCol = cols['Last Activity'];

View file

@ -7,7 +7,7 @@
import expect from '@kbn/expect';
import { DatasetQualityFtrProviderContext } from './config';
import { datasetNames, defaultNamespace, getInitialTestLogs, getLogsForDataset } from './data';
import { datasetNames, getInitialTestLogs, getLogsForDataset, productionNamespace } from './data';
export default function ({ getService, getPageObjects }: DatasetQualityFtrProviderContext) {
const PageObjects = getPageObjects([
@ -19,55 +19,73 @@ export default function ({ getService, getPageObjects }: DatasetQualityFtrProvid
const synthtrace = getService('logSynthtraceEsClient');
const testSubjects = getService('testSubjects');
const to = '2024-01-01T12:00:00.000Z';
const apacheAccessDatasetName = 'apache.access';
const apacheAccessDatasetHumanName = 'Apache access logs';
const apacheIntegrationName = 'Apache HTTP Server';
const pkg = {
name: 'apache',
version: '1.14.0',
};
const allDatasetNames = [apacheAccessDatasetHumanName, ...datasetNames];
describe('Dataset quality table filters', () => {
before(async () => {
await synthtrace.index(getInitialTestLogs({ to, count: 4 }));
// Install Integration and ingest logs for it
await PageObjects.observabilityLogsExplorer.installPackage(pkg);
// Ingest basic logs
await synthtrace.index([
// Ingest basic logs
getInitialTestLogs({ to, count: 4 }),
// Ingest Degraded Logs
getLogsForDataset({
to: new Date().toISOString(),
count: 1,
dataset: datasetNames[2],
isMalformed: true,
}),
// Index 10 logs for `logs-apache.access` dataset
getLogsForDataset({
to,
count: 10,
dataset: apacheAccessDatasetName,
namespace: productionNamespace,
}),
]);
await PageObjects.datasetQuality.navigateTo();
});
after(async () => {
await PageObjects.observabilityLogsExplorer.uninstallPackage(pkg);
await synthtrace.clean();
await PageObjects.observabilityLogsExplorer.removeInstalledPackages();
});
it('hides inactive datasets when toggled', async () => {
const initialRows = await PageObjects.datasetQuality.getDatasetTableRows();
expect(initialRows.length).to.eql(3);
await PageObjects.datasetQuality.toggleShowInactiveDatasets();
const afterToggleRows = await PageObjects.datasetQuality.getDatasetTableRows();
expect(afterToggleRows.length).to.eql(1);
await PageObjects.datasetQuality.toggleShowInactiveDatasets();
const afterReToggleRows = await PageObjects.datasetQuality.getDatasetTableRows();
expect(afterReToggleRows.length).to.eql(3);
});
it('shows full dataset names when toggled', async () => {
const cols = await PageObjects.datasetQuality.parseDatasetTable();
const datasetNameCol = cols['Data Set Name'];
const datasetNameColCellTexts = await datasetNameCol.getCellTexts();
expect(datasetNameColCellTexts).to.eql(datasetNames);
expect(datasetNameColCellTexts).to.eql(allDatasetNames);
await PageObjects.datasetQuality.toggleShowFullDatasetNames();
const datasetNameColCellTextsAfterToggle = await datasetNameCol.getCellTexts();
const duplicateNames = datasetNames.map((name) => `${name}\n${name}`);
const duplicateNames = [
`${apacheAccessDatasetHumanName}\n${apacheAccessDatasetName}`,
...datasetNames.map((name) => `${name}\n${name}`),
];
expect(datasetNameColCellTextsAfterToggle).to.eql(duplicateNames);
// resetting the toggle
await PageObjects.datasetQuality.toggleShowFullDatasetNames();
const datasetNameColCellTextsAfterReToggle = await datasetNameCol.getCellTexts();
expect(datasetNameColCellTextsAfterReToggle).to.eql(datasetNames);
expect(datasetNameColCellTextsAfterReToggle).to.eql(allDatasetNames);
});
it('searches the datasets', async () => {
const cols = await PageObjects.datasetQuality.parseDatasetTable();
const datasetNameCol = cols['Data Set Name'];
const datasetNameColCellTexts = await datasetNameCol.getCellTexts();
expect(datasetNameColCellTexts).to.eql(datasetNames);
expect(datasetNameColCellTexts).to.eql(allDatasetNames);
// Search for a dataset
await testSubjects.setValue(
@ -79,33 +97,16 @@ export default function ({ getService, getPageObjects }: DatasetQualityFtrProvid
const datasetNameColAfterSearch = colsAfterSearch['Data Set Name'];
const datasetNameColCellTextsAfterSearch = await datasetNameColAfterSearch.getCellTexts();
expect(datasetNameColCellTextsAfterSearch).to.eql([datasetNames[2]]);
await testSubjects.setValue(
PageObjects.datasetQuality.testSubjectSelectors.datasetQualityFilterBarFieldSearch,
''
);
// Reset the search field
await testSubjects.click('clearSearchButton');
});
it('filters for integration', async () => {
const apacheAccessDatasetName = 'apache.access';
const apacheAccessDatasetHumanName = 'Apache access logs';
const apacheIntegrationName = 'Apache HTTP Server';
await PageObjects.observabilityLogsExplorer.navigateTo();
// Add initial integrations
await PageObjects.observabilityLogsExplorer.setupInitialIntegrations();
// Index 10 logs for `logs-apache.access` dataset
await synthtrace.index(
getLogsForDataset({ to, count: 10, dataset: apacheAccessDatasetName })
);
await PageObjects.datasetQuality.navigateTo();
const cols = await PageObjects.datasetQuality.parseDatasetTable();
const datasetNameCol = cols['Data Set Name'];
const datasetNameColCellTexts = await datasetNameCol.getCellTexts();
expect(datasetNameColCellTexts).to.eql([apacheAccessDatasetHumanName, ...datasetNames]);
expect(datasetNameColCellTexts).to.eql(allDatasetNames);
// Filter for integration
await PageObjects.datasetQuality.filterForIntegrations([apacheIntegrationName]);
@ -113,65 +114,33 @@ export default function ({ getService, getPageObjects }: DatasetQualityFtrProvid
const colsAfterFilter = await PageObjects.datasetQuality.parseDatasetTable();
const datasetNameColAfterFilter = colsAfterFilter['Data Set Name'];
const datasetNameColCellTextsAfterFilter = await datasetNameColAfterFilter.getCellTexts();
expect(datasetNameColCellTextsAfterFilter).to.eql([apacheAccessDatasetHumanName]);
// Reset the filter by selecting from the dropdown again
await PageObjects.datasetQuality.filterForIntegrations([apacheIntegrationName]);
});
it('filters for namespace', async () => {
const apacheAccessDatasetName = 'apache.access';
const datasetNamespace = 'prod';
await PageObjects.observabilityLogsExplorer.navigateTo();
// Add initial integrations
await PageObjects.observabilityLogsExplorer.setupInitialIntegrations();
// Index 10 logs for `logs-apache.access` dataset
await synthtrace.index(
getLogsForDataset({
to,
count: 10,
dataset: apacheAccessDatasetName,
namespace: datasetNamespace,
})
);
await PageObjects.datasetQuality.navigateTo();
// Get default namespaces
const cols = await PageObjects.datasetQuality.parseDatasetTable();
const namespaceCol = cols.Namespace;
const namespaceColCellTexts = await namespaceCol.getCellTexts();
expect(namespaceColCellTexts).to.contain(defaultNamespace);
expect(namespaceColCellTexts).to.contain(productionNamespace);
// Filter for prod namespace
await PageObjects.datasetQuality.filterForNamespaces([datasetNamespace]);
// Filter for production namespace
await PageObjects.datasetQuality.filterForNamespaces([productionNamespace]);
const colsAfterFilter = await PageObjects.datasetQuality.parseDatasetTable();
const namespaceColAfterFilter = colsAfterFilter.Namespace;
const namespaceColCellTextsAfterFilter = await namespaceColAfterFilter.getCellTexts();
expect(namespaceColCellTextsAfterFilter).to.eql([datasetNamespace]);
expect(namespaceColCellTextsAfterFilter).to.eql([productionNamespace]);
// Reset the namespace by selecting from the dropdown again
await PageObjects.datasetQuality.filterForNamespaces([productionNamespace]);
});
it('filters for quality', async () => {
const apacheAccessDatasetName = 'apache.access';
const expectedQuality = 'Poor';
// Add initial integrations
await PageObjects.observabilityLogsExplorer.setupInitialIntegrations();
// Index 10 logs for `logs-apache.access` dataset
await synthtrace.index(
getLogsForDataset({
to: new Date().toISOString(),
count: 10,
dataset: apacheAccessDatasetName,
isMalformed: true,
})
);
await PageObjects.datasetQuality.navigateTo();
// Get default quality
const cols = await PageObjects.datasetQuality.parseDatasetTable();
const datasetQuality = cols['Data Set Quality'];
@ -186,6 +155,9 @@ export default function ({ getService, getPageObjects }: DatasetQualityFtrProvid
const datasetQualityCellTextsAfterFilter = await datasetQualityAfterFilter.getCellTexts();
expect(datasetQualityCellTextsAfterFilter).to.eql([expectedQuality]);
// Reset the namespace by selecting from the dropdown again
await PageObjects.datasetQuality.filterForQualities([expectedQuality]);
});
});
}

View file

@ -51,12 +51,26 @@ type SummaryPanelKpi = Record<
type FlyoutKpi = Record<'docsCountTotal' | 'size' | 'services' | 'hosts' | 'degradedDocs', string>;
const texts = {
noActivityText: 'No activity in the selected timeframe',
datasetHealthPoor: 'Poor',
datasetHealthDegraded: 'Degraded',
datasetHealthGood: 'Good',
activeDatasets: 'Active Data Sets',
estimatedData: 'Estimated Data',
docsCountTotal: 'Docs count (total)',
size: 'Size',
services: 'Services',
hosts: 'Hosts',
degradedDocs: 'Degraded docs',
};
export function DatasetQualityPageObject({ getPageObjects, getService }: FtrProviderContext) {
const PageObjects = getPageObjects(['common']);
const testSubjects = getService('testSubjects');
const euiSelectable = getService('selectable');
const retry = getService('retry');
const find = getService('find');
const retry = getService('retry');
const selectors = {
datasetQualityTable: '[data-test-subj="datasetQualityTable"]',
@ -80,6 +94,8 @@ export function DatasetQualityPageObject({ getPageObjects, getService }: FtrProv
datasetQualitySparkPlot: 'datasetQualitySparkPlot',
datasetQualityHeaderButton: 'datasetQualityHeaderButton',
datasetQualityFlyoutFieldValue: 'datasetQualityFlyoutFieldValue',
datasetQualityFlyoutFieldsListIntegrationDetails:
'datasetQualityFlyoutFieldsList-integration_details',
datasetQualityFlyoutIntegrationActionsButton: 'datasetQualityFlyoutIntegrationActionsButton',
datasetQualityFlyoutIntegrationAction: (action: string) =>
`datasetQualityFlyoutIntegrationAction${action}`,
@ -147,13 +163,17 @@ export function DatasetQualityPageObject({ getPageObjects, getService }: FtrProv
await find.waitForDeletedByCssSelector('.euiFlyoutBody .euiBasicTable-loading', 20 * 1000);
},
async waitUntilSummaryPanelLoaded() {
async waitUntilSummaryPanelLoaded(isStateful: boolean = true) {
await testSubjects.missingOrFail(`datasetQuality-${texts.activeDatasets}-loading`);
await testSubjects.missingOrFail(`datasetQuality-${texts.estimatedData}-loading`);
if (isStateful) {
await testSubjects.missingOrFail(`datasetQuality-${texts.estimatedData}-loading`);
}
},
async parseSummaryPanel(excludeKeys: string[] = []): Promise<SummaryPanelKpi> {
await this.waitUntilSummaryPanelLoaded();
const isStateful = !excludeKeys.includes('estimatedData');
await this.waitUntilSummaryPanelLoaded(isStateful);
const kpiTitleAndKeys = [
{ title: texts.datasetHealthPoor, key: 'datasetHealthPoor' },
@ -221,8 +241,9 @@ export function DatasetQualityPageObject({ getPageObjects, getService }: FtrProv
},
async parseDatasetTable() {
await this.waitUntilTableLoaded();
const table = await this.getDatasetsTable();
return parseDatasetTable(table, [
return this.parseTable(table, [
'0',
'Data Set Name',
'Namespace',
@ -235,8 +256,9 @@ export function DatasetQualityPageObject({ getPageObjects, getService }: FtrProv
},
async parseDegradedFieldTable() {
await this.waitUntilTableInFlyoutLoaded();
const table = await this.getDatasetQualityFlyoutDegradedFieldTable();
return parseDatasetTable(table, ['Field', 'Docs count', 'Last Occurrence']);
return this.parseTable(table, ['Field', 'Docs count', 'Last Occurrence']);
},
async filterForIntegrations(integrations: string[]) {
@ -272,29 +294,32 @@ export function DatasetQualityPageObject({ getPageObjects, getService }: FtrProv
},
async openDatasetFlyout(datasetName: string) {
await this.waitUntilTableLoaded();
const cols = await this.parseDatasetTable();
const datasetNameCol = cols['Data Set Name'];
const datasetNameColCellTexts = await datasetNameCol.getCellTexts();
const testDatasetRowIndex = datasetNameColCellTexts.findIndex(
(dName) => dName === datasetName
);
const expanderColumn = cols['0'];
let expanderButtons: WebElementWrapper[];
await retry.try(async () => {
expanderButtons = await expanderColumn.getCellChildren(
`[data-test-subj=${testSubjectSelectors.datasetQualityExpandButton}]`
);
expect(expanderButtons.length).to.be.greaterThan(0);
// Check if 'title' attribute is "Expand" or "Collapse"
const isCollapsed =
(await expanderButtons[testDatasetRowIndex].getAttribute('title')) === 'Expand';
expect(testDatasetRowIndex).to.be.greaterThan(-1);
// Open if collapsed
if (isCollapsed) {
await expanderButtons[testDatasetRowIndex].click();
}
});
const expandColumn = cols['0'];
const expandButtons = await expandColumn.getCellChildren(
`[data-test-subj=${testSubjectSelectors.datasetQualityExpandButton}]`
);
expect(expandButtons.length).to.be.greaterThan(0);
const datasetExpandButton = expandButtons[testDatasetRowIndex];
// Check if 'title' attribute is "Expand" or "Collapse"
const isCollapsed = (await datasetExpandButton.getAttribute('title')) === 'Expand';
// Open if collapsed
if (isCollapsed) {
await datasetExpandButton.click();
}
},
async closeFlyout() {
@ -430,6 +455,85 @@ export function DatasetQualityPageObject({ getPageObjects, getService }: FtrProv
fieldText
);
},
async parseTable(tableWrapper: WebElementWrapper, columnNamesOrIndexes: string[]) {
const headerElementWrappers = await tableWrapper.findAllByCssSelector('thead th, thead td');
const result: Record<
string,
{
columnNameOrIndex: string;
sortDirection?: 'ascending' | 'descending';
headerElement: WebElementWrapper;
cellElements: WebElementWrapper[];
cellContentElements: WebElementWrapper[];
getSortDirection: () => Promise<'ascending' | 'descending' | undefined>;
sort: (sortDirection: 'ascending' | 'descending') => Promise<void>;
getCellTexts: (selector?: string) => Promise<string[]>;
getCellChildren: (selector: string) => Promise<WebElementWrapper[]>;
}
> = {};
for (let i = 0; i < headerElementWrappers.length; i++) {
const tdSelector = `table > tbody > tr td:nth-child(${i + 1})`;
const cellContentSelector = `${tdSelector} .euiTableCellContent`;
const thWrapper = headerElementWrappers[i];
const columnName = await thWrapper.getVisibleText();
const columnIndex = `${i}`;
const columnNameOrIndex = columnNamesOrIndexes.includes(columnName)
? columnName
: columnNamesOrIndexes.includes(columnIndex)
? columnIndex
: undefined;
if (columnNameOrIndex) {
const headerElement = thWrapper;
const tdWrappers = await tableWrapper.findAllByCssSelector(tdSelector);
const cellContentWrappers = await tableWrapper.findAllByCssSelector(cellContentSelector);
const getSortDirection = () =>
headerElement.getAttribute('aria-sort') as Promise<
'ascending' | 'descending' | undefined
>;
result[columnNameOrIndex] = {
columnNameOrIndex,
headerElement,
cellElements: tdWrappers,
cellContentElements: cellContentWrappers,
getSortDirection,
sort: async (sortDirection: 'ascending' | 'descending') => {
await retry.tryForTime(5000, async () => {
while ((await getSortDirection()) !== sortDirection) {
await headerElement.click();
}
});
},
getCellTexts: async (textContainerSelector?: string) => {
const cellContentContainerWrappers = textContainerSelector
? await tableWrapper.findAllByCssSelector(`${tdSelector} ${textContainerSelector}`)
: cellContentWrappers;
const cellContentContainerWrapperTexts: string[] = [];
for (let j = 0; j < cellContentContainerWrappers.length; j++) {
const cellContentContainerWrapper = cellContentContainerWrappers[j];
const cellContentContainerWrapperText =
await cellContentContainerWrapper.getVisibleText();
cellContentContainerWrapperTexts.push(cellContentContainerWrapperText);
}
return cellContentContainerWrapperTexts;
},
getCellChildren: (childSelector: string) => {
return tableWrapper.findAllByCssSelector(`${cellContentSelector} ${childSelector}`);
},
};
}
}
return result;
},
};
}
@ -440,86 +544,6 @@ async function getDatasetTableHeaderTexts(tableWrapper: WebElementWrapper) {
);
}
async function parseDatasetTable(tableWrapper: WebElementWrapper, columnNamesOrIndexes: string[]) {
const headerElementWrappers = await tableWrapper.findAllByCssSelector('thead th, thead td');
const result: Record<
string,
{
columnNameOrIndex: string;
sortDirection?: 'ascending' | 'descending';
headerElement: WebElementWrapper;
cellElements: WebElementWrapper[];
cellContentElements: WebElementWrapper[];
getSortDirection: () => Promise<'ascending' | 'descending' | undefined>;
sort: (sortDirection: 'ascending' | 'descending') => Promise<void>;
getCellTexts: (selector?: string) => Promise<string[]>;
getCellChildren: (selector: string) => Promise<WebElementWrapper[]>;
}
> = {};
for (let i = 0; i < headerElementWrappers.length; i++) {
const tdSelector = `table > tbody > tr td:nth-child(${i + 1})`;
const cellContentSelector = `${tdSelector} .euiTableCellContent`;
const thWrapper = headerElementWrappers[i];
const columnName = await thWrapper.getVisibleText();
const columnIndex = `${i}`;
const columnNameOrIndex = columnNamesOrIndexes.includes(columnName)
? columnName
: columnNamesOrIndexes.includes(columnIndex)
? columnIndex
: undefined;
if (columnNameOrIndex) {
const headerElement = thWrapper;
const tdWrappers = await tableWrapper.findAllByCssSelector(tdSelector);
const cellContentWrappers = await tableWrapper.findAllByCssSelector(cellContentSelector);
const getSortDirection = () =>
headerElement.getAttribute('aria-sort') as Promise<'ascending' | 'descending' | undefined>;
result[columnNameOrIndex] = {
columnNameOrIndex,
headerElement,
cellElements: tdWrappers,
cellContentElements: cellContentWrappers,
getSortDirection,
sort: async (sortDirection: 'ascending' | 'descending') => {
if ((await getSortDirection()) !== sortDirection) {
await headerElement.click();
}
// Sorting twice if the sort was in neutral state
if ((await getSortDirection()) !== sortDirection) {
await headerElement.click();
}
},
getCellTexts: async (textContainerSelector?: string) => {
const cellContentContainerWrappers = textContainerSelector
? await tableWrapper.findAllByCssSelector(`${tdSelector} ${textContainerSelector}`)
: cellContentWrappers;
const cellContentContainerWrapperTexts: string[] = [];
for (let j = 0; j < cellContentContainerWrappers.length; j++) {
const cellContentContainerWrapper = cellContentContainerWrappers[j];
const cellContentContainerWrapperText =
await cellContentContainerWrapper.getVisibleText();
cellContentContainerWrapperTexts.push(cellContentContainerWrapperText);
}
return cellContentContainerWrapperTexts;
},
getCellChildren: (childSelector: string) => {
return tableWrapper.findAllByCssSelector(`${cellContentSelector} ${childSelector}`);
},
};
}
}
return result;
}
/**
* Get all elements matching the given selector and text
* @example
@ -544,17 +568,3 @@ export async function getAllByText(container: WebElementWrapper, selector: strin
return matchingElements;
}
const texts = {
noActivityText: 'No activity in the selected timeframe',
datasetHealthPoor: 'Poor',
datasetHealthDegraded: 'Degraded',
datasetHealthGood: 'Good',
activeDatasets: 'Active Data Sets',
estimatedData: 'Estimated Data',
docsCountTotal: 'Docs count (total)',
size: 'Size',
services: 'Services',
hosts: 'Hosts',
degradedDocs: 'Degraded docs',
};

View file

@ -191,6 +191,7 @@ export function createDegradedFieldsRecord({
export const datasetNames = ['synth.1', 'synth.2', 'synth.3'];
export const defaultNamespace = 'default';
export const productionNamespace = 'production';
// Logs Data logic
const MESSAGE_LOG_LEVELS: MessageWithLevel[] = [

View file

@ -12,6 +12,7 @@ import {
getInitialTestLogs,
getLogsForDataset,
createDegradedFieldsRecord,
productionNamespace,
} from './data';
const integrationActions = {
@ -36,25 +37,72 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const to = '2024-01-01T12:00:00.000Z';
const excludeKeysFromServerless = ['size']; // https://github.com/elastic/kibana/issues/178954
describe('Dataset quality flyout', function () {
// FLAKY: https://github.com/elastic/kibana/issues/183771
// Added this sub describe block so that the existing flaky tests can be skipped and new ones can be added in the other describe block
const apacheAccessDatasetName = 'apache.access';
const apacheAccessDatasetHumanName = 'Apache access logs';
const apacheIntegrationId = 'apache';
const apachePkg = {
name: 'apache',
version: '1.14.0',
};
describe.skip('Other dataset quality flyout tests', () => {
this.tags(['failsOnMKI']); // Failing https://github.com/elastic/kibana/issues/183495
const bitbucketDatasetName = 'atlassian_bitbucket.audit';
const bitbucketDatasetHumanName = 'Bitbucket Audit Logs';
const bitbucketPkg = {
name: 'atlassian_bitbucket',
version: '1.14.0',
};
before(async () => {
await PageObjects.svlCommonPage.loginWithRole('admin');
await synthtrace.index(getInitialTestLogs({ to, count: 4 }));
await PageObjects.datasetQuality.navigateTo();
});
const degradedDatasetName = datasetNames[2];
after(async () => {
await synthtrace.clean();
await PageObjects.observabilityLogsExplorer.removeInstalledPackages();
});
describe('Flyout', function () {
before(async () => {
// Install Apache Integration and ingest logs for it
await PageObjects.observabilityLogsExplorer.installPackage(apachePkg);
it('opens the flyout for the right dataset', async () => {
// Install Bitbucket Integration (package which does not has Dashboards) and ingest logs for it
await PageObjects.observabilityLogsExplorer.installPackage(bitbucketPkg);
await synthtrace.index([
// Ingest basic logs
getInitialTestLogs({ to, count: 4 }),
// Ingest Degraded Logs
createDegradedFieldsRecord({
to: new Date().toISOString(),
count: 2,
dataset: degradedDatasetName,
}),
// Index 15 logs for `logs-apache.access` dataset
getLogsForDataset({
to: new Date().toISOString(),
count: 15,
dataset: apacheAccessDatasetName,
namespace: productionNamespace,
}),
// Index degraded docs for Apache integration
getLogsForDataset({
to: new Date().toISOString(),
count: 1,
dataset: apacheAccessDatasetName,
namespace: productionNamespace,
isMalformed: true,
}),
// Index logs for Bitbucket integration
getLogsForDataset({ to, count: 10, dataset: bitbucketDatasetName }),
]);
await PageObjects.svlCommonPage.loginWithRole('admin');
await PageObjects.datasetQuality.navigateTo();
});
after(async () => {
await PageObjects.observabilityLogsExplorer.uninstallPackage(apachePkg);
await PageObjects.observabilityLogsExplorer.uninstallPackage(bitbucketPkg);
await synthtrace.clean();
});
describe('open flyout', () => {
it('should open the flyout for the right dataset', async () => {
const testDatasetName = datasetNames[1];
await PageObjects.datasetQuality.openDatasetFlyout(testDatasetName);
@ -62,44 +110,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await testSubjects.existOrFail(
PageObjects.datasetQuality.testSubjectSelectors.datasetQualityFlyoutTitle
);
});
// Fails on Serverless. TODO: Need to update the UI as well as the test
it.skip('shows the correct last activity', async () => {
const testDatasetName = datasetNames[0];
// Update last activity for the dataset
await PageObjects.datasetQuality.closeFlyout();
await synthtrace.index(
getLogsForDataset({ to: new Date().toISOString(), count: 1, dataset: testDatasetName })
);
await PageObjects.datasetQuality.refreshTable();
const cols = await PageObjects.datasetQuality.parseDatasetTable();
const datasetNameCol = cols['Data Set Name'];
const datasetNameColCellTexts = await datasetNameCol.getCellTexts();
const testDatasetRowIndex = datasetNameColCellTexts.findIndex(
(dName: string) => dName === testDatasetName
);
const lastActivityText = (await cols['Last Activity'].getCellTexts())[testDatasetRowIndex];
await PageObjects.datasetQuality.openDatasetFlyout(testDatasetName);
const lastActivityTextExists = await PageObjects.datasetQuality.doestTextExistInFlyout(
lastActivityText,
`[data-test-subj=${PageObjects.datasetQuality.testSubjectSelectors.datasetQualityFlyoutFieldValue}]`
);
expect(lastActivityTextExists).to.eql(true);
});
// FLAKY: https://github.com/elastic/kibana/issues/180994
it.skip('reflects the breakdown field state in url', async () => {
const testDatasetName = datasetNames[0];
await PageObjects.datasetQuality.openDatasetFlyout(testDatasetName);
it('reflects the breakdown field state in url', async () => {
await PageObjects.datasetQuality.openDatasetFlyout(degradedDatasetName);
const breakdownField = 'service.name';
await PageObjects.datasetQuality.selectBreakdownField(breakdownField);
@ -118,25 +134,25 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const currentUrl = await browser.getCurrentUrl();
expect(currentUrl).to.not.contain('breakdownField');
});
await PageObjects.datasetQuality.closeFlyout();
});
});
it('shows the integration details', async () => {
const apacheAccessDatasetName = 'apache.access';
const apacheAccessDatasetHumanName = 'Apache access logs';
const apacheIntegrationId = 'apache';
describe('integrations', () => {
it('should hide the integration section for non integrations', async () => {
const testDatasetName = datasetNames[1];
await PageObjects.observabilityLogsExplorer.navigateTo();
await PageObjects.datasetQuality.openDatasetFlyout(testDatasetName);
// Add initial integrations
await PageObjects.observabilityLogsExplorer.setupInitialIntegrations();
// Index 10 logs for `logs-apache.access` dataset
await synthtrace.index(
getLogsForDataset({ to, count: 10, dataset: apacheAccessDatasetName })
await testSubjects.missingOrFail(
PageObjects.datasetQuality.testSubjectSelectors
.datasetQualityFlyoutFieldsListIntegrationDetails
);
await PageObjects.datasetQuality.navigateTo();
await PageObjects.datasetQuality.closeFlyout();
});
it('should shows the integration section for integrations', async () => {
await PageObjects.datasetQuality.openDatasetFlyout(apacheAccessDatasetHumanName);
const integrationNameElements = await PageObjects.datasetQuality.getFlyoutElementsByText(
@ -144,185 +160,17 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
apacheIntegrationId
);
await PageObjects.datasetQuality.closeFlyout();
await testSubjects.existOrFail(
PageObjects.datasetQuality.testSubjectSelectors
.datasetQualityFlyoutFieldsListIntegrationDetails
);
expect(integrationNameElements.length).to.eql(1);
await PageObjects.datasetQuality.closeFlyout();
});
it('goes to log explorer page when open button is clicked', async () => {
const testDatasetName = datasetNames[2];
await PageObjects.datasetQuality.openDatasetFlyout(testDatasetName);
await (await PageObjects.datasetQuality.getFlyoutLogsExplorerButton()).click();
// Confirm dataset selector text in observability logs explorer
const datasetSelectorText =
await PageObjects.observabilityLogsExplorer.getDataSourceSelectorButtonText();
expect(datasetSelectorText).to.eql(testDatasetName);
});
it('shows summary KPIs', async () => {
await PageObjects.datasetQuality.navigateTo();
const apacheAccessDatasetHumanName = 'Apache access logs';
await PageObjects.datasetQuality.openDatasetFlyout(apacheAccessDatasetHumanName);
const summary = await PageObjects.datasetQuality.parseFlyoutKpis(excludeKeysFromServerless);
expect(summary).to.eql({
docsCountTotal: '0',
// size: '0.0 B', // `_stats` not available on Serverless
services: '0',
hosts: '0',
degradedDocs: '0',
});
});
it('shows the updated KPIs', async () => {
const apacheAccessDatasetName = 'apache.access';
const apacheAccessDatasetHumanName = 'Apache access logs';
await PageObjects.datasetQuality.openDatasetFlyout(apacheAccessDatasetHumanName);
const summaryBefore = await PageObjects.datasetQuality.parseFlyoutKpis(
excludeKeysFromServerless
);
// Set time range to 3 days ago
const flyoutBodyContainer = await testSubjects.find(
PageObjects.datasetQuality.testSubjectSelectors.datasetQualityFlyoutBody
);
await PageObjects.datasetQuality.setDatePickerLastXUnits(flyoutBodyContainer, 3, 'd');
// Index 2 doc 2 days ago
const time2DaysAgo = Date.now() - 2 * 24 * 60 * 60 * 1000;
await synthtrace.index(
getLogsForDataset({
to: time2DaysAgo,
count: 2,
dataset: apacheAccessDatasetName,
isMalformed: false,
})
);
// Index 5 degraded docs 2 days ago
await synthtrace.index(
getLogsForDataset({
to: time2DaysAgo,
count: 5,
dataset: apacheAccessDatasetName,
isMalformed: true,
})
);
await PageObjects.datasetQuality.refreshFlyout();
const summaryAfter = await PageObjects.datasetQuality.parseFlyoutKpis(
excludeKeysFromServerless
);
expect(parseInt(summaryAfter.docsCountTotal, 10)).to.be.greaterThan(
parseInt(summaryBefore.docsCountTotal, 10)
);
expect(parseInt(summaryAfter.degradedDocs, 10)).to.be.greaterThan(
parseInt(summaryBefore.degradedDocs, 10)
);
// `_stats` not available on Serverless so we can't compare size // https://github.com/elastic/kibana/issues/178954
// expect(parseInt(summaryAfter.size, 10)).to.be.greaterThan(parseInt(summaryBefore.size, 10));
expect(parseInt(summaryAfter.services, 10)).to.be.greaterThan(
parseInt(summaryBefore.services, 10)
);
expect(parseInt(summaryAfter.hosts, 10)).to.be.greaterThan(
parseInt(summaryBefore.hosts, 10)
);
});
it('shows the right number of services', async () => {
const apacheAccessDatasetName = 'apache.access';
const apacheAccessDatasetHumanName = 'Apache access logs';
await PageObjects.datasetQuality.openDatasetFlyout(apacheAccessDatasetHumanName);
const summaryBefore = await PageObjects.datasetQuality.parseFlyoutKpis(
excludeKeysFromServerless
);
const testServices = ['test-srv-1', 'test-srv-2'];
// Index 2 docs with different services
const timeNow = Date.now();
await synthtrace.index(
getLogsForDataset({
to: timeNow,
count: 2,
dataset: apacheAccessDatasetName,
isMalformed: false,
services: testServices,
})
);
await PageObjects.datasetQuality.refreshFlyout();
const summaryAfter = await PageObjects.datasetQuality.parseFlyoutKpis(
excludeKeysFromServerless
);
expect(parseInt(summaryAfter.services, 10)).to.eql(
parseInt(summaryBefore.services, 10) + testServices.length
);
});
it('goes to log explorer for degraded docs when show all is clicked', async () => {
const apacheAccessDatasetName = 'apache.access';
const apacheAccessDatasetHumanName = 'Apache access logs';
await PageObjects.datasetQuality.openDatasetFlyout(apacheAccessDatasetHumanName);
const degradedDocsShowAllSelector = `${PageObjects.datasetQuality.testSubjectSelectors.datasetQualityFlyoutKpiLink}-${PageObjects.datasetQuality.texts.degradedDocs}`;
await testSubjects.click(degradedDocsShowAllSelector);
await browser.switchTab(1);
// Confirm dataset selector text in observability logs explorer
const datasetSelectorText =
await PageObjects.observabilityLogsExplorer.getDataSourceSelectorButtonText();
expect(datasetSelectorText).to.contain(apacheAccessDatasetName);
await browser.closeCurrentWindow();
await browser.switchTab(0);
});
// Blocked by https://github.com/elastic/kibana/issues/181705
it.skip('goes to infra hosts for hosts when show all is clicked', async () => {
const apacheAccessDatasetHumanName = 'Apache access logs';
await PageObjects.datasetQuality.openDatasetFlyout(apacheAccessDatasetHumanName);
const hostsShowAllSelector = `${PageObjects.datasetQuality.testSubjectSelectors.datasetQualityFlyoutKpiLink}-${PageObjects.datasetQuality.texts.hosts}`;
await testSubjects.click(hostsShowAllSelector);
await browser.switchTab(1);
// Confirm url contains metrics/hosts
await retry.tryForTime(5000, async () => {
const currentUrl = await browser.getCurrentUrl();
const parsedUrl = new URL(currentUrl);
expect(parsedUrl.pathname).to.contain('/app/metrics/hosts');
});
await browser.closeCurrentWindow();
await browser.switchTab(0);
});
it('Integration actions menu is present with correct actions', async () => {
const apacheAccessDatasetName = 'apache.access';
const apacheAccessDatasetHumanName = 'Apache access logs';
await PageObjects.observabilityLogsExplorer.navigateTo();
// Add initial integrations
await PageObjects.observabilityLogsExplorer.setupInitialIntegrations();
// Index 10 logs for `logs-apache.access` dataset
await synthtrace.index(
getLogsForDataset({ to, count: 10, dataset: apacheAccessDatasetName })
);
await PageObjects.datasetQuality.navigateTo();
it('should show the integration actions menu with correct actions', async () => {
await PageObjects.datasetQuality.openDatasetFlyout(apacheAccessDatasetHumanName);
await PageObjects.datasetQuality.openIntegrationActionsMenu();
@ -333,25 +181,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
);
expect(actions.length).to.eql(3);
await PageObjects.datasetQuality.closeFlyout();
});
it('Integration dashboard action hidden for integrations without dashboards', async () => {
const bitbucketDatasetName = 'atlassian_bitbucket.audit';
const bitbucketDatasetHumanName = 'Bitbucket Audit Logs';
await PageObjects.observabilityLogsExplorer.navigateTo();
// Add initial integrations
await PageObjects.observabilityLogsExplorer.installPackage({
name: 'atlassian_bitbucket',
version: '1.14.0',
});
// Index 10 logs for `atlassian_bitbucket.audit` dataset
await synthtrace.index(getLogsForDataset({ to, count: 10, dataset: bitbucketDatasetName }));
await PageObjects.datasetQuality.navigateTo();
it('should hide integration dashboard for integrations without dashboards', async () => {
await PageObjects.datasetQuality.openDatasetFlyout(bitbucketDatasetHumanName);
await PageObjects.datasetQuality.openIntegrationActionsMenu();
@ -360,25 +193,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
integrationActions.viewDashboards
)
);
await PageObjects.datasetQuality.closeFlyout();
});
it('Integration overview action should navigate to the integration overview page', async () => {
const bitbucketDatasetName = 'atlassian_bitbucket.audit';
const bitbucketDatasetHumanName = 'Bitbucket Audit Logs';
await PageObjects.observabilityLogsExplorer.navigateTo();
// Add initial integrations
await PageObjects.observabilityLogsExplorer.installPackage({
name: 'atlassian_bitbucket',
version: '1.14.0',
});
// Index 10 logs for `atlassian_bitbucket.audit` dataset
await synthtrace.index(getLogsForDataset({ to, count: 10, dataset: bitbucketDatasetName }));
await PageObjects.datasetQuality.navigateTo();
it('Should navigate to integration overview page on clicking integration overview action', async () => {
await PageObjects.datasetQuality.openDatasetFlyout(bitbucketDatasetHumanName);
await PageObjects.datasetQuality.openIntegrationActionsMenu();
@ -394,58 +212,31 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
expect(parsedUrl.pathname).to.contain('/app/integrations/detail/atlassian_bitbucket');
});
});
it('Integration template action should navigate to the index template page', async () => {
const apacheAccessDatasetName = 'apache.access';
const apacheAccessDatasetHumanName = 'Apache access logs';
await PageObjects.observabilityLogsExplorer.navigateTo();
// Add initial integrations
await PageObjects.observabilityLogsExplorer.setupInitialIntegrations();
// Index 10 logs for `logs-apache.access` dataset
await synthtrace.index(
getLogsForDataset({ to, count: 10, dataset: apacheAccessDatasetName })
);
await PageObjects.datasetQuality.navigateTo();
});
it('should navigate to index template page in clicking Integration template', async () => {
await PageObjects.datasetQuality.openDatasetFlyout(apacheAccessDatasetHumanName);
await PageObjects.datasetQuality.openIntegrationActionsMenu();
const action = await PageObjects.datasetQuality.getIntegrationActionButtonByAction(
integrationActions.template
);
await action.click();
await retry.tryForTime(5000, async () => {
const action = await PageObjects.datasetQuality.getIntegrationActionButtonByAction(
integrationActions.template
);
await action.click();
const currentUrl = await browser.getCurrentUrl();
const parsedUrl = new URL(currentUrl);
expect(parsedUrl.pathname).to.contain(
`/app/management/data/index_management/templates/logs-${apacheAccessDatasetName}`
);
});
await PageObjects.datasetQuality.navigateTo();
});
it('Integration dashboard action should navigate to the selected dashboard', async () => {
const apacheAccessDatasetName = 'apache.access';
const apacheAccessDatasetHumanName = 'Apache access logs';
await PageObjects.observabilityLogsExplorer.navigateTo();
// Add initial integrations
await PageObjects.observabilityLogsExplorer.setupInitialIntegrations();
// Index 10 logs for `logs-apache.access` dataset
await synthtrace.index(
getLogsForDataset({ to, count: 10, dataset: apacheAccessDatasetName })
);
await PageObjects.datasetQuality.navigateTo();
it('should navigate to the selected dashboard on clicking integration dashboard action ', async () => {
await PageObjects.datasetQuality.openDatasetFlyout(apacheAccessDatasetHumanName);
await PageObjects.datasetQuality.openIntegrationActionsMenu();
@ -464,149 +255,206 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const breadcrumbText = await testSubjects.getVisibleText('breadcrumb last');
expect(breadcrumbText).to.eql(dashboardText);
await PageObjects.datasetQuality.navigateTo();
});
});
// The above describe block has some failing/flaky tests which will
// be fixed as part of the tech debt mentioned here
// https://github.com/elastic/kibana/issues/184145
// Until then, the below describe block is added to cover the tests for the
// newly added degraded Fields Table. This must be merged under the above
// describe block once the tech debt is fixed.
//
// FLAKY: https://github.com/elastic/kibana/issues/184438
describe.skip('Dataset quality flyout with degraded fields', () => {
const goodDatasetName = 'good';
const degradedDatasetName = 'degraded';
const today = new Date().toISOString();
describe('Degraded Fields Table with common data', () => {
before(async () => {
await PageObjects.svlCommonPage.loginWithRole('admin');
await synthtrace.index([
getLogsForDataset({
to: today,
count: 2,
dataset: goodDatasetName,
isMalformed: false,
}),
createDegradedFieldsRecord({
to: today,
count: 2,
dataset: degradedDatasetName,
}),
]);
await PageObjects.datasetQuality.navigateTo();
});
describe('summary panel', () => {
it('should show summary KPIs', async () => {
await PageObjects.datasetQuality.openDatasetFlyout(apacheAccessDatasetHumanName);
after(async () => {
await synthtrace.clean();
});
it('shows the degraded fields table with no data when no degraded fields are present', async () => {
await PageObjects.datasetQuality.openDatasetFlyout(goodDatasetName);
const { docsCountTotal, degradedDocs, services, hosts } =
await PageObjects.datasetQuality.parseFlyoutKpis(excludeKeysFromServerless);
expect(parseInt(docsCountTotal, 10)).to.be(226);
expect(parseInt(degradedDocs, 10)).to.be(1);
expect(parseInt(services, 10)).to.be(3);
expect(parseInt(hosts, 10)).to.be(52);
await testSubjects.existOrFail(
PageObjects.datasetQuality.testSubjectSelectors.datasetQualityFlyoutDegradedTableNoData
);
await PageObjects.datasetQuality.closeFlyout();
});
});
await PageObjects.datasetQuality.closeFlyout();
});
describe('navigation', () => {
it('should go to log explorer page when the open in log explorer button is clicked', async () => {
const testDatasetName = datasetNames[2];
await PageObjects.datasetQuality.openDatasetFlyout(testDatasetName);
it('should load the degraded fields table with data', async () => {
await PageObjects.datasetQuality.openDatasetFlyout(degradedDatasetName);
const logExplorerButton = await PageObjects.datasetQuality.getFlyoutLogsExplorerButton();
await testSubjects.existOrFail(
PageObjects.datasetQuality.testSubjectSelectors.datasetQualityFlyoutDegradedFieldTable
);
await logExplorerButton.click();
const rows =
await PageObjects.datasetQuality.getDatasetQualityFlyoutDegradedFieldTableRows();
// Confirm dataset selector text in observability logs explorer
const datasetSelectorText =
await PageObjects.observabilityLogsExplorer.getDataSourceSelectorButtonText();
expect(datasetSelectorText).to.eql(testDatasetName);
expect(rows.length).to.eql(2);
await PageObjects.datasetQuality.closeFlyout();
});
it('should display Spark Plot for every row of degraded fields', async () => {
await PageObjects.datasetQuality.openDatasetFlyout(degradedDatasetName);
const rows =
await PageObjects.datasetQuality.getDatasetQualityFlyoutDegradedFieldTableRows();
const sparkPlots = await testSubjects.findAll(
PageObjects.datasetQuality.testSubjectSelectors.datasetQualitySparkPlot
);
expect(rows.length).to.be(sparkPlots.length);
await PageObjects.datasetQuality.closeFlyout();
});
it('should sort the table when the count table header is clicked', async () => {
await PageObjects.datasetQuality.openDatasetFlyout(degradedDatasetName);
const table = await PageObjects.datasetQuality.parseDegradedFieldTable();
const countColumn = table['Docs count'];
const cellTexts = await countColumn.getCellTexts();
await countColumn.sort('ascending');
const sortedCellTexts = await countColumn.getCellTexts();
expect(cellTexts.reverse()).to.eql(sortedCellTexts);
await PageObjects.datasetQuality.closeFlyout();
});
// Should bring back the test to the dataset quality page
await PageObjects.datasetQuality.navigateTo();
});
describe('Degraded Fields Table with data ingestion', () => {
before(async () => {
await PageObjects.svlCommonPage.loginWithRole('admin');
await synthtrace.index([
getLogsForDataset({
to: today,
count: 2,
dataset: goodDatasetName,
isMalformed: false,
}),
createDegradedFieldsRecord({
to: today,
count: 2,
dataset: degradedDatasetName,
}),
]);
await PageObjects.datasetQuality.navigateTo();
it('should go log explorer for degraded docs when the show all button is clicked', async () => {
await PageObjects.datasetQuality.openDatasetFlyout(apacheAccessDatasetHumanName);
const degradedDocsShowAllSelector = `${PageObjects.datasetQuality.testSubjectSelectors.datasetQualityFlyoutKpiLink}-${PageObjects.datasetQuality.texts.degradedDocs}`;
await testSubjects.click(degradedDocsShowAllSelector);
await browser.switchTab(1);
// Confirm dataset selector text in observability logs explorer
const datasetSelectorText =
await PageObjects.observabilityLogsExplorer.getDataSourceSelectorButtonText();
expect(datasetSelectorText).to.contain(apacheAccessDatasetName);
await browser.closeCurrentWindow();
await browser.switchTab(0);
await PageObjects.datasetQuality.closeFlyout();
});
// Blocked by https://github.com/elastic/kibana/issues/181705
// Its a test written ahead of its time.
it.skip('goes to infra hosts for hosts when show all is clicked', async () => {
await PageObjects.datasetQuality.openDatasetFlyout(apacheAccessDatasetHumanName);
const hostsShowAllSelector = `${PageObjects.datasetQuality.testSubjectSelectors.datasetQualityFlyoutKpiLink}-${PageObjects.datasetQuality.texts.hosts}`;
await testSubjects.click(hostsShowAllSelector);
await browser.switchTab(1);
// Confirm url contains metrics/hosts
await retry.tryForTime(5000, async () => {
const currentUrl = await browser.getCurrentUrl();
const parsedUrl = new URL(currentUrl);
expect(parsedUrl.pathname).to.contain('/app/metrics/hosts');
});
after(async () => {
await synthtrace.clean();
await browser.closeCurrentWindow();
await browser.switchTab(0);
await PageObjects.datasetQuality.closeFlyout();
});
});
describe('degraded fields table', () => {
it(' should show empty degraded fields table when no degraded fields are present', async () => {
await PageObjects.datasetQuality.openDatasetFlyout(datasetNames[0]);
await testSubjects.existOrFail(
PageObjects.datasetQuality.testSubjectSelectors.datasetQualityFlyoutDegradedTableNoData
);
await PageObjects.datasetQuality.closeFlyout();
});
it('should show the degraded fields table with data when present', async () => {
await PageObjects.datasetQuality.openDatasetFlyout(degradedDatasetName);
await testSubjects.existOrFail(
PageObjects.datasetQuality.testSubjectSelectors.datasetQualityFlyoutDegradedFieldTable
);
const rows =
await PageObjects.datasetQuality.getDatasetQualityFlyoutDegradedFieldTableRows();
expect(rows.length).to.eql(2);
await PageObjects.datasetQuality.closeFlyout();
});
it('should display Spark Plot for every row of degraded fields', async () => {
await PageObjects.datasetQuality.openDatasetFlyout(degradedDatasetName);
const rows =
await PageObjects.datasetQuality.getDatasetQualityFlyoutDegradedFieldTableRows();
const sparkPlots = await testSubjects.findAll(
PageObjects.datasetQuality.testSubjectSelectors.datasetQualitySparkPlot
);
expect(rows.length).to.be(sparkPlots.length);
await PageObjects.datasetQuality.closeFlyout();
});
it('should sort the table when the count table header is clicked', async () => {
await PageObjects.datasetQuality.openDatasetFlyout(degradedDatasetName);
const table = await PageObjects.datasetQuality.parseDegradedFieldTable();
const countColumn = table['Docs count'];
const cellTexts = await countColumn.getCellTexts();
await countColumn.sort('ascending');
const sortedCellTexts = await countColumn.getCellTexts();
expect(cellTexts.reverse()).to.eql(sortedCellTexts);
await PageObjects.datasetQuality.closeFlyout();
});
it('should update the URL when the table is sorted', async () => {
await PageObjects.datasetQuality.openDatasetFlyout(degradedDatasetName);
const table = await PageObjects.datasetQuality.parseDegradedFieldTable();
const countColumn = table['Docs count'];
await retry.tryForTime(5000, async () => {
const currentUrl = await browser.getCurrentUrl();
const parsedUrl = new URL(currentUrl);
const pageState = parsedUrl.searchParams.get('pageState');
expect(decodeURIComponent(pageState as string)).to.contain(
'sort:(direction:desc,field:count)'
);
});
it('should update the table when new data is ingested and the flyout is refreshed using the time selector', async () => {
await PageObjects.datasetQuality.openDatasetFlyout(degradedDatasetName);
const table = await PageObjects.datasetQuality.parseDegradedFieldTable();
countColumn.sort('ascending');
const countColumn = table['Docs count'];
const cellTexts = await countColumn.getCellTexts();
const singleValuePreviously = parseInt(cellTexts[0], 10);
await retry.tryForTime(5000, async () => {
const currentUrl = await browser.getCurrentUrl();
const parsedUrl = new URL(currentUrl);
const pageState = parsedUrl.searchParams.get('pageState');
await synthtrace.index([
createDegradedFieldsRecord({
to: today,
count: 2,
dataset: degradedDatasetName,
}),
]);
await PageObjects.datasetQuality.refreshFlyout();
const updatedCellTexts = await countColumn.getCellTexts();
const singleValueNow = parseInt(updatedCellTexts[0], 10);
expect(singleValueNow).to.be(singleValuePreviously * 2);
await PageObjects.datasetQuality.closeFlyout();
expect(decodeURIComponent(pageState as string)).to.contain(
'sort:(direction:asc,field:count)'
);
});
await PageObjects.datasetQuality.closeFlyout();
});
// This is the only test which ingest data during the test.
// This block tests the refresh behavior of the degraded fields table.
// Even though this test ingest data, it can also be freely moved inside
// this describe block, and it won't affect any of the existing tests
it('should update the table when new data is ingested and the flyout is refreshed using the time selector', async () => {
await PageObjects.datasetQuality.openDatasetFlyout(degradedDatasetName);
const table = await PageObjects.datasetQuality.parseDegradedFieldTable();
const countColumn = table['Docs count'];
const cellTexts = await countColumn.getCellTexts();
await synthtrace.index([
createDegradedFieldsRecord({
to: new Date().toISOString(),
count: 2,
dataset: degradedDatasetName,
}),
]);
await PageObjects.datasetQuality.refreshFlyout();
const updatedTable = await PageObjects.datasetQuality.parseDegradedFieldTable();
const updatedCountColumn = updatedTable['Docs count'];
const updatedCellTexts = await updatedCountColumn.getCellTexts();
const singleValuePreviously = parseInt(cellTexts[0], 10);
const singleValueNow = parseInt(updatedCellTexts[0], 10);
expect(singleValueNow).to.be.greaterThan(singleValuePreviously);
await PageObjects.datasetQuality.closeFlyout();
});
});
});

View file

@ -17,11 +17,37 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
'svlCommonPage',
]);
const synthtrace = getService('svlLogsSynthtraceClient');
const browser = getService('browser');
const retry = getService('retry');
const to = '2024-01-01T12:00:00.000Z';
const excludeKeysFromServerless = ['estimatedData']; // https://github.com/elastic/kibana/issues/178954
const ingestDataForSummary = async () => {
// Ingest documents for 3 type of datasets
return synthtrace.index([
// Ingest good data to all 3 datasets
getInitialTestLogs({ to, count: 4 }),
// Ingesting poor data to one dataset
getLogsForDataset({
to: Date.now(),
count: 1,
dataset: datasetNames[1],
isMalformed: true,
}),
// Ingesting degraded docs into another dataset by ingesting malformed 1st and then good data
getLogsForDataset({
to: Date.now(),
count: 1,
dataset: datasetNames[2],
isMalformed: true,
}),
getLogsForDataset({
to: Date.now(),
count: 10,
dataset: datasetNames[2],
isMalformed: false,
}),
]);
};
describe('Dataset quality summary', () => {
before(async () => {
await synthtrace.index(getInitialTestLogs({ to, count: 4 }));
@ -29,110 +55,31 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await PageObjects.datasetQuality.navigateTo();
});
after(async () => {
afterEach(async () => {
await synthtrace.clean();
});
it('shows poor, degraded and good count', async () => {
it('shows poor, degraded and good count as 0 and all dataset as healthy', async () => {
await PageObjects.datasetQuality.refreshTable();
const summary = await PageObjects.datasetQuality.parseSummaryPanel(excludeKeysFromServerless);
expect(summary).to.eql({
datasetHealthPoor: '0',
datasetHealthDegraded: '0',
datasetHealthGood: '3',
activeDatasets: '0 of 3',
// estimatedData: '0.0 B', https://github.com/elastic/kibana/issues/178954
});
});
it('updates the poor count when degraded docs are ingested', async () => {
// Index malformed document with current timestamp
await synthtrace.index(
getLogsForDataset({
to: Date.now(),
count: 1,
dataset: datasetNames[2],
isMalformed: true,
})
);
it('shows updated count for poor, degraded and good datasets and updates active datasets', async () => {
await ingestDataForSummary();
await PageObjects.datasetQuality.refreshTable();
await browser.refresh();
await PageObjects.datasetQuality.waitUntilSummaryPanelLoaded();
await retry.try(async () => {
const summary = await PageObjects.datasetQuality.parseSummaryPanel(
excludeKeysFromServerless
);
const { estimatedData, ...restOfSummary } = summary;
expect(restOfSummary).to.eql({
datasetHealthPoor: '1',
datasetHealthDegraded: '0',
datasetHealthGood: '2',
activeDatasets: '1 of 3',
});
});
});
it('updates the degraded count when degraded docs are ingested', async () => {
// Index malformed document with current timestamp
await synthtrace.index(
getLogsForDataset({
to: Date.now(),
count: 1,
dataset: datasetNames[1],
isMalformed: true,
})
);
// Index healthy documents
await synthtrace.index(
getLogsForDataset({
to: Date.now(),
count: 10,
dataset: datasetNames[1],
isMalformed: false,
})
);
await browser.refresh();
await PageObjects.datasetQuality.waitUntilSummaryPanelLoaded();
await retry.try(async () => {
const { estimatedData, ...restOfSummary } =
await PageObjects.datasetQuality.parseSummaryPanel(excludeKeysFromServerless);
expect(restOfSummary).to.eql({
datasetHealthPoor: '1',
datasetHealthDegraded: '1',
datasetHealthGood: '1',
activeDatasets: '2 of 3',
});
});
});
it('updates active datasets and estimated data KPIs', async () => {
const { estimatedData: _existingEstimatedData } =
await PageObjects.datasetQuality.parseSummaryPanel(excludeKeysFromServerless);
// Index document at current time to mark dataset as active
await synthtrace.index(
getLogsForDataset({
to: Date.now(),
count: 4,
dataset: datasetNames[0],
isMalformed: false,
})
);
await browser.refresh(); // Summary panel doesn't update reactively
await PageObjects.datasetQuality.waitUntilSummaryPanelLoaded();
await retry.try(async () => {
const { activeDatasets: updatedActiveDatasets, estimatedData: _updatedEstimatedData } =
await PageObjects.datasetQuality.parseSummaryPanel(excludeKeysFromServerless);
expect(updatedActiveDatasets).to.eql('3 of 3');
// TODO: `_stats` not available on Serverless. // https://github.com/elastic/kibana/issues/178954
// expect(_updatedEstimatedData).to.not.eql(_existingEstimatedData);
const summary = await PageObjects.datasetQuality.parseSummaryPanel(excludeKeysFromServerless);
expect(summary).to.eql({
datasetHealthPoor: '1',
datasetHealthDegraded: '1',
datasetHealthGood: '1',
activeDatasets: '2 of 3',
});
});
});

View file

@ -7,7 +7,13 @@
import expect from '@kbn/expect';
import { FtrProviderContext } from '../../../ftr_provider_context';
import { datasetNames, defaultNamespace, getInitialTestLogs, getLogsForDataset } from './data';
import {
datasetNames,
defaultNamespace,
getInitialTestLogs,
getLogsForDataset,
productionNamespace,
} from './data';
export default function ({ getService, getPageObjects }: FtrProviderContext) {
const PageObjects = getPageObjects([
@ -19,42 +25,79 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
'svlCommonPage',
]);
const synthtrace = getService('svlLogsSynthtraceClient');
const testSubjects = getService('testSubjects');
const retry = getService('retry');
const to = '2024-01-01T12:00:00.000Z';
const apacheAccessDatasetName = 'apache.access';
const apacheAccessDatasetHumanName = 'Apache access logs';
const pkg = {
name: 'apache',
version: '1.14.0',
};
describe('Dataset quality table', function () {
this.tags(['failsOnMKI']); // Failing https://github.com/elastic/kibana/issues/183495
before(async () => {
await synthtrace.index(getInitialTestLogs({ to, count: 4 }));
// Install Integration and ingest logs for it
await PageObjects.observabilityLogsExplorer.installPackage(pkg);
// Ingest basic logs
await synthtrace.index([
// Ingest basic logs
getInitialTestLogs({ to, count: 4 }),
// Ingest Degraded Logs
getLogsForDataset({
to: new Date().toISOString(),
count: 1,
dataset: datasetNames[2],
isMalformed: true,
}),
// Index 10 logs for `logs-apache.access` dataset
getLogsForDataset({
to,
count: 10,
dataset: apacheAccessDatasetName,
namespace: productionNamespace,
}),
]);
await PageObjects.svlCommonPage.loginWithRole('admin');
await PageObjects.datasetQuality.navigateTo();
});
after(async () => {
await synthtrace.clean();
await PageObjects.observabilityLogsExplorer.removeInstalledPackages();
await PageObjects.observabilityLogsExplorer.uninstallPackage(pkg);
});
it('shows the right number of rows in correct order', async () => {
it('shows sort by dataset name and show namespace', async () => {
const cols = await PageObjects.datasetQuality.parseDatasetTable();
const datasetNameCol = cols['Data Set Name'];
await datasetNameCol.sort('descending');
const datasetNameColCellTexts = await datasetNameCol.getCellTexts();
expect(datasetNameColCellTexts).to.eql([...datasetNames].reverse());
expect(datasetNameColCellTexts).to.eql(
[apacheAccessDatasetHumanName, ...datasetNames].reverse()
);
const namespaceCol = cols.Namespace;
const namespaceColCellTexts = await namespaceCol.getCellTexts();
expect(namespaceColCellTexts).to.eql([defaultNamespace, defaultNamespace, defaultNamespace]);
expect(namespaceColCellTexts).to.eql([
defaultNamespace,
defaultNamespace,
defaultNamespace,
productionNamespace,
]);
const degradedDocsCol = cols['Degraded Docs (%)'];
const degradedDocsColCellTexts = await degradedDocsCol.getCellTexts();
expect(degradedDocsColCellTexts).to.eql(['0%', '0%', '0%']);
// Cleaning the sort
await datasetNameCol.sort('ascending');
});
it('shows the last activity', async () => {
const cols = await PageObjects.datasetQuality.parseDatasetTable();
const lastActivityCol = cols['Last Activity'];
const lastActivityColCellTexts = await lastActivityCol.getCellTexts();
expect(lastActivityColCellTexts).to.eql([
const activityCells = await lastActivityCol.getCellTexts();
const lastActivityCell = activityCells[activityCells.length - 1];
const restActivityCells = activityCells.slice(0, -1);
// The first cell of lastActivity should have data
expect(lastActivityCell).to.not.eql(PageObjects.datasetQuality.texts.noActivityText);
// The rest of the rows must show no activity
expect(restActivityCells).to.eql([
PageObjects.datasetQuality.texts.noActivityText,
PageObjects.datasetQuality.texts.noActivityText,
PageObjects.datasetQuality.texts.noActivityText,
@ -63,112 +106,19 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
it('shows degraded docs percentage', async () => {
const cols = await PageObjects.datasetQuality.parseDatasetTable();
const datasetNameCol = cols['Data Set Name'];
await datasetNameCol.sort('ascending');
const degradedDocsCol = cols['Degraded Docs (%)'];
const degradedDocsColCellTexts = await degradedDocsCol.getCellTexts();
expect(degradedDocsColCellTexts).to.eql(['0%', '0%', '0%']);
// Index malformed document with current timestamp
await synthtrace.index(
getLogsForDataset({
to: Date.now(),
count: 1,
dataset: datasetNames[2],
isMalformed: true,
})
);
// Set time range to Last 5 minute
const filtersContainer = await testSubjects.find(
PageObjects.datasetQuality.testSubjectSelectors.datasetQualityFiltersContainer
);
await PageObjects.datasetQuality.setDatePickerLastXUnits(filtersContainer, 5, 'm');
const updatedDegradedDocsColCellTexts = await degradedDocsCol.getCellTexts();
expect(updatedDegradedDocsColCellTexts[2]).to.not.eql('0%');
});
// https://github.com/elastic/kibana/issues/178954
it.skip('shows the updated size of the index', async () => {
const testDatasetIndex = 2;
const cols = await PageObjects.datasetQuality.parseDatasetTable();
const datasetNameCol = cols['Data Set Name'];
await datasetNameCol.sort('ascending');
const datasetNameColCellTexts = await datasetNameCol.getCellTexts();
const datasetToUpdateRowIndex = datasetNameColCellTexts.findIndex(
(dName: string) => dName === datasetNames[testDatasetIndex]
);
const sizeColCellTexts = await cols.Size.getCellTexts();
const beforeSize = sizeColCellTexts[datasetToUpdateRowIndex];
// Index documents with current timestamp
await synthtrace.index(
getLogsForDataset({
to: Date.now(),
count: 4,
dataset: datasetNames[testDatasetIndex],
isMalformed: false,
})
);
const colsAfterUpdate = await PageObjects.datasetQuality.parseDatasetTable();
// Assert that size has changed
await retry.tryForTime(15000, async () => {
// Refresh the table
await PageObjects.datasetQuality.refreshTable();
const updatedSizeColCellTexts = await colsAfterUpdate.Size.getCellTexts();
expect(updatedSizeColCellTexts[datasetToUpdateRowIndex]).to.not.eql(beforeSize);
});
});
it('sorts by dataset name', async () => {
// const header = await PageObjects.datasetQuality.getDatasetTableHeader('Dataset Name');
const cols = await PageObjects.datasetQuality.parseDatasetTable();
const datasetNameCol = cols['Data Set Name'];
// Sort ascending
await datasetNameCol.sort('ascending');
const cellTexts = await datasetNameCol.getCellTexts();
const datasetNamesAsc = [...datasetNames].sort();
expect(cellTexts).to.eql(datasetNamesAsc);
expect(degradedDocsColCellTexts).to.eql(['0%', '0%', '0%', '100%']);
});
it('shows dataset from integration', async () => {
const apacheAccessDatasetName = 'apache.access';
const apacheAccessDatasetHumanName = 'Apache access logs';
await PageObjects.observabilityLogsExplorer.navigateTo();
// Add initial integrations
await PageObjects.observabilityLogsExplorer.setupInitialIntegrations();
// Index 10 logs for `logs-apache.access` dataset
await synthtrace.index(
getLogsForDataset({ to, count: 10, dataset: apacheAccessDatasetName })
);
await PageObjects.datasetQuality.navigateTo();
const cols = await PageObjects.datasetQuality.parseDatasetTable();
const datasetNameCol = cols['Data Set Name'];
// Sort ascending
await datasetNameCol.sort('ascending');
const datasetNameColCellTexts = await datasetNameCol.getCellTexts();
const datasetNamesAsc = [...datasetNames, apacheAccessDatasetHumanName].sort();
// Assert there are 4 rows
expect(datasetNameColCellTexts.length).to.eql(4);
expect(datasetNameColCellTexts).to.eql(datasetNamesAsc);
expect(datasetNameColCellTexts[0]).to.eql(apacheAccessDatasetHumanName);
});
it('goes to log explorer page when opened', async () => {
@ -184,50 +134,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const datasetSelectorText =
await PageObjects.observabilityLogsExplorer.getDataSourceSelectorButtonText();
expect(datasetSelectorText).to.eql(datasetName);
});
it('shows the last activity when in time range', async () => {
// Return to Dataset Quality Page
await PageObjects.datasetQuality.navigateTo();
const cols = await PageObjects.datasetQuality.parseDatasetTable();
const lastActivityCol = cols['Last Activity'];
const datasetNameCol = cols['Data Set Name'];
// Set time range to Last 1 minute
const filtersContainer = await testSubjects.find(
PageObjects.datasetQuality.testSubjectSelectors.datasetQualityFiltersContainer
);
await PageObjects.datasetQuality.setDatePickerLastXUnits(filtersContainer, 1, 's');
const lastActivityColCellTexts = await lastActivityCol.getCellTexts();
expect(lastActivityColCellTexts).to.eql([
PageObjects.datasetQuality.texts.noActivityText,
PageObjects.datasetQuality.texts.noActivityText,
PageObjects.datasetQuality.texts.noActivityText,
PageObjects.datasetQuality.texts.noActivityText,
]);
const datasetToUpdate = datasetNames[0];
await synthtrace.index(
getLogsForDataset({ to: new Date().toISOString(), count: 1, dataset: datasetToUpdate })
);
const datasetNameColCellTexts = await datasetNameCol.getCellTexts();
const datasetToUpdateRowIndex = datasetNameColCellTexts.findIndex(
(dName: string) => dName === datasetToUpdate
);
await PageObjects.datasetQuality.setDatePickerLastXUnits(filtersContainer, 1, 'h');
await retry.tryForTime(5000, async () => {
const updatedLastActivityColCellTexts = await lastActivityCol.getCellTexts();
expect(updatedLastActivityColCellTexts[datasetToUpdateRowIndex]).to.not.eql(
PageObjects.datasetQuality.texts.noActivityText
);
});
});
it('hides inactive datasets', async () => {
await PageObjects.datasetQuality.waitUntilTableLoaded();
// Get number of rows with Last Activity not equal to "No activity in the selected timeframe"
const cols = await PageObjects.datasetQuality.parseDatasetTable();
const lastActivityCol = cols['Last Activity'];

View file

@ -7,7 +7,7 @@
import expect from '@kbn/expect';
import { FtrProviderContext } from '../../../ftr_provider_context';
import { datasetNames, defaultNamespace, getInitialTestLogs, getLogsForDataset } from './data';
import { datasetNames, getInitialTestLogs, getLogsForDataset, productionNamespace } from './data';
export default function ({ getService, getPageObjects }: FtrProviderContext) {
const PageObjects = getPageObjects([
@ -20,58 +20,73 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const synthtrace = getService('svlLogsSynthtraceClient');
const testSubjects = getService('testSubjects');
const to = '2024-01-01T12:00:00.000Z';
const apacheAccessDatasetName = 'apache.access';
const apacheAccessDatasetHumanName = 'Apache access logs';
const apacheIntegrationName = 'Apache HTTP Server';
const pkg = {
name: 'apache',
version: '1.14.0',
};
const allDatasetNames = [apacheAccessDatasetHumanName, ...datasetNames];
describe('Dataset quality table filters', function () {
this.tags(['failsOnMKI']); // Failing https://github.com/elastic/kibana/issues/183495
before(async () => {
await synthtrace.index(getInitialTestLogs({ to, count: 4 }));
// Install Integration and ingest logs for it
await PageObjects.observabilityLogsExplorer.installPackage(pkg);
// Ingest basic logs
await synthtrace.index([
// Ingest basic logs
getInitialTestLogs({ to, count: 4 }),
// Ingest Degraded Logs
getLogsForDataset({
to: new Date().toISOString(),
count: 1,
dataset: datasetNames[2],
isMalformed: true,
}),
// Index 10 logs for `logs-apache.access` dataset
getLogsForDataset({
to,
count: 10,
dataset: apacheAccessDatasetName,
namespace: productionNamespace,
}),
]);
await PageObjects.svlCommonPage.loginWithRole('admin');
await PageObjects.datasetQuality.navigateTo();
});
after(async () => {
await PageObjects.observabilityLogsExplorer.uninstallPackage(pkg);
await synthtrace.clean();
await PageObjects.observabilityLogsExplorer.removeInstalledPackages();
});
it('hides inactive datasets when toggled', async () => {
const initialRows = await PageObjects.datasetQuality.getDatasetTableRows();
expect(initialRows.length).to.eql(3);
await PageObjects.datasetQuality.toggleShowInactiveDatasets();
const afterToggleRows = await PageObjects.datasetQuality.getDatasetTableRows();
expect(afterToggleRows.length).to.eql(1);
await PageObjects.datasetQuality.toggleShowInactiveDatasets();
const afterReToggleRows = await PageObjects.datasetQuality.getDatasetTableRows();
expect(afterReToggleRows.length).to.eql(3);
});
it('shows full dataset names when toggled', async () => {
const cols = await PageObjects.datasetQuality.parseDatasetTable();
const datasetNameCol = cols['Data Set Name'];
const datasetNameColCellTexts = await datasetNameCol.getCellTexts();
expect(datasetNameColCellTexts).to.eql(datasetNames);
expect(datasetNameColCellTexts).to.eql(allDatasetNames);
await PageObjects.datasetQuality.toggleShowFullDatasetNames();
const datasetNameColCellTextsAfterToggle = await datasetNameCol.getCellTexts();
const duplicateNames = datasetNames.map((name) => `${name}\n${name}`);
const duplicateNames = [
`${apacheAccessDatasetHumanName}\n${apacheAccessDatasetName}`,
...datasetNames.map((name) => `${name}\n${name}`),
];
expect(datasetNameColCellTextsAfterToggle).to.eql(duplicateNames);
// resetting the toggle
await PageObjects.datasetQuality.toggleShowFullDatasetNames();
const datasetNameColCellTextsAfterReToggle = await datasetNameCol.getCellTexts();
expect(datasetNameColCellTextsAfterReToggle).to.eql(datasetNames);
expect(datasetNameColCellTextsAfterReToggle).to.eql(allDatasetNames);
});
it('searches the datasets', async () => {
const cols = await PageObjects.datasetQuality.parseDatasetTable();
const datasetNameCol = cols['Data Set Name'];
const datasetNameColCellTexts = await datasetNameCol.getCellTexts();
expect(datasetNameColCellTexts).to.eql(datasetNames);
expect(datasetNameColCellTexts).to.eql(allDatasetNames);
// Search for a dataset
await testSubjects.setValue(
@ -83,33 +98,15 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const datasetNameColAfterSearch = colsAfterSearch['Data Set Name'];
const datasetNameColCellTextsAfterSearch = await datasetNameColAfterSearch.getCellTexts();
expect(datasetNameColCellTextsAfterSearch).to.eql([datasetNames[2]]);
await testSubjects.setValue(
PageObjects.datasetQuality.testSubjectSelectors.datasetQualityFilterBarFieldSearch,
''
);
// Reset the search field
await testSubjects.click('clearSearchButton');
});
it('filters for integration', async () => {
const apacheAccessDatasetName = 'apache.access';
const apacheAccessDatasetHumanName = 'Apache access logs';
const apacheIntegrationName = 'Apache HTTP Server';
await PageObjects.observabilityLogsExplorer.navigateTo();
// Add initial integrations
await PageObjects.observabilityLogsExplorer.setupInitialIntegrations();
// Index 10 logs for `logs-apache.access` dataset
await synthtrace.index(
getLogsForDataset({ to, count: 10, dataset: apacheAccessDatasetName })
);
await PageObjects.datasetQuality.navigateTo();
const cols = await PageObjects.datasetQuality.parseDatasetTable();
const datasetNameCol = cols['Data Set Name'];
const datasetNameColCellTexts = await datasetNameCol.getCellTexts();
expect(datasetNameColCellTexts).to.eql([apacheAccessDatasetHumanName, ...datasetNames]);
expect(datasetNameColCellTexts).to.eql(allDatasetNames);
// Filter for integration
await PageObjects.datasetQuality.filterForIntegrations([apacheIntegrationName]);
@ -118,64 +115,31 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const datasetNameColAfterFilter = colsAfterFilter['Data Set Name'];
const datasetNameColCellTextsAfterFilter = await datasetNameColAfterFilter.getCellTexts();
expect(datasetNameColCellTextsAfterFilter).to.eql([apacheAccessDatasetHumanName]);
// Reset the filter by selecting from the dropdown again
await PageObjects.datasetQuality.filterForIntegrations([apacheIntegrationName]);
});
it('filters for namespace', async () => {
const apacheAccessDatasetName = 'apache.access';
const datasetNamespace = 'prod';
await PageObjects.observabilityLogsExplorer.navigateTo();
// Add initial integrations
await PageObjects.observabilityLogsExplorer.setupInitialIntegrations();
// Index 10 logs for `logs-apache.access` dataset
await synthtrace.index(
getLogsForDataset({
to,
count: 10,
dataset: apacheAccessDatasetName,
namespace: datasetNamespace,
})
);
await PageObjects.datasetQuality.navigateTo();
// Get default namespaces
const cols = await PageObjects.datasetQuality.parseDatasetTable();
const namespaceCol = cols.Namespace;
const namespaceColCellTexts = await namespaceCol.getCellTexts();
expect(namespaceColCellTexts).to.contain(defaultNamespace);
expect(namespaceColCellTexts).to.contain(productionNamespace);
// Filter for prod namespace
await PageObjects.datasetQuality.filterForNamespaces([datasetNamespace]);
await PageObjects.datasetQuality.filterForNamespaces([productionNamespace]);
const colsAfterFilter = await PageObjects.datasetQuality.parseDatasetTable();
const namespaceColAfterFilter = colsAfterFilter.Namespace;
const namespaceColCellTextsAfterFilter = await namespaceColAfterFilter.getCellTexts();
expect(namespaceColCellTextsAfterFilter).to.eql([datasetNamespace]);
expect(namespaceColCellTextsAfterFilter).to.eql([productionNamespace]);
// Reset the namespace by selecting from the dropdown again
await PageObjects.datasetQuality.filterForNamespaces([productionNamespace]);
});
it('filters for quality', async () => {
const apacheAccessDatasetName = 'apache.access';
const expectedQuality = 'Poor';
// Add initial integrations
await PageObjects.observabilityLogsExplorer.setupInitialIntegrations();
// Index 10 logs for `logs-apache.access` dataset
await synthtrace.index(
getLogsForDataset({
to: new Date().toISOString(),
count: 10,
dataset: apacheAccessDatasetName,
isMalformed: true,
})
);
await PageObjects.datasetQuality.navigateTo();
// Get default quality
const cols = await PageObjects.datasetQuality.parseDatasetTable();
const datasetQuality = cols['Data Set Quality'];
@ -190,6 +154,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const datasetQualityCellTextsAfterFilter = await datasetQualityAfterFilter.getCellTexts();
expect(datasetQualityCellTextsAfterFilter).to.eql([expectedQuality]);
// Reset the namespace by selecting from the dropdown again
await PageObjects.datasetQuality.filterForQualities([expectedQuality]);
});
});
}