diff --git a/dashboards-observability/.cypress/integration/event_analytics.spec.js b/dashboards-observability/.cypress/integration/1_event_analytics.spec.js similarity index 100% rename from dashboards-observability/.cypress/integration/event_analytics.spec.js rename to dashboards-observability/.cypress/integration/1_event_analytics.spec.js diff --git a/dashboards-observability/.cypress/integration/notebooks.spec.js b/dashboards-observability/.cypress/integration/2_notebooks.spec.js similarity index 100% rename from dashboards-observability/.cypress/integration/notebooks.spec.js rename to dashboards-observability/.cypress/integration/2_notebooks.spec.js diff --git a/dashboards-observability/.cypress/integration/panels.spec.js b/dashboards-observability/.cypress/integration/3_panels.spec.js similarity index 100% rename from dashboards-observability/.cypress/integration/panels.spec.js rename to dashboards-observability/.cypress/integration/3_panels.spec.js diff --git a/dashboards-observability/.cypress/integration/trace_analytics_dashboard.spec.js b/dashboards-observability/.cypress/integration/4_trace_analytics_dashboard.spec.js similarity index 100% rename from dashboards-observability/.cypress/integration/trace_analytics_dashboard.spec.js rename to dashboards-observability/.cypress/integration/4_trace_analytics_dashboard.spec.js diff --git a/dashboards-observability/.cypress/integration/trace_analytics_services.spec.js b/dashboards-observability/.cypress/integration/5_trace_analytics_services.spec.js similarity index 98% rename from dashboards-observability/.cypress/integration/trace_analytics_services.spec.js rename to dashboards-observability/.cypress/integration/5_trace_analytics_services.spec.js index 6421982a9..b03e10e64 100644 --- a/dashboards-observability/.cypress/integration/trace_analytics_services.spec.js +++ b/dashboards-observability/.cypress/integration/5_trace_analytics_services.spec.js @@ -117,6 +117,7 @@ describe('Testing service view', () => { cy.wait(delay); cy.get('.euiBadge__text').contains('spanId: ').should('exist'); + cy.get('[data-test-subj="euiFlyoutCloseButton"]').click({ force: true }); cy.contains('Spans (1)').should('exist'); }); }); diff --git a/dashboards-observability/.cypress/integration/trace_analytics_traces.spec.js b/dashboards-observability/.cypress/integration/6_trace_analytics_traces.spec.js similarity index 100% rename from dashboards-observability/.cypress/integration/trace_analytics_traces.spec.js rename to dashboards-observability/.cypress/integration/6_trace_analytics_traces.spec.js diff --git a/dashboards-observability/.cypress/integration/app_analytics.spec.js b/dashboards-observability/.cypress/integration/7_app_analytics.spec.js similarity index 86% rename from dashboards-observability/.cypress/integration/app_analytics.spec.js rename to dashboards-observability/.cypress/integration/7_app_analytics.spec.js index c98096fcf..62db0adbe 100644 --- a/dashboards-observability/.cypress/integration/app_analytics.spec.js +++ b/dashboards-observability/.cypress/integration/7_app_analytics.spec.js @@ -16,6 +16,7 @@ import { baseQuery, nameOne, nameTwo, + nameThree, description, service_one, service_two, @@ -24,11 +25,13 @@ import { trace_three, query_one, query_two, + availability_default, visOneName, visTwoName, composition, newName, - TYPING_DELAY + TYPING_DELAY, + timeoutDelay } from '../utils/app_constants'; import { supressResizeObserverIssue } from '../utils/constants'; @@ -37,27 +40,6 @@ describe('Creating application', () => { moveToCreatePage(); }); - it('Disables create button if missing fields', () => { - expectMessageOnHover('Name is required.'); - cy.get('[data-test-subj="nameFormRow"]').type(nameOne); - expectMessageOnHover('Provide at least one log source, service, entity or trace group.'); - cy.get('[data-test-subj="servicesEntitiesAccordion"]').trigger('mouseover').click(); - cy.get('[data-test-subj="servicesEntitiesComboBox"]').trigger('mouseover').click(); - cy.focused().type('{downArrow}'); - cy.focused().type('{enter}'); - cy.get('[data-test-subj="servicesEntitiesCountBadge"]').should('contain', '1'); - cy.get('[data-test-subj="createButton"]').should('not.be.disabled'); - cy.get('[data-test-subj="logSourceAccordion"]').trigger('mouseover').click(); - cy.get('[data-test-subj="searchAutocompleteTextArea"]').focus().type(baseQuery, {delay: TYPING_DELAY}); - cy.get('[data-test-subj="traceGroupsAccordion"]').scrollIntoView(); - cy.get('[data-test-subj="traceGroupsAccordion"]').trigger('mouseover').click(); - cy.get('[data-test-subj="traceGroupsComboBox"]').scrollIntoView().type('http'); - cy.get('.euiFilterSelectItem').contains(trace_one).click({ force: true }); - cy.get('.euiFilterSelectItem').contains(trace_two).click({ force: true }); - cy.get('[data-test-subj="traceGroupsCountBadge"]').should('contain', '2'); - cy.get('[data-test-subj="createButton"]').should('not.be.disabled'); - }); - it('Suggests correct autocompletion', () => { cy.get('[data-test-subj="logSourceAccordion"]').trigger('mouseover').click(); cy.get('[data-test-subj="searchAutocompleteTextArea"]').click(); @@ -90,14 +72,19 @@ describe('Creating application', () => { }); it('Creates an application and redirects to application', () => { + expectMessageOnHover('createButton', 'Name is required.'); cy.get('[data-test-subj="nameFormRow"]').type(nameOne); cy.get('[data-test-subj="descriptionFormRow"]').type('This application is for testing.'); + expectMessageOnHover('createButton', 'Provide at least one log source, service, entity or trace group.'); cy.get('[data-test-subj="servicesEntitiesAccordion"]').trigger('mouseover').click(); cy.get('[data-test-subj="servicesEntitiesComboBox"]').click(); cy.focused().type('{downArrow}'); cy.focused().type('{enter}'); cy.get('[data-test-subj="servicesEntitiesCountBadge"]').should('contain', '1'); cy.get('[data-test-subj="logSourceAccordion"]').trigger('mouseover').click(); + cy.get('[data-test-subj="createButton"]').should('not.be.disabled'); + cy.get('[data-test-subj="createAndSetButton"]').should('be.disabled'); + expectMessageOnHover('createAndSetButton', 'Log source is required to set availability.'); cy.get('[data-test-subj="searchAutocompleteTextArea"]').focus().type(baseQuery, {delay: TYPING_DELAY}); cy.get('[data-test-subj="traceGroupsAccordion"]').trigger('mouseover').click(); cy.get('[data-test-subj="traceGroupsComboBox"]').scrollIntoView().type('http'); @@ -112,21 +99,6 @@ describe('Creating application', () => { cy.get('[data-test-subj="addFirstVisualizationText"]').should('exist'); }); - it('Hides application panels in Operational Panels', () => { - cy.visit( - `${Cypress.env('opensearchDashboards')}/app/observability-dashboards#/operational_panels/` - ); - cy.get('body').then(($body) => { - if ($body.find('[data-test-subj="customPanels__emptyCreateNewPanels"]').length == 1) { - cy.get('[data-test-subj="operationalPanelSearchBar"]').type(`${nameOne}'s Panel`, {delay: TYPING_DELAY}); - cy.wait(delay); - cy.get('.euiTableCellContent__text').contains('No items found').should('exist'); - cy.get('.euiFormControlLayoutClearButton').click(); - cy.wait(delay); - } - }); - }); - it('Redirects to home page on cancel', () => { cy.get('[data-test-subj="cancelCreateButton"]').contains('Cancel').click(); cy.get('[data-test-subj="applicationHomePageTitle"]').should('exist'); @@ -185,10 +157,64 @@ describe('Creating application', () => { cy.get('.euiButton--danger').contains('Clear all').click(); cy.get('[data-test-subj="traceGroupsCountBadge"]').should('contain', '0'); }); + + it('Saves time range for each application', () => { + cy.get('[data-test-subj="nameFormRow"]').type(nameTwo); + cy.get('[data-test-subj="logSourceAccordion"]').trigger('mouseover').click(); + cy.get('[data-test-subj="searchAutocompleteTextArea"]').focus().type(baseQuery, {delay: TYPING_DELAY}); + cy.get('[data-test-subj="createButton"]').should('not.be.disabled'); + cy.get('[data-test-subj="createButton"]').click(); + cy.get('[data-test-subj="applicationTitle"]').should('contain', nameTwo); + changeTimeTo24('weeks'); + cy.get('[data-test-subj="superDatePickerShowDatesButton"]').should('contain', 'Last 24 weeks'); + cy.get('.euiBreadcrumb[href="#/application_analytics"]').click(); + cy.wait(delay); + cy.get(`[data-test-subj="${nameOne}ApplicationLink"]`).click(); + cy.get('[data-test-subj="applicationTitle"]').should('contain', nameOne); + changeTimeTo24('months'); + cy.get('[data-test-subj="superDatePickerShowDatesButton"]').should('contain', 'Last 24 months'); + cy.get('.euiBreadcrumb[href="#/application_analytics"]').click(); + cy.get(`[data-test-subj="${nameTwo}ApplicationLink"]`).click(); + cy.get('[data-test-subj="applicationTitle"]').should('contain', nameTwo); + cy.get('[data-test-subj="superDatePickerShowDatesButton"]').should('contain', 'Last 24 weeks'); + cy.get('.euiBreadcrumb[href="#/application_analytics"]').click(); + cy.get(`[data-test-subj="${nameOne}ApplicationLink"]`).click(); + cy.get('[data-test-subj="applicationTitle"]').should('contain', nameOne); + cy.get('[data-test-subj="superDatePickerShowDatesButton"]').should('contain', 'Last 24 months'); + }); +}); + +describe('Setting availability', () => { + it('Redirects to set availability at three entry points', () => { + moveToCreatePage(); + cy.get('[data-test-subj="nameFormRow"]').type(nameThree); + cy.get('[data-test-subj="logSourceAccordion"]').trigger('mouseover').click(); + cy.get('[data-test-subj="searchAutocompleteTextArea"]').focus(); + cy.focused().type('source = ', { delay: TYPING_DELAY }); + cy.focused().type('{enter}'); + cy.get('[data-test-subj="createAndSetButton"]').click(); + cy.get('[data-test-subj="applicationTitle"]').should('contain', nameThree); + cy.get('.euiBreadcrumb[href="#/application_analytics"]').click(); + cy.get('[data-test-subj="setAvailabilityHomePageLink"]').first().click(); + cy.get('[data-test-subj="applicationTitle"]').should('contain', nameThree); + cy.get('.euiTab-isSelected[id="app-analytics-log"]').should('exist', { timeout: timeoutDelay }); + cy.get('[data-test-subj="searchAutocompleteTextArea"]').should('contain.value', availability_default); + cy.get('[id="explorerPlotComponent"]').should('exist'); + cy.get('.euiTab-isSelected[id="availability-panel"]').should('exist'); + cy.get('.euiBreadcrumb[href="#/application_analytics"]').click(); + cy.get(`[data-test-subj="${nameThree}ApplicationLink"]`).click(); + cy.get('[data-test-subj="applicationTitle"]').should('contain', nameThree); + cy.get('[data-test-subj="app-analytics-configTab"]').click(); + cy.get('[data-test-subj="setAvailabilityConfigLink"]').click(); + cy.get('.euiTab-isSelected[id="app-analytics-log"]').should('exist', { timeout: timeoutDelay }); + cy.get('[data-test-subj="searchAutocompleteTextArea"]').should('contain.value', availability_default); + cy.get('[id="explorerPlotComponent"]').should('exist'); + cy.get('.euiTab-isSelected[id="availability-panel"]').should('exist'); + }); }); describe('Viewing application', () => { - before(() => { + beforeEach(() => { moveToApplication(nameOne); }); @@ -202,7 +228,6 @@ describe('Viewing application', () => { }); it('Shares time range among tabs', () => { - moveToApplication(nameOne); changeTimeTo24('months'); cy.get('[data-test-subj="superDatePickerShowDatesButton"]').should('contain', 'Last 24 months'); cy.get('[data-test-subj="app-analytics-serviceTab"]').click(); @@ -217,38 +242,15 @@ describe('Viewing application', () => { }); it('Shows latency variance in dashboards table', () => { - moveToApplication(nameOne); changeTimeTo24('months'); cy.get('[data-test-subj="dashboardTable"]').first().within(($table) => { cy.get('.plot-container').should('have.length.at.least', 1); }) }); - it('Saves time range for each application', () => { - moveToCreatePage(); - cy.get('[data-test-subj="nameFormRow"]').type(nameTwo); - cy.get('[data-test-subj="logSourceAccordion"]').trigger('mouseover').click(); - cy.get('[data-test-subj="searchAutocompleteTextArea"]').focus().type(baseQuery, {delay: TYPING_DELAY}); - cy.get('[data-test-subj="createButton"]').should('not.be.disabled'); - cy.get('[data-test-subj="createButton"]').click(); - cy.get('[data-test-subj="applicationTitle"]').should('contain', nameTwo); - changeTimeTo24('weeks'); - cy.get('[data-test-subj="superDatePickerShowDatesButton"]').should('contain', 'Last 24 weeks'); - cy.get('.euiBreadcrumb[href="#/application_analytics"]').click(); - cy.wait(delay); - cy.get('.euiLink').contains(nameOne).click(); - cy.get('[data-test-subj="applicationTitle"]').should('contain', nameOne); - cy.get('[data-test-subj="superDatePickerShowDatesButton"]').should('contain', 'Last 24 months'); - cy.get('.euiBreadcrumb[href="#/application_analytics"]').click(); - cy.get('.euiLink').contains(nameTwo).click(); - cy.get('[data-test-subj="applicationTitle"]').should('contain', nameTwo); - cy.get('[data-test-subj="superDatePickerShowDatesButton"]').should('contain', 'Last 24 weeks'); - }); - it('Adds filter when Trace group name is clicked', () => { - moveToApplication(nameOne); cy.get('[data-test-subj="app-analytics-overviewTab"]').click(); - cy.get('.euiLink').contains('client_create_order').click(); + cy.get('[data-test-subj="dashboard-table-trace-group-name-button"]').contains('client_create_order').click(); cy.get('.euiTableRow').should('have.length', 1); cy.get('[data-test-subj="client_create_orderFilterBadge"]').should('exist'); cy.get('[data-test-subj="filterBadge"]').click(); @@ -337,6 +339,7 @@ describe('Viewing application', () => { cy.get('[data-test-subj="eventExplorer__querySaveName"]').click().type(visOneName); cy.wait(delay); cy.get('[data-test-subj="eventExplorer__querySaveConfirm"]').click(); + cy.wait(delay); cy.get('[data-test-subj="app-analytics-panelTab"]').click(); cy.wait(delay); cy.get('[data-test-subj="Flights to VeniceVisualizationPanel"]').should('exist'); @@ -350,10 +353,11 @@ describe('Viewing application', () => { cy.get('[data-test-subj="editVizContextMenuItem"]').click(); supressResizeObserverIssue(); cy.get('[data-test-subj="superDatePickerShowDatesButton"]').should('contain', 'Last 24 months'); + cy.get('.euiTab[id="availability-panel"]').click(); cy.get('[title="Bar"]').click(); cy.focused().type('{downArrow}'); cy.focused().type('{enter}'); - cy.get('[data-test-subj="addThresholdButton"]').click(); + cy.get('[data-test-subj="addAvailabilityButton"]').click(); cy.get('[data-test-subj="euiColorPickerAnchor"]').click(); cy.get('[aria-label="Select #54B399 as the color"]').click(); cy.get('[data-test-subj="nameFieldText"]').click().type('Available'); @@ -378,24 +382,20 @@ describe('Viewing application', () => { }); it('Saves visualization #2 to panel with availability level', () => { - moveToApplication(nameOne); changeTimeTo24('months'); cy.get('[data-test-subj="app-analytics-logTab"]').click(); - cy.wait(delay); - cy.get('[id="explorerPlotComponent"]').should('exist'); - cy.get('[data-test-subj="searchAutocompleteTextArea"]').clear(); - cy.get('[data-test-subj="searchAutocompleteTextArea"]').focus().type('x', {delay: TYPING_DELAY}); - cy.focused().clear(); + cy.get('[id="explorerPlotComponent"]', { timeout: timeoutDelay }).should('exist'); cy.get('[data-test-subj="searchAutocompleteTextArea"]').focus().type(query_two, {delay: TYPING_DELAY}); cy.get('[data-test-subj="superDatePickerApplyTimeButton"]').click(); cy.wait(delay); cy.get('[data-test-subj="main-content-visTab"]').click(); supressResizeObserverIssue(); + cy.get('.euiTab[id="availability-panel"]').click(); cy.get('[title="Bar"]').click(); cy.focused().type('{downArrow}'); cy.focused().type('{enter}'); cy.wait(delay); - cy.get('[data-test-subj="addThresholdButton"]').click(); + cy.get('[data-test-subj="addAvailabilityButton"]').click(); cy.get('[data-test-subj="euiColorPickerAnchor"]').click(); cy.get('[aria-label="Select #9170B8 as the color"]').click(); cy.wait(delay); @@ -404,7 +404,7 @@ describe('Viewing application', () => { cy.get('[data-test-subj="valueFieldNumber"]').clear().type('5.5'); cy.get('[data-test-subj="visualizeEditorRenderButton"]').click(); cy.wait(delay); - cy.get('[data-test-subj="addThresholdButton"]').click(); + cy.get('[data-test-subj="addAvailabilityButton"]').click(); cy.get('[data-test-subj="euiColorPickerAnchor"]').first().click(); cy.get('[aria-label="Select #CA8EAE as the color"]').click(); cy.wait(delay); @@ -426,8 +426,6 @@ describe('Viewing application', () => { }); it('Configuration tab shows details', () => { - cy.get(`[data-test-subj="${nameOne}ApplicationLink"]`).click(); - cy.wait(delay); cy.get('[data-test-subj="app-analytics-configTab"]').click(); cy.wait(delay); cy.get('[data-test-subj="configBaseQueryCode"]').should('contain', baseQuery); @@ -449,7 +447,9 @@ describe('Viewing application', () => { cy.wait(delay); cy.get('select').find('option:selected').should('have.text', visOneName); }) +}); +describe('Separate from other plugins', () => { it('Hides application visualizations in Event Analytics', () => { cy.visit(`${Cypress.env('opensearchDashboards')}/app/observability-dashboards#/event_analytics`); cy.wait(delay * 3); @@ -485,31 +485,34 @@ describe('Viewing application', () => { cy.visit( `${Cypress.env('opensearchDashboards')}/app/observability-dashboards#/operational_panels/` ); - cy.get('[data-test-subj="operationalPanelsActionsButton"]').click(); - cy.wait(delay); - cy.get('[data-test-subj="addSampleContextMenuItem"]').click(); - cy.wait(delay); - cy.get('[data-test-subj="confirmModalConfirmButton"]').click(); + cy.get('[data-test-subj="operationalPanelsActionsButton"]', { timeout: timeoutDelay }).click(); + cy.get('[data-test-subj="addSampleContextMenuItem"]', { timeout: timeoutDelay }).click(); + cy.get('[data-test-subj="confirmModalConfirmButton"]', { timeout: timeoutDelay }).click(); cy.wait(delay * 2); cy.get('.euiLink').contains('[Logs] Web traffic Panel').first().click(); - cy.wait(delay); + cy.wait(delay * 2); cy.get('[data-test-subj="addVisualizationButton"]').click(); cy.get('.euiContextMenuItem__text').contains('Select existing visualization').click(); cy.get('option').contains(visOneName).should('not.exist'); cy.get('option').contains(visTwoName).should('not.exist'); + }); + + it('Hides application panels in Operational Panels', () => { cy.visit( `${Cypress.env('opensearchDashboards')}/app/observability-dashboards#/operational_panels/` ); + cy.get('[data-test-subj="operationalPanelSearchBar"]', { timeout: timeoutDelay }).type(`${nameOne}'s Panel`, {delay: TYPING_DELAY}); + cy.get('.euiTableCellContent__text').contains('No items found').should('exist'); + cy.get('.euiFormControlLayoutClearButton').click(); cy.get('[data-test-subj="operationalPanelSearchBar"]').type('[Logs] Web traffic Panel', {delay: TYPING_DELAY}); - cy.wait(delay); cy.get('.euiTableRow').first().within(($row) => { cy.get('.euiCheckbox').click(); }); - cy.get('[data-test-subj="operationalPanelsActionsButton"]').click(); - cy.get('[data-test-subj="deleteContextMenuItem"]').click(); - cy.get('[data-test-subj="popoverModal__deleteTextInput"]').type('delete'); - cy.get('[data-test-subj="popoverModal__deleteButton"]').should('not.be.disabled'); - cy.get('[data-test-subj="popoverModal__deleteButton"]').click(); + cy.get('[data-test-subj="operationalPanelsActionsButton"]', { timeout: timeoutDelay }).click(); + cy.get('[data-test-subj="deleteContextMenuItem"]', { timeout: timeoutDelay }).click(); + cy.get('[data-test-subj="popoverModal__deleteTextInput"]', { timeout: timeoutDelay }).type('delete'); + cy.get('[data-test-subj="popoverModal__deleteButton"]', { timeout: timeoutDelay }).should('not.be.disabled'); + cy.get('[data-test-subj="popoverModal__deleteButton"]', { timeout: timeoutDelay }).click(); }); }); @@ -578,6 +581,9 @@ describe('Application Analytics home page', () => { cy.get('.euiTableRow').eq(1).within(($row) => { cy.get('.euiCheckbox').click(); }); + cy.get('.euiTableRow').eq(2).within(($row) => { + cy.get('.euiCheckbox').click(); + }); cy.get('[data-test-subj="appAnalyticsActionsButton"]').click(); cy.get('[data-test-subj="deleteApplicationContextMenuItem"]').click(); cy.get('[data-test-subj="confirmModalConfirmButton"]').click(); diff --git a/dashboards-observability/.cypress/utils/app_constants.js b/dashboards-observability/.cypress/utils/app_constants.js index a36a6fb50..f87432409 100644 --- a/dashboards-observability/.cypress/utils/app_constants.js +++ b/dashboards-observability/.cypress/utils/app_constants.js @@ -6,7 +6,7 @@ import { supressResizeObserverIssue } from './constants'; export const delay = 1000; - +export const timeoutDelay = 30000; export const TYPING_DELAY = 500; export const moveToHomePage = () => { @@ -47,11 +47,11 @@ export const changeTimeTo24 = (timeUnit) => { cy.get('[aria-label="Time unit"]').select(timeUnit); cy.get('.euiButton').contains('Apply').click(); cy.wait(delay); - cy.get('.euiButton').contains('Refresh').click(); + cy.get('[data-test-subj="superDatePickerApplyTimeButton"]').click(); }; -export const expectMessageOnHover = (message) => { - cy.get('.euiToolTipAnchor').contains('Create').click({ force: true }); +export const expectMessageOnHover = (button, message) => { + cy.get(`[data-test-subj="${button}"]`).click({ force: true }); cy.get('.euiToolTipPopover').contains(message).should('exist'); }; @@ -70,9 +70,11 @@ export const deleteAllSavedApplications = () => { cy.get('.euiButton__text').contains('Delete').click(); }; +export const uniqueId = Date.now(); export const baseQuery = 'source = opensearch_dashboards_sample_data_flights'; -export const nameOne = 'Cypress'; -export const nameTwo = 'Pine'; +export const nameOne = `Cypress-${uniqueId}`; +export const nameTwo = `Pine-${uniqueId}`; +export const nameThree = `Cedar-${uniqueId}`; export const description = 'This is my application for cypress testing.'; export const service_one = 'order'; export const service_two = 'payment'; @@ -81,7 +83,8 @@ export const trace_two = 'HTTP GET'; export const trace_three = 'client_pay_order'; export const query_one = 'where DestCityName = "Venice" | stats count() by span( timestamp , 6h )'; export const query_two = 'where OriginCityName = "Seoul" | stats count() by span( timestamp , 6h )'; +export const availability_default = 'stats count() by span( timestamp, 1h )'; export const visOneName = 'Flights to Venice'; export const visTwoName = 'Flights from Seoul'; export const composition = 'order, payment, HTTP POST, HTTP GET, client_pay_order' -export const newName = 'Monterey Cypress'; \ No newline at end of file +export const newName = `Monterey Cypress-${uniqueId}`; diff --git a/dashboards-observability/common/constants/explorer.ts b/dashboards-observability/common/constants/explorer.ts index df666d9d5..fbe09f13b 100644 --- a/dashboards-observability/common/constants/explorer.ts +++ b/dashboards-observability/common/constants/explorer.ts @@ -76,3 +76,4 @@ export const REDUX_EXPL_SLICE_VISUALIZATION = 'explorerVisualization'; export const REDUX_EXPL_SLICE_COUNT_DISTRIBUTION = 'countDistributionVisualization'; export const PLOTLY_GAUGE_COLUMN_NUMBER = 5; export const APP_ANALYTICS_TAB_ID_REGEX = /application-analytics-tab.+/; +export const DEFAULT_AVAILABILITY_QUERY = 'stats count() by span( timestamp, 1h )'; diff --git a/dashboards-observability/common/types/explorer.ts b/dashboards-observability/common/types/explorer.ts index fcf9017d2..d43aba751 100644 --- a/dashboards-observability/common/types/explorer.ts +++ b/dashboards-observability/common/types/explorer.ts @@ -110,6 +110,8 @@ export interface IExplorerProps { setStartTime?: any; setEndTime?: any; appBaseQuery?: string; + callback?: any; + callbackInApp?: any; } export interface SavedQuery { diff --git a/dashboards-observability/public/components/application_analytics/__tests__/__snapshots__/create.test.tsx.snap b/dashboards-observability/public/components/application_analytics/__tests__/__snapshots__/create.test.tsx.snap index c8710d638..fd2d3f4e4 100644 --- a/dashboards-observability/public/components/application_analytics/__tests__/__snapshots__/create.test.tsx.snap +++ b/dashboards-observability/public/components/application_analytics/__tests__/__snapshots__/create.test.tsx.snap @@ -1096,7 +1096,7 @@ Object { class="euiToolTipAnchor" > + + @@ -2325,7 +2349,7 @@ Object { class="euiToolTipAnchor" > + + @@ -3527,7 +3575,7 @@ Object { class="euiToolTipAnchor" > + + @@ -4670,7 +4742,7 @@ Object { class="euiToolTipAnchor" > + + @@ -5872,7 +5968,7 @@ Object { class="euiToolTipAnchor" > + + @@ -7015,7 +7135,7 @@ Object { class="euiToolTipAnchor" > + + @@ -8152,7 +8296,7 @@ Object { class="euiToolTipAnchor" > + + @@ -9232,7 +9400,7 @@ Object { class="euiToolTipAnchor" > + + @@ -10400,7 +10592,7 @@ Object { class="euiToolTipAnchor" > +
+ + + +
@@ -11510,7 +11725,7 @@ Object { class="euiToolTipAnchor" > +
+ + + +
@@ -12711,7 +12949,7 @@ Object { class="euiToolTipAnchor" > + + @@ -13854,7 +14116,7 @@ Object { class="euiToolTipAnchor" > + + @@ -15056,7 +15342,7 @@ Object { class="euiToolTipAnchor" > + + @@ -16199,7 +16509,7 @@ Object { class="euiToolTipAnchor" > + + @@ -17371,7 +17705,7 @@ Object { class="euiToolTipAnchor" > + + @@ -18596,7 +18954,7 @@ Object { class="euiToolTipAnchor" > + + diff --git a/dashboards-observability/public/components/application_analytics/components/app_table.tsx b/dashboards-observability/public/components/application_analytics/components/app_table.tsx index 5f6b33b37..27611aa76 100644 --- a/dashboards-observability/public/components/application_analytics/components/app_table.tsx +++ b/dashboards-observability/public/components/application_analytics/components/app_table.tsx @@ -39,6 +39,7 @@ import { getCustomModal } from '../../custom_panels/helpers/modal_containers'; import { getClearModal } from '../helpers/modal_containers'; import { pageStyles, UI_DATE_FORMAT } from '../../../../common/constants/shared'; import { ApplicationListType } from '../../../../common/types/app_analytics'; +import { AvailabilityType } from '../helpers/types'; interface AppTableProps extends AppAnalyticsComponentDeps { loading: boolean; @@ -47,6 +48,7 @@ interface AppTableProps extends AppAnalyticsComponentDeps { renameApplication: (newAppName: string, appId: string) => void; deleteApplication: (appList: string[], panelIdList: string[], toastMessage?: string) => void; clearStorage: () => void; + moveToApp: (id: string, type: string) => void; } export function AppTable(props: AppTableProps) { @@ -59,6 +61,7 @@ export function AppTable(props: AppTableProps) { deleteApplication, setFilters, clearStorage, + moveToApp, } = props; const [isModalVisible, setIsModalVisible] = useState(false); const [isActionsPopoverOpen, setIsActionsPopoverOpen] = useState(false); @@ -179,6 +182,34 @@ export function AppTable(props: AppTableProps) { // Add sample application, ]; + const renderAvailability = (value: AvailabilityType, record: ApplicationListType) => { + if (value.color === 'loading') { + return ; + } else if (value.name) { + return ( + + {value.name} + + ); + } else if (value.color === 'undefined') { + return No match; + } else if (value.color === 'null') { + return -; + } else { + return ( + moveToApp(record.id, 'createSetAvailability')} + > + Set Availability + + ); + } + }; + const tableColumns = [ { field: 'name', @@ -211,22 +242,7 @@ export function AppTable(props: AppTableProps) { field: 'availability', name: 'Current Availability', sortable: true, - render: (value, record) => { - if (value.name === 'loading') { - return ; - } else if (value.name) { - return ( - - {value.name} - - ); - } else { - return Undefined; - } - }, + render: renderAvailability, }, { field: 'dateModified', diff --git a/dashboards-observability/public/components/application_analytics/components/application.tsx b/dashboards-observability/public/components/application_analytics/components/application.tsx index a5d3c26d5..dd766ce0b 100644 --- a/dashboards-observability/public/components/application_analytics/components/application.tsx +++ b/dashboards-observability/public/components/application_analytics/components/application.tsx @@ -75,14 +75,6 @@ const searchBarConfigs = { }, }; -export interface DetailTab { - id: string; - label: string; - description: string; - onClick: () => void; - testId: string; -} - interface AppDetailProps extends AppAnalyticsComponentDeps { disabled?: boolean; appId: string; @@ -93,6 +85,7 @@ interface AppDetailProps extends AppAnalyticsComponentDeps { notifications: NotificationsStart; updateApp: (appId: string, updateAppData: Partial, type: string) => void; setToasts: (title: string, color?: string, text?: ReactChild) => void; + callback: (childfunction: () => void) => void; } export function Application(props: AppDetailProps) { @@ -113,6 +106,7 @@ export function Application(props: AppDetailProps) { setAppConfigs, setToasts, setFilters, + callback, } = props; const [application, setApplication] = useState({ name: '', @@ -124,6 +118,7 @@ export function Application(props: AppDetailProps) { availabilityVisId: '', }); const dispatch = useDispatch(); + const [triggerAvailability, setTriggerAvailability] = useState(false); const [selectedTabId, setSelectedTab] = useState(TAB_OVERVIEW_ID); const [serviceFlyoutName, setServiceFlyoutName] = useState(''); const [traceFlyoutId, setTraceFlyoutId] = useState(''); @@ -206,6 +201,7 @@ export function Application(props: AppDetailProps) { ); const tabId = `application-analytics-tab-${appId}`; initializeTabData(dispatch, tabId, NEW_TAB); + callback(switchToEvent); }, [appId]); useEffect(() => { @@ -373,6 +369,8 @@ export function Application(props: AppDetailProps) { setStartTime={setStartTimeForApp} setEndTime={setEndTimeForApp} appBaseQuery={application.baseQuery} + callback={callback} + callbackInApp={callbackInApp} curSelectedTabId={selectedTabId} /> ); @@ -422,13 +420,25 @@ export function Application(props: AppDetailProps) { } }; + const switchToAvailability = () => { + switchToEvent(); + setTriggerAvailability(true); + }; + + const callbackInApp = (childFunc: () => void) => { + if (childFunc && triggerAvailability) { + childFunc(); + setTriggerAvailability(false); + } + }; + const getConfig = () => { return ( diff --git a/dashboards-observability/public/components/application_analytics/components/configuration.tsx b/dashboards-observability/public/components/application_analytics/components/configuration.tsx index c155b115f..c8c4ce2cb 100644 --- a/dashboards-observability/public/components/application_analytics/components/configuration.tsx +++ b/dashboards-observability/public/components/application_analytics/components/configuration.tsx @@ -11,6 +11,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, + EuiLink, EuiPage, EuiPageBody, EuiPageContent, @@ -25,14 +26,14 @@ import { } from '@elastic/eui'; import { ApplicationType } from 'common/types/app_analytics'; import { last } from 'lodash'; -import React, { useEffect, useState } from 'react'; +import React, { useState } from 'react'; interface ConfigProps { appId: string; application: ApplicationType; parentBreadcrumbs: EuiBreadcrumb[]; visWithAvailability: EuiSelectOption[]; - switchToEditViz: (savedVizId: string) => void; + switchToAvailability: () => void; updateApp: (appId: string, updateAppData: Partial, type: string) => void; } @@ -43,12 +44,9 @@ export const Configuration = (props: ConfigProps) => { parentBreadcrumbs, visWithAvailability, updateApp, - switchToEditViz, + switchToAvailability, } = props; const [availabilityVisId, setAvailabilityVisId] = useState(application.availabilityVisId || ''); - useEffect(() => { - switchToEditViz(''); - }, []); const onAvailabilityVisChange = (event: any) => { setAvailabilityVisId(event.target.value); @@ -127,11 +125,20 @@ export const Configuration = (props: ConfigProps) => {

Availability

- + {visWithAvailability.length > 0 ? ( + + ) : ( + switchToAvailability()} + > + Set Availability + + )} diff --git a/dashboards-observability/public/components/application_analytics/components/create.tsx b/dashboards-observability/public/components/application_analytics/components/create.tsx index e247878de..390aa3612 100644 --- a/dashboards-observability/public/components/application_analytics/components/create.tsx +++ b/dashboards-observability/public/components/application_analytics/components/create.tsx @@ -39,7 +39,7 @@ interface CreateAppProps extends AppAnalyticsComponentDeps { dslService: DSLService; pplService: PPLService; setToasts: (title: string, color?: string, text?: ReactChild) => void; - createApp: (app: ApplicationType) => void; + createApp: (app: ApplicationType, type: string) => void; updateApp: (appId: string, updateAppData: Partial, type: string) => void; clearStorage: () => void; existingAppId: string; @@ -126,19 +126,21 @@ export const CreateApp = (props: CreateAppProps) => { const isDisabled = !name || (!query && !selectedTraces.length && !selectedServices.length); - const missingField = () => { - if (isDisabled) { - let popoverContent = ''; + const missingField = (needLog: boolean) => { + let popoverContent = ''; + if (isDisabled || (needLog && !query)) { if (!name) { popoverContent = 'Name is required.'; } else if (!query && !selectedServices.length && !selectedTraces.length) { popoverContent = 'Provide at least one log source, service, entity or trace group.'; + } else if (needLog && !query) { + popoverContent = 'Log source is required to set availability.'; } return

{popoverContent}

; } }; - const onCreate = () => { + const onCreate = (type: string) => { const appData = { name, description, @@ -148,7 +150,7 @@ export const CreateApp = (props: CreateAppProps) => { panelId: '', availabilityVisId: '', }; - createApp(appData); + createApp(appData, type); }; const onUpdate = () => { @@ -235,17 +237,31 @@ export const CreateApp = (props: CreateAppProps) => { - + onCreate('create')} + fill={editMode ? true : false} > {editMode ? 'Save' : 'Create'} + {editMode || ( + + + onCreate('createSetAvailability')} + > + Create and Set Availability + + + + )} diff --git a/dashboards-observability/public/components/application_analytics/helpers/types.tsx b/dashboards-observability/public/components/application_analytics/helpers/types.tsx new file mode 100644 index 000000000..07ac03c9d --- /dev/null +++ b/dashboards-observability/public/components/application_analytics/helpers/types.tsx @@ -0,0 +1,10 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export interface AvailabilityType { + name: string; + color: string; + mainVisId: string; +} diff --git a/dashboards-observability/public/components/application_analytics/helpers/utils.tsx b/dashboards-observability/public/components/application_analytics/helpers/utils.tsx index 970957418..d956fb06b 100644 --- a/dashboards-observability/public/components/application_analytics/helpers/utils.tsx +++ b/dashboards-observability/public/components/application_analytics/helpers/utils.tsx @@ -18,7 +18,10 @@ import { VisualizationType } from '../../../../common/types/custom_panels'; import { NEW_SELECTED_QUERY_TAB, TAB_CREATED_TYPE } from '../../../../common/constants/explorer'; import { APP_ANALYTICS_API_PREFIX } from '../../../../common/constants/application_analytics'; import { HttpSetup } from '../../../../../../src/core/public'; -import { init as initFields, remove as removefields } from '../../event_analytics/redux/slices/field_slice'; +import { + init as initFields, + remove as removefields, +} from '../../event_analytics/redux/slices/field_slice'; import { init as initVisualizationConfig, reset as resetVisualizationConfig, @@ -33,6 +36,7 @@ import { remove as removeQueryResult, } from '../../event_analytics/redux/slices/query_result_slice'; import { addTab, removeTab } from '../../event_analytics/redux/slices/query_tab_slice'; +import { AvailabilityType } from './types'; // Name validation export const isNameValid = (name: string, existingNames: string[]) => { @@ -193,7 +197,7 @@ export const calculateAvailability = async ( application: ApplicationType | ApplicationListType, availabilityVisId: string, setVisWithAvailability: (visList: EuiSelectOption[]) => void -): Promise<{ name: string; color: string; mainVisId: string }> => { +): Promise => { let availability = { name: '', color: '', mainVisId: '' }; const panelId = application.panelId; if (!panelId) return availability; @@ -204,15 +208,18 @@ export const calculateAvailability = async ( const visWithAvailability = []; let availabilityFound = false; for (let i = 0; i < savedVisualizationsIds.length; i++) { - let hasAvailability = false; const visualizationId = savedVisualizationsIds[i]; // Fetches data for visualization const visData = await fetchVisualizationById(http, visualizationId, (value: string) => console.error(value) ); - // If there are thresholds, we get the current value - if (visData.user_configs.dataConfig?.hasOwnProperty('thresholds')) { - const thresholds = visData.user_configs.dataConfig.thresholds.reverse(); + // If there are levels, we get the current value + if (visData.user_configs.availabilityConfig?.hasOwnProperty('level')) { + // For every saved visualization with availability levels we push it to visWithAvailability + // This is used to populate the options in configuration + visWithAvailability.push({ value: visualizationId, text: visData.name }); + + const levels = visData.user_configs.availabilityConfig.level.reverse(); let currValue = Number.MIN_VALUE; const finalQuery = preprocessQuery({ rawQuery: visData.query, @@ -236,72 +243,76 @@ export const calculateAvailability = async ( .catch((err) => { console.error(err); }); - // We check each threshold if it has expression which means it is an availability level - for (let j = 0; j < thresholds.length; j++) { - const threshold = thresholds[j]; - if (threshold.hasOwnProperty('expression')) { - hasAvailability = true; - // If there is an availiabilityVisId selected we only want to compute availability based on that - if (availabilityVisId ? availabilityVisId === visualizationId : true) { - if (threshold.value !== null) { - if (!availabilityFound && threshold.expression) { - const expression = threshold.expression; + for (let j = 0; j < levels.length; j++) { + const level = levels[j]; + // If there is an availiabilityVisId selected we only want to compute availability based on that + if (availabilityVisId ? availabilityVisId === visualizationId : true) { + if (level.value !== null) { + if (currValue === null) { + availability = { + name: '', + color: 'null', + mainVisId: '', + }; + } else { + if (!availabilityFound) { + const expression = level.expression; switch (expression) { case '≥': - if (currValue >= parseFloat(threshold.value)) { + if (currValue >= parseFloat(level.value)) { availability = { - name: threshold.name, - color: threshold.color, + name: level.name, + color: level.color, mainVisId: visualizationId, }; availabilityFound = true; } break; case '≤': - if (currValue <= parseFloat(threshold.value)) { + if (currValue <= parseFloat(level.value)) { availability = { - name: threshold.name, - color: threshold.color, + name: level.name, + color: level.color, mainVisId: visualizationId, }; availabilityFound = true; } break; case '>': - if (currValue > parseFloat(threshold.value)) { + if (currValue > parseFloat(level.value)) { availability = { - name: threshold.name, - color: threshold.color, + name: level.name, + color: level.color, mainVisId: visualizationId, }; availabilityFound = true; } break; case '<': - if (currValue < parseFloat(threshold.value)) { + if (currValue < parseFloat(level.value)) { availability = { - name: threshold.name, - color: threshold.color, + name: level.name, + color: level.color, mainVisId: visualizationId, }; availabilityFound = true; } break; case '=': - if (currValue === parseFloat(threshold.value)) { + if (currValue === parseFloat(level.value)) { availability = { - name: threshold.name, - color: threshold.color, + name: level.name, + color: level.color, mainVisId: visualizationId, }; availabilityFound = true; } break; case '≠': - if (currValue !== parseFloat(threshold.value)) { + if (currValue !== parseFloat(level.value)) { availability = { - name: threshold.name, - color: threshold.color, + name: level.name, + color: level.color, mainVisId: visualizationId, }; availabilityFound = true; @@ -314,12 +325,10 @@ export const calculateAvailability = async ( } } } - // For every saved visualization with availability levels we push it to visWithAvailability - if (hasAvailability) { - // This is used to populate the options in configuration - visWithAvailability.push({ value: visualizationId, text: visData.name }); - } } setVisWithAvailability(visWithAvailability); + if (!availabilityFound && visWithAvailability.length > 0) { + return { name: '', color: 'undefined', mainVisId: '' }; + } return availability; }; diff --git a/dashboards-observability/public/components/application_analytics/home.tsx b/dashboards-observability/public/components/application_analytics/home.tsx index d3074da8b..1d837d437 100644 --- a/dashboards-observability/public/components/application_analytics/home.tsx +++ b/dashboards-observability/public/components/application_analytics/home.tsx @@ -67,6 +67,7 @@ export const Home = (props: HomeProps) => { chrome, notifications, } = props; + const [triggerSwitchToEvent, setTriggerSwitchToEvent] = useState(0); const dispatch = useDispatch(); const [applicationList, setApplicationList] = useState([]); const [toasts, setToasts] = useState([]); @@ -145,7 +146,14 @@ export const Home = (props: HomeProps) => { setQueryWithStorage(''); }; - const createPanelForApp = (applicationId: string, appName: string) => { + const moveToApp = (id: string, type: string) => { + window.location.assign(`${last(parentBreadcrumbs)!.href}application_analytics/${id}`); + if (type === 'createSetAvailability') { + setTriggerSwitchToEvent(2); + } + }; + + const createPanelForApp = (applicationId: string, appName: string, type: string) => { return http .post(`${CUSTOM_PANELS_API_PREFIX}/panels`, { body: JSON.stringify({ @@ -154,7 +162,7 @@ export const Home = (props: HomeProps) => { }), }) .then((res) => { - updateApp(applicationId, { panelId: res.newPanelId }, 'addPanel'); + updateApp(applicationId, { panelId: res.newPanelId }, type); }) .catch((err) => { setToast( @@ -203,7 +211,7 @@ export const Home = (props: HomeProps) => { const mainVisIdStore: Record = {}; for (let i = 0; i < res.data.length; i++) { mainVisIdStore[res.data[i].id] = res.data[i].availability.mainVisId; - res.data[i].availability = { name: 'loading', color: '', mainVisId: '' }; + res.data[i].availability = { name: '', color: 'loading', mainVisId: '' }; } setApplicationList(res.data); for (let i = res.data.length - 1; i > -1; i--) { @@ -228,7 +236,7 @@ export const Home = (props: HomeProps) => { }; // Create a new application - const createApp = (application: ApplicationType) => { + const createApp = (application: ApplicationType, type: string) => { const toast = isNameValid( application.name, applicationList.map((obj) => obj.name) @@ -252,7 +260,7 @@ export const Home = (props: HomeProps) => { body: JSON.stringify(requestBody), }) .then(async (res) => { - createPanelForApp(res.newAppId, application.name); + createPanelForApp(res.newAppId, application.name, type); setToast(`Application "${application.name}" successfully created!`); clearStorage(); }) @@ -314,11 +322,10 @@ export const Home = (props: HomeProps) => { if (type === 'update') { setToast('Application successfully updated.'); clearStorage(); + moveToApp(res.updatedAppId, type); } - if (type !== 'editAvailability') { - window.location.assign( - `${last(parentBreadcrumbs)!.href}application_analytics/${res.updatedAppId}` - ); + if (type.startsWith('create')) { + moveToApp(res.updatedAppId, type); } }) .catch((err) => { @@ -355,6 +362,13 @@ export const Home = (props: HomeProps) => { }); }; + const callback = (childFunc: () => void) => { + if (childFunc && triggerSwitchToEvent > 0) { + childFunc(); + setTriggerSwitchToEvent(triggerSwitchToEvent - 1); + } + }; + return (
{ renameApplication={renameApp} deleteApplication={deleteApp} clearStorage={clearStorage} + moveToApp={moveToApp} {...commonProps} /> @@ -412,6 +427,7 @@ export const Home = (props: HomeProps) => { notifications={notifications} setToasts={setToast} updateApp={updateApp} + callback={callback} {...commonProps} /> )} diff --git a/dashboards-observability/public/components/common/search/date_picker.tsx b/dashboards-observability/public/components/common/search/date_picker.tsx index 2d3ec8b19..75087c9f1 100644 --- a/dashboards-observability/public/components/common/search/date_picker.tsx +++ b/dashboards-observability/public/components/common/search/date_picker.tsx @@ -11,7 +11,7 @@ import { uiSettingsService } from '../../../../common/utils'; export function DatePicker(props: IDatePickerProps) { const { startTime, endTime, handleTimePickerChange, handleTimeRangePickerRefresh } = props; - const handleTimeChange = (e) => handleTimePickerChange([e.start, e.end]); + const handleTimeChange = (e: any) => handleTimePickerChange([e.start, e.end]); return ( void; setIsOutputStale: () => void; handleTimePickerChange: (timeRange: string[]) => any; + handleTimeRangePickerRefresh: () => any; } export const Search = (props: any) => { @@ -119,10 +120,10 @@ export const Search = (props: any) => { const liveButton = ( ); @@ -192,10 +193,7 @@ export const Search = (props: any) => { )} {isLiveTailOn && ( - + )} {showSaveButton && searchBarConfigs[selectedSubTabId]?.showSaveButton && ( diff --git a/dashboards-observability/public/components/custom_panels/helpers/__tests__/__snapshots__/utils.test.tsx.snap b/dashboards-observability/public/components/custom_panels/helpers/__tests__/__snapshots__/utils.test.tsx.snap index aa8a1a9f4..5a513f839 100644 --- a/dashboards-observability/public/components/custom_panels/helpers/__tests__/__snapshots__/utils.test.tsx.snap +++ b/dashboards-observability/public/components/custom_panels/helpers/__tests__/__snapshots__/utils.test.tsx.snap @@ -170,6 +170,12 @@ exports[`Utils helper functions renders displayVisualization function 1`] = ` "mapTo": "layoutConfig", "name": "Layout", }, + Object { + "editor": [Function], + "id": "availability-panel", + "mapTo": "availabilityConfig", + "name": "Availability", + }, ], }, "fullLabel": "Bar", @@ -378,6 +384,12 @@ exports[`Utils helper functions renders displayVisualization function 1`] = ` "mapTo": "layoutConfig", "name": "Layout", }, + Object { + "editor": [Function], + "id": "availability-panel", + "mapTo": "availabilityConfig", + "name": "Availability", + }, ], }, "fullLabel": "Bar", @@ -609,6 +621,12 @@ exports[`Utils helper functions renders displayVisualization function 1`] = ` "mapTo": "layoutConfig", "name": "Layout", }, + Object { + "editor": [Function], + "id": "availability-panel", + "mapTo": "availabilityConfig", + "name": "Availability", + }, ], }, "fullLabel": "Bar", @@ -983,6 +1001,12 @@ exports[`Utils helper functions renders displayVisualization function 2`] = ` "mapTo": "layoutConfig", "name": "Layout", }, + Object { + "editor": [Function], + "id": "availability-panel", + "mapTo": "availabilityConfig", + "name": "Availability", + }, ], }, "fullLabel": "Line", @@ -1208,6 +1232,12 @@ exports[`Utils helper functions renders displayVisualization function 2`] = ` "mapTo": "layoutConfig", "name": "Layout", }, + Object { + "editor": [Function], + "id": "availability-panel", + "mapTo": "availabilityConfig", + "name": "Availability", + }, ], }, "fullLabel": "Line", @@ -1487,6 +1517,12 @@ exports[`Utils helper functions renders displayVisualization function 2`] = ` "mapTo": "layoutConfig", "name": "Layout", }, + Object { + "editor": [Function], + "id": "availability-panel", + "mapTo": "availabilityConfig", + "name": "Availability", + }, ], }, "fullLabel": "Line", diff --git a/dashboards-observability/public/components/event_analytics/explorer/explorer.tsx b/dashboards-observability/public/components/event_analytics/explorer/explorer.tsx index e406a8d85..a7fd42262 100644 --- a/dashboards-observability/public/components/event_analytics/explorer/explorer.tsx +++ b/dashboards-observability/public/components/event_analytics/explorer/explorer.tsx @@ -3,7 +3,6 @@ * SPDX-License-Identifier: Apache-2.0 */ /* eslint-disable react-hooks/exhaustive-deps */ -/* eslint-disable no-console */ import './explorer.scss'; import React, { useState, useMemo, useEffect, useRef, useCallback, ReactElement } from 'react'; @@ -50,11 +49,15 @@ import { EVENT_ANALYTICS_DOCUMENTATION_URL, TAB_EVENT_ID, TAB_CHART_ID, - INDEX, - FINAL_QUERY, + DEFAULT_AVAILABILITY_QUERY, DATE_PICKER_FORMAT, } from '../../../../common/constants/explorer'; -import { PPL_STATS_REGEX, PPL_NEWLINE_REGEX, LIVE_OPTIONS, LIVE_END_TIME } from '../../../../common/constants/shared'; +import { + PPL_STATS_REGEX, + PPL_NEWLINE_REGEX, + LIVE_OPTIONS, + LIVE_END_TIME, +} from '../../../../common/constants/shared'; import { getIndexPatternFromRawQuery, preprocessQuery, buildQuery } from '../../../../common/utils'; import { useFetchEvents, useFetchVisualizations } from '../hooks'; import { changeQuery, changeDateRange, selectQueries } from '../redux/slices/query_slice'; @@ -71,10 +74,7 @@ import { change as updateVizConfig } from '../redux/slices/viualization_config_s import { IExplorerProps, IVisualizationContainerProps } from '../../../../common/types/explorer'; import { TabContext } from '../hooks'; import { getVizContainerProps } from '../../visualizations/charts/helpers'; -import { - parseGetSuggestions, - onItemSelect, -} from '../../common/search/autocomplete_logic'; +import { parseGetSuggestions, onItemSelect } from '../../common/search/autocomplete_logic'; import { formatError } from '../utils'; import { sleep } from '../../common/live_tail/live_tail_button'; @@ -103,6 +103,8 @@ export const Explorer = ({ endTime, setStartTime, setEndTime, + callback, + callbackInApp, }: IExplorerProps) => { const dispatch = useDispatch(); const requestParams = { tabId }; @@ -138,8 +140,11 @@ export const Explorer = ({ const [liveHits, setLiveHits] = useState(0); const [browserTabFocus, setBrowserTabFocus] = useState(true); const [liveTimestamp, setLiveTimestamp] = useState(DATE_PICKER_FORMAT); + const [triggerAvailability, setTriggerAvailability] = useState(false); const queryRef = useRef(); + const appBasedRef = useRef(''); + appBasedRef.current = appBaseQuery; const selectedPanelNameRef = useRef(''); const explorerFieldsRef = useRef(); const isLiveTailOnRef = useRef(false); @@ -159,7 +164,7 @@ export const Explorer = ({ const momentStart = dateMath.parse(start)!; const momentEnd = dateMath.parse(end)!; const diffSeconds = momentEnd.unix() - momentStart.unix(); - + // less than 1 second if (diffSeconds <= 1) minInterval = 'ms'; // less than 2 minutes @@ -174,7 +179,7 @@ export const Explorer = ({ else if (diffSeconds <= 86400 * 93) minInterval = 'w'; // less than 1 year else if (diffSeconds <= 86400 * 366) minInterval = 'M'; - + setTimeIntervalOptions([ { text: 'Auto', value: 'auto_' + minInterval }, ...TIME_INTERVAL_OPTIONS, @@ -182,11 +187,10 @@ export const Explorer = ({ }; useEffect(() => { - document.addEventListener("visibilitychange", function() { + document.addEventListener('visibilitychange', function () { if (document.hidden) { setBrowserTabFocus(false); - } - else { + } else { setBrowserTabFocus(true); } }); @@ -199,7 +203,7 @@ export const Explorer = ({ timeField: string, isLiveQuery: boolean ) => { - const fullQuery = buildQuery(appBaseQuery, curQuery![RAW_QUERY]); + const fullQuery = buildQuery(appBasedRef.current, curQuery![RAW_QUERY]); if (isEmpty(fullQuery)) return ''; return preprocessQuery({ rawQuery: fullQuery, @@ -224,7 +228,7 @@ export const Explorer = ({ const currQuery = appLogEvents ? objectData?.query.replace(appBaseQuery + '| ', '') : objectData?.query || ''; - + if (appLogEvents) { if (objectData?.selected_date_range?.start && objectData?.selected_date_range?.end) { setStartTime(objectData.selected_date_range.start); @@ -298,9 +302,9 @@ export const Explorer = ({ indexPattern: string ): Promise => await timestampUtils.getTimestamp(indexPattern); - const fetchData = async (startTime?: string, endTime?: string) => { + const fetchData = async (startingTime?: string, endingTime?: string) => { const curQuery = queryRef.current; - const rawQueryStr = buildQuery(appBaseQuery, curQuery![RAW_QUERY]); + const rawQueryStr = buildQuery(appBasedRef.current, curQuery![RAW_QUERY]); const curIndex = getIndexPatternFromRawQuery(rawQueryStr); if (isEmpty(rawQueryStr)) return; @@ -323,16 +327,16 @@ export const Explorer = ({ } } - if ((isEqual(typeof startTime, 'undefined')) && (isEqual(typeof endTime, 'undefined'))) { - startTime = curQuery![SELECTED_DATE_RANGE][0]; - endTime = curQuery![SELECTED_DATE_RANGE][1]; + if (isEqual(typeof startingTime, 'undefined') && isEqual(typeof endingTime, 'undefined')) { + startingTime = curQuery![SELECTED_DATE_RANGE][0]; + endingTime = curQuery![SELECTED_DATE_RANGE][1]; } // compose final query const finalQuery = composeFinalQuery( curQuery, - startTime, - endTime, + startingTime!, + endingTime!, curTimestamp, isLiveTailOnRef.current ); @@ -348,12 +352,12 @@ export const Explorer = ({ ); // search - if (rawQueryStr.match(PPL_STATS_REGEX)) { + if (finalQuery.match(PPL_STATS_REGEX)) { getVisualizations(); getAvailableFields(`search source=${curIndex}`); } else { findAutoInterval(startTime, endTime); - if (isLiveTailOnRef.current){ + if (isLiveTailOnRef.current) { getLiveTail(undefined, (error) => { const formattedError = formatError(error.name, error.message, error.body.message); notifications.toasts.addError(formattedError, { @@ -372,7 +376,7 @@ export const Explorer = ({ } // for comparing usage if for the same tab, user changed index from one to another - if (!isLiveTailOnRef.current){ + if (!isLiveTailOnRef.current) { setPrevIndex(curTimestamp); if (!queryRef.current!.isLoaded) { dispatch( @@ -394,6 +398,25 @@ export const Explorer = ({ await getSavedDataById(objectId); }; + const prepareAvailability = async () => { + setSelectedContentTab(TAB_CHART_ID); + setTriggerAvailability(true); + await setTempQuery(DEFAULT_AVAILABILITY_QUERY); + await updateQueryInStore(DEFAULT_AVAILABILITY_QUERY); + await handleTimeRangePickerRefresh(true); + }; + + useEffect(() => { + if (!isEmpty(appBasedRef.current)) { + if (callback) { + callback(() => prepareAvailability()); + } + if (callbackInApp) { + callbackInApp(() => prepareAvailability()); + } + } + }, [appBasedRef.current]); + useEffect(() => { if (queryRef.current!.isLoaded) return; let objectId; @@ -413,29 +436,10 @@ export const Explorer = ({ if (appLogEvents) { if (savedObjectId) { updateTabData(savedObjectId); - } else { - setTempQuery(''); - emptyTab(); } } }, [savedObjectId]); - const emptyTab = async () => { - await dispatch( - changeQuery({ - tabId, - query: { - [RAW_QUERY]: '', - [FINAL_QUERY]: '', - [INDEX]: '', - [SELECTED_TIMESTAMP]: '', - [SAVED_OBJECT_ID]: '', - }, - }) - ); - await fetchData(); - }; - const handleAddField = (field: IField) => toggleFields(field, AVAILABLE_FIELDS, SELECTED_FIELDS); const handleRemoveField = (field: IField) => @@ -467,8 +471,8 @@ export const Explorer = ({ ); }; - const handleTimeRangePickerRefresh = () => { - handleQuerySearch(); + const handleTimeRangePickerRefresh = (availability?: boolean) => { + handleQuerySearch(availability); }; /** @@ -535,17 +539,19 @@ export const Explorer = ({ }; const totalHits: number = useMemo(() => { - if (isLiveTailOn && countDistribution?.data) { - let hits = reduce( - countDistribution['data']['count()'], - (sum, n) => { - return sum + n; - }, - liveHits - ) - setLiveHits(hits); - return hits - }} , [countDistribution?.data]); + if (isLiveTailOn && countDistribution?.data) { + const hits = reduce( + countDistribution.data['count()'], + (sum, n) => { + return sum + n; + }, + liveHits + ); + setLiveHits(hits); + return hits; + } + return 0; + }, [countDistribution?.data]); const getMainContent = () => { return ( @@ -637,24 +643,24 @@ export const Explorer = ({
{isLiveTailOnRef.current && ( - <> - - - - -   Live streaming - - - { } } /> - - - since {liveTimestamp} - - - + <> + + + + +   Live streaming + + + {}} + /> + + since {liveTimestamp} + + + )} void) => { + if (childFunc && triggerAvailability) { + childFunc(); + setTriggerAvailability(false); + } + }; + const getExplorerVis = () => { return ( ); }; @@ -786,18 +801,23 @@ export const Explorer = ({ ); }; - const handleQuerySearch = useCallback(async () => { - // clear previous selected timestamp when index pattern changes - if ( - !isEmpty(tempQuery) && - !isEmpty(query[RAW_QUERY]) && - isIndexPatternChanged(tempQuery, query[RAW_QUERY]) - ) { - await updateCurrentTimeStamp(''); - } - await updateQueryInStore(tempQuery); - fetchData(); - }, [tempQuery, query[RAW_QUERY]]); + const handleQuerySearch = useCallback( + async (availability?: boolean) => { + // clear previous selected timestamp when index pattern changes + if ( + !isEmpty(tempQuery) && + !isEmpty(query[RAW_QUERY]) && + isIndexPatternChanged(tempQuery, query[RAW_QUERY]) + ) { + await updateCurrentTimeStamp(''); + } + if (availability !== true) { + await updateQueryInStore(tempQuery); + } + fetchData(); + }, + [tempQuery, query[RAW_QUERY]] + ); const handleQueryChange = async (newQuery: string) => { setTempQuery(newQuery); @@ -1019,21 +1039,21 @@ export const Explorer = ({ const liveTailLoop = async ( name: string, - startTime: string, - endTime: string, + startingTime: string, + endingTime: string, delayTime: number ) => { setLiveTailName(name); - setLiveTailTabId(curSelectedTabId.current); + setLiveTailTabId((curSelectedTabId.current as unknown) as string); setIsLiveTailOn(true); setToast('Live tail On', 'success'); setIsLiveTailPopoverOpen(false); - setLiveTimestamp(dateMath.parse(endTime)?.utc().format(DATE_PICKER_FORMAT)); + setLiveTimestamp(dateMath.parse(endingTime)?.utc().format(DATE_PICKER_FORMAT) || ''); setLiveHits(0); await sleep(2000); const curLiveTailname = liveTailNameRef.current; while (isLiveTailOnRef.current === true && curLiveTailname === liveTailNameRef.current) { - handleLiveTailSearch(startTime, endTime); + handleLiveTailSearch(startingTime, endingTime); if (liveTailTabIdRef.current !== curSelectedTabId.current) { setIsLiveTailOn(false); isLiveTailOnRef.current = false; @@ -1050,36 +1070,36 @@ export const Explorer = ({ setLiveHits(0); setIsLiveTailPopoverOpen(false); if (isLiveTailOnRef.current) setToast('Live tail Off', 'danger'); - } + }; useEffect(() => { - if ((isEqual(selectedContentTabId, TAB_CHART_ID)) || (!browserTabFocus)) { + if (isEqual(selectedContentTabId, TAB_CHART_ID) || !browserTabFocus) { stopLive(); } }, [selectedContentTabId, browserTabFocus]); - //stop live tail if the page is moved using breadcrumbs - let lastUrl = location.href; + // stop live tail if the page is moved using breadcrumbs + let lastUrl = location.href; new MutationObserver(() => { const url = location.href; - if (url !== lastUrl) { - lastUrl = url; - stopLive(); - } - }).observe(document, {subtree: true, childList: true}); + if (url !== lastUrl) { + lastUrl = url; + stopLive(); + } + }).observe(document, { subtree: true, childList: true }); const popoverItems: ReactElement[] = LIVE_OPTIONS.map((e) => { return ( - { - liveTailLoop(e.label, e.startTime, LIVE_END_TIME, e.delayTime); - }} - data-test-subj={'eventLiveTail__delay'+e.label} - > - {e.label} - - ) + { + liveTailLoop(e.label, e.startTime, LIVE_END_TIME, e.delayTime); + }} + data-test-subj={'eventLiveTail__delay' + e.label} + > + {e.label} + + ); }); const dateRange = @@ -1090,9 +1110,9 @@ export const Explorer = ({ : [startTime, endTime]; const handleLiveTailSearch = useCallback( - async (startTime: string, endTime: string) => { + async (startingTime: string, endingTime: string) => { await updateQueryInStore(tempQuery); - fetchData(startTime, endTime); + fetchData(startingTime, endingTime); }, [tempQuery] ); @@ -1153,4 +1173,4 @@ export const Explorer = ({
); -}; \ No newline at end of file +}; diff --git a/dashboards-observability/public/components/event_analytics/explorer/sidebar/sidebar.tsx b/dashboards-observability/public/components/event_analytics/explorer/sidebar/sidebar.tsx index 9a49ca926..c949c9e49 100644 --- a/dashboards-observability/public/components/event_analytics/explorer/sidebar/sidebar.tsx +++ b/dashboards-observability/public/components/event_analytics/explorer/sidebar/sidebar.tsx @@ -15,6 +15,7 @@ import { Field } from './field'; import { IExplorerFields, IField } from '../../../../../common/types/explorer'; interface ISidebarProps { + query: string; explorerFields: IExplorerFields; explorerData: any; selectedTimestamp: string; diff --git a/dashboards-observability/public/components/event_analytics/explorer/visualizations/config_panel/__tests__/__snapshots__/config_panel.test.tsx.snap b/dashboards-observability/public/components/event_analytics/explorer/visualizations/config_panel/__tests__/__snapshots__/config_panel.test.tsx.snap index 69eda470d..065ec0e9c 100644 --- a/dashboards-observability/public/components/event_analytics/explorer/visualizations/config_panel/__tests__/__snapshots__/config_panel.test.tsx.snap +++ b/dashboards-observability/public/components/event_analytics/explorer/visualizations/config_panel/__tests__/__snapshots__/config_panel.test.tsx.snap @@ -282,6 +282,12 @@ exports[`Config panel component Renders config panel with visualization data 1`] "mapTo": "layoutConfig", "name": "Layout", }, + Object { + "editor": [Function], + "id": "availability-panel", + "mapTo": "availabilityConfig", + "name": "Availability", + }, ], }, "fullLabel": "Bar", @@ -452,6 +458,12 @@ exports[`Config panel component Renders config panel with visualization data 1`] "mapTo": "layoutConfig", "name": "Layout", }, + Object { + "editor": [Function], + "id": "availability-panel", + "mapTo": "availabilityConfig", + "name": "Availability", + }, ], }, "fullLabel": "Bar", @@ -574,6 +586,12 @@ exports[`Config panel component Renders config panel with visualization data 1`] "mapTo": "layoutConfig", "name": "Layout", }, + Object { + "editor": [Function], + "id": "availability-panel", + "mapTo": "availabilityConfig", + "name": "Availability", + }, ], }, "fullLabel": "Line", @@ -1030,6 +1048,12 @@ exports[`Config panel component Renders config panel with visualization data 1`] "mapTo": "layoutConfig", "name": "Layout", }, + Object { + "editor": [Function], + "id": "availability-panel", + "mapTo": "availabilityConfig", + "name": "Availability", + }, ], }, "fullLabel": "Bar", @@ -1197,6 +1221,12 @@ exports[`Config panel component Renders config panel with visualization data 1`] "mapTo": "layoutConfig", "name": "Layout", }, + Object { + "editor": [Function], + "id": "availability-panel", + "mapTo": "availabilityConfig", + "name": "Availability", + }, ], }, "fullLabel": "Bar", @@ -1374,6 +1404,12 @@ exports[`Config panel component Renders config panel with visualization data 1`] "mapTo": "layoutConfig", "name": "Layout", }, + Object { + "editor": [Function], + "id": "availability-panel", + "mapTo": "availabilityConfig", + "name": "Availability", + }, ], } } @@ -1481,6 +1517,12 @@ exports[`Config panel component Renders config panel with visualization data 1`] "mapTo": "layoutConfig", "name": "Layout", }, + Object { + "editor": [Function], + "id": "availability-panel", + "mapTo": "availabilityConfig", + "name": "Availability", + }, ], }, "fullLabel": "Bar", @@ -1652,6 +1694,12 @@ exports[`Config panel component Renders config panel with visualization data 1`] "mapTo": "layoutConfig", "name": "Layout", }, + Object { + "editor": [Function], + "id": "availability-panel", + "mapTo": "availabilityConfig", + "name": "Availability", + }, ], } } @@ -1794,6 +1842,12 @@ exports[`Config panel component Renders config panel with visualization data 1`] "mapTo": "layoutConfig", "name": "Layout", }, + Object { + "editor": [Function], + "id": "availability-panel", + "mapTo": "availabilityConfig", + "name": "Availability", + }, ], } } @@ -2069,9 +2123,10 @@ exports[`Config panel component Renders config panel with visualization data 1`] className="euiPanel euiPanel--paddingSmall euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow" > , + "id": "availability-panel", + "name": "Availability", + }, ] } >
+ + +