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 bb3c43490..638eac65f 100644 --- a/dashboards-observability/.cypress/integration/trace_analytics_services.spec.js +++ b/dashboards-observability/.cypress/integration/5_trace_analytics_services.spec.js @@ -118,6 +118,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 bb190a5c1..4f76e94eb 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,10 +25,13 @@ import { trace_three, query_one, query_two, + availability_default, visOneName, visTwoName, composition, newName, + TYPING_DELAY, + timeoutDelay } from '../utils/app_constants'; import { supressResizeObserverIssue } from '../utils/constants'; @@ -36,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(); @@ -89,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'); @@ -111,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'); @@ -184,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); }); @@ -201,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(); @@ -216,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(); @@ -336,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'); @@ -349,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'); @@ -377,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); @@ -403,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); @@ -425,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); @@ -448,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); @@ -484,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 +582,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 626e7be65..f2a4e9c6a 100644 --- a/dashboards-observability/.cypress/utils/app_constants.js +++ b/dashboards-observability/.cypress/utils/app_constants.js @@ -6,6 +6,8 @@ import { supressResizeObserverIssue } from './constants'; export const delay = 1000; +export const timeoutDelay = 30000; +export const TYPING_DELAY = 500; export const moveToHomePage = () => { cy.visit(`${Cypress.env('opensearchDashboards')}/app/observability-dashboards#/application_analytics/`); @@ -43,11 +45,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'); }; @@ -58,9 +60,19 @@ export const moveToPanelHome = () => { cy.wait(delay * 3); }; +export const deleteAllSavedApplications = () => { + moveToHomePage(); + cy.get('[data-test-subj="checkboxSelectAll"]').click(); + cy.get('.euiPopover').contains('Actions').click(); + cy.get('.euiContextMenuItem').contains('Delete').click(); + 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'; @@ -69,7 +81,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 581d2733c..079cdc1b1 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 @@ -1087,7 +1087,7 @@ Object { class="euiToolTipAnchor" > + + @@ -2309,7 +2333,7 @@ Object { class="euiToolTipAnchor" > + + @@ -3501,7 +3549,7 @@ Object { class="euiToolTipAnchor" > + + @@ -4634,7 +4706,7 @@ Object { class="euiToolTipAnchor" > + + @@ -5826,7 +5922,7 @@ Object { class="euiToolTipAnchor" > + + @@ -6959,7 +7079,7 @@ Object { class="euiToolTipAnchor" > + + @@ -8087,7 +8231,7 @@ Object { class="euiToolTipAnchor" > + + @@ -9158,7 +9326,7 @@ Object { class="euiToolTipAnchor" > + + @@ -10317,7 +10509,7 @@ Object { class="euiToolTipAnchor" > +
+ + + +
@@ -11418,7 +11633,7 @@ Object { class="euiToolTipAnchor" > +
+ + + +
@@ -12609,7 +12847,7 @@ Object { class="euiToolTipAnchor" > + + @@ -13742,7 +14004,7 @@ Object { class="euiToolTipAnchor" > + + @@ -14934,7 +15220,7 @@ Object { class="euiToolTipAnchor" > + + @@ -16067,7 +16377,7 @@ Object { class="euiToolTipAnchor" > + + @@ -17230,7 +17564,7 @@ Object { class="euiToolTipAnchor" > + + @@ -18448,7 +18806,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 9a29daba3..157f4d9c8 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 3704e3832..c21a3f1c8 100644 --- a/dashboards-observability/public/components/application_analytics/helpers/utils.tsx +++ b/dashboards-observability/public/components/application_analytics/helpers/utils.tsx @@ -33,6 +33,7 @@ import { remove as removeQueryResult, } from '../../explorer/slices/query_result_slice'; import { addTab, removeTab } from '../../explorer/slices/query_tab_slice'; +import { AvailabilityType } from './types'; // Name validation export const isNameValid = (name: string, existingNames: string[]) => { @@ -193,7 +194,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 +205,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 +240,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 +322,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 0711b8eb1..039fcd1b7 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 @@ -155,7 +155,404 @@ exports[`Utils helper functions renders displayVisualization function 1`] = ` 46.81318681318681, ], }, - ] + "jsonData": Array [ + Object { + "Carrier": "BeatsWest", + "avg(FlightDelayMin)": 53.65384615384615, + }, + Object { + "Carrier": "Logstash Airways", + "avg(FlightDelayMin)": 45.36144578313253, + }, + Object { + "Carrier": "OpenSearch Dashboards Airlines", + "avg(FlightDelayMin)": 63.1578947368421, + }, + Object { + "Carrier": "OpenSearch-Air", + "avg(FlightDelayMin)": 46.81318681318681, + }, + ], + "metadata": Object { + "fields": Array [ + Object { + "name": "avg(FlightDelayMin)", + "type": "double", + }, + Object { + "name": "Carrier", + "type": "keyword", + }, + ], + }, + "size": 4, + "status": 200, + }, + "userConfigs": Object {}, + }, + "vis": Object { + "category": "Visualizations", + "categoryAxis": "xaxis", + "component": [Function], + "editorConfig": Object { + "panelTabs": Array [ + Object { + "editor": [Function], + "id": "data-panel", + "mapTo": "dataConfig", + "name": "Data", + "sections": Array [ + Object { + "editor": [Function], + "id": "value_options", + "mapTo": "valueOptions", + "name": "Value options", + "schemas": Array [ + Object { + "component": null, + "isSingleSelection": false, + "mapTo": "xaxis", + "name": "X-axis", + }, + Object { + "component": null, + "isSingleSelection": false, + "mapTo": "yaxis", + "name": "Y-axis", + }, + ], + }, + Object { + "editor": [Function], + "id": "chart_options", + "mapTo": "chartOptions", + "name": "Chart options", + "schemas": Array [ + Object { + "component": null, + "isSingleSelection": true, + "mapTo": "orientation", + "name": "Orientation", + "props": Object { + "defaultSelections": Array [ + Object { + "name": "Vertical", + "orientationId": "v", + }, + ], + "dropdownList": Array [ + Object { + "name": "Vertical", + "orientationId": "v", + }, + Object { + "name": "Horizontal", + "orientationId": "h", + }, + ], + }, + }, + Object { + "component": null, + "isSingleSelection": true, + "mapTo": "mode", + "name": "Mode", + "props": Object { + "defaultSelections": Array [ + Object { + "modeId": "group", + "name": "Group", + }, + ], + "dropdownList": Array [ + Object { + "modeId": "group", + "name": "Group", + }, + Object { + "modeId": "stack", + "name": "Stack", + }, + ], + }, + }, + ], + }, + ], + }, + Object { + "content": Array [], + "editor": [Function], + "id": "style-panel", + "mapTo": "layoutConfig", + "name": "Layout", + }, + Object { + "editor": [Function], + "id": "availability-panel", + "mapTo": "availabilityConfig", + "name": "Availability", + }, + ], + }, + "fullLabel": "Bar", + "icon": [Function], + "iconType": "visBarVerticalStacked", + "id": "bar", + "label": "Bar", + "name": "bar", + "orientation": "v", + "selection": Object { + "dataLoss": "nothing", + }, + "seriesAxis": "yaxis", + "type": "bar", + "visConfig": Object { + "config": Object { + "displaylogo": false, + "responsive": true, + }, + "isUniColor": false, + "layout": Object { + "height": 500, + "legend": Object { + "orientation": "v", + "traceorder": "normal", + }, + "margin": Object { + "b": 30, + "l": 60, + "pad": 0, + "r": 30, + "t": 50, + }, + "showlegend": true, + }, + }, + }, + } + } + > + +
- - - -======= data={ Array [ Object { @@ -386,7 +983,6 @@ exports[`Utils helper functions renders displayVisualization function 1`] = ` ->>>>>>> 6b15239 (Add data test subj to app analytics (#704))
`; @@ -609,9 +1205,162 @@ exports[`Utils helper functions renders displayVisualization function 3`] = ` "fixedrange": false, "showgrid": false, }, - "yaxis": Object { - "fixedrange": false, - "showgrid": false, + "vis": Object { + "category": "Visualizations", + "categoryAxis": "xaxis", + "component": [Function], + "editorConfig": Object { + "panelTabs": Array [ + Object { + "editor": [Function], + "id": "data-panel", + "mapTo": "dataConfig", + "name": "Data", + "sections": Array [ + Object { + "editor": [Function], + "id": "value_options", + "mapTo": "valueOptions", + "name": "Value options", + "schemas": Array [ + Object { + "component": null, + "isSingleSelection": true, + "mapTo": "xaxis", + "name": "X-axis", + }, + Object { + "component": null, + "isSingleSelection": false, + "mapTo": "yaxis", + "name": "Y-axis", + }, + ], + }, + Object { + "editor": [Function], + "id": "chart_options", + "mapTo": "chartOptions", + "name": "Chart options", + "schemas": Array [ + Object { + "component": null, + "isSingleSelection": true, + "mapTo": "mode", + "name": "Mode", + "props": Object { + "defaultSelections": Array [ + Object { + "modeId": "lines", + "name": "Lines", + }, + ], + "dropdownList": Array [ + Object { + "modeId": "markers", + "name": "Markers", + }, + Object { + "modeId": "lines", + "name": "Lines", + }, + Object { + "modeId": "lines+markers", + "name": "Lines + Markers", + }, + ], + }, + }, + ], + }, + Object { + "defaultState": Array [], + "editor": [Function], + "id": "thresholds", + "mapTo": "thresholds", + "name": "Thresholds", + "schemas": Array [], + }, + ], + }, + Object { + "content": Array [], + "editor": [Function], + "id": "style-panel", + "mapTo": "layoutConfig", + "name": "Layout", + }, + Object { + "editor": [Function], + "id": "availability-panel", + "mapTo": "availabilityConfig", + "name": "Availability", + }, + ], + }, + "fullLabel": "Line", + "icon": [Function], + "iconType": "visLine", + "id": "line", + "label": "Line", + "name": "line", + "selection": Object { + "dataLoss": "nothing", + }, + "seriesAxis": "yaxis", + "type": "line", + "visConfig": Object { + "config": Object { + "barmode": "line", + "displaylogo": false, + "responsive": true, + "xaxis": Object { + "automargin": true, + }, + "yaxis": Object { + "automargin": true, + }, + }, + "layout": Object { + "colorway": Array [ + "#3CA1C7", + "#8C55A3", + "#DB748A", + "#F2BE4B", + "#68CCC2", + "#2A7866", + "#843769", + "#374FB8", + "#BD6F26", + "#4C636F", + ], + "height": 500, + "legend": Object { + "orientation": "v", + "traceorder": "normal", + }, + "margin": Object { + "b": 30, + "l": 60, + "pad": 0, + "r": 30, + "t": 50, + }, + "paper_bgcolor": "rgba(0, 0, 0, 0)", + "plot_bgcolor": "rgba(0, 0, 0, 0)", + "showlegend": true, + "xaxis": Object { + "fixedrange": true, + "showgrid": false, + "visible": true, + }, + "yaxis": Object { + "fixedrange": true, + "showgrid": false, + "visible": true, + }, + }, + }, }, } } @@ -701,10 +1450,98 @@ exports[`Utils helper functions renders displayVisualization function 3`] = ` 46.81318681318681, ], }, - "jsonData": Array [ - Object { - "Carrier": "BeatsWest", - "avg(FlightDelayMin)": 53.65384615384615, + "vis": Object { + "category": "Visualizations", + "categoryAxis": "xaxis", + "component": [Function], + "editorConfig": Object { + "panelTabs": Array [ + Object { + "editor": [Function], + "id": "data-panel", + "mapTo": "dataConfig", + "name": "Data", + "sections": Array [ + Object { + "editor": [Function], + "id": "value_options", + "mapTo": "valueOptions", + "name": "Value options", + "schemas": Array [ + Object { + "component": null, + "isSingleSelection": true, + "mapTo": "xaxis", + "name": "X-axis", + }, + Object { + "component": null, + "isSingleSelection": false, + "mapTo": "yaxis", + "name": "Y-axis", + }, + ], + }, + Object { + "editor": [Function], + "id": "chart_options", + "mapTo": "chartOptions", + "name": "Chart options", + "schemas": Array [ + Object { + "component": null, + "isSingleSelection": true, + "mapTo": "mode", + "name": "Mode", + "props": Object { + "defaultSelections": Array [ + Object { + "modeId": "lines", + "name": "Lines", + }, + ], + "dropdownList": Array [ + Object { + "modeId": "markers", + "name": "Markers", + }, + Object { + "modeId": "lines", + "name": "Lines", + }, + Object { + "modeId": "lines+markers", + "name": "Lines + Markers", + }, + ], + }, + }, + ], + }, + Object { + "defaultState": Array [], + "editor": [Function], + "id": "thresholds", + "mapTo": "thresholds", + "name": "Thresholds", + "schemas": Array [], + }, + ], + }, + Object { + "content": Array [], + "editor": [Function], + "id": "style-panel", + "mapTo": "layoutConfig", + "name": "Layout", + }, + Object { + "editor": [Function], + "id": "availability-panel", + "mapTo": "availabilityConfig", + "name": "Availability", + }, + ], }, Object { "Carrier": "Logstash Airways", @@ -794,6 +1631,236 @@ exports[`Utils helper functions renders displayVisualization function 3`] = ` "automargin": true, "fixedrange": false, "showgrid": false, + "visible": true, + }, + } + } + visualizations={ + Object { + "data": Object { + "appData": Object {}, + "defaultAxes": Object { + "xaxis": Array [ + Object { + "name": "Carrier", + "type": "keyword", + }, + ], + "yaxis": Array [ + Object { + "name": "avg(FlightDelayMin)", + "type": "double", + }, + ], + }, + "indexFields": Object {}, + "query": Object {}, + "rawVizData": Object { + "data": Object { + "Carrier": Array [ + "BeatsWest", + "Logstash Airways", + "OpenSearch Dashboards Airlines", + "OpenSearch-Air", + ], + "avg(FlightDelayMin)": Array [ + 53.65384615384615, + 45.36144578313253, + 63.1578947368421, + 46.81318681318681, + ], + }, + "jsonData": Array [ + Object { + "Carrier": "BeatsWest", + "avg(FlightDelayMin)": 53.65384615384615, + }, + Object { + "Carrier": "Logstash Airways", + "avg(FlightDelayMin)": 45.36144578313253, + }, + Object { + "Carrier": "OpenSearch Dashboards Airlines", + "avg(FlightDelayMin)": 63.1578947368421, + }, + Object { + "Carrier": "OpenSearch-Air", + "avg(FlightDelayMin)": 46.81318681318681, + }, + ], + "metadata": Object { + "fields": Array [ + Object { + "name": "avg(FlightDelayMin)", + "type": "double", + }, + Object { + "name": "Carrier", + "type": "keyword", + }, + ], + }, + "size": 4, + "status": 200, + }, + "userConfigs": Object {}, + }, + "vis": Object { + "category": "Visualizations", + "categoryAxis": "xaxis", + "component": [Function], + "editorConfig": Object { + "panelTabs": Array [ + Object { + "editor": [Function], + "id": "data-panel", + "mapTo": "dataConfig", + "name": "Data", + "sections": Array [ + Object { + "editor": [Function], + "id": "value_options", + "mapTo": "valueOptions", + "name": "Value options", + "schemas": Array [ + Object { + "component": null, + "isSingleSelection": true, + "mapTo": "xaxis", + "name": "X-axis", + }, + Object { + "component": null, + "isSingleSelection": false, + "mapTo": "yaxis", + "name": "Y-axis", + }, + ], + }, + Object { + "editor": [Function], + "id": "chart_options", + "mapTo": "chartOptions", + "name": "Chart options", + "schemas": Array [ + Object { + "component": null, + "isSingleSelection": true, + "mapTo": "mode", + "name": "Mode", + "props": Object { + "defaultSelections": Array [ + Object { + "modeId": "lines", + "name": "Lines", + }, + ], + "dropdownList": Array [ + Object { + "modeId": "markers", + "name": "Markers", + }, + Object { + "modeId": "lines", + "name": "Lines", + }, + Object { + "modeId": "lines+markers", + "name": "Lines + Markers", + }, + ], + }, + }, + ], + }, + Object { + "defaultState": Array [], + "editor": [Function], + "id": "thresholds", + "mapTo": "thresholds", + "name": "Thresholds", + "schemas": Array [], + }, + ], + }, + Object { + "content": Array [], + "editor": [Function], + "id": "style-panel", + "mapTo": "layoutConfig", + "name": "Layout", + }, + Object { + "editor": [Function], + "id": "availability-panel", + "mapTo": "availabilityConfig", + "name": "Availability", + }, + ], + }, + "fullLabel": "Line", + "icon": [Function], + "iconType": "visLine", + "id": "line", + "label": "Line", + "name": "line", + "selection": Object { + "dataLoss": "nothing", + }, + "seriesAxis": "yaxis", + "type": "line", + "visConfig": Object { + "config": Object { + "barmode": "line", + "displaylogo": false, + "responsive": true, + "xaxis": Object { + "automargin": true, + }, + "yaxis": Object { + "automargin": true, + }, + }, + "layout": Object { + "colorway": Array [ + "#3CA1C7", + "#8C55A3", + "#DB748A", + "#F2BE4B", + "#68CCC2", + "#2A7866", + "#843769", + "#374FB8", + "#BD6F26", + "#4C636F", + ], + "height": 500, + "legend": Object { + "orientation": "v", + "traceorder": "normal", + }, + "margin": Object { + "b": 30, + "l": 60, + "pad": 0, + "r": 30, + "t": 50, + }, + "paper_bgcolor": "rgba(0, 0, 0, 0)", + "plot_bgcolor": "rgba(0, 0, 0, 0)", + "showlegend": true, + "xaxis": Object { + "fixedrange": true, + "showgrid": false, + "visible": true, + }, + "yaxis": Object { + "fixedrange": true, + "showgrid": false, + "visible": true, + }, + }, + }, }, } } @@ -801,9 +1868,6 @@ exports[`Utils helper functions renders displayVisualization function 3`] = ` >>>>>> 6b15239 (Add data test subj to app analytics (#704)) "orientation": "h", } } @@ -1687,9 +2750,6 @@ exports[`Utils helper functions renders displayVisualization function 3`] = ` } useResizeHandler={true} > -<<<<<<< HEAD -
>>>>>> 6b15239 (Add data test subj to app analytics (#704)) style={ Object { "height": "100%", "width": "100%", } } -<<<<<<< HEAD - /> - - - - -======= useResizeHandler={true} >
->>>>>>> 6b15239 (Add data test subj to app analytics (#704))
`; @@ -2007,9 +3058,6 @@ exports[`Utils helper functions renders displayVisualization function 4`] = ` } useResizeHandler={true} > -<<<<<<< HEAD -
>>>>>> 6b15239 (Add data test subj to app analytics (#704)) style={ Object { "height": "100%", "width": "100%", } } -<<<<<<< HEAD - /> - - - - -======= useResizeHandler={true} >
->>>>>>> 6b15239 (Add data test subj to app analytics (#704))
`; diff --git a/dashboards-observability/public/components/explorer/explorer.tsx b/dashboards-observability/public/components/explorer/explorer.tsx index 9d79b2d76..27cb168fa 100644 --- a/dashboards-observability/public/components/explorer/explorer.tsx +++ b/dashboards-observability/public/components/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,8 +49,7 @@ 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 { @@ -105,6 +103,8 @@ export const Explorer = ({ endTime, setStartTime, setEndTime, + callback, + callbackInApp, }: IExplorerProps) => { const dispatch = useDispatch(); const requestParams = { tabId }; @@ -140,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); @@ -161,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 @@ -176,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, @@ -184,11 +187,10 @@ export const Explorer = ({ }; useEffect(() => { - document.addEventListener("visibilitychange", function() { + document.addEventListener('visibilitychange', function () { if (document.hidden) { setBrowserTabFocus(false); - } - else { + } else { setBrowserTabFocus(true); } }); @@ -201,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, @@ -226,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); @@ -300,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; @@ -325,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 ); @@ -350,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, { @@ -374,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( @@ -396,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; @@ -415,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) => @@ -469,8 +471,8 @@ export const Explorer = ({ ); }; - const handleTimeRangePickerRefresh = () => { - handleQuerySearch(); + const handleTimeRangePickerRefresh = (availability?: boolean) => { + handleQuerySearch(availability); }; /** @@ -537,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 ( @@ -639,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 ( ); }; @@ -788,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); @@ -1021,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; @@ -1052,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 = @@ -1092,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] ); @@ -1155,4 +1173,4 @@ export const Explorer = ({
); -}; \ No newline at end of file +}; diff --git a/dashboards-observability/public/components/explorer/sidebar/sidebar.tsx b/dashboards-observability/public/components/explorer/sidebar/sidebar.tsx index be58d19b1..2d520dd4d 100644 --- a/dashboards-observability/public/components/explorer/sidebar/sidebar.tsx +++ b/dashboards-observability/public/components/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/explorer/visualizations/config_panel/DefaultEditorControls.tsx b/dashboards-observability/public/components/explorer/visualizations/config_panel/DefaultEditorControls.tsx index 4520102d2..2e653cd1d 100644 --- a/dashboards-observability/public/components/explorer/visualizations/config_panel/DefaultEditorControls.tsx +++ b/dashboards-observability/public/components/explorer/visualizations/config_panel/DefaultEditorControls.tsx @@ -37,19 +37,18 @@ export const DefaultEditorControls = ({ {isInvalid ? ( - Apply + Preview ) : ( - Apply + Preview )} diff --git a/dashboards-observability/public/components/explorer/visualizations/config_panel/config_editor/config_controls/config_availability.tsx b/dashboards-observability/public/components/explorer/visualizations/config_panel/config_editor/config_controls/config_availability.tsx new file mode 100644 index 000000000..b6a64303f --- /dev/null +++ b/dashboards-observability/public/components/explorer/visualizations/config_panel/config_editor/config_controls/config_availability.tsx @@ -0,0 +1,182 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React, { useCallback } from 'react'; +import { + EuiButton, + EuiAccordion, + EuiFormRow, + EuiFieldNumber, + EuiColorPicker, + EuiSpacer, + EuiIcon, + EuiFlexGroup, + EuiFlexItem, + EuiFieldText, + EuiSelect, + htmlIdGenerator, +} from '@elastic/eui'; +import { isEmpty } from 'lodash'; +import { PPL_SPAN_REGEX } from '../../../../../../../common/constants/shared'; + +export interface AvailabilityUnitType { + thid: string; + name: string; + color: string; + value: number; + expression: string; +} + +export const ConfigAvailability = ({ visualizations, onConfigChange, vizState = {} }: any) => { + const addButtonText = '+ Add availability level'; + const getAvailabilityUnit = () => { + return { + thid: htmlIdGenerator('avl')(), + name: '', + color: '#FC0505', + value: 0, + expression: '≥', + }; + }; + + const expressionOptions = [ + { value: '≥', text: '≥' }, + { value: '≤', text: '≤' }, + { value: '>', text: '>' }, + { value: '<', text: '<' }, + { value: '=', text: '=' }, + { value: '≠', text: '≠' }, + ]; + + const hasSpanInApp = + visualizations.data.query.finalQuery.search(PPL_SPAN_REGEX) > 0 && + visualizations.data.appData.fromApp && + ['bar', 'line'].includes(visualizations.vis.id); + + const handleConfigChange = useCallback( + (changes: any) => { + onConfigChange({ + ...vizState, + level: changes, + }); + }, + [onConfigChange, vizState] + ); + + const handleAddAvailability = useCallback(() => { + let res = vizState.level; + if (isEmpty(vizState.level)) res = []; + handleConfigChange([getAvailabilityUnit(), ...res]); + }, [vizState, handleConfigChange]); + + const handleAvailabilityChange = useCallback( + (thrId, thrName) => { + return (event: any) => { + handleConfigChange([ + ...vizState.level.map((th: AvailabilityUnitType) => { + if (thrId !== th.thid) return th; + return { + ...th, + [thrName]: (thrName === 'color' ? event : event?.target?.value) || '', + }; + }), + ]); + }; + }, + [vizState, handleConfigChange] + ); + + const handleAvailabilityDelete = useCallback( + (thrId) => { + return (event: any) => { + handleConfigChange([ + ...vizState.level.filter((th: AvailabilityUnitType) => th.thid !== thrId), + ]); + }; + }, + [vizState, handleConfigChange] + ); + + return ( + <> + + + + {addButtonText} + + + {!isEmpty(vizState.level) && + vizState.level.map((thr: AvailabilityUnitType) => { + return ( + <> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); + })} + + + ); +}; diff --git a/dashboards-observability/public/components/explorer/visualizations/config_panel/config_editor/config_controls/config_thresholds.tsx b/dashboards-observability/public/components/explorer/visualizations/config_panel/config_editor/config_controls/config_thresholds.tsx index 30bd2cc3e..c38e8e812 100644 --- a/dashboards-observability/public/components/explorer/visualizations/config_panel/config_editor/config_controls/config_thresholds.tsx +++ b/dashboards-observability/public/components/explorer/visualizations/config_panel/config_editor/config_controls/config_thresholds.tsx @@ -15,11 +15,16 @@ import { EuiFlexGroup, EuiFlexItem, EuiFieldText, - EuiSelect, htmlIdGenerator, } from '@elastic/eui'; import { isEmpty } from 'lodash'; -import { PPL_SPAN_REGEX } from '../../../../../../../common/constants/shared'; + +export interface ThresholdUnitType { + thid: string; + name: string; + color: string; + value: number; +} export const ConfigThresholds = ({ visualizations, @@ -28,34 +33,16 @@ export const ConfigThresholds = ({ handleConfigChange, sectionName = 'Thresholds', }: any) => { - let addButtonText = '+ Add threadshold'; + const addButtonText = '+ Add threadshold'; const getThresholdUnit = () => { return { thid: htmlIdGenerator('thr')(), name: '', color: '#FC0505', value: 0, - ...(hasSpanInApp && { expression: '≥' }), }; }; - const expressionOptions = [ - { value: '≥', text: '≥' }, - { value: '≤', text: '≤' }, - { value: '>', text: '>' }, - { value: '<', text: '<' }, - { value: '=', text: '=' }, - { value: '≠', text: '≠' }, - ]; - - const hasSpanInApp = - visualizations.data.query.finalQuery.search(PPL_SPAN_REGEX) > 0 && - visualizations.data.appData.fromApp; - if (hasSpanInApp) { - sectionName = 'Availability Levels'; - addButtonText = '+ Add availability level'; - } - const handleAddThreshold = useCallback(() => { let res = vizState; if (isEmpty(vizState)) res = []; @@ -64,9 +51,9 @@ export const ConfigThresholds = ({ const handleThresholdChange = useCallback( (thrId, thrName) => { - return (event) => { + return (event: any) => { handleConfigChange([ - ...vizState.map((th) => { + ...vizState.map((th: ThresholdUnitType) => { if (thrId !== th.thid) return th; return { ...th, @@ -81,8 +68,8 @@ export const ConfigThresholds = ({ const handleThresholdDelete = useCallback( (thrId) => { - return (event) => { - handleConfigChange([...vizState.filter((th) => th.thid !== thrId)]); + return () => { + handleConfigChange([...vizState.filter((th: ThresholdUnitType) => th.thid !== thrId)]); }; }, [vizState, handleConfigChange] @@ -105,7 +92,7 @@ export const ConfigThresholds = ({ {!isEmpty(vizState) && - vizState.map((thr) => { + vizState.map((thr: ThresholdUnitType) => { return ( <> @@ -129,19 +116,6 @@ export const ConfigThresholds = ({ /> - {hasSpanInApp && ( - - - - - - )} >>>>>> 6b15239 (Add data test subj to app analytics (#704)):dashboards-observability/public/components/event_analytics/explorer/visualizations/config_panel/config_panel.tsx + EuiTabbedContentTab, } from '@elastic/eui'; import { reset as resetVisualizationConfig } from '../../slices/viualization_config_slice'; import { getDefaultSpec } from '../visualization_specs/default_spec'; @@ -53,7 +50,16 @@ const HJSON_STRINGIFY_OPTIONS = { bracesSameLine: true, }; -export const ConfigPanel = ({ visualizations, setCurVisId }: any) => { +interface PanelTabType { + id: string; + name: string; + mapTo: string; + editor: any; + section?: any; + content?: any; +} + +export const ConfigPanel = ({ visualizations, setCurVisId, callback }: any) => { const { tabId, curVisId, dispatch, changeVisualizationConfig, setToast } = useContext( TabContext ); @@ -65,6 +71,7 @@ export const ConfigPanel = ({ visualizations, setCurVisId }: any) => { layoutConfig: userConfigs?.layoutConfig ? hjson.stringify({ ...userConfigs.layoutConfig }, HJSON_STRINGIFY_OPTIONS) : getDefaultSpec(), + availabilityConfig: {}, }); useEffect(() => { @@ -74,6 +81,9 @@ export const ConfigPanel = ({ visualizations, setCurVisId }: any) => { ? hjson.stringify({ ...userConfigs.layoutConfig }, HJSON_STRINGIFY_OPTIONS) : getDefaultSpec(), }); + if (callback) { + callback(() => switchToAvailability()); + } }, [userConfigs, curVisId]); const getParsedLayoutConfig = useCallback( @@ -98,13 +108,13 @@ export const ConfigPanel = ({ visualizations, setCurVisId }: any) => { }, }) ); - } catch (e) { + } catch (e: any) { setToast(`Invalid visualization configurations. error: ${e.message}`, 'danger'); } }, [tabId, vizConfigs, changeVisualizationConfig, dispatch, setToast, curVisId]); - const handleConfigChange = (configSchema) => { - return (configChanges) => { + const handleConfigChange = (configSchema: string) => { + return (configChanges: any) => { setVizConfigs((staleState) => { return { ...staleState, @@ -114,22 +124,30 @@ export const ConfigPanel = ({ visualizations, setCurVisId }: any) => { }; }; - const params = { - dataConfig: { - visualizations, - curVisId, - onConfigChange: handleConfigChange('dataConfig'), - vizState: vizConfigs.dataConfig, - }, - layoutConfig: { - onConfigEditorChange: handleConfigChange('layoutConfig'), - spec: vizConfigs.layoutConfig, - setToast, - }, - }; + const params = useMemo(() => { + return { + dataConfig: { + visualizations, + curVisId, + onConfigChange: handleConfigChange('dataConfig'), + vizState: vizConfigs.dataConfig, + }, + layoutConfig: { + onConfigEditorChange: handleConfigChange('layoutConfig'), + spec: vizConfigs.layoutConfig, + setToast, + }, + availabilityConfig: { + visualizations, + curVisId, + onConfigChange: handleConfigChange('availabilityConfig'), + vizState: vizConfigs.availabilityConfig, + }, + }; + }, [visualizations, vizConfigs, setToast, curVisId]); - const tabs = useMemo(() => { - return vis.editorConfig.panelTabs.map((tab) => { + const tabs: EuiTabbedContentTab[] = useMemo(() => { + return vis.editorConfig.panelTabs.map((tab: PanelTabType) => { const Editor = tab.editor; return { id: tab.id, @@ -139,6 +157,16 @@ export const ConfigPanel = ({ visualizations, setCurVisId }: any) => { }); }, [vis.editorConfig.panelTabs, params]); + const [currTabId, setCurrTabId] = useState(tabs[0].id); + + const switchToAvailability = () => { + setCurrTabId('availability-panel'); + }; + + const onTabClick = (selectedTab: EuiTabbedContentTab) => { + setCurrTabId(selectedTab.id); + }; + const handleDiscardConfig = () => { dispatch( resetVisualizationConfig({ @@ -190,11 +218,7 @@ export const ConfigPanel = ({ visualizations, setCurVisId }: any) => { gutterSize="none" responsive={false} > -<<<<<<< HEAD:dashboards-observability/public/components/explorer/visualizations/config_panel/config_panel.tsx - -======= ->>>>>>> 6b15239 (Add data test subj to app analytics (#704)):dashboards-observability/public/components/event_analytics/explorer/visualizations/config_panel/config_panel.tsx { tab.id === currTabId) || tabs[0]} + onTabClick={onTabClick} /> diff --git a/dashboards-observability/public/components/explorer/visualizations/index.tsx b/dashboards-observability/public/components/explorer/visualizations/index.tsx index 4042aab30..6ae2a5704 100644 --- a/dashboards-observability/public/components/explorer/visualizations/index.tsx +++ b/dashboards-observability/public/components/explorer/visualizations/index.tsx @@ -26,6 +26,7 @@ interface IExplorerVisualizationsProps { handleRemoveField: (field: IField) => void; visualizations: IVisualizationContainerProps; handleOverrideTimestamp: (field: IField) => void; + callback?: any; } export const ExplorerVisualizations = ({ @@ -39,6 +40,7 @@ export const ExplorerVisualizations = ({ handleRemoveField, visualizations, handleOverrideTimestamp, + callback, }: IExplorerVisualizationsProps) => { return ( @@ -73,6 +75,7 @@ export const ExplorerVisualizations = ({ visualizations={visualizations} curVisId={curVisId} setCurVisId={setCurVisId} + callback={callback} /> diff --git a/dashboards-observability/public/components/trace_analytics/components/common/__tests__/__snapshots__/search_bar.test.tsx.snap b/dashboards-observability/public/components/trace_analytics/components/common/__tests__/__snapshots__/search_bar.test.tsx.snap index 7233ec18d..1667bfcc8 100644 --- a/dashboards-observability/public/components/trace_analytics/components/common/__tests__/__snapshots__/search_bar.test.tsx.snap +++ b/dashboards-observability/public/components/trace_analytics/components/common/__tests__/__snapshots__/search_bar.test.tsx.snap @@ -899,24 +899,22 @@ exports[`Search bar components renders search bar 1`] = ` className="euiFlexItem euiFlexItem--flexGrowZero" >