diff --git a/.ci/Dockerfile b/.ci/Dockerfile deleted file mode 100644 index 201e17b93c116..0000000000000 --- a/.ci/Dockerfile +++ /dev/null @@ -1,35 +0,0 @@ -ARG NODE_VERSION=10.21.0 - -FROM node:${NODE_VERSION} AS base - -RUN apt-get update && \ - apt-get -y install xvfb gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 \ - libdbus-1-3 libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 \ - libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 \ - libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 \ - libxtst6 ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget openjdk-8-jre && \ - rm -rf /var/lib/apt/lists/* - -RUN curl -sSL https://dl.google.com/linux/linux_signing_key.pub | apt-key add - \ - && sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \ - && apt-get update \ - && apt-get install -y rsync jq bsdtar google-chrome-stable \ - --no-install-recommends \ - && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* - -RUN LATEST_VAULT_RELEASE=$(curl -s https://api.github.com/repos/hashicorp/vault/tags | jq --raw-output .[0].name[1:]) \ - && curl -L https://releases.hashicorp.com/vault/${LATEST_VAULT_RELEASE}/vault_${LATEST_VAULT_RELEASE}_linux_amd64.zip -o vault.zip \ - && unzip vault.zip \ - && rm vault.zip \ - && chmod +x vault \ - && mv vault /usr/local/bin/vault - -RUN groupadd -r kibana && useradd -r -g kibana kibana && mkdir /home/kibana && chown kibana:kibana /home/kibana - -COPY ./bash_standard_lib.sh /usr/local/bin/bash_standard_lib.sh -RUN chmod +x /usr/local/bin/bash_standard_lib.sh - -COPY ./runbld /usr/local/bin/runbld -RUN chmod +x /usr/local/bin/runbld - -USER kibana diff --git a/.ci/runbld_no_junit.yml b/.ci/runbld_no_junit.yml index 1bcb7e22a2648..67b5002c1c437 100644 --- a/.ci/runbld_no_junit.yml +++ b/.ci/runbld_no_junit.yml @@ -3,4 +3,4 @@ profiles: - ".*": # Match any job tests: - junit-filename-pattern: false + junit-filename-pattern: "8d8bd494-d909-4e67-a052-7e8b5aaeb5e4" # A bogus path that should never exist diff --git a/.gitignore b/.gitignore index 25a8c369bb704..32377ec0f1ffe 100644 --- a/.gitignore +++ b/.gitignore @@ -47,8 +47,6 @@ npm-debug.log* .tern-project .nyc_output .ci/pipeline-library/build/ -.ci/runbld -.ci/bash_standard_lib.sh .gradle # apm plugin diff --git a/Jenkinsfile b/Jenkinsfile index 491a1e386deb1..f6f77ccae8427 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -8,7 +8,50 @@ kibanaPipeline(timeoutMinutes: 155, checkPrChanges: true, setCommitStatus: true) ciStats.trackBuild { catchError { retryable.enable() - kibanaPipeline.allCiTasks() + parallel([ + 'kibana-intake-agent': workers.intake('kibana-intake', './test/scripts/jenkins_unit.sh'), + 'x-pack-intake-agent': workers.intake('x-pack-intake', './test/scripts/jenkins_xpack.sh'), + 'kibana-oss-agent': workers.functional('kibana-oss-tests', { kibanaPipeline.buildOss() }, [ + 'oss-firefoxSmoke': kibanaPipeline.functionalTestProcess('kibana-firefoxSmoke', './test/scripts/jenkins_firefox_smoke.sh'), + 'oss-ciGroup1': kibanaPipeline.ossCiGroupProcess(1), + 'oss-ciGroup2': kibanaPipeline.ossCiGroupProcess(2), + 'oss-ciGroup3': kibanaPipeline.ossCiGroupProcess(3), + 'oss-ciGroup4': kibanaPipeline.ossCiGroupProcess(4), + 'oss-ciGroup5': kibanaPipeline.ossCiGroupProcess(5), + 'oss-ciGroup6': kibanaPipeline.ossCiGroupProcess(6), + 'oss-ciGroup7': kibanaPipeline.ossCiGroupProcess(7), + 'oss-ciGroup8': kibanaPipeline.ossCiGroupProcess(8), + 'oss-ciGroup9': kibanaPipeline.ossCiGroupProcess(9), + 'oss-ciGroup10': kibanaPipeline.ossCiGroupProcess(10), + 'oss-ciGroup11': kibanaPipeline.ossCiGroupProcess(11), + 'oss-ciGroup12': kibanaPipeline.ossCiGroupProcess(12), + 'oss-accessibility': kibanaPipeline.functionalTestProcess('kibana-accessibility', './test/scripts/jenkins_accessibility.sh'), + // 'oss-visualRegression': kibanaPipeline.functionalTestProcess('visualRegression', './test/scripts/jenkins_visual_regression.sh'), + ]), + 'kibana-xpack-agent': workers.functional('kibana-xpack-tests', { kibanaPipeline.buildXpack() }, [ + 'xpack-firefoxSmoke': kibanaPipeline.functionalTestProcess('xpack-firefoxSmoke', './test/scripts/jenkins_xpack_firefox_smoke.sh'), + 'xpack-ciGroup1': kibanaPipeline.xpackCiGroupProcess(1), + 'xpack-ciGroup2': kibanaPipeline.xpackCiGroupProcess(2), + 'xpack-ciGroup3': kibanaPipeline.xpackCiGroupProcess(3), + 'xpack-ciGroup4': kibanaPipeline.xpackCiGroupProcess(4), + 'xpack-ciGroup5': kibanaPipeline.xpackCiGroupProcess(5), + 'xpack-ciGroup6': kibanaPipeline.xpackCiGroupProcess(6), + 'xpack-ciGroup7': kibanaPipeline.xpackCiGroupProcess(7), + 'xpack-ciGroup8': kibanaPipeline.xpackCiGroupProcess(8), + 'xpack-ciGroup9': kibanaPipeline.xpackCiGroupProcess(9), + 'xpack-ciGroup10': kibanaPipeline.xpackCiGroupProcess(10), + 'xpack-accessibility': kibanaPipeline.functionalTestProcess('xpack-accessibility', './test/scripts/jenkins_xpack_accessibility.sh'), + 'xpack-savedObjectsFieldMetrics': kibanaPipeline.functionalTestProcess('xpack-savedObjectsFieldMetrics', './test/scripts/jenkins_xpack_saved_objects_field_metrics.sh'), + // 'xpack-pageLoadMetrics': kibanaPipeline.functionalTestProcess('xpack-pageLoadMetrics', './test/scripts/jenkins_xpack_page_load_metrics.sh'), + 'xpack-securitySolutionCypress': { processNumber -> + whenChanged(['x-pack/plugins/security_solution/', 'x-pack/test/security_solution_cypress/']) { + kibanaPipeline.functionalTestProcess('xpack-securitySolutionCypress', './test/scripts/jenkins_security_solution_cypress.sh')(processNumber) + } + }, + + // 'xpack-visualRegression': kibanaPipeline.functionalTestProcess('xpack-visualRegression', './test/scripts/jenkins_xpack_visual_regression.sh'), + ]), + ]) } } } diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ui_settings.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ui_settings.md index a48f4920b3d26..e515c3513df6c 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ui_settings.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ui_settings.md @@ -8,32 +8,33 @@ ```typescript UI_SETTINGS: { - META_FIELDS: string; - DOC_HIGHLIGHT: string; - QUERY_STRING_OPTIONS: string; - QUERY_ALLOW_LEADING_WILDCARDS: string; - SEARCH_QUERY_LANGUAGE: string; - SORT_OPTIONS: string; - COURIER_IGNORE_FILTER_IF_FIELD_NOT_IN_INDEX: string; - COURIER_SET_REQUEST_PREFERENCE: string; - COURIER_CUSTOM_REQUEST_PREFERENCE: string; - COURIER_MAX_CONCURRENT_SHARD_REQUESTS: string; - COURIER_BATCH_SEARCHES: string; - SEARCH_INCLUDE_FROZEN: string; - HISTOGRAM_BAR_TARGET: string; - HISTOGRAM_MAX_BARS: string; - HISTORY_LIMIT: string; - SHORT_DOTS_ENABLE: string; - FORMAT_DEFAULT_TYPE_MAP: string; - FORMAT_NUMBER_DEFAULT_PATTERN: string; - FORMAT_PERCENT_DEFAULT_PATTERN: string; - FORMAT_BYTES_DEFAULT_PATTERN: string; - FORMAT_CURRENCY_DEFAULT_PATTERN: string; - FORMAT_NUMBER_DEFAULT_LOCALE: string; - TIMEPICKER_REFRESH_INTERVAL_DEFAULTS: string; - TIMEPICKER_QUICK_RANGES: string; - INDEXPATTERN_PLACEHOLDER: string; - FILTERS_PINNED_BY_DEFAULT: string; - FILTERS_EDITOR_SUGGEST_VALUES: string; + readonly META_FIELDS: "metaFields"; + readonly DOC_HIGHLIGHT: "doc_table:highlight"; + readonly QUERY_STRING_OPTIONS: "query:queryString:options"; + readonly QUERY_ALLOW_LEADING_WILDCARDS: "query:allowLeadingWildcards"; + readonly SEARCH_QUERY_LANGUAGE: "search:queryLanguage"; + readonly SORT_OPTIONS: "sort:options"; + readonly COURIER_IGNORE_FILTER_IF_FIELD_NOT_IN_INDEX: "courier:ignoreFilterIfFieldNotInIndex"; + readonly COURIER_SET_REQUEST_PREFERENCE: "courier:setRequestPreference"; + readonly COURIER_CUSTOM_REQUEST_PREFERENCE: "courier:customRequestPreference"; + readonly COURIER_MAX_CONCURRENT_SHARD_REQUESTS: "courier:maxConcurrentShardRequests"; + readonly COURIER_BATCH_SEARCHES: "courier:batchSearches"; + readonly SEARCH_INCLUDE_FROZEN: "search:includeFrozen"; + readonly HISTOGRAM_BAR_TARGET: "histogram:barTarget"; + readonly HISTOGRAM_MAX_BARS: "histogram:maxBars"; + readonly HISTORY_LIMIT: "history:limit"; + readonly SHORT_DOTS_ENABLE: "shortDots:enable"; + readonly FORMAT_DEFAULT_TYPE_MAP: "format:defaultTypeMap"; + readonly FORMAT_NUMBER_DEFAULT_PATTERN: "format:number:defaultPattern"; + readonly FORMAT_PERCENT_DEFAULT_PATTERN: "format:percent:defaultPattern"; + readonly FORMAT_BYTES_DEFAULT_PATTERN: "format:bytes:defaultPattern"; + readonly FORMAT_CURRENCY_DEFAULT_PATTERN: "format:currency:defaultPattern"; + readonly FORMAT_NUMBER_DEFAULT_LOCALE: "format:number:defaultLocale"; + readonly TIMEPICKER_REFRESH_INTERVAL_DEFAULTS: "timepicker:refreshIntervalDefaults"; + readonly TIMEPICKER_QUICK_RANGES: "timepicker:quickRanges"; + readonly TIMEPICKER_TIME_DEFAULTS: "timepicker:timeDefaults"; + readonly INDEXPATTERN_PLACEHOLDER: "indexPattern:placeholder"; + readonly FILTERS_PINNED_BY_DEFAULT: "filters:pinnedByDefault"; + readonly FILTERS_EDITOR_SUGGEST_VALUES: "filterEditor:suggestValues"; } ``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ui_settings.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ui_settings.md index 855cfd11d00ea..e419b64cd43aa 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ui_settings.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ui_settings.md @@ -8,32 +8,33 @@ ```typescript UI_SETTINGS: { - META_FIELDS: string; - DOC_HIGHLIGHT: string; - QUERY_STRING_OPTIONS: string; - QUERY_ALLOW_LEADING_WILDCARDS: string; - SEARCH_QUERY_LANGUAGE: string; - SORT_OPTIONS: string; - COURIER_IGNORE_FILTER_IF_FIELD_NOT_IN_INDEX: string; - COURIER_SET_REQUEST_PREFERENCE: string; - COURIER_CUSTOM_REQUEST_PREFERENCE: string; - COURIER_MAX_CONCURRENT_SHARD_REQUESTS: string; - COURIER_BATCH_SEARCHES: string; - SEARCH_INCLUDE_FROZEN: string; - HISTOGRAM_BAR_TARGET: string; - HISTOGRAM_MAX_BARS: string; - HISTORY_LIMIT: string; - SHORT_DOTS_ENABLE: string; - FORMAT_DEFAULT_TYPE_MAP: string; - FORMAT_NUMBER_DEFAULT_PATTERN: string; - FORMAT_PERCENT_DEFAULT_PATTERN: string; - FORMAT_BYTES_DEFAULT_PATTERN: string; - FORMAT_CURRENCY_DEFAULT_PATTERN: string; - FORMAT_NUMBER_DEFAULT_LOCALE: string; - TIMEPICKER_REFRESH_INTERVAL_DEFAULTS: string; - TIMEPICKER_QUICK_RANGES: string; - INDEXPATTERN_PLACEHOLDER: string; - FILTERS_PINNED_BY_DEFAULT: string; - FILTERS_EDITOR_SUGGEST_VALUES: string; + readonly META_FIELDS: "metaFields"; + readonly DOC_HIGHLIGHT: "doc_table:highlight"; + readonly QUERY_STRING_OPTIONS: "query:queryString:options"; + readonly QUERY_ALLOW_LEADING_WILDCARDS: "query:allowLeadingWildcards"; + readonly SEARCH_QUERY_LANGUAGE: "search:queryLanguage"; + readonly SORT_OPTIONS: "sort:options"; + readonly COURIER_IGNORE_FILTER_IF_FIELD_NOT_IN_INDEX: "courier:ignoreFilterIfFieldNotInIndex"; + readonly COURIER_SET_REQUEST_PREFERENCE: "courier:setRequestPreference"; + readonly COURIER_CUSTOM_REQUEST_PREFERENCE: "courier:customRequestPreference"; + readonly COURIER_MAX_CONCURRENT_SHARD_REQUESTS: "courier:maxConcurrentShardRequests"; + readonly COURIER_BATCH_SEARCHES: "courier:batchSearches"; + readonly SEARCH_INCLUDE_FROZEN: "search:includeFrozen"; + readonly HISTOGRAM_BAR_TARGET: "histogram:barTarget"; + readonly HISTOGRAM_MAX_BARS: "histogram:maxBars"; + readonly HISTORY_LIMIT: "history:limit"; + readonly SHORT_DOTS_ENABLE: "shortDots:enable"; + readonly FORMAT_DEFAULT_TYPE_MAP: "format:defaultTypeMap"; + readonly FORMAT_NUMBER_DEFAULT_PATTERN: "format:number:defaultPattern"; + readonly FORMAT_PERCENT_DEFAULT_PATTERN: "format:percent:defaultPattern"; + readonly FORMAT_BYTES_DEFAULT_PATTERN: "format:bytes:defaultPattern"; + readonly FORMAT_CURRENCY_DEFAULT_PATTERN: "format:currency:defaultPattern"; + readonly FORMAT_NUMBER_DEFAULT_LOCALE: "format:number:defaultLocale"; + readonly TIMEPICKER_REFRESH_INTERVAL_DEFAULTS: "timepicker:refreshIntervalDefaults"; + readonly TIMEPICKER_QUICK_RANGES: "timepicker:quickRanges"; + readonly TIMEPICKER_TIME_DEFAULTS: "timepicker:timeDefaults"; + readonly INDEXPATTERN_PLACEHOLDER: "indexPattern:placeholder"; + readonly FILTERS_PINNED_BY_DEFAULT: "filters:pinnedByDefault"; + readonly FILTERS_EDITOR_SUGGEST_VALUES: "filterEditor:suggestValues"; } ``` diff --git a/docs/uptime-guide/alerting.asciidoc b/docs/uptime-guide/alerting.asciidoc deleted file mode 100644 index bf9e7693fc7a5..0000000000000 --- a/docs/uptime-guide/alerting.asciidoc +++ /dev/null @@ -1,33 +0,0 @@ -[role="xpack"] -[[uptime-alerting]] - -=== Uptime alerting - -The Uptime app integrates with Kibana's {kibana-ref}/alerting-getting-started.html[alerting and actions] -feature. It provides a set of built-in actions and Uptime specific threshold alerts for you to use -and enables central management of all alerts from {kibana-ref}/management.html[Kibana Management]. - -[role="screenshot"] -image::images/create-alert.png[Create alert] - -[float] -==== Monitor status alerts - -To receive alerts when a monitor goes down, use the alerting menu at the top of the -overview page. Use a query in the alert flyout to determine which monitors to check -with your alert. If you already have a query in the overview page search bar it will -be carried over into this box. - -[role="screenshot"] -image::images/monitor-status-alert.png[Create monitor status alert flyout] - -[float] -==== TLS alerts - -Uptime also provides the ability to create an alert that will notify you when one or -more of your monitors have a TLS certificate that will expire within some threshold, -or when its age exceeds a limit. The values for these thresholds are configurable on -the <>. - -[role="screenshot"] -image::images/tls-alert.png[Create TLS alert flyout] diff --git a/docs/uptime-guide/app-overview.asciidoc b/docs/uptime-guide/app-overview.asciidoc deleted file mode 100644 index 692489a7ad311..0000000000000 --- a/docs/uptime-guide/app-overview.asciidoc +++ /dev/null @@ -1,70 +0,0 @@ -[role="xpack"] -[[uptime-app]] -== Uptime app - -The Uptime app in {kib} enables you to monitor the status of network endpoints via HTTP/S, TCP, and ICMP. -You can explore endpoint status over time, drill down into specific monitors, -and view a high-level snapshot of your environment at any point in time. - -[role="screenshot"] -image::images/uptime-overview.png[Uptime app overview] - -[role="xpack"] -[[uptime-app-overview]] -=== Overview - -The Uptime overview helps you quickly identify and diagnose outages and -other connectivity issues within your network or environment. You can use the date range -selection that is global to the Uptime app, to highlight -an absolute date range, or a relative one, similar to other areas of {kib}. - -[float] -=== Filter bar - -The Filter bar enables you to quickly view specific groups of monitors, or even -an individual monitor if you have defined many. - -This control allows you to use automated filter options, as well as input custom filter -text to select specific monitors by field, URL, ID, and other attributes. - -[role="screenshot"] -image::images/filter-bar.png[Filter bar] - -[float] -=== Snapshot panel - -The Snapshot panel displays the overall -status of the environment you're monitoring or a subset of those monitors. -You can see the total number of detected monitors within the selected -Uptime date range, along with the number of monitors -in an `up` or `down` state, which is based on the last check reported by Heartbeat -for each monitor. - -Next to the counts, there is a histogram displaying the change over time throughout the -selected date range. - -[role="screenshot"] -image::images/snapshot-view.png[Snapshot view] - -[float] -=== Monitor list - -Information about individual monitors is displayed in the monitor list and provides a quick -way to navigate to a more in-depth visualization for interesting hosts or endpoints. - -The information displayed includes the recent status of a host or endpoint, when the monitor was last checked, its -ID and URL, and its IP address. There is also sparkline showing its check status over time. - -[role="screenshot"] -image::images/monitor-list.png[Monitor list] - -[float] -=== Observability integrations - -The Monitor list also contains a menu of available integrations. When Uptime detects Kubernetes or -Docker related host information, it provides links to open the Metrics app or Logs app pre-filtered -for this host. Additionally, to help you quickly determine if these solutions contain data relevant to you, -this feature contains links to filter the other views on the host's IP address. - -[role="screenshot"] -image::images/observability_integrations.png[Observability integrations] diff --git a/docs/uptime-guide/certificates.asciidoc b/docs/uptime-guide/certificates.asciidoc deleted file mode 100644 index 58db91aa080eb..0000000000000 --- a/docs/uptime-guide/certificates.asciidoc +++ /dev/null @@ -1,15 +0,0 @@ -[role="xpack"] -[[uptime-certificates]] - -=== Certificates - -The certificates page enables you to visualize TLS certificate data in your indices. In addition to the -common name, associated monitors, issuer information, and SHA fingerprints, Uptime also assigns a status -derived from the threshold values in the <>. - -Several of the columns on this page are sortable. You can use the search bar at the top of the view -to find values in most of the TLS-related fields in your Uptime indices. Additionally, using the `Alerts` -dropdown at the top of the page you can create a TLS alert. - -[role="screenshot"] -image::images/certificates-page.png[Certificates] diff --git a/docs/uptime-guide/deployment-arch.asciidoc b/docs/uptime-guide/deployment-arch.asciidoc deleted file mode 100644 index c1b2f596c6665..0000000000000 --- a/docs/uptime-guide/deployment-arch.asciidoc +++ /dev/null @@ -1,27 +0,0 @@ -[role="xpack"] -[[uptime-deployment-arch]] -== Deployment Architecture - -There are multiple ways to deploy Uptime and Heartbeat. -Use the information in this section to determine the best deployment for you. -A guiding principle is that when an outage takes down the service being monitored it should not also take down Heartbeat. -You want Heartbeat to be functioning even when your service is not, so the guidelines here help you maximize this possibility. - -Heartbeat is commonly run as a centralized service within a data center. -While it is possible to run it as a separate "sidecar" process paired with each process/container, we recommend against it. -Running Heartbeat centrally ensures you will still be able to see monitoring data in the event of an overloaded, disconnected, or otherwise malfunctioning server. - -For further redundancy, you may want to deploy multiple Heartbeats across geographic and network boundaries to provide more data. -To do so, specify Heartbeat's observer {heartbeat-ref}/configuration-observer-options.html[geo options]. - -Some examples might be: - -* **A site served from a content delivery network (CDN) with points of presence (POPs) around the globe:** -To check if your site is reachable via CDN POPS, you may want to have multiple Heartbeat instances at different data centers around the world. -* **A service within a single data center that is accessed across multiple VPNs:** -Set up one Heartbeat instance within the VPN the service operates from, and another within an additional VPN that users access the service from. -Having both instances helps pinpoint network errors in the event of an outage. -* **A single service running primarily in a US east coast data center, with a hot failover located in a US west coast data center:** -In each data center, run a Heartbeat instance that checks both the local copy of the service and its counterpart across the country. -Set up two monitors in each region, one for the local service and one for the remote service. -In the event of a data center failure it will be immediately apparent if the service had a connectivity issue to the outside world or if the failure was only internal. diff --git a/docs/uptime-guide/images/cert-exp.png b/docs/uptime-guide/images/cert-exp.png deleted file mode 100644 index cd87668db96dd..0000000000000 Binary files a/docs/uptime-guide/images/cert-exp.png and /dev/null differ diff --git a/docs/uptime-guide/images/certificates-page.png b/docs/uptime-guide/images/certificates-page.png deleted file mode 100644 index 598aae982cd6a..0000000000000 Binary files a/docs/uptime-guide/images/certificates-page.png and /dev/null differ diff --git a/docs/uptime-guide/images/check-history.png b/docs/uptime-guide/images/check-history.png deleted file mode 100644 index aac5efd9b91d3..0000000000000 Binary files a/docs/uptime-guide/images/check-history.png and /dev/null differ diff --git a/docs/uptime-guide/images/create-alert.png b/docs/uptime-guide/images/create-alert.png deleted file mode 100644 index 54a0c400cad4c..0000000000000 Binary files a/docs/uptime-guide/images/create-alert.png and /dev/null differ diff --git a/docs/uptime-guide/images/crosshair-example.png b/docs/uptime-guide/images/crosshair-example.png deleted file mode 100644 index f9e89c4f622e0..0000000000000 Binary files a/docs/uptime-guide/images/crosshair-example.png and /dev/null differ diff --git a/docs/uptime-guide/images/filter-bar.png b/docs/uptime-guide/images/filter-bar.png deleted file mode 100644 index b7c424d3d0d91..0000000000000 Binary files a/docs/uptime-guide/images/filter-bar.png and /dev/null differ diff --git a/docs/uptime-guide/images/indices.png b/docs/uptime-guide/images/indices.png deleted file mode 100644 index 4090747b6726c..0000000000000 Binary files a/docs/uptime-guide/images/indices.png and /dev/null differ diff --git a/docs/uptime-guide/images/monitor-charts.png b/docs/uptime-guide/images/monitor-charts.png deleted file mode 100644 index 522f34662657e..0000000000000 Binary files a/docs/uptime-guide/images/monitor-charts.png and /dev/null differ diff --git a/docs/uptime-guide/images/monitor-list.png b/docs/uptime-guide/images/monitor-list.png deleted file mode 100644 index c9a8eccf01f6e..0000000000000 Binary files a/docs/uptime-guide/images/monitor-list.png and /dev/null differ diff --git a/docs/uptime-guide/images/monitor-status-alert.png b/docs/uptime-guide/images/monitor-status-alert.png deleted file mode 100644 index 847a0f58f02ce..0000000000000 Binary files a/docs/uptime-guide/images/monitor-status-alert.png and /dev/null differ diff --git a/docs/uptime-guide/images/observability_integrations.png b/docs/uptime-guide/images/observability_integrations.png deleted file mode 100644 index 3b23aa2dbd2a5..0000000000000 Binary files a/docs/uptime-guide/images/observability_integrations.png and /dev/null differ diff --git a/docs/uptime-guide/images/settings.png b/docs/uptime-guide/images/settings.png deleted file mode 100644 index d19b7f842ea68..0000000000000 Binary files a/docs/uptime-guide/images/settings.png and /dev/null differ diff --git a/docs/uptime-guide/images/snapshot-view.png b/docs/uptime-guide/images/snapshot-view.png deleted file mode 100644 index b6f07fb0721aa..0000000000000 Binary files a/docs/uptime-guide/images/snapshot-view.png and /dev/null differ diff --git a/docs/uptime-guide/images/status-bar.png b/docs/uptime-guide/images/status-bar.png deleted file mode 100644 index fd72e2b78c2a0..0000000000000 Binary files a/docs/uptime-guide/images/status-bar.png and /dev/null differ diff --git a/docs/uptime-guide/images/tls-alert.png b/docs/uptime-guide/images/tls-alert.png deleted file mode 100644 index 19efe07838903..0000000000000 Binary files a/docs/uptime-guide/images/tls-alert.png and /dev/null differ diff --git a/docs/uptime-guide/images/uptime-multi-deployment.png b/docs/uptime-guide/images/uptime-multi-deployment.png deleted file mode 100644 index 5440d91e48e23..0000000000000 Binary files a/docs/uptime-guide/images/uptime-multi-deployment.png and /dev/null differ diff --git a/docs/uptime-guide/images/uptime-overview.png b/docs/uptime-guide/images/uptime-overview.png deleted file mode 100644 index 25c88b2d14287..0000000000000 Binary files a/docs/uptime-guide/images/uptime-overview.png and /dev/null differ diff --git a/docs/uptime-guide/images/uptime-setup.png b/docs/uptime-guide/images/uptime-setup.png deleted file mode 100644 index 398125202fc4a..0000000000000 Binary files a/docs/uptime-guide/images/uptime-setup.png and /dev/null differ diff --git a/docs/uptime-guide/images/uptime-simple-deployment.png b/docs/uptime-guide/images/uptime-simple-deployment.png deleted file mode 100644 index f46dfdb2b8b86..0000000000000 Binary files a/docs/uptime-guide/images/uptime-simple-deployment.png and /dev/null differ diff --git a/docs/uptime-guide/index.asciidoc b/docs/uptime-guide/index.asciidoc deleted file mode 100644 index 01a93cb454ea9..0000000000000 --- a/docs/uptime-guide/index.asciidoc +++ /dev/null @@ -1,22 +0,0 @@ - -include::{asciidoc-dir}/../../shared/versions/stack/{source_branch}.asciidoc[] -include::{asciidoc-dir}/../../shared/attributes.asciidoc[] - -= Uptime monitoring guide - -include::overview.asciidoc[] - -include::install.asciidoc[] - -include::deployment-arch.asciidoc[] - -include::app-overview.asciidoc[] - -include::monitor.asciidoc[] - -include::settings.asciidoc[] - -include::certificates.asciidoc[] - -include::alerting.asciidoc[] - diff --git a/docs/uptime-guide/install.asciidoc b/docs/uptime-guide/install.asciidoc deleted file mode 100644 index 05b9c6665562f..0000000000000 --- a/docs/uptime-guide/install.asciidoc +++ /dev/null @@ -1,74 +0,0 @@ -[[install-uptime]] -== Install Uptime - -The easiest way to get started with Elastic Uptime is by using our hosted {es} Service on Elastic Cloud. -The {es} Service is available on both AWS and GCP, -and automatically configures {es} and {kib}. - -[float] -=== Hosted Elasticsearch Service - -Skip managing your own {es} and {kib} instance by using our -https://www.elastic.co/cloud/elasticsearch-service[hosted {es} Service] on -Elastic Cloud. - -{ess-trial}[Try out the {es} Service for free], -then jump straight to <>. - -[float] -[[before-installation]] -=== Install the stack yourself - -If you'd rather install the stack yourself, -first see the https://www.elastic.co/support/matrix[Elastic Support Matrix] for information about supported operating systems and product compatibility. - -* <> -* <> -* <> - -[[install-elasticsearch]] -=== Step 1: Install Elasticsearch - -Install an {es} cluster, start it up, and make sure it's running. - -. Verify that your system meets the -https://www.elastic.co/support/matrix#matrix_jvm[minimum JVM requirements] for {es}. -. {stack-gs}/get-started-elastic-stack.html#install-elasticsearch[Install Elasticsearch]. -. {stack-gs}/get-started-elastic-stack.html#_make_sure_elasticsearch_is_up_and_running[Make sure elasticsearch is up and running]. - -[[install-kibana]] -=== Step 2: Install Kibana - -Install {kib}, start it up, and open up the web interface: - -. {stack-gs}/get-started-elastic-stack.html#install-kibana[Install Kibana]. -. {stack-gs}/get-started-elastic-stack.html#_launch_the_kibana_web_interface[Launch the Kibana Web Interface]. - -[[install-heartbeat]] -=== Step 3: Install and configure Heartbeat - -Uptime requires the setup of monitors in Heartbeat. -These monitors provide the data you'll be visualizing in the {kibana-ref}/xpack-uptime.html[Uptime app]. - -For instructions on installing and configuring Heartbeat, see the *Setup Instructions* in {kib}. -Additional information is available in {heartbeat-ref}/heartbeat-configuration.html[Configure Heartbeat]. - -[role="screenshot"] -image::images/uptime-setup.png[Installation instructions on the Uptime page in Kibana] - -[[setup-security]] -=== Step 4: Set up Security - -Secure your installation by following the {heartbeat-ref}/securing-heartbeat.html[Secure Heartbeat] documentation. - -[float] -==== Important considerations - -* Make sure you're using the same major versions of Heartbeat and {kib}. - -* Index patterns tell {kib} which {es} indices you want to explore. -The Uptime app requires a +heartbeat-{major-version-only}*+ index pattern. -If you have configured a different index pattern, you can use {ref}/indices-aliases.html[index aliases] to ensure data is recognized by the Uptime app. - -After you install and configure Heartbeat, -the {kibana-ref}/xpack-uptime.html[Uptime app] is automatically populated with the Heartbeat monitors. diff --git a/docs/uptime-guide/monitor.asciidoc b/docs/uptime-guide/monitor.asciidoc deleted file mode 100644 index bb5d315cf63eb..0000000000000 --- a/docs/uptime-guide/monitor.asciidoc +++ /dev/null @@ -1,59 +0,0 @@ -[role="xpack"] -[[uptime-monitor]] -=== Monitor - -The Monitor page helps you gain insights into the performance -of a specific network endpoint. A detailed visualization of -the monitor's request duration over time, as well as the `up`/`down` -status over time, is displayed. By configuring Machine Learning jobs -on this page, you can also also detect anomalies in response time data. - - -==== Status panel - -The Status panel displays a quick summary of the latest information -regarding your monitor. You can view its latest status, click a link to -visit the targeted URL, see its most recent request duration, and determine the -amount of time that has elapsed since the last check. - -When two Heartbeat instances are configured in different geographic locations -the map will show each location as a pinpoint on the map, along with the -amount of time elapsed since data was last received from that location. - -[role="screenshot"] -image::images/status-bar.png[Status bar] - - -[float] -==== Monitor charts - -The Monitor charts visualize information over the time specified in the -date range. These charts help you gain insights into how quickly requests are being resolved -by the targeted endpoint, and give you a sense of how frequently a host or endpoint -was down in your selected timespan. - -[role="screenshot"] -image::images/monitor-charts.png[Monitor charts] - -The Monitor duration chart displays request duration information for your monitor. -The area surrounding the line is the range of request time for the corresponding -bucket. The line is the average time. In the upper right hand of this panel -you can enable Anomaly detection using Machine Learning. When response times change -in an unexpected way the time range in which they occurred are highlighted with a color. - -The pings over time chart is a graphical representation of the check statuses over time. -Hover over the charts to display crosshairs with specific numeric data. - -[role="screenshot"] -image::images/crosshair-example.png[Chart crosshair] - -[float] -==== Check history - -The Check history table lists the total count of this monitor's checks for the selected -date range. To help find recent problems on a per-check basis, you can filter the checks -by status and location. This table can help you gain some insight into more granular details -about recent individual data points that Heartbeat is logging about your host or endpoint. - -[role="screenshot"] -image::images/check-history.png[Check history view] diff --git a/docs/uptime-guide/overview.asciidoc b/docs/uptime-guide/overview.asciidoc deleted file mode 100644 index ab230b27f8cda..0000000000000 --- a/docs/uptime-guide/overview.asciidoc +++ /dev/null @@ -1,57 +0,0 @@ -[role="xpack"] -[[uptime-overview]] -== Elastic Uptime overview - -++++ -Overview -++++ - -Elastic Uptime enables you to monitor the availability and response times of applications and services in real time and to detect problems before they affect users. - -Elastic Uptime helps you to understand uptime and response time characteristics for your services and applications. -It can be deployed both inside and outside your organization's network, so that you can analyze problems from multiple vantage points. - -Elastic Uptime uses these components: *Heartbeat*, *Elasticsearch* and *Kibana*. - -[float] -=== Heartbeat - -{heartbeat-ref}/index.html[Heartbeat] is an open source data shipper that performs uptime monitoring. -Elastic Uptime uses Heartbeat to collect monitoring data from your target applications and services, and ship it to Elasticsearch. - -[float] -=== Elasticsearch - -{ref}/index.html[Elasticsearch] is a highly scalable, open source, search and analytics engine. -Elasticsearch can store, search, and analyze large volumes of data in near real-time. -Elastic Uptime uses Elasticsearch to store monitoring data from Heartbeat in Elasticsearch documents. - -[float] -=== Kibana - -{kibana-ref}/index.html[Kibana] is an open source analytics and visualization platform designed to work with Elasticsearch. -You can use Kibana to search, view, and interact with data stored in Elasticsearch. -You can easily perform advanced data analysis and visualize your data in a variety of charts, tables, and maps. - -The {kibana-ref}/xpack-uptime.html[Elasticsearch Uptime app] in Kibana provides a dedicated user interface for viewing uptime data and identifying problem areas. - -[float] -=== Example deployments -// ++ I like the Infra/logging diagram which shows Metrics and Logging apps as separate components inside Kibana -// ++ In diagram, should be Uptime app, not Uptime UI, possibly even Elastic Uptime? Also applies to Metrics/Logging/APM. -// ++ Need more whitespace around components. - -In this simple deployment, a single instance of Heartbeat is deployed at a single monitoring location to monitor a single service. -The Heartbeat instance sends the monitoring data to Elasticsearch. -Then you can use the Uptime app in Kibana to view the data from Heartbeat and determine the status of the service. - -image::images/uptime-simple-deployment.png[Uptime simple deployment] - -In this deployment, two instances of Heartbeat are deployed at two different monitoring locations. -Both instances monitor the same service. -The Heartbeat instances send the monitoring data to Elasticsearch. -As before, you can use the Uptime app in Kibana to view the Heartbeat data and determine the status of the service. -When a failure occurs, the multiple monitoring locations enable you to pinpoint the area in which the failure has occurred. - -image::images/uptime-multi-deployment.png[Uptime multiple server deployment] - diff --git a/docs/uptime-guide/settings.asciidoc b/docs/uptime-guide/settings.asciidoc deleted file mode 100644 index 59f9af631bfa7..0000000000000 --- a/docs/uptime-guide/settings.asciidoc +++ /dev/null @@ -1,51 +0,0 @@ -[role="xpack"] -[[uptime-settings]] - -=== Settings - -The Uptime settings page lets you change which Heartbeat indices are displayed -by the uptime app. Users must have the 'all' permission to modify items on this page. -Uptime settings apply to the current space only. Use different settings in different -spaces to segment different uptime use cases and domains. - -==== Indices - -Imagine your organization has one team for internal IT services, and another -for public services. Each team operates independently and is only responsible for its -own services. In this scenario, you might set up separate Heartbeat instances for each team, -writing out to index patterns named `it-heartbeat-\*`, and `external-heartbeat-\*`. You would -create separate roles and users for each in Elasticsearch, each with access to their own spaces, -named `it` and `external` respectively. Within each space you would navigate to the settings page -and set the correct index pattern to match only the indices that space is allowed to access. - -Note: The pattern set here only restricts what the Uptime app shows. Users may still be able -to manually query Elasticsearch for data outside this pattern. - -[role="screenshot"] -image::images/indices.png[Heartbeat indices] - -See the {kibana-ref}/uptime-security.html[Uptime security] and {heartbeat-ref}/securing-heartbeat.html[Heartbeat security] -docs for more information. - -==== Certificate thresholds - -You can modify settings in this section to control how Uptime will visualize your TLS values in -the <>. These settings also determine which certificates will be -selected by any TLS alert you define. - -There are two fields, `age` and `expiration`. Use the `age` threshold to specify when Uptime should warn -you about certificates that have been valid for too long. Use the `expiration` threshold to specify when Uptime should warn you -about certificates that have approaching expiration dates. - -For example, a common security requirement is to make sure that none of your organization's TLS certificates have been -valid for longer than one year. Modifying the `Age limit` field's value to 365 days will help you keep track of which -certificates you may want to refresh. - -Likewise, to see which of your TLS certificates are close to expiring ahead of time, specify -an `Expiration threshold` on this page. When the count of a certificate's remaining valid days falls -below this threshold, Uptime will consider it in a warning state. When you define a TLS alert, you receive a -notification from Uptime about the certificate. - -[role="screenshot"] -image::images/cert-exp.png[Certification expiration thresholds] - diff --git a/src/dev/ci_setup/checkout_sibling_es.sh b/src/dev/ci_setup/checkout_sibling_es.sh index 3832ec9b4076a..915759d4214f9 100755 --- a/src/dev/ci_setup/checkout_sibling_es.sh +++ b/src/dev/ci_setup/checkout_sibling_es.sh @@ -7,11 +7,10 @@ function checkout_sibling { targetDir=$2 useExistingParamName=$3 useExisting="$(eval "echo "\$$useExistingParamName"")" - repoAddress="https://github.com/" if [ -z ${useExisting:+x} ]; then if [ -d "$targetDir" ]; then - echo "I expected a clean workspace but an '${project}' sibling directory already exists in [$WORKSPACE]!" + echo "I expected a clean workspace but an '${project}' sibling directory already exists in [$PARENT_DIR]!" echo echo "Either define '${useExistingParamName}' or remove the existing '${project}' sibling." exit 1 @@ -22,9 +21,8 @@ function checkout_sibling { cloneBranch="" function clone_target_is_valid { - echo " -> checking for '${cloneBranch}' branch at ${cloneAuthor}/${project}" - if [[ -n "$(git ls-remote --heads "${repoAddress}${cloneAuthor}/${project}.git" ${cloneBranch} 2>/dev/null)" ]]; then + if [[ -n "$(git ls-remote --heads "git@github.com:${cloneAuthor}/${project}.git" ${cloneBranch} 2>/dev/null)" ]]; then return 0 else return 1 @@ -73,7 +71,7 @@ function checkout_sibling { fi echo " -> checking out '${cloneBranch}' branch from ${cloneAuthor}/${project}..." - git clone -b "$cloneBranch" "${repoAddress}${cloneAuthor}/${project}.git" "$targetDir" --depth=1 + git clone -b "$cloneBranch" "git@github.com:${cloneAuthor}/${project}.git" "$targetDir" --depth=1 echo " -> checked out ${project} revision: $(git -C "${targetDir}" rev-parse HEAD)" echo } @@ -89,12 +87,12 @@ function checkout_sibling { fi } -checkout_sibling "elasticsearch" "${WORKSPACE}/elasticsearch" "USE_EXISTING_ES" +checkout_sibling "elasticsearch" "${PARENT_DIR}/elasticsearch" "USE_EXISTING_ES" export TEST_ES_FROM=${TEST_ES_FROM:-snapshot} # Set the JAVA_HOME based on the Java property file in the ES repo # This assumes the naming convention used on CI (ex: ~/.java/java10) -ES_DIR="$WORKSPACE/elasticsearch" +ES_DIR="$PARENT_DIR/elasticsearch" ES_JAVA_PROP_PATH=$ES_DIR/.ci/java-versions.properties diff --git a/src/dev/ci_setup/setup_env.sh b/src/dev/ci_setup/setup_env.sh index f96a2240917e2..343ff47199375 100644 --- a/src/dev/ci_setup/setup_env.sh +++ b/src/dev/ci_setup/setup_env.sh @@ -53,8 +53,6 @@ export PARENT_DIR="$parentDir" kbnBranch="$(jq -r .branch "$KIBANA_DIR/package.json")" export KIBANA_PKG_BRANCH="$kbnBranch" -export WORKSPACE="${WORKSPACE:-$PARENT_DIR}" - ### ### download node ### @@ -163,7 +161,7 @@ export -f checks-reporter-with-killswitch source "$KIBANA_DIR/src/dev/ci_setup/load_env_keys.sh" -ES_DIR="$WORKSPACE/elasticsearch" +ES_DIR="$PARENT_DIR/elasticsearch" ES_JAVA_PROP_PATH=$ES_DIR/.ci/java-versions.properties if [[ -d "$ES_DIR" && -f "$ES_JAVA_PROP_PATH" ]]; then diff --git a/src/dev/notice/generate_notice_from_source.ts b/src/dev/notice/generate_notice_from_source.ts index a2b05c6dc8a4e..fb74bed0f26f4 100644 --- a/src/dev/notice/generate_notice_from_source.ts +++ b/src/dev/notice/generate_notice_from_source.ts @@ -49,10 +49,8 @@ export async function generateNoticeFromSource({ productName, directory, log }: ignore: [ '{node_modules,build,target,dist,data,built_assets}/**', 'packages/*/{node_modules,build,target,dist}/**', - 'src/plugins/*/{node_modules,build,target,dist}/**', 'x-pack/{node_modules,build,target,dist,data}/**', 'x-pack/packages/*/{node_modules,build,target,dist}/**', - 'x-pack/plugins/*/{node_modules,build,target,dist}/**', ], }; diff --git a/src/dev/storybook/aliases.ts b/src/dev/storybook/aliases.ts index 85bfd4a7a4d26..9d9f5616b5a33 100644 --- a/src/dev/storybook/aliases.ts +++ b/src/dev/storybook/aliases.ts @@ -26,4 +26,5 @@ export const storybookAliases = { infra: 'x-pack/legacy/plugins/infra/scripts/storybook.js', security_solution: 'x-pack/plugins/security_solution/scripts/storybook.js', ui_actions_enhanced: 'x-pack/plugins/ui_actions_enhanced/scripts/storybook.js', + observability: 'x-pack/plugins/observability/scripts/storybook.js', }; diff --git a/src/plugins/data/common/constants.ts b/src/plugins/data/common/constants.ts index 8ec72dc1f9a74..22db1552e4303 100644 --- a/src/plugins/data/common/constants.ts +++ b/src/plugins/data/common/constants.ts @@ -44,7 +44,8 @@ export const UI_SETTINGS = { FORMAT_NUMBER_DEFAULT_LOCALE: 'format:number:defaultLocale', TIMEPICKER_REFRESH_INTERVAL_DEFAULTS: 'timepicker:refreshIntervalDefaults', TIMEPICKER_QUICK_RANGES: 'timepicker:quickRanges', + TIMEPICKER_TIME_DEFAULTS: 'timepicker:timeDefaults', INDEXPATTERN_PLACEHOLDER: 'indexPattern:placeholder', FILTERS_PINNED_BY_DEFAULT: 'filters:pinnedByDefault', FILTERS_EDITOR_SUGGEST_VALUES: 'filterEditor:suggestValues', -}; +} as const; diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index c8110dbfd0041..edc8748a29ecd 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -1906,33 +1906,34 @@ export interface TimeRange { // // @public (undocumented) export const UI_SETTINGS: { - META_FIELDS: string; - DOC_HIGHLIGHT: string; - QUERY_STRING_OPTIONS: string; - QUERY_ALLOW_LEADING_WILDCARDS: string; - SEARCH_QUERY_LANGUAGE: string; - SORT_OPTIONS: string; - COURIER_IGNORE_FILTER_IF_FIELD_NOT_IN_INDEX: string; - COURIER_SET_REQUEST_PREFERENCE: string; - COURIER_CUSTOM_REQUEST_PREFERENCE: string; - COURIER_MAX_CONCURRENT_SHARD_REQUESTS: string; - COURIER_BATCH_SEARCHES: string; - SEARCH_INCLUDE_FROZEN: string; - HISTOGRAM_BAR_TARGET: string; - HISTOGRAM_MAX_BARS: string; - HISTORY_LIMIT: string; - SHORT_DOTS_ENABLE: string; - FORMAT_DEFAULT_TYPE_MAP: string; - FORMAT_NUMBER_DEFAULT_PATTERN: string; - FORMAT_PERCENT_DEFAULT_PATTERN: string; - FORMAT_BYTES_DEFAULT_PATTERN: string; - FORMAT_CURRENCY_DEFAULT_PATTERN: string; - FORMAT_NUMBER_DEFAULT_LOCALE: string; - TIMEPICKER_REFRESH_INTERVAL_DEFAULTS: string; - TIMEPICKER_QUICK_RANGES: string; - INDEXPATTERN_PLACEHOLDER: string; - FILTERS_PINNED_BY_DEFAULT: string; - FILTERS_EDITOR_SUGGEST_VALUES: string; + readonly META_FIELDS: "metaFields"; + readonly DOC_HIGHLIGHT: "doc_table:highlight"; + readonly QUERY_STRING_OPTIONS: "query:queryString:options"; + readonly QUERY_ALLOW_LEADING_WILDCARDS: "query:allowLeadingWildcards"; + readonly SEARCH_QUERY_LANGUAGE: "search:queryLanguage"; + readonly SORT_OPTIONS: "sort:options"; + readonly COURIER_IGNORE_FILTER_IF_FIELD_NOT_IN_INDEX: "courier:ignoreFilterIfFieldNotInIndex"; + readonly COURIER_SET_REQUEST_PREFERENCE: "courier:setRequestPreference"; + readonly COURIER_CUSTOM_REQUEST_PREFERENCE: "courier:customRequestPreference"; + readonly COURIER_MAX_CONCURRENT_SHARD_REQUESTS: "courier:maxConcurrentShardRequests"; + readonly COURIER_BATCH_SEARCHES: "courier:batchSearches"; + readonly SEARCH_INCLUDE_FROZEN: "search:includeFrozen"; + readonly HISTOGRAM_BAR_TARGET: "histogram:barTarget"; + readonly HISTOGRAM_MAX_BARS: "histogram:maxBars"; + readonly HISTORY_LIMIT: "history:limit"; + readonly SHORT_DOTS_ENABLE: "shortDots:enable"; + readonly FORMAT_DEFAULT_TYPE_MAP: "format:defaultTypeMap"; + readonly FORMAT_NUMBER_DEFAULT_PATTERN: "format:number:defaultPattern"; + readonly FORMAT_PERCENT_DEFAULT_PATTERN: "format:percent:defaultPattern"; + readonly FORMAT_BYTES_DEFAULT_PATTERN: "format:bytes:defaultPattern"; + readonly FORMAT_CURRENCY_DEFAULT_PATTERN: "format:currency:defaultPattern"; + readonly FORMAT_NUMBER_DEFAULT_LOCALE: "format:number:defaultLocale"; + readonly TIMEPICKER_REFRESH_INTERVAL_DEFAULTS: "timepicker:refreshIntervalDefaults"; + readonly TIMEPICKER_QUICK_RANGES: "timepicker:quickRanges"; + readonly TIMEPICKER_TIME_DEFAULTS: "timepicker:timeDefaults"; + readonly INDEXPATTERN_PLACEHOLDER: "indexPattern:placeholder"; + readonly FILTERS_PINNED_BY_DEFAULT: "filters:pinnedByDefault"; + readonly FILTERS_EDITOR_SUGGEST_VALUES: "filterEditor:suggestValues"; }; diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md index 0048816456e17..6b62d942de688 100644 --- a/src/plugins/data/server/server.api.md +++ b/src/plugins/data/server/server.api.md @@ -767,33 +767,34 @@ export type TStrategyTypes = typeof ES_SEARCH_STRATEGY | string; // // @public (undocumented) export const UI_SETTINGS: { - META_FIELDS: string; - DOC_HIGHLIGHT: string; - QUERY_STRING_OPTIONS: string; - QUERY_ALLOW_LEADING_WILDCARDS: string; - SEARCH_QUERY_LANGUAGE: string; - SORT_OPTIONS: string; - COURIER_IGNORE_FILTER_IF_FIELD_NOT_IN_INDEX: string; - COURIER_SET_REQUEST_PREFERENCE: string; - COURIER_CUSTOM_REQUEST_PREFERENCE: string; - COURIER_MAX_CONCURRENT_SHARD_REQUESTS: string; - COURIER_BATCH_SEARCHES: string; - SEARCH_INCLUDE_FROZEN: string; - HISTOGRAM_BAR_TARGET: string; - HISTOGRAM_MAX_BARS: string; - HISTORY_LIMIT: string; - SHORT_DOTS_ENABLE: string; - FORMAT_DEFAULT_TYPE_MAP: string; - FORMAT_NUMBER_DEFAULT_PATTERN: string; - FORMAT_PERCENT_DEFAULT_PATTERN: string; - FORMAT_BYTES_DEFAULT_PATTERN: string; - FORMAT_CURRENCY_DEFAULT_PATTERN: string; - FORMAT_NUMBER_DEFAULT_LOCALE: string; - TIMEPICKER_REFRESH_INTERVAL_DEFAULTS: string; - TIMEPICKER_QUICK_RANGES: string; - INDEXPATTERN_PLACEHOLDER: string; - FILTERS_PINNED_BY_DEFAULT: string; - FILTERS_EDITOR_SUGGEST_VALUES: string; + readonly META_FIELDS: "metaFields"; + readonly DOC_HIGHLIGHT: "doc_table:highlight"; + readonly QUERY_STRING_OPTIONS: "query:queryString:options"; + readonly QUERY_ALLOW_LEADING_WILDCARDS: "query:allowLeadingWildcards"; + readonly SEARCH_QUERY_LANGUAGE: "search:queryLanguage"; + readonly SORT_OPTIONS: "sort:options"; + readonly COURIER_IGNORE_FILTER_IF_FIELD_NOT_IN_INDEX: "courier:ignoreFilterIfFieldNotInIndex"; + readonly COURIER_SET_REQUEST_PREFERENCE: "courier:setRequestPreference"; + readonly COURIER_CUSTOM_REQUEST_PREFERENCE: "courier:customRequestPreference"; + readonly COURIER_MAX_CONCURRENT_SHARD_REQUESTS: "courier:maxConcurrentShardRequests"; + readonly COURIER_BATCH_SEARCHES: "courier:batchSearches"; + readonly SEARCH_INCLUDE_FROZEN: "search:includeFrozen"; + readonly HISTOGRAM_BAR_TARGET: "histogram:barTarget"; + readonly HISTOGRAM_MAX_BARS: "histogram:maxBars"; + readonly HISTORY_LIMIT: "history:limit"; + readonly SHORT_DOTS_ENABLE: "shortDots:enable"; + readonly FORMAT_DEFAULT_TYPE_MAP: "format:defaultTypeMap"; + readonly FORMAT_NUMBER_DEFAULT_PATTERN: "format:number:defaultPattern"; + readonly FORMAT_PERCENT_DEFAULT_PATTERN: "format:percent:defaultPattern"; + readonly FORMAT_BYTES_DEFAULT_PATTERN: "format:bytes:defaultPattern"; + readonly FORMAT_CURRENCY_DEFAULT_PATTERN: "format:currency:defaultPattern"; + readonly FORMAT_NUMBER_DEFAULT_LOCALE: "format:number:defaultLocale"; + readonly TIMEPICKER_REFRESH_INTERVAL_DEFAULTS: "timepicker:refreshIntervalDefaults"; + readonly TIMEPICKER_QUICK_RANGES: "timepicker:quickRanges"; + readonly TIMEPICKER_TIME_DEFAULTS: "timepicker:timeDefaults"; + readonly INDEXPATTERN_PLACEHOLDER: "indexPattern:placeholder"; + readonly FILTERS_PINNED_BY_DEFAULT: "filters:pinnedByDefault"; + readonly FILTERS_EDITOR_SUGGEST_VALUES: "filterEditor:suggestValues"; }; diff --git a/tasks/config/karma.js b/tasks/config/karma.js index 7c4f75bea8801..fa4bdc8ed2266 100644 --- a/tasks/config/karma.js +++ b/tasks/config/karma.js @@ -110,7 +110,7 @@ module.exports = function (grunt) { customLaunchers: { Chrome_Headless: { base: 'Chrome', - flags: ['--headless', '--disable-gpu', '--remote-debugging-port=9222', '--no-sandbox'], + flags: ['--headless', '--disable-gpu', '--remote-debugging-port=9222'], }, }, diff --git a/tasks/test_jest.js b/tasks/test_jest.js index 810ed42324840..d8f51806e8ddc 100644 --- a/tasks/test_jest.js +++ b/tasks/test_jest.js @@ -22,7 +22,7 @@ const { resolve } = require('path'); module.exports = function (grunt) { grunt.registerTask('test:jest', function () { const done = this.async(); - runJest(resolve(__dirname, '../scripts/jest.js'), ['--maxWorkers=10']).then(done, done); + runJest(resolve(__dirname, '../scripts/jest.js')).then(done, done); }); grunt.registerTask('test:jest_integration', function () { @@ -30,10 +30,10 @@ module.exports = function (grunt) { runJest(resolve(__dirname, '../scripts/jest_integration.js')).then(done, done); }); - function runJest(jestScript, args = []) { + function runJest(jestScript) { const serverCmd = { cmd: 'node', - args: [jestScript, '--ci', ...args], + args: [jestScript, '--ci'], opts: { stdio: 'inherit' }, }; diff --git a/test/scripts/checks/doc_api_changes.sh b/test/scripts/checks/doc_api_changes.sh deleted file mode 100755 index 503d12b2f6d73..0000000000000 --- a/test/scripts/checks/doc_api_changes.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash - -source src/dev/ci_setup/setup_env.sh - -yarn run grunt run:checkDocApiChanges diff --git a/test/scripts/checks/file_casing.sh b/test/scripts/checks/file_casing.sh deleted file mode 100755 index 513664263791b..0000000000000 --- a/test/scripts/checks/file_casing.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash - -source src/dev/ci_setup/setup_env.sh - -yarn run grunt run:checkFileCasing diff --git a/test/scripts/checks/i18n.sh b/test/scripts/checks/i18n.sh deleted file mode 100755 index 7a6fd46c46c76..0000000000000 --- a/test/scripts/checks/i18n.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash - -source src/dev/ci_setup/setup_env.sh - -yarn run grunt run:i18nCheck diff --git a/test/scripts/checks/licenses.sh b/test/scripts/checks/licenses.sh deleted file mode 100755 index a08d7d07a24a1..0000000000000 --- a/test/scripts/checks/licenses.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash - -source src/dev/ci_setup/setup_env.sh - -yarn run grunt run:licenses diff --git a/test/scripts/checks/lock_file_symlinks.sh b/test/scripts/checks/lock_file_symlinks.sh deleted file mode 100755 index 1d43d32c9feb8..0000000000000 --- a/test/scripts/checks/lock_file_symlinks.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash - -source src/dev/ci_setup/setup_env.sh - -yarn run grunt run:checkLockfileSymlinks diff --git a/test/scripts/checks/test_hardening.sh b/test/scripts/checks/test_hardening.sh deleted file mode 100755 index 9184758577654..0000000000000 --- a/test/scripts/checks/test_hardening.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash - -source src/dev/ci_setup/setup_env.sh - -yarn run grunt run:test_hardening diff --git a/test/scripts/checks/test_projects.sh b/test/scripts/checks/test_projects.sh deleted file mode 100755 index 5f9aafe80e10e..0000000000000 --- a/test/scripts/checks/test_projects.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash - -source src/dev/ci_setup/setup_env.sh - -yarn run grunt run:test_projects diff --git a/test/scripts/checks/ts_projects.sh b/test/scripts/checks/ts_projects.sh deleted file mode 100755 index d667c753baec2..0000000000000 --- a/test/scripts/checks/ts_projects.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash - -source src/dev/ci_setup/setup_env.sh - -yarn run grunt run:checkTsProjects diff --git a/test/scripts/checks/type_check.sh b/test/scripts/checks/type_check.sh deleted file mode 100755 index 07c49638134be..0000000000000 --- a/test/scripts/checks/type_check.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash - -source src/dev/ci_setup/setup_env.sh - -yarn run grunt run:typeCheck diff --git a/test/scripts/checks/verify_dependency_versions.sh b/test/scripts/checks/verify_dependency_versions.sh deleted file mode 100755 index b73a71e7ff7fd..0000000000000 --- a/test/scripts/checks/verify_dependency_versions.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash - -source src/dev/ci_setup/setup_env.sh - -yarn run grunt run:verifyDependencyVersions diff --git a/test/scripts/checks/verify_notice.sh b/test/scripts/checks/verify_notice.sh deleted file mode 100755 index 9f8343e540861..0000000000000 --- a/test/scripts/checks/verify_notice.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash - -source src/dev/ci_setup/setup_env.sh - -yarn run grunt run:verifyNotice diff --git a/test/scripts/jenkins_build_kbn_sample_panel_action.sh b/test/scripts/jenkins_build_kbn_sample_panel_action.sh old mode 100755 new mode 100644 diff --git a/test/scripts/jenkins_build_kibana.sh b/test/scripts/jenkins_build_kibana.sh index f449986713f97..3e49edc8e6ae5 100755 --- a/test/scripts/jenkins_build_kibana.sh +++ b/test/scripts/jenkins_build_kibana.sh @@ -2,9 +2,19 @@ source src/dev/ci_setup/setup_env.sh -if [[ ! "$TASK_QUEUE_PROCESS_ID" ]]; then - ./test/scripts/jenkins_build_plugins.sh -fi +echo " -> building examples separate from test plugins" +node scripts/build_kibana_platform_plugins \ + --oss \ + --examples \ + --verbose; + +echo " -> building test plugins" +node scripts/build_kibana_platform_plugins \ + --oss \ + --no-examples \ + --scan-dir "$KIBANA_DIR/test/plugin_functional/plugins" \ + --scan-dir "$KIBANA_DIR/test/interpreter_functional/plugins" \ + --verbose; # doesn't persist, also set in kibanaPipeline.groovy export KBN_NP_PLUGINS_BUILT=true @@ -16,7 +26,4 @@ yarn run grunt functionalTests:ensureAllTestsInCiGroup; if [[ -z "$CODE_COVERAGE" ]] ; then echo " -> building and extracting OSS Kibana distributable for use in functional tests" node scripts/build --debug --oss - - mkdir -p "$WORKSPACE/kibana-build-oss" - cp -pR build/oss/kibana-*-SNAPSHOT-linux-x86_64/. $WORKSPACE/kibana-build-oss/ fi diff --git a/test/scripts/jenkins_build_plugins.sh b/test/scripts/jenkins_build_plugins.sh deleted file mode 100755 index 32b3942074b34..0000000000000 --- a/test/scripts/jenkins_build_plugins.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env bash - -source src/dev/ci_setup/setup_env.sh - -echo " -> building examples separate from test plugins" -node scripts/build_kibana_platform_plugins \ - --oss \ - --examples \ - --workers 6 \ - --verbose - -echo " -> building kibana platform plugins" -node scripts/build_kibana_platform_plugins \ - --oss \ - --no-examples \ - --scan-dir "$KIBANA_DIR/test/plugin_functional/plugins" \ - --scan-dir "$KIBANA_DIR/test/interpreter_functional/plugins" \ - --workers 6 \ - --verbose diff --git a/test/scripts/jenkins_ci_group.sh b/test/scripts/jenkins_ci_group.sh index 2542d7032e83b..60d7f0406f4c9 100755 --- a/test/scripts/jenkins_ci_group.sh +++ b/test/scripts/jenkins_ci_group.sh @@ -5,7 +5,7 @@ source test/scripts/jenkins_test_setup_oss.sh if [[ -z "$CODE_COVERAGE" ]]; then checks-reporter-with-killswitch "Functional tests / Group ${CI_GROUP}" yarn run grunt "run:functionalTests_ciGroup${CI_GROUP}"; - if [[ ! "$TASK_QUEUE_PROCESS_ID" && "$CI_GROUP" == "1" ]]; then + if [ "$CI_GROUP" == "1" ]; then source test/scripts/jenkins_build_kbn_sample_panel_action.sh yarn run grunt run:pluginFunctionalTestsRelease --from=source; yarn run grunt run:exampleFunctionalTestsRelease --from=source; diff --git a/test/scripts/jenkins_plugin_functional.sh b/test/scripts/jenkins_plugin_functional.sh deleted file mode 100755 index 1d691d98982de..0000000000000 --- a/test/scripts/jenkins_plugin_functional.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env bash - -source test/scripts/jenkins_test_setup_oss.sh - -cd test/plugin_functional/plugins/kbn_sample_panel_action; -if [[ ! -d "target" ]]; then - yarn build; -fi -cd -; - -pwd - -yarn run grunt run:pluginFunctionalTestsRelease --from=source; -yarn run grunt run:exampleFunctionalTestsRelease --from=source; -yarn run grunt run:interpreterFunctionalTestsRelease; diff --git a/test/scripts/jenkins_security_solution_cypress.sh b/test/scripts/jenkins_security_solution_cypress.sh old mode 100755 new mode 100644 index a5a1a2103801f..204911a3eedaa --- a/test/scripts/jenkins_security_solution_cypress.sh +++ b/test/scripts/jenkins_security_solution_cypress.sh @@ -1,6 +1,12 @@ #!/usr/bin/env bash -source test/scripts/jenkins_test_setup_xpack.sh +source test/scripts/jenkins_test_setup.sh + +installDir="$PARENT_DIR/install/kibana" +destDir="${installDir}-${CI_WORKER_NUMBER}" +cp -R "$installDir" "$destDir" + +export KIBANA_INSTALL_DIR="$destDir" echo " -> Running security solution cypress tests" cd "$XPACK_DIR" diff --git a/test/scripts/jenkins_setup_parallel_workspace.sh b/test/scripts/jenkins_setup_parallel_workspace.sh deleted file mode 100755 index 5274d05572e71..0000000000000 --- a/test/scripts/jenkins_setup_parallel_workspace.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env bash -set -e - -CURRENT_DIR=$(pwd) - -# Copy everything except node_modules into the current workspace -rsync -a ${WORKSPACE}/kibana/* . --exclude node_modules -rsync -a ${WORKSPACE}/kibana/.??* . - -# Symlink all non-root, non-fixture node_modules into our new workspace -cd ${WORKSPACE}/kibana -find . -type d -name node_modules -not -path '*__fixtures__*' -not -path './node_modules*' -prune -print0 | xargs -0I % ln -s "${WORKSPACE}/kibana/%" "${CURRENT_DIR}/%" -find . -type d -wholename '*__fixtures__*node_modules' -not -path './node_modules*' -prune -print0 | xargs -0I % cp -R "${WORKSPACE}/kibana/%" "${CURRENT_DIR}/%" -cd "${CURRENT_DIR}" - -# Symlink all of the individual root-level node_modules into the node_modules/ directory -mkdir -p node_modules -ln -s ${WORKSPACE}/kibana/node_modules/* node_modules/ -ln -s ${WORKSPACE}/kibana/node_modules/.??* node_modules/ - -# Copy a few node_modules instead of symlinking them. They don't work correctly if symlinked -unlink node_modules/@kbn -unlink node_modules/css-loader -unlink node_modules/style-loader - -# packages/kbn-optimizer/src/integration_tests/basic_optimization.test.ts will fail if this is a symlink -unlink node_modules/val-loader - -cp -R ${WORKSPACE}/kibana/node_modules/@kbn node_modules/ -cp -R ${WORKSPACE}/kibana/node_modules/css-loader node_modules/ -cp -R ${WORKSPACE}/kibana/node_modules/style-loader node_modules/ -cp -R ${WORKSPACE}/kibana/node_modules/val-loader node_modules/ diff --git a/test/scripts/jenkins_test_setup.sh b/test/scripts/jenkins_test_setup.sh old mode 100755 new mode 100644 index 7cced76eb650f..49ee8a6b526ca --- a/test/scripts/jenkins_test_setup.sh +++ b/test/scripts/jenkins_test_setup.sh @@ -14,7 +14,3 @@ trap 'post_work' EXIT export TEST_BROWSER_HEADLESS=1 source src/dev/ci_setup/setup_env.sh - -if [[ ! -d .es && -d "$WORKSPACE/kibana/.es" ]]; then - cp -R $WORKSPACE/kibana/.es ./ -fi diff --git a/test/scripts/jenkins_test_setup_oss.sh b/test/scripts/jenkins_test_setup_oss.sh old mode 100755 new mode 100644 index b7eac33f35176..7bbb867526384 --- a/test/scripts/jenkins_test_setup_oss.sh +++ b/test/scripts/jenkins_test_setup_oss.sh @@ -2,17 +2,10 @@ source test/scripts/jenkins_test_setup.sh -if [[ -z "$CODE_COVERAGE" ]]; then - - destDir="build/kibana-build-oss" - if [[ ! "$TASK_QUEUE_PROCESS_ID" ]]; then - destDir="${destDir}-${CI_PARALLEL_PROCESS_NUMBER}" - fi - - if [[ ! -d $destDir ]]; then - mkdir -p $destDir - cp -pR "$WORKSPACE/kibana-build-oss/." $destDir/ - fi +if [[ -z "$CODE_COVERAGE" ]] ; then + installDir="$(realpath $PARENT_DIR/kibana/build/oss/kibana-*-SNAPSHOT-linux-x86_64)" + destDir=${installDir}-${CI_PARALLEL_PROCESS_NUMBER} + cp -R "$installDir" "$destDir" export KIBANA_INSTALL_DIR="$destDir" fi diff --git a/test/scripts/jenkins_test_setup_xpack.sh b/test/scripts/jenkins_test_setup_xpack.sh old mode 100755 new mode 100644 index 74a3de77e3a76..a72e9749ebbd5 --- a/test/scripts/jenkins_test_setup_xpack.sh +++ b/test/scripts/jenkins_test_setup_xpack.sh @@ -3,18 +3,11 @@ source test/scripts/jenkins_test_setup.sh if [[ -z "$CODE_COVERAGE" ]]; then + installDir="$PARENT_DIR/install/kibana" + destDir="${installDir}-${CI_PARALLEL_PROCESS_NUMBER}" + cp -R "$installDir" "$destDir" - destDir="build/kibana-build-xpack" - if [[ ! "$TASK_QUEUE_PROCESS_ID" ]]; then - destDir="${destDir}-${CI_PARALLEL_PROCESS_NUMBER}" - fi - - if [[ ! -d $destDir ]]; then - mkdir -p $destDir - cp -pR "$WORKSPACE/kibana-build-xpack/." $destDir/ - fi - - export KIBANA_INSTALL_DIR="$(realpath $destDir)" + export KIBANA_INSTALL_DIR="$destDir" cd "$XPACK_DIR" fi diff --git a/test/scripts/jenkins_xpack_build_kibana.sh b/test/scripts/jenkins_xpack_build_kibana.sh index 2452e2f5b8c58..58ef6a42d3fe4 100755 --- a/test/scripts/jenkins_xpack_build_kibana.sh +++ b/test/scripts/jenkins_xpack_build_kibana.sh @@ -3,9 +3,21 @@ cd "$KIBANA_DIR" source src/dev/ci_setup/setup_env.sh -if [[ ! "$TASK_QUEUE_PROCESS_ID" ]]; then - ./test/scripts/jenkins_xpack_build_plugins.sh -fi +echo " -> building examples separate from test plugins" +node scripts/build_kibana_platform_plugins \ + --examples \ + --verbose; + +echo " -> building test plugins" +node scripts/build_kibana_platform_plugins \ + --no-examples \ + --scan-dir "$KIBANA_DIR/test/plugin_functional/plugins" \ + --scan-dir "$XPACK_DIR/test/plugin_functional/plugins" \ + --scan-dir "$XPACK_DIR/test/functional_with_es_ssl/fixtures/plugins" \ + --scan-dir "$XPACK_DIR/test/alerting_api_integration/plugins" \ + --scan-dir "$XPACK_DIR/test/plugin_api_integration/plugins" \ + --scan-dir "$XPACK_DIR/test/plugin_api_perf/plugins" \ + --verbose; # doesn't persist, also set in kibanaPipeline.groovy export KBN_NP_PLUGINS_BUILT=true @@ -30,10 +42,7 @@ if [[ -z "$CODE_COVERAGE" ]] ; then cd "$KIBANA_DIR" node scripts/build --debug --no-oss linuxBuild="$(find "$KIBANA_DIR/target" -name 'kibana-*-linux-x86_64.tar.gz')" - installDir="$KIBANA_DIR/install/kibana" + installDir="$PARENT_DIR/install/kibana" mkdir -p "$installDir" tar -xzf "$linuxBuild" -C "$installDir" --strip=1 - - mkdir -p "$WORKSPACE/kibana-build-xpack" - cp -pR install/kibana/. $WORKSPACE/kibana-build-xpack/ fi diff --git a/test/scripts/jenkins_xpack_build_plugins.sh b/test/scripts/jenkins_xpack_build_plugins.sh deleted file mode 100755 index fea30c547bd5f..0000000000000 --- a/test/scripts/jenkins_xpack_build_plugins.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env bash - -source src/dev/ci_setup/setup_env.sh - -echo " -> building examples separate from test plugins" -node scripts/build_kibana_platform_plugins \ - --workers 12 \ - --examples \ - --verbose - -echo " -> building kibana platform plugins" -node scripts/build_kibana_platform_plugins \ - --no-examples \ - --scan-dir "$KIBANA_DIR/test/plugin_functional/plugins" \ - --scan-dir "$XPACK_DIR/test/plugin_functional/plugins" \ - --scan-dir "$XPACK_DIR/test/functional_with_es_ssl/fixtures/plugins" \ - --scan-dir "$XPACK_DIR/test/alerting_api_integration/plugins" \ - --scan-dir "$XPACK_DIR/test/plugin_api_integration/plugins" \ - --scan-dir "$XPACK_DIR/test/plugin_api_perf/plugins" \ - --workers 12 \ - --verbose diff --git a/test/scripts/jenkins_xpack_page_load_metrics.sh b/test/scripts/jenkins_xpack_page_load_metrics.sh old mode 100755 new mode 100644 diff --git a/test/scripts/lint/eslint.sh b/test/scripts/lint/eslint.sh deleted file mode 100755 index c3211300b96c5..0000000000000 --- a/test/scripts/lint/eslint.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash - -source src/dev/ci_setup/setup_env.sh - -yarn run grunt run:eslint diff --git a/test/scripts/lint/sasslint.sh b/test/scripts/lint/sasslint.sh deleted file mode 100755 index b9c683bcb049e..0000000000000 --- a/test/scripts/lint/sasslint.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash - -source src/dev/ci_setup/setup_env.sh - -yarn run grunt run:sasslint diff --git a/test/scripts/test/api_integration.sh b/test/scripts/test/api_integration.sh deleted file mode 100755 index 152c97a3ca7df..0000000000000 --- a/test/scripts/test/api_integration.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash - -source src/dev/ci_setup/setup_env.sh - -yarn run grunt run:apiIntegrationTests diff --git a/test/scripts/test/jest_integration.sh b/test/scripts/test/jest_integration.sh deleted file mode 100755 index 73dbbddfb38f6..0000000000000 --- a/test/scripts/test/jest_integration.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash - -source src/dev/ci_setup/setup_env.sh - -yarn run grunt run:test_jest_integration diff --git a/test/scripts/test/jest_unit.sh b/test/scripts/test/jest_unit.sh deleted file mode 100755 index e25452698cebc..0000000000000 --- a/test/scripts/test/jest_unit.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash - -source src/dev/ci_setup/setup_env.sh - -yarn run grunt run:test_jest diff --git a/test/scripts/test/karma_ci.sh b/test/scripts/test/karma_ci.sh deleted file mode 100755 index e9985300ba19d..0000000000000 --- a/test/scripts/test/karma_ci.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash - -source src/dev/ci_setup/setup_env.sh - -yarn run grunt run:test_karma_ci diff --git a/test/scripts/test/mocha.sh b/test/scripts/test/mocha.sh deleted file mode 100755 index 43c00f0a09dcf..0000000000000 --- a/test/scripts/test/mocha.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash - -source src/dev/ci_setup/setup_env.sh - -yarn run grunt run:mocha diff --git a/test/scripts/test/xpack_jest_unit.sh b/test/scripts/test/xpack_jest_unit.sh deleted file mode 100755 index 93d70ec355391..0000000000000 --- a/test/scripts/test/xpack_jest_unit.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash - -source src/dev/ci_setup/setup_env.sh - -cd x-pack -checks-reporter-with-killswitch "X-Pack Jest" node --max-old-space-size=6144 scripts/jest --ci --verbose --maxWorkers=10 diff --git a/test/scripts/test/xpack_karma.sh b/test/scripts/test/xpack_karma.sh deleted file mode 100755 index 9078f01f1b870..0000000000000 --- a/test/scripts/test/xpack_karma.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash - -source src/dev/ci_setup/setup_env.sh - -cd x-pack -checks-reporter-with-killswitch "X-Pack Karma Tests" yarn test:karma diff --git a/test/scripts/test/xpack_list_cyclic_dependency.sh b/test/scripts/test/xpack_list_cyclic_dependency.sh deleted file mode 100755 index 493fe9f58d322..0000000000000 --- a/test/scripts/test/xpack_list_cyclic_dependency.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash - -source src/dev/ci_setup/setup_env.sh - -cd x-pack -checks-reporter-with-killswitch "X-Pack List cyclic dependency test" node plugins/lists/scripts/check_circular_deps diff --git a/test/scripts/test/xpack_siem_cyclic_dependency.sh b/test/scripts/test/xpack_siem_cyclic_dependency.sh deleted file mode 100755 index b21301f25ad08..0000000000000 --- a/test/scripts/test/xpack_siem_cyclic_dependency.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash - -source src/dev/ci_setup/setup_env.sh - -cd x-pack -checks-reporter-with-killswitch "X-Pack SIEM cyclic dependency test" node plugins/security_solution/scripts/check_circular_deps diff --git a/vars/catchErrors.groovy b/vars/catchErrors.groovy index 2a1b55d832606..460a90b8ec0c0 100644 --- a/vars/catchErrors.groovy +++ b/vars/catchErrors.groovy @@ -1,15 +1,8 @@ // Basically, this is a shortcut for catchError(catchInterruptions: false) {} // By default, catchError will swallow aborts/timeouts, which we almost never want -// Also, by wrapping it in an additional try/catch, we cut down on spam in Pipeline Steps def call(Map params = [:], Closure closure) { - try { - closure() - } catch (ex) { - params.catchInterruptions = false - catchError(params) { - throw ex - } - } + params.catchInterruptions = false + return catchError(params, closure) } return this diff --git a/vars/kibanaPipeline.groovy b/vars/kibanaPipeline.groovy index 0f11204311451..f3fc5f84583c9 100644 --- a/vars/kibanaPipeline.groovy +++ b/vars/kibanaPipeline.groovy @@ -16,34 +16,27 @@ def withPostBuildReporting(Closure closure) { } } -def withFunctionalTestEnv(List additionalEnvs = [], Closure closure) { - // This can go away once everything that uses the deprecated workers.parallelProcesses() is moved to task queue - def parallelId = env.TASK_QUEUE_PROCESS_ID ?: env.CI_PARALLEL_PROCESS_NUMBER - - def kibanaPort = "61${parallelId}1" - def esPort = "61${parallelId}2" - def esTransportPort = "61${parallelId}3" - def ingestManagementPackageRegistryPort = "61${parallelId}4" - - withEnv([ - "CI_GROUP=${parallelId}", - "REMOVE_KIBANA_INSTALL_DIR=1", - "CI_PARALLEL_PROCESS_NUMBER=${parallelId}", - "TEST_KIBANA_HOST=localhost", - "TEST_KIBANA_PORT=${kibanaPort}", - "TEST_KIBANA_URL=http://elastic:changeme@localhost:${kibanaPort}", - "TEST_ES_URL=http://elastic:changeme@localhost:${esPort}", - "TEST_ES_TRANSPORT_PORT=${esTransportPort}", - "KBN_NP_PLUGINS_BUILT=true", - "INGEST_MANAGEMENT_PACKAGE_REGISTRY_PORT=${ingestManagementPackageRegistryPort}", - ] + additionalEnvs) { - closure() - } -} - def functionalTestProcess(String name, Closure closure) { - return { - withFunctionalTestEnv(["JOB=${name}"], closure) + return { processNumber -> + def kibanaPort = "61${processNumber}1" + def esPort = "61${processNumber}2" + def esTransportPort = "61${processNumber}3" + def ingestManagementPackageRegistryPort = "61${processNumber}4" + + withEnv([ + "CI_PARALLEL_PROCESS_NUMBER=${processNumber}", + "TEST_KIBANA_HOST=localhost", + "TEST_KIBANA_PORT=${kibanaPort}", + "TEST_KIBANA_URL=http://elastic:changeme@localhost:${kibanaPort}", + "TEST_ES_URL=http://elastic:changeme@localhost:${esPort}", + "TEST_ES_TRANSPORT_PORT=${esTransportPort}", + "INGEST_MANAGEMENT_PACKAGE_REGISTRY_PORT=${ingestManagementPackageRegistryPort}", + "IS_PIPELINE_JOB=1", + "JOB=${name}", + "KBN_NP_PLUGINS_BUILT=true", + ]) { + closure() + } } } @@ -107,17 +100,11 @@ def withGcsArtifactUpload(workerName, closure) { def uploadPrefix = "kibana-ci-artifacts/jobs/${env.JOB_NAME}/${BUILD_NUMBER}/${workerName}" def ARTIFACT_PATTERNS = [ 'target/kibana-*', - 'target/test-metrics/*', 'target/kibana-security-solution/**/*.png', 'target/junit/**/*', - 'target/test-suites-ci-plan.json', - 'test/**/screenshots/session/*.png', - 'test/**/screenshots/failure/*.png', - 'test/**/screenshots/diff/*.png', + 'test/**/screenshots/**/*.png', 'test/functional/failure_debug/html/*.html', - 'x-pack/test/**/screenshots/session/*.png', - 'x-pack/test/**/screenshots/failure/*.png', - 'x-pack/test/**/screenshots/diff/*.png', + 'x-pack/test/**/screenshots/**/*.png', 'x-pack/test/functional/failure_debug/html/*.html', 'x-pack/test/functional/apps/reporting/reports/session/*.pdf', ] @@ -132,12 +119,6 @@ def withGcsArtifactUpload(workerName, closure) { ARTIFACT_PATTERNS.each { pattern -> uploadGcsArtifact(uploadPrefix, pattern) } - - dir(env.WORKSPACE) { - ARTIFACT_PATTERNS.each { pattern -> - uploadGcsArtifact(uploadPrefix, "parallel/*/kibana/${pattern}") - } - } } } }) @@ -150,11 +131,6 @@ def withGcsArtifactUpload(workerName, closure) { def publishJunit() { junit(testResults: 'target/junit/**/*.xml', allowEmptyResults: true, keepLongStdio: true) - - // junit() is weird about paths for security reasons, so we need to actually change to an upper directory first - dir(env.WORKSPACE) { - junit(testResults: 'parallel/*/kibana/target/junit/**/*.xml', allowEmptyResults: true, keepLongStdio: true) - } } def sendMail() { @@ -218,16 +194,12 @@ def doSetup() { } } -def buildOss(maxWorkers = '') { - withEnv(["KBN_OPTIMIZER_MAX_WORKERS=${maxWorkers}"]) { - runbld("./test/scripts/jenkins_build_kibana.sh", "Build OSS/Default Kibana") - } +def buildOss() { + runbld("./test/scripts/jenkins_build_kibana.sh", "Build OSS/Default Kibana") } -def buildXpack(maxWorkers = '') { - withEnv(["KBN_OPTIMIZER_MAX_WORKERS=${maxWorkers}"]) { - runbld("./test/scripts/jenkins_xpack_build_kibana.sh", "Build X-Pack Kibana") - } +def buildXpack() { + runbld("./test/scripts/jenkins_xpack_build_kibana.sh", "Build X-Pack Kibana") } def runErrorReporter() { @@ -276,100 +248,6 @@ def call(Map params = [:], Closure closure) { } } -// Creates a task queue using withTaskQueue, and copies the bootstrapped kibana repo into each process's workspace -// Note that node_modules are mostly symlinked to save time/space. See test/scripts/jenkins_setup_parallel_workspace.sh -def withCiTaskQueue(Map options = [:], Closure closure) { - def setupClosure = { - // This can't use runbld, because it expects the source to be there, which isn't yet - bash("${env.WORKSPACE}/kibana/test/scripts/jenkins_setup_parallel_workspace.sh", "Set up duplicate workspace for parallel process") - } - - def config = [parallel: 24, setup: setupClosure] + options - - withTaskQueue(config) { - closure.call() - } -} - -def scriptTask(description, script) { - return { - withFunctionalTestEnv { - runbld(script, description) - } - } -} - -def scriptTaskDocker(description, script) { - return { - withDocker(scriptTask(description, script)) - } -} - -def buildDocker() { - sh( - script: """ - cp /usr/local/bin/runbld .ci/ - cp /usr/local/bin/bash_standard_lib.sh .ci/ - cd .ci - docker build -t kibana-ci -f ./Dockerfile . - """, - label: 'Build CI Docker image' - ) -} - -def withDocker(Closure closure) { - docker - .image('kibana-ci') - .inside( - "-v /etc/runbld:/etc/runbld:ro -v '${env.JENKINS_HOME}:${env.JENKINS_HOME}' -v '/dev/shm/workspace:/dev/shm/workspace' --shm-size 2GB --cpus 4", - closure - ) -} - -def buildOssPlugins() { - runbld('./test/scripts/jenkins_build_plugins.sh', 'Build OSS Plugins') -} - -def buildXpackPlugins() { - runbld('./test/scripts/jenkins_xpack_build_plugins.sh', 'Build X-Pack Plugins') -} - -def withTasks(Map params = [worker: [:]], Closure closure) { - catchErrors { - def config = [name: 'ci-worker', size: 'xxl', ramDisk: true] + (params.worker ?: [:]) - - workers.ci(config) { - withCiTaskQueue(parallel: 24) { - parallel([ - docker: { - retry(2) { - buildDocker() - } - }, - - // There are integration tests etc that require the plugins to be built first, so let's go ahead and build them before set up the parallel workspaces - ossPlugins: { buildOssPlugins() }, - xpackPlugins: { buildXpackPlugins() }, - ]) - - catchErrors { - closure() - } - } - } - } -} - -def allCiTasks() { - withTasks { - tasks.check() - tasks.lint() - tasks.test() - tasks.functionalOss() - tasks.functionalXpack() - } -} - def pipelineLibraryTests() { whenChanged(['vars/', '.ci/pipeline-library/']) { workers.base(size: 'flyweight', bootstrapped: false, ramDisk: false) { @@ -380,4 +258,5 @@ def pipelineLibraryTests() { } } + return this diff --git a/vars/task.groovy b/vars/task.groovy deleted file mode 100644 index 0c07b519b6fef..0000000000000 --- a/vars/task.groovy +++ /dev/null @@ -1,5 +0,0 @@ -def call(Closure closure) { - withTaskQueue.addTask(closure) -} - -return this diff --git a/vars/tasks.groovy b/vars/tasks.groovy deleted file mode 100644 index 3ff9a7b4850ae..0000000000000 --- a/vars/tasks.groovy +++ /dev/null @@ -1,124 +0,0 @@ -def call(List closures) { - withTaskQueue.addTasks(closures) -} - -def check() { - tasks([ - kibanaPipeline.scriptTask('Check TypeScript Projects', 'test/scripts/checks/ts_projects.sh'), - kibanaPipeline.scriptTask('Check Doc API Changes', 'test/scripts/checks/doc_api_changes.sh'), - kibanaPipeline.scriptTask('Check Types', 'test/scripts/checks/type_check.sh'), - kibanaPipeline.scriptTask('Check i18n', 'test/scripts/checks/i18n.sh'), - kibanaPipeline.scriptTask('Check File Casing', 'test/scripts/checks/file_casing.sh'), - kibanaPipeline.scriptTask('Check Lockfile Symlinks', 'test/scripts/checks/lock_file_symlinks.sh'), - kibanaPipeline.scriptTask('Check Licenses', 'test/scripts/checks/licenses.sh'), - kibanaPipeline.scriptTask('Verify Dependency Versions', 'test/scripts/checks/verify_dependency_versions.sh'), - kibanaPipeline.scriptTask('Verify NOTICE', 'test/scripts/checks/verify_notice.sh'), - kibanaPipeline.scriptTask('Test Projects', 'test/scripts/checks/test_projects.sh'), - kibanaPipeline.scriptTask('Test Hardening', 'test/scripts/checks/test_hardening.sh'), - ]) -} - -def lint() { - tasks([ - kibanaPipeline.scriptTask('Lint: eslint', 'test/scripts/lint/eslint.sh'), - kibanaPipeline.scriptTask('Lint: sasslint', 'test/scripts/lint/sasslint.sh'), - ]) -} - -def test() { - tasks([ - // These 4 tasks require isolation because of hard-coded, conflicting ports and such, so let's use Docker here - kibanaPipeline.scriptTaskDocker('Jest Integration Tests', 'test/scripts/test/jest_integration.sh'), - kibanaPipeline.scriptTaskDocker('Mocha Tests', 'test/scripts/test/mocha.sh'), - kibanaPipeline.scriptTaskDocker('Karma CI Tests', 'test/scripts/test/karma_ci.sh'), - kibanaPipeline.scriptTaskDocker('X-Pack Karma Tests', 'test/scripts/test/xpack_karma.sh'), - - kibanaPipeline.scriptTask('Jest Unit Tests', 'test/scripts/test/jest_unit.sh'), - kibanaPipeline.scriptTask('API Integration Tests', 'test/scripts/test/api_integration.sh'), - kibanaPipeline.scriptTask('X-Pack SIEM cyclic dependency', 'test/scripts/test/xpack_siem_cyclic_dependency.sh'), - kibanaPipeline.scriptTask('X-Pack List cyclic dependency', 'test/scripts/test/xpack_list_cyclic_dependency.sh'), - kibanaPipeline.scriptTask('X-Pack Jest Unit Tests', 'test/scripts/test/xpack_jest_unit.sh'), - ]) -} - -def functionalOss(Map params = [:]) { - def config = params ?: [ - ciGroups: true, - firefox: !githubPr.isPr(), - accessibility: true, - pluginFunctional: true, - visualRegression: false - ] - - task { - kibanaPipeline.buildOss(6) - - if (config.ciGroups) { - def ciGroups = 1..12 - tasks(ciGroups.collect { kibanaPipeline.ossCiGroupProcess(it) }) - } - - if (config.firefox) { - task(kibanaPipeline.functionalTestProcess('oss-firefox', './test/scripts/jenkins_firefox_smoke.sh')) - } - - if (config.accessibility) { - task(kibanaPipeline.functionalTestProcess('oss-accessibility', './test/scripts/jenkins_accessibility.sh')) - } - - if (config.pluginFunctional) { - task(kibanaPipeline.functionalTestProcess('oss-pluginFunctional', './test/scripts/jenkins_plugin_functional.sh')) - } - - if (config.visualRegression) { - task(kibanaPipeline.functionalTestProcess('oss-visualRegression', './test/scripts/jenkins_visual_regression.sh')) - } - } -} - -def functionalXpack(Map params = [:]) { - def config = params ?: [ - ciGroups: true, - firefox: !githubPr.isPr(), - accessibility: true, - pluginFunctional: true, - savedObjectsFieldMetrics: true, - pageLoadMetrics: false, - visualRegression: false, - ] - - task { - kibanaPipeline.buildXpack(10) - - if (config.ciGroups) { - def ciGroups = 1..10 - tasks(ciGroups.collect { kibanaPipeline.xpackCiGroupProcess(it) }) - } - - if (config.firefox) { - task(kibanaPipeline.functionalTestProcess('xpack-firefox', './test/scripts/jenkins_xpack_firefox_smoke.sh')) - } - - if (config.accessibility) { - task(kibanaPipeline.functionalTestProcess('xpack-accessibility', './test/scripts/jenkins_xpack_accessibility.sh')) - } - - if (config.visualRegression) { - task(kibanaPipeline.functionalTestProcess('xpack-visualRegression', './test/scripts/jenkins_xpack_visual_regression.sh')) - } - - if (config.pageLoadMetrics) { - task(kibanaPipeline.functionalTestProcess('xpack-pageLoadMetrics', './test/scripts/jenkins_xpack_page_load_metrics.sh')) - } - - if (config.savedObjectsFieldMetrics) { - task(kibanaPipeline.functionalTestProcess('xpack-savedObjectsFieldMetrics', './test/scripts/jenkins_xpack_saved_objects_field_metrics.sh')) - } - - whenChanged(['x-pack/plugins/security_solution/', 'x-pack/test/security_solution_cypress/']) { - task(kibanaPipeline.functionalTestProcess('xpack-securitySolutionCypress', './test/scripts/jenkins_security_solution_cypress.sh')) - } - } -} - -return this diff --git a/vars/withTaskQueue.groovy b/vars/withTaskQueue.groovy deleted file mode 100644 index 8132d6264744f..0000000000000 --- a/vars/withTaskQueue.groovy +++ /dev/null @@ -1,154 +0,0 @@ -import groovy.transform.Field - -public static @Field TASK_QUEUES = [:] -public static @Field TASK_QUEUES_COUNTER = 0 - -/** - withTaskQueue creates a queue of "tasks" (just plain closures to execute), and executes them with your desired level of concurrency. - This way, you can define, for example, 40 things that need to execute, then only allow 10 of them to execute at once. - - Each "process" will execute in a separate, unique, empty directory. - If you want each process to have a bootstrapped kibana repo, check out kibanaPipeline.withCiTaskQueue - - Using the queue currently requires an agent/worker. - - Usage: - - withTaskQueue(parallel: 10) { - task { print "This is a task" } - - // This is the same as calling task() multiple times - tasks([ { print "Another task" }, { print "And another task" } ]) - - // Tasks can queue up subsequent tasks - task { - buildThing() - task { print "I depend on buildThing()" } - } - } - - You can also define a setup task that each process should execute one time before executing tasks: - withTaskQueue(parallel: 10, setup: { sh "my-setup-scrupt.sh" }) { - ... - } - -*/ -def call(Map options = [:], Closure closure) { - def config = [ parallel: 10 ] + options - def counter = ++TASK_QUEUES_COUNTER - - // We're basically abusing withEnv() to create a "scope" for all steps inside of a withTaskQueue block - // This way, we could have multiple task queue instances in the same pipeline - withEnv(["TASK_QUEUE_ID=${counter}"]) { - withTaskQueue.TASK_QUEUES[env.TASK_QUEUE_ID] = [ - tasks: [], - tmpFile: sh(script: 'mktemp', returnStdout: true).trim() - ] - - closure.call() - - def processesExecuting = 0 - def processes = [:] - def iterationId = 0 - - for(def i = 1; i <= config.parallel; i++) { - def j = i - processes["task-queue-process-${j}"] = { - catchErrors { - withEnv([ - "TASK_QUEUE_PROCESS_ID=${j}", - "TASK_QUEUE_ITERATION_ID=${++iterationId}" - ]) { - dir("${WORKSPACE}/parallel/${j}/kibana") { - if (config.setup) { - config.setup.call(j) - } - - def isDone = false - while(!isDone) { // TODO some kind of timeout? - catchErrors { - if (!getTasks().isEmpty()) { - processesExecuting++ - catchErrors { - def task - try { - task = getTasks().pop() - } catch (java.util.NoSuchElementException ex) { - return - } - - task.call() - } - processesExecuting-- - // If a task finishes, and no new tasks were queued up, and nothing else is executing - // Then all of the processes should wake up and exit - if (processesExecuting < 1 && getTasks().isEmpty()) { - taskNotify() - } - return - } - - if (processesExecuting > 0) { - taskSleep() - return - } - - // Queue is empty, no processes are executing - isDone = true - } - } - } - } - } - } - } - parallel(processes) - } -} - -// If we sleep in a loop using Groovy code, Pipeline Steps is flooded with Sleep steps -// So, instead, we just watch a file and `touch` it whenever something happens that could modify the queue -// There's a 20 minute timeout just in case something goes wrong, -// in which case this method will get called again if the process is actually supposed to be waiting. -def taskSleep() { - sh(script: """#!/bin/bash - TIMESTAMP=\$(date '+%s' -d "0 seconds ago") - for (( i=1; i<=240; i++ )) - do - if [ "\$(stat -c %Y '${getTmpFile()}')" -ge "\$TIMESTAMP" ] - then - break - else - sleep 5 - if [[ \$i == 240 ]]; then - echo "Waited for new tasks for 20 minutes, exiting in case something went wrong" - fi - fi - done - """, label: "Waiting for new tasks...") -} - -// Used to let the task queue processes know that either a new task has been queued up, or work is complete -def taskNotify() { - sh "touch '${getTmpFile()}'" -} - -def getTasks() { - return withTaskQueue.TASK_QUEUES[env.TASK_QUEUE_ID].tasks -} - -def getTmpFile() { - return withTaskQueue.TASK_QUEUES[env.TASK_QUEUE_ID].tmpFile -} - -def addTask(Closure closure) { - getTasks() << closure - taskNotify() -} - -def addTasks(List closures) { - closures.reverse().each { - getTasks() << it - } - taskNotify() -} diff --git a/vars/workers.groovy b/vars/workers.groovy index 2e94ce12f34c0..8b7e8525a7ce3 100644 --- a/vars/workers.groovy +++ b/vars/workers.groovy @@ -13,8 +13,6 @@ def label(size) { return 'docker && tests-l' case 'xl': return 'docker && tests-xl' - case 'xl-highmem': - return 'docker && tests-xl-highmem' case 'xxl': return 'docker && tests-xxl' } @@ -57,11 +55,6 @@ def base(Map params, Closure closure) { } } - sh( - script: "mkdir -p ${env.WORKSPACE}/tmp", - label: "Create custom temp directory" - ) - def checkoutInfo = [:] if (config.scm) { @@ -96,7 +89,6 @@ def base(Map params, Closure closure) { "PR_AUTHOR=${env.ghprbPullAuthorLogin ?: ''}", "TEST_BROWSER_HEADLESS=1", "GIT_BRANCH=${checkoutInfo.branch}", - "TMPDIR=${env.WORKSPACE}/tmp", // For Chrome and anything else that respects it ]) { withCredentials([ string(credentialsId: 'vault-addr', variable: 'VAULT_ADDR'), @@ -175,9 +167,7 @@ def parallelProcesses(Map params) { sleep(delay) } - withEnv(["CI_PARALLEL_PROCESS_NUMBER=${processNumber}"]) { - processClosure() - } + processClosure(processNumber) } } diff --git a/x-pack/plugins/apm/public/plugin.ts b/x-pack/plugins/apm/public/plugin.ts index f31ad83666a17..6e3a29d9f3dbc 100644 --- a/x-pack/plugins/apm/public/plugin.ts +++ b/x-pack/plugins/apm/public/plugin.ts @@ -6,7 +6,6 @@ import { i18n } from '@kbn/i18n'; import { lazy } from 'react'; -import { euiThemeVars as theme } from '@kbn/ui-shared-deps/theme'; import { ConfigSchema } from '.'; import { ObservabilityPluginSetup } from '../../observability/public'; import { @@ -83,7 +82,7 @@ export class ApmPlugin implements Plugin { plugins.observability.dashboard.register({ appName: 'apm', fetchData: async (params) => { - return fetchLandingPageData(params, { theme }); + return fetchLandingPageData(params); }, hasData, }); diff --git a/x-pack/plugins/apm/public/services/rest/observability.dashboard.test.ts b/x-pack/plugins/apm/public/services/rest/observability.dashboard.test.ts index a14d827eeaec5..fd407a8bf72ad 100644 --- a/x-pack/plugins/apm/public/services/rest/observability.dashboard.test.ts +++ b/x-pack/plugins/apm/public/services/rest/observability.dashboard.test.ts @@ -6,7 +6,6 @@ import { fetchLandingPageData, hasData } from './observability_dashboard'; import * as createCallApmApi from './createCallApmApi'; -import { euiThemeVars as theme } from '@kbn/ui-shared-deps/theme'; describe('Observability dashboard data', () => { const callApmApiMock = jest.spyOn(createCallApmApi, 'callApmApi'); @@ -38,39 +37,31 @@ describe('Observability dashboard data', () => { ], }) ); - const response = await fetchLandingPageData( - { - startTime: '1', - endTime: '2', - bucketSize: '3', - }, - { theme } - ); + const response = await fetchLandingPageData({ + startTime: '1', + endTime: '2', + bucketSize: '3', + }); expect(response).toEqual({ title: 'APM', appLink: '/app/apm', stats: { services: { type: 'number', - label: 'Services', value: 10, }, transactions: { type: 'number', - label: 'Transactions', value: 2, - color: '#6092c0', }, }, series: { transactions: { - label: 'Transactions', coordinates: [ { x: 1, y: 1 }, { x: 2, y: 2 }, { x: 3, y: 3 }, ], - color: '#6092c0', }, }, }); @@ -82,35 +73,27 @@ describe('Observability dashboard data', () => { transactionCoordinates: [], }) ); - const response = await fetchLandingPageData( - { - startTime: '1', - endTime: '2', - bucketSize: '3', - }, - { theme } - ); + const response = await fetchLandingPageData({ + startTime: '1', + endTime: '2', + bucketSize: '3', + }); expect(response).toEqual({ title: 'APM', appLink: '/app/apm', stats: { services: { type: 'number', - label: 'Services', value: 0, }, transactions: { type: 'number', - label: 'Transactions', value: 0, - color: '#6092c0', }, }, series: { transactions: { - label: 'Transactions', coordinates: [], - color: '#6092c0', }, }, }); @@ -122,35 +105,27 @@ describe('Observability dashboard data', () => { transactionCoordinates: [{ x: 1 }, { x: 2 }, { x: 3 }], }) ); - const response = await fetchLandingPageData( - { - startTime: '1', - endTime: '2', - bucketSize: '3', - }, - { theme } - ); + const response = await fetchLandingPageData({ + startTime: '1', + endTime: '2', + bucketSize: '3', + }); expect(response).toEqual({ title: 'APM', appLink: '/app/apm', stats: { services: { type: 'number', - label: 'Services', value: 0, }, transactions: { type: 'number', - label: 'Transactions', value: 0, - color: '#6092c0', }, }, series: { transactions: { - label: 'Transactions', coordinates: [{ x: 1 }, { x: 2 }, { x: 3 }], - color: '#6092c0', }, }, }); diff --git a/x-pack/plugins/apm/public/services/rest/observability_dashboard.ts b/x-pack/plugins/apm/public/services/rest/observability_dashboard.ts index 79ccf8dbd6f9b..409cec8b9ce10 100644 --- a/x-pack/plugins/apm/public/services/rest/observability_dashboard.ts +++ b/x-pack/plugins/apm/public/services/rest/observability_dashboard.ts @@ -6,21 +6,17 @@ import { i18n } from '@kbn/i18n'; import { mean } from 'lodash'; -import { Theme } from '@kbn/ui-shared-deps/theme'; import { ApmFetchDataResponse, FetchDataParams, } from '../../../../observability/public'; import { callApmApi } from './createCallApmApi'; -interface Options { - theme: Theme; -} - -export const fetchLandingPageData = async ( - { startTime, endTime, bucketSize }: FetchDataParams, - { theme }: Options -): Promise => { +export const fetchLandingPageData = async ({ + startTime, + endTime, + bucketSize, +}: FetchDataParams): Promise => { const data = await callApmApi({ pathname: '/api/apm/observability_dashboard', params: { query: { start: startTime, end: endTime, bucketSize } }, @@ -36,34 +32,20 @@ export const fetchLandingPageData = async ( stats: { services: { type: 'number', - label: i18n.translate( - 'xpack.apm.observabilityDashboard.stats.services', - { defaultMessage: 'Services' } - ), value: serviceCount, }, transactions: { type: 'number', - label: i18n.translate( - 'xpack.apm.observabilityDashboard.stats.transactions', - { defaultMessage: 'Transactions' } - ), value: mean( transactionCoordinates .map(({ y }) => y) .filter((y) => y && isFinite(y)) ) || 0, - color: theme.euiColorVis1, }, }, series: { transactions: { - label: i18n.translate( - 'xpack.apm.observabilityDashboard.chart.transactions', - { defaultMessage: 'Transactions' } - ), - color: theme.euiColorVis1, coordinates: transactionCoordinates, }, }, diff --git a/x-pack/plugins/canvas/.storybook/storyshots.test.js b/x-pack/plugins/canvas/.storybook/storyshots.test.js index e3217ad4dbe58..b9fe0914b3698 100644 --- a/x-pack/plugins/canvas/.storybook/storyshots.test.js +++ b/x-pack/plugins/canvas/.storybook/storyshots.test.js @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import fs from 'fs'; import path from 'path'; import moment from 'moment'; import 'moment-timezone'; @@ -77,12 +76,6 @@ import { RenderedElement } from '../shareable_runtime/components/rendered_elemen jest.mock('../shareable_runtime/components/rendered_element'); RenderedElement.mockImplementation(() => 'RenderedElement'); -// Some of the code requires that this directory exists, but the tests don't actually require any css to be present -const cssDir = path.resolve(__dirname, '../../../../built_assets/css'); -if (!fs.existsSync(cssDir)) { - fs.mkdirSync(cssDir, { recursive: true }); -} - addSerializer(styleSheetSerializer); // Initialize Storyshots and build the Jest Snapshots diff --git a/x-pack/plugins/infra/public/__snapshots__/metrics_overview_fetchers.test.ts.snap b/x-pack/plugins/infra/public/__snapshots__/metrics_overview_fetchers.test.ts.snap index 99ab129fc36e3..4680414493a2c 100644 --- a/x-pack/plugins/infra/public/__snapshots__/metrics_overview_fetchers.test.ts.snap +++ b/x-pack/plugins/infra/public/__snapshots__/metrics_overview_fetchers.test.ts.snap @@ -91,7 +91,6 @@ Object { "y": 3.5, }, ], - "label": "Inbound traffic", }, "outboundTraffic": Object { "coordinates": Array [ @@ -180,32 +179,26 @@ Object { "y": 4, }, ], - "label": "Outbound traffic", }, }, "stats": Object { "cpu": Object { - "label": "CPU usage", "type": "percent", "value": 0.0015, }, "hosts": Object { - "label": "Hosts", "type": "number", "value": 2, }, "inboundTraffic": Object { - "label": "Inbound traffic", "type": "bytesPerSecond", "value": 3.5, }, "memory": Object { - "label": "Memory usage", "type": "percent", "value": 0.0015, }, "outboundTraffic": Object { - "label": "Outbound traffic", "type": "bytesPerSecond", "value": 3, }, diff --git a/x-pack/plugins/infra/public/metrics_overview_fetchers.ts b/x-pack/plugins/infra/public/metrics_overview_fetchers.ts index 15751fab39abc..25b334d03c4f7 100644 --- a/x-pack/plugins/infra/public/metrics_overview_fetchers.ts +++ b/x-pack/plugins/infra/public/metrics_overview_fetchers.ts @@ -103,14 +103,6 @@ export const createMetricsFetchData = ( body: JSON.stringify(snapshotRequest), }); - const inboundLabel = i18n.translate('xpack.infra.observabilityHomepage.metrics.rxLabel', { - defaultMessage: 'Inbound traffic', - }); - - const outboundLabel = i18n.translate('xpack.infra.observabilityHomepage.metrics.txLabel', { - defaultMessage: 'Outbound traffic', - }); - return { title: i18n.translate('xpack.infra.observabilityHomepage.metrics.title', { defaultMessage: 'Metrics', @@ -119,43 +111,30 @@ export const createMetricsFetchData = ( stats: { hosts: { type: 'number', - label: i18n.translate('xpack.infra.observabilityHomepage.metrics.hostsLabel', { - defaultMessage: 'Hosts', - }), value: results.nodes.length, }, cpu: { type: 'percent', - label: i18n.translate('xpack.infra.observabilityHomepage.metrics.cpuLabel', { - defaultMessage: 'CPU usage', - }), value: combineNodesBy('cpu', results.nodes, average), }, memory: { type: 'percent', - label: i18n.translate('xpack.infra.observabilityHomepage.metrics.memoryLabel', { - defaultMessage: 'Memory usage', - }), value: combineNodesBy('memory', results.nodes, average), }, inboundTraffic: { type: 'bytesPerSecond', - label: inboundLabel, value: combineNodesBy('rx', results.nodes, average), }, outboundTraffic: { type: 'bytesPerSecond', - label: outboundLabel, value: combineNodesBy('tx', results.nodes, average), }, }, series: { inboundTraffic: { - label: inboundLabel, coordinates: combineNodeTimeseriesBy('rx', results.nodes, average), }, outboundTraffic: { - label: outboundLabel, coordinates: combineNodeTimeseriesBy('tx', results.nodes, average), }, }, diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/__snapshots__/template.test.ts.snap b/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/__snapshots__/template.test.ts.snap index 848e65b7931eb..7437321163749 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/__snapshots__/template.test.ts.snap +++ b/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/__snapshots__/template.test.ts.snap @@ -99,7 +99,8 @@ exports[`tests loading base.yml: base.yml 1`] = ` "package": { "name": "nginx" }, - "managed_by": "ingest-manager" + "managed_by": "ingest-manager", + "managed": true } } `; @@ -203,7 +204,8 @@ exports[`tests loading coredns.logs.yml: coredns.logs.yml 1`] = ` "package": { "name": "coredns" }, - "managed_by": "ingest-manager" + "managed_by": "ingest-manager", + "managed": true } } `; @@ -1691,7 +1693,8 @@ exports[`tests loading system.yml: system.yml 1`] = ` "package": { "name": "system" }, - "managed_by": "ingest-manager" + "managed_by": "ingest-manager", + "managed": true } } `; diff --git a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/template.ts b/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/template.ts index e7867532ed176..77ad96952269f 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/template.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/elasticsearch/template/template.ts @@ -317,6 +317,7 @@ function getBaseTemplate( name: packageName, }, managed_by: 'ingest-manager', + managed: true, }, }; } diff --git a/x-pack/plugins/license_management/__jest__/__snapshots__/request_trial_extension.test.js.snap b/x-pack/plugins/license_management/__jest__/__snapshots__/request_trial_extension.test.js.snap index a3bb32337f9f8..096f26eb22fe3 100644 --- a/x-pack/plugins/license_management/__jest__/__snapshots__/request_trial_extension.test.js.snap +++ b/x-pack/plugins/license_management/__jest__/__snapshots__/request_trial_extension.test.js.snap @@ -1,9 +1,9 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`RequestTrialExtension component should display when enterprise license is not active and trial has been used 1`] = `"
Extend your trial

If you’d like to continue using machine learning, advanced security, and our other awesome Platinum features, request an extension now.

"`; +exports[`RequestTrialExtension component should display when enterprise license is not active and trial has been used 1`] = `"
Extend your trial

If you’d like to continue using machine learning, advanced security, and our other awesome subscription features, request an extension now.

"`; -exports[`RequestTrialExtension component should display when license is active and trial has been used 1`] = `"
Extend your trial

If you’d like to continue using machine learning, advanced security, and our other awesome Platinum features, request an extension now.

"`; +exports[`RequestTrialExtension component should display when license is active and trial has been used 1`] = `"
Extend your trial

If you’d like to continue using machine learning, advanced security, and our other awesome subscription features, request an extension now.

"`; -exports[`RequestTrialExtension component should display when license is not active and trial has been used 1`] = `"
Extend your trial

If you’d like to continue using machine learning, advanced security, and our other awesome Platinum features, request an extension now.

"`; +exports[`RequestTrialExtension component should display when license is not active and trial has been used 1`] = `"
Extend your trial

If you’d like to continue using machine learning, advanced security, and our other awesome subscription features, request an extension now.

"`; -exports[`RequestTrialExtension component should display when platinum license is not active and trial has been used 1`] = `"
Extend your trial

If you’d like to continue using machine learning, advanced security, and our other awesome Platinum features, request an extension now.

"`; +exports[`RequestTrialExtension component should display when platinum license is not active and trial has been used 1`] = `"
Extend your trial

If you’d like to continue using machine learning, advanced security, and our other awesome subscription features, request an extension now.

"`; diff --git a/x-pack/plugins/license_management/__jest__/__snapshots__/revert_to_basic.test.js.snap b/x-pack/plugins/license_management/__jest__/__snapshots__/revert_to_basic.test.js.snap index cb2a41dadbe9e..0a5656aa266bc 100644 --- a/x-pack/plugins/license_management/__jest__/__snapshots__/revert_to_basic.test.js.snap +++ b/x-pack/plugins/license_management/__jest__/__snapshots__/revert_to_basic.test.js.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`RevertToBasic component should display when license is about to expire 1`] = `"
Revert to Basic license

You’ll revert to our free features and lose access to machine learning, advanced security, and other Platinum features.

"`; +exports[`RevertToBasic component should display when license is about to expire 1`] = `"
Revert to Basic license

You’ll revert to our free features and lose access to machine learning, advanced security, and other subscription features.

"`; -exports[`RevertToBasic component should display when license is expired 1`] = `"
Revert to Basic license

You’ll revert to our free features and lose access to machine learning, advanced security, and other Platinum features.

"`; +exports[`RevertToBasic component should display when license is expired 1`] = `"
Revert to Basic license

You’ll revert to our free features and lose access to machine learning, advanced security, and other subscription features.

"`; -exports[`RevertToBasic component should display when trial is active 1`] = `"
Revert to Basic license

You’ll revert to our free features and lose access to machine learning, advanced security, and other Platinum features.

"`; +exports[`RevertToBasic component should display when trial is active 1`] = `"
Revert to Basic license

You’ll revert to our free features and lose access to machine learning, advanced security, and other subscription features.

"`; diff --git a/x-pack/plugins/license_management/__jest__/__snapshots__/start_trial.test.js.snap b/x-pack/plugins/license_management/__jest__/__snapshots__/start_trial.test.js.snap index 9370b77e29560..9da8bb958941b 100644 --- a/x-pack/plugins/license_management/__jest__/__snapshots__/start_trial.test.js.snap +++ b/x-pack/plugins/license_management/__jest__/__snapshots__/start_trial.test.js.snap @@ -1,9 +1,9 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`StartTrial component when trial is allowed display for basic license 1`] = `"
Start a 30-day trial

Experience what machine learning, advanced security, and all our other Platinum features have to offer.

"`; +exports[`StartTrial component when trial is allowed display for basic license 1`] = `"
Start a 30-day trial

Experience what machine learning, advanced security, and all our other subscription features have to offer.

"`; -exports[`StartTrial component when trial is allowed should display for expired enterprise license 1`] = `"
Start a 30-day trial

Experience what machine learning, advanced security, and all our other Platinum features have to offer.

"`; +exports[`StartTrial component when trial is allowed should display for expired enterprise license 1`] = `"
Start a 30-day trial

Experience what machine learning, advanced security, and all our other subscription features have to offer.

"`; -exports[`StartTrial component when trial is allowed should display for expired platinum license 1`] = `"
Start a 30-day trial

Experience what machine learning, advanced security, and all our other Platinum features have to offer.

"`; +exports[`StartTrial component when trial is allowed should display for expired platinum license 1`] = `"
Start a 30-day trial

Experience what machine learning, advanced security, and all our other subscription features have to offer.

"`; -exports[`StartTrial component when trial is allowed should display for gold license 1`] = `"
Start a 30-day trial

Experience what machine learning, advanced security, and all our other Platinum features have to offer.

"`; +exports[`StartTrial component when trial is allowed should display for gold license 1`] = `"
Start a 30-day trial

Experience what machine learning, advanced security, and all our other subscription features have to offer.

"`; diff --git a/x-pack/plugins/license_management/public/application/sections/license_dashboard/request_trial_extension/request_trial_extension.js b/x-pack/plugins/license_management/public/application/sections/license_dashboard/request_trial_extension/request_trial_extension.js index fb1ea026abaa0..77fb10c71091d 100644 --- a/x-pack/plugins/license_management/public/application/sections/license_dashboard/request_trial_extension/request_trial_extension.js +++ b/x-pack/plugins/license_management/public/application/sections/license_dashboard/request_trial_extension/request_trial_extension.js @@ -19,13 +19,13 @@ export const RequestTrialExtension = ({ shouldShowRequestTrialExtension }) => { ), diff --git a/x-pack/plugins/license_management/public/application/sections/license_dashboard/revert_to_basic/revert_to_basic.js b/x-pack/plugins/license_management/public/application/sections/license_dashboard/revert_to_basic/revert_to_basic.js index a1a46d8616554..24b51cccb4e45 100644 --- a/x-pack/plugins/license_management/public/application/sections/license_dashboard/revert_to_basic/revert_to_basic.js +++ b/x-pack/plugins/license_management/public/application/sections/license_dashboard/revert_to_basic/revert_to_basic.js @@ -82,13 +82,13 @@ export class RevertToBasic extends React.PureComponent { ), diff --git a/x-pack/plugins/license_management/public/application/sections/license_dashboard/start_trial/start_trial.tsx b/x-pack/plugins/license_management/public/application/sections/license_dashboard/start_trial/start_trial.tsx index 65d40f1de2009..7220f377cf386 100644 --- a/x-pack/plugins/license_management/public/application/sections/license_dashboard/start_trial/start_trial.tsx +++ b/x-pack/plugins/license_management/public/application/sections/license_dashboard/start_trial/start_trial.tsx @@ -94,14 +94,14 @@ export class StartTrial extends Component {

), @@ -236,15 +236,15 @@ export class StartTrial extends Component { const description = ( ), diff --git a/x-pack/plugins/ml/public/application/components/controls/select_interval/select_interval.test.tsx b/x-pack/plugins/ml/public/application/components/controls/select_interval/select_interval.test.tsx index 83e7b82986cf8..d71a180cd2206 100644 --- a/x-pack/plugins/ml/public/application/components/controls/select_interval/select_interval.test.tsx +++ b/x-pack/plugins/ml/public/application/components/controls/select_interval/select_interval.test.tsx @@ -11,13 +11,17 @@ import { mount } from 'enzyme'; import { EuiSelect } from '@elastic/eui'; +import { UrlStateProvider } from '../../../util/url_state'; + import { SelectInterval } from './select_interval'; describe('SelectInterval', () => { test('creates correct initial selected value', () => { const wrapper = mount( - + + + ); const select = wrapper.find(EuiSelect); @@ -29,7 +33,9 @@ describe('SelectInterval', () => { test('currently selected value is updated correctly on click', (done) => { const wrapper = mount( - + + + ); const select = wrapper.find(EuiSelect).first(); diff --git a/x-pack/plugins/ml/public/application/components/controls/select_severity/select_severity.test.tsx b/x-pack/plugins/ml/public/application/components/controls/select_severity/select_severity.test.tsx index 484a0c395f3f8..cb4f80bfe6809 100644 --- a/x-pack/plugins/ml/public/application/components/controls/select_severity/select_severity.test.tsx +++ b/x-pack/plugins/ml/public/application/components/controls/select_severity/select_severity.test.tsx @@ -11,13 +11,17 @@ import { mount } from 'enzyme'; import { EuiSuperSelect } from '@elastic/eui'; +import { UrlStateProvider } from '../../../util/url_state'; + import { SelectSeverity } from './select_severity'; describe('SelectSeverity', () => { test('creates correct severity options and initial selected value', () => { const wrapper = mount( - + + + ); const select = wrapper.find(EuiSuperSelect); @@ -65,7 +69,9 @@ describe('SelectSeverity', () => { test('state for currently selected value is updated correctly on click', (done) => { const wrapper = mount( - + + + ); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/common/analytics.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/common/analytics.ts index aa637f71db1cc..618ea5184007d 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/common/analytics.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/common/analytics.ts @@ -121,16 +121,24 @@ export interface DfAnalyticsExplainResponse { } export interface Eval { - meanSquaredError: number | string; + mse: number | string; + msle: number | string; + huber: number | string; rSquared: number | string; error: null | string; } export interface RegressionEvaluateResponse { regression: { + huber: { + value: number; + }; mse: { value: number; }; + msle: { + value: number; + }; r_squared: { value: number; }; @@ -414,19 +422,37 @@ export const useRefreshAnalyticsList = ( const DEFAULT_SIG_FIGS = 3; -export function getValuesFromResponse(response: RegressionEvaluateResponse) { - let meanSquaredError = response?.regression?.mse?.value; +interface RegressionEvaluateExtractedResponse { + mse: number | string; + msle: number | string; + huber: number | string; + r_squared: number | string; +} - if (meanSquaredError) { - meanSquaredError = Number(meanSquaredError.toPrecision(DEFAULT_SIG_FIGS)); - } +export const EMPTY_STAT = '--'; - let rSquared = response?.regression?.r_squared?.value; - if (rSquared) { - rSquared = Number(rSquared.toPrecision(DEFAULT_SIG_FIGS)); +export function getValuesFromResponse(response: RegressionEvaluateResponse) { + const results: RegressionEvaluateExtractedResponse = { + mse: EMPTY_STAT, + msle: EMPTY_STAT, + huber: EMPTY_STAT, + r_squared: EMPTY_STAT, + }; + + if (response?.regression) { + for (const statType in response.regression) { + if (response.regression.hasOwnProperty(statType)) { + let currentStatValue = + response.regression[statType as keyof RegressionEvaluateResponse['regression']]?.value; + if (currentStatValue) { + currentStatValue = Number(currentStatValue.toPrecision(DEFAULT_SIG_FIGS)); + } + results[statType as keyof RegressionEvaluateExtractedResponse] = currentStatValue; + } + } } - return { meanSquaredError, rSquared }; + return results; } interface ResultsSearchBoolQuery { bool: Dictionary; @@ -490,13 +516,22 @@ export function getEvalQueryBody({ return query; } +export enum REGRESSION_STATS { + MSE = 'mse', + MSLE = 'msle', + R_SQUARED = 'rSquared', + HUBER = 'huber', +} + interface EvaluateMetrics { classification: { multiclass_confusion_matrix: object; }; regression: { r_squared: object; - mean_squared_error: object; + mse: object; + msle: object; + huber: object; }; } @@ -541,7 +576,9 @@ export const loadEvalData = async ({ }, regression: { r_squared: {}, - mean_squared_error: {}, + mse: {}, + msle: {}, + huber: {}, }, }; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_form.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_form.tsx index b83dd2e4329e0..9dae54b6537b3 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_form.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_form.tsx @@ -71,12 +71,8 @@ export const ConfigurationStepForm: FC = ({ EuiComboBoxOptionOption[] >([]); const [includesTableItems, setIncludesTableItems] = useState([]); - const [maxDistinctValuesError, setMaxDistinctValuesError] = useState( - undefined - ); - const [unsupportedFieldsError, setUnsupportedFieldsError] = useState( - undefined - ); + const [maxDistinctValuesError, setMaxDistinctValuesError] = useState(); + const [unsupportedFieldsError, setUnsupportedFieldsError] = useState(); const { setEstimatedModelMemoryLimit, setFormState } = actions; const { estimatedModelMemoryLimit, form, isJobCreated, requestMessages } = state; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/details_step/details_step_details.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/details_step/details_step_details.tsx index a4d86b48006e8..8a41eb4b8a865 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/details_step/details_step_details.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/details_step/details_step_details.tsx @@ -26,7 +26,7 @@ export const DetailsStepDetails: FC<{ setCurrentStep: any; state: State }> = ({ state, }) => { const { form, isJobCreated } = state; - const { description, jobId, destinationIndex } = form; + const { description, jobId, destinationIndex, resultsField } = form; const detailsFirstCol: ListItems[] = [ { @@ -37,6 +37,19 @@ export const DetailsStepDetails: FC<{ setCurrentStep: any; state: State }> = ({ }, ]; + if ( + resultsField !== undefined && + typeof resultsField === 'string' && + resultsField.trim() !== '' + ) { + detailsFirstCol.push({ + title: i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.resultsField', { + defaultMessage: 'Results field', + }), + description: resultsField, + }); + } + const detailsSecondCol: ListItems[] = [ { title: i18n.translate('xpack.ml.dataframe.analytics.create.configDetails.jobDescription', { diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/details_step/details_step_form.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/details_step/details_step_form.tsx index d846ae95c2c7e..168d5e31f57c3 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/details_step/details_step_form.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/details_step/details_step_form.tsx @@ -47,6 +47,7 @@ export const DetailsStepForm: FC = ({ jobIdExists, jobIdInvalidMaxLength, jobIdValid, + resultsField, } = form; const forceInput = useRef(null); @@ -195,6 +196,22 @@ export const DetailsStepForm: FC = ({ data-test-subj="mlAnalyticsCreateJobFlyoutDestinationIndexInput" /> + + setFormState({ resultsField: e.target.value })} + data-test-subj="mlAnalyticsCreateJobWizardResultsFieldInput" + /> + = ({ jobConfig, jobStatus, searchQuery }) => { const { @@ -82,18 +94,19 @@ export const EvaluatePanel: FC = ({ jobConfig, jobStatus, searchQuery }) genErrorEval.eval && isRegressionEvaluateResponse(genErrorEval.eval) ) { - const { meanSquaredError, rSquared } = getValuesFromResponse(genErrorEval.eval); + const { mse, msle, huber, r_squared } = getValuesFromResponse(genErrorEval.eval); setGeneralizationEval({ - meanSquaredError, - rSquared, + mse, + msle, + huber, + rSquared: r_squared, error: null, }); setIsLoadingGeneralization(false); } else { setIsLoadingGeneralization(false); setGeneralizationEval({ - meanSquaredError: '--', - rSquared: '--', + ...EMPTY_STATS, error: genErrorEval.error, }); } @@ -118,18 +131,19 @@ export const EvaluatePanel: FC = ({ jobConfig, jobStatus, searchQuery }) trainingErrorEval.eval && isRegressionEvaluateResponse(trainingErrorEval.eval) ) { - const { meanSquaredError, rSquared } = getValuesFromResponse(trainingErrorEval.eval); + const { mse, msle, huber, r_squared } = getValuesFromResponse(trainingErrorEval.eval); setTrainingEval({ - meanSquaredError, - rSquared, + mse, + msle, + huber, + rSquared: r_squared, error: null, }); setIsLoadingTraining(false); } else { setIsLoadingTraining(false); setTrainingEval({ - meanSquaredError: '--', - rSquared: '--', + ...EMPTY_STATS, error: trainingErrorEval.error, }); } @@ -274,22 +288,48 @@ export const EvaluatePanel: FC = ({ jobConfig, jobStatus, searchQuery }) - + + {/* First row stats */} - + + + + + + + + + {/* Second row stats */} - + + + + + + + + @@ -331,22 +371,48 @@ export const EvaluatePanel: FC = ({ jobConfig, jobStatus, searchQuery }) - + + {/* First row stats */} - + + + + + + + + + {/* Second row stats */} - + + + + + + + + diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/evaluate_stat.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/evaluate_stat.tsx index 1b4461b2bb075..114ec75efb2e7 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/evaluate_stat.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/evaluate_stat.tsx @@ -6,58 +6,99 @@ import React, { FC } from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiStat, EuiIconTip, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiStat, EuiIconTip, EuiFlexGroup, EuiFlexItem, EuiLink } from '@elastic/eui'; +import { REGRESSION_STATS } from '../../../../common/analytics'; interface Props { isLoading: boolean; title: number | string; - isMSE: boolean; + statType: REGRESSION_STATS; dataTestSubj: string; } -const meanSquaredErrorText = i18n.translate( - 'xpack.ml.dataframe.analytics.regressionExploration.meanSquaredErrorText', - { - defaultMessage: 'Mean squared error', - } -); -const rSquaredText = i18n.translate( - 'xpack.ml.dataframe.analytics.regressionExploration.rSquaredText', - { - defaultMessage: 'R squared', - } -); -const meanSquaredErrorTooltipContent = i18n.translate( - 'xpack.ml.dataframe.analytics.regressionExploration.meanSquaredErrorTooltipContent', - { - defaultMessage: - 'Measures how well the regression analysis model is performing. Mean squared sum of the difference between true and predicted values.', - } -); -const rSquaredTooltipContent = i18n.translate( - 'xpack.ml.dataframe.analytics.regressionExploration.rSquaredTooltipContent', - { - defaultMessage: - 'Represents the goodness of fit. Measures how well the observed outcomes are replicated by the model.', - } -); +const statDescriptions = { + [REGRESSION_STATS.MSE]: i18n.translate( + 'xpack.ml.dataframe.analytics.regressionExploration.meanSquaredErrorText', + { + defaultMessage: 'Mean squared error', + } + ), + [REGRESSION_STATS.MSLE]: i18n.translate( + 'xpack.ml.dataframe.analytics.regressionExploration.msleText', + { + defaultMessage: 'Mean squared logarithmic error', + } + ), + [REGRESSION_STATS.R_SQUARED]: i18n.translate( + 'xpack.ml.dataframe.analytics.regressionExploration.rSquaredText', + { + defaultMessage: 'R squared', + } + ), + [REGRESSION_STATS.HUBER]: ( + + {i18n.translate('xpack.ml.dataframe.analytics.regressionExploration.huberLinkText', { + defaultMessage: 'Pseudo Huber loss function', + })} + + ), + }} + /> + ), +}; + +const tooltipContent = { + [REGRESSION_STATS.MSE]: i18n.translate( + 'xpack.ml.dataframe.analytics.regressionExploration.meanSquaredErrorTooltipContent', + { + defaultMessage: + 'Measures how well the regression analysis model is performing. Mean squared sum of the difference between true and predicted values.', + } + ), + [REGRESSION_STATS.MSLE]: i18n.translate( + 'xpack.ml.dataframe.analytics.regressionExploration.msleTooltipContent', + { + defaultMessage: + 'Average squared difference between the logarithm of the predicted values and the logarithm of the actual (ground truth) value', + } + ), + [REGRESSION_STATS.R_SQUARED]: i18n.translate( + 'xpack.ml.dataframe.analytics.regressionExploration.rSquaredTooltipContent', + { + defaultMessage: + 'Represents the goodness of fit. Measures how well the observed outcomes are replicated by the model.', + } + ), +}; -export const EvaluateStat: FC = ({ isLoading, isMSE, title, dataTestSubj }) => ( +export const EvaluateStat: FC = ({ isLoading, statType, title, dataTestSubj }) => ( - + {statType !== REGRESSION_STATS.HUBER && ( + + )} ); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_clone/clone_button.test.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_clone/clone_button.test.ts index 006cccf3b4610..9db32e298691e 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_clone/clone_button.test.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_clone/clone_button.test.ts @@ -131,7 +131,7 @@ describe('Analytics job clone action', () => { }, analyzed_fields: { includes: [], - excludes: [], + excludes: ['excluded_field'], }, model_memory_limit: '350mb', allow_lazy_start: false, diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_clone/clone_button.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_clone/clone_button.tsx index f8b6fdfbe2119..280ec544c1e5e 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_clone/clone_button.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_clone/clone_button.tsx @@ -247,6 +247,7 @@ const getAnalyticsJobMeta = (config: CloneDataFrameAnalyticsConfig): AnalyticsJo }, results_field: { optional: true, + formKey: 'resultsField', defaultValue: DEFAULT_RESULTS_FIELD, }, }, diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/expanded_row.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/expanded_row.tsx index 4d029ff1d9546..5276fedff0fde 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/expanded_row.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/expanded_row.tsx @@ -29,6 +29,7 @@ import { getDataFrameAnalyticsProgressPhase, isCompletedAnalyticsJob } from './c import { isRegressionAnalysis, ANALYSIS_CONFIG_TYPE, + REGRESSION_STATS, isRegressionEvaluateResponse, } from '../../../../common/analytics'; import { ExpandedRowMessagesPane } from './expanded_row_messages_pane'; @@ -44,7 +45,7 @@ function getItemDescription(value: any) { interface LoadedStatProps { isLoading: boolean; evalData: Eval; - resultProperty: 'meanSquaredError' | 'rSquared'; + resultProperty: REGRESSION_STATS; } const LoadedStat: FC = ({ isLoading, evalData, resultProperty }) => { @@ -61,7 +62,7 @@ interface Props { item: DataFrameAnalyticsListRow; } -const defaultEval: Eval = { meanSquaredError: '', rSquared: '', error: null }; +const defaultEval: Eval = { mse: '', msle: '', huber: '', rSquared: '', error: null }; export const ExpandedRow: FC = ({ item }) => { const [trainingEval, setTrainingEval] = useState(defaultEval); @@ -94,17 +95,21 @@ export const ExpandedRow: FC = ({ item }) => { genErrorEval.eval && isRegressionEvaluateResponse(genErrorEval.eval) ) { - const { meanSquaredError, rSquared } = getValuesFromResponse(genErrorEval.eval); + const { mse, msle, huber, r_squared } = getValuesFromResponse(genErrorEval.eval); setGeneralizationEval({ - meanSquaredError, - rSquared, + mse, + msle, + huber, + rSquared: r_squared, error: null, }); setIsLoadingGeneralization(false); } else { setIsLoadingGeneralization(false); setGeneralizationEval({ - meanSquaredError: '', + mse: '', + msle: '', + huber: '', rSquared: '', error: genErrorEval.error, }); @@ -124,17 +129,21 @@ export const ExpandedRow: FC = ({ item }) => { trainingErrorEval.eval && isRegressionEvaluateResponse(trainingErrorEval.eval) ) { - const { meanSquaredError, rSquared } = getValuesFromResponse(trainingErrorEval.eval); + const { mse, msle, huber, r_squared } = getValuesFromResponse(trainingErrorEval.eval); setTrainingEval({ - meanSquaredError, - rSquared, + mse, + msle, + huber, + rSquared: r_squared, error: null, }); setIsLoadingTraining(false); } else { setIsLoadingTraining(false); setTrainingEval({ - meanSquaredError: '', + mse: '', + msle: '', + huber: '', rSquared: '', error: genErrorEval.error, }); @@ -221,7 +230,17 @@ export const ExpandedRow: FC = ({ item }) => { + ), + }, + { + title: 'generalization mean squared logarithmic error', + description: ( + ), }, @@ -231,7 +250,17 @@ export const ExpandedRow: FC = ({ item }) => { + ), + }, + { + title: 'generalization pseudo huber loss function', + description: ( + ), }, @@ -241,7 +270,17 @@ export const ExpandedRow: FC = ({ item }) => { + ), + }, + { + title: 'training mean squared logarithmic error', + description: ( + ), }, @@ -251,7 +290,17 @@ export const ExpandedRow: FC = ({ item }) => { + ), + }, + { + title: 'training pseudo huber loss function', + description: ( + ), } diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts index 81d35679443b8..b344e44c97d59 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts @@ -144,6 +144,11 @@ export const validateAdvancedEditor = (state: State): State => { const destinationIndexNameValid = isValidIndexName(destinationIndexName); const destinationIndexPatternTitleExists = state.indexPatternsMap[destinationIndexName] !== undefined; + + const resultsFieldEmptyString = + typeof jobConfig?.dest?.results_field === 'string' && + jobConfig?.dest?.results_field.trim() === ''; + const mml = jobConfig.model_memory_limit; const modelMemoryLimitEmpty = mml === '' || mml === undefined; if (!modelMemoryLimitEmpty && mml !== undefined) { @@ -292,6 +297,18 @@ export const validateAdvancedEditor = (state: State): State => { }); } + if (resultsFieldEmptyString) { + state.advancedEditorMessages.push({ + error: i18n.translate( + 'xpack.ml.dataframe.analytics.create.advancedEditorMessage.resultsFieldEmptyString', + { + defaultMessage: 'The results field must not be an empty string.', + } + ), + message: '', + }); + } + if (dependentVariableEmpty) { state.advancedEditorMessages.push({ error: i18n.translate( @@ -336,6 +353,7 @@ export const validateAdvancedEditor = (state: State): State => { sourceIndexNameValid && !destinationIndexNameEmpty && destinationIndexNameValid && + !resultsFieldEmptyString && !dependentVariableEmpty && !modelMemoryLimitEmpty && numTopFeatureImportanceValuesValid && diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts index cedbe9094cb20..0d425c8ead4a2 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts @@ -82,6 +82,7 @@ export interface State { previousJobType: null | AnalyticsJobType; requiredFieldsError: string | undefined; randomizeSeed: undefined | number; + resultsField: undefined | string; sourceIndex: EsIndexName; sourceIndexNameEmpty: boolean; sourceIndexNameValid: boolean; @@ -147,6 +148,7 @@ export const getInitialState = (): State => ({ previousJobType: null, requiredFieldsError: undefined, randomizeSeed: undefined, + resultsField: undefined, sourceIndex: '', sourceIndexNameEmpty: true, sourceIndexNameValid: false, @@ -198,6 +200,13 @@ export const getJobConfigFromFormState = ( model_memory_limit: formState.modelMemoryLimit, }; + const resultsFieldEmpty = + typeof formState?.resultsField === 'string' && formState?.resultsField.trim() === ''; + + if (jobConfig.dest && !resultsFieldEmpty) { + jobConfig.dest.results_field = formState.resultsField; + } + if ( formState.jobType === ANALYSIS_CONFIG_TYPE.REGRESSION || formState.jobType === ANALYSIS_CONFIG_TYPE.CLASSIFICATION @@ -277,6 +286,7 @@ export function getCloneFormStateFromJobConfig( const resultState: Partial = { jobType, description: analyticsJobConfig.description ?? '', + resultsField: analyticsJobConfig.dest.results_field, sourceIndex: Array.isArray(analyticsJobConfig.source.index) ? analyticsJobConfig.source.index.join(',') : analyticsJobConfig.source.index, diff --git a/x-pack/plugins/ml/public/application/explorer/hooks/use_selected_cells.ts b/x-pack/plugins/ml/public/application/explorer/hooks/use_selected_cells.ts index 068f43a140c90..f356d79c0a8e1 100644 --- a/x-pack/plugins/ml/public/application/explorer/hooks/use_selected_cells.ts +++ b/x-pack/plugins/ml/public/application/explorer/hooks/use_selected_cells.ts @@ -9,12 +9,10 @@ import { useUrlState } from '../../util/url_state'; import { SWIMLANE_TYPE } from '../explorer_constants'; import { AppStateSelectedCells } from '../explorer_utils'; -export const useSelectedCells = (): [ - AppStateSelectedCells | undefined, - (swimlaneSelectedCells: AppStateSelectedCells) => void -] => { - const [appState, setAppState] = useUrlState('_a'); - +export const useSelectedCells = ( + appState: any, + setAppState: ReturnType[1] +): [AppStateSelectedCells | undefined, (swimlaneSelectedCells: AppStateSelectedCells) => void] => { // keep swimlane selection, restore selectedCells from AppState const selectedCells = useMemo(() => { return appState?.mlExplorerSwimlane?.selectedType !== undefined diff --git a/x-pack/plugins/ml/public/application/routing/router.tsx b/x-pack/plugins/ml/public/application/routing/router.tsx index 281493c4e31b7..f1b8083f19ccf 100644 --- a/x-pack/plugins/ml/public/application/routing/router.tsx +++ b/x-pack/plugins/ml/public/application/routing/router.tsx @@ -12,6 +12,7 @@ import { IUiSettingsClient, ChromeStart } from 'kibana/public'; import { ChromeBreadcrumb } from 'kibana/public'; import { IndexPatternsContract } from 'src/plugins/data/public'; import { MlContext, MlContextValue } from '../contexts/ml'; +import { UrlStateProvider } from '../util/url_state'; import * as routes from './routes'; @@ -48,21 +49,23 @@ export const MlRouter: FC<{ pageDeps: PageDependencies }> = ({ pageDeps }) => { return ( -

- {Object.entries(routes).map(([name, route]) => ( - { - window.setTimeout(() => { - setBreadcrumbs(route.breadcrumbs); - }); - return route.render(props, pageDeps); - }} - /> - ))} -
+ +
+ {Object.entries(routes).map(([name, route]) => ( + { + window.setTimeout(() => { + setBreadcrumbs(route.breadcrumbs); + }); + return route.render(props, pageDeps); + }} + /> + ))} +
+
); }; diff --git a/x-pack/plugins/ml/public/application/routing/routes/explorer.tsx b/x-pack/plugins/ml/public/application/routing/routes/explorer.tsx index 52b4408d1ac5b..7a7865c9bd738 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/explorer.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/explorer.tsx @@ -152,7 +152,7 @@ const ExplorerUrlStateManager: FC = ({ jobsWithTim const [tableInterval] = useTableInterval(); const [tableSeverity] = useTableSeverity(); - const [selectedCells, setSelectedCells] = useSelectedCells(); + const [selectedCells, setSelectedCells] = useSelectedCells(appState, setAppState); useEffect(() => { explorerService.setSelectedCells(selectedCells); }, [JSON.stringify(selectedCells)]); diff --git a/x-pack/plugins/ml/public/application/util/url_state.test.ts b/x-pack/plugins/ml/public/application/util/url_state.test.tsx similarity index 82% rename from x-pack/plugins/ml/public/application/util/url_state.test.ts rename to x-pack/plugins/ml/public/application/util/url_state.test.tsx index 0813f2e3da97f..9c03369648554 100644 --- a/x-pack/plugins/ml/public/application/util/url_state.test.ts +++ b/x-pack/plugins/ml/public/application/util/url_state.test.tsx @@ -4,8 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { renderHook, act } from '@testing-library/react-hooks'; -import { getUrlState, useUrlState } from './url_state'; +import React, { FC } from 'react'; +import { render, act } from '@testing-library/react'; +import { parseUrlState, useUrlState, UrlStateProvider } from './url_state'; const mockHistoryPush = jest.fn(); @@ -22,7 +23,7 @@ jest.mock('react-router-dom', () => ({ describe('getUrlState', () => { test('properly decode url with _g and _a', () => { expect( - getUrlState( + parseUrlState( "?_a=(mlExplorerFilter:(),mlExplorerSwimlane:(viewByFieldName:action),query:(query_string:(analyze_wildcard:!t,query:'*')))&_g=(ml:(jobIds:!(dec-2)),refreshInterval:(display:Off,pause:!f,value:0),time:(from:'2019-01-01T00:03:40.000Z',mode:absolute,to:'2019-08-30T11:55:07.000Z'))&savedSearchId=571aaf70-4c88-11e8-b3d7-01146121b73d" ) ).toEqual({ @@ -64,13 +65,19 @@ describe('useUrlState', () => { }); test('pushes a properly encoded search string to history', () => { - const { result } = renderHook(() => useUrlState('_a')); + const TestComponent: FC = () => { + const [, setUrlState] = useUrlState('_a'); + return ; + }; + + const { getByText } = render( + + + + ); act(() => { - const [, setUrlState] = result.current; - setUrlState({ - query: {}, - }); + getByText('ButtonText').click(); }); expect(mockHistoryPush).toHaveBeenCalledWith({ diff --git a/x-pack/plugins/ml/public/application/util/url_state.ts b/x-pack/plugins/ml/public/application/util/url_state.tsx similarity index 54% rename from x-pack/plugins/ml/public/application/util/url_state.ts rename to x-pack/plugins/ml/public/application/util/url_state.tsx index beff5340ce7e4..c288a00bb06da 100644 --- a/x-pack/plugins/ml/public/application/util/url_state.ts +++ b/x-pack/plugins/ml/public/application/util/url_state.tsx @@ -5,7 +5,7 @@ */ import { parse, stringify } from 'query-string'; -import { useCallback } from 'react'; +import React, { createContext, useCallback, useContext, useMemo, FC } from 'react'; import { isEqual } from 'lodash'; import { decode, encode } from 'rison-node'; import { useHistory, useLocation } from 'react-router-dom'; @@ -14,8 +14,16 @@ import { Dictionary } from '../../../common/types/common'; import { getNestedProperty } from './object_utils'; -export type SetUrlState = (attribute: string | Dictionary, value?: any) => void; -export type UrlState = [Dictionary, SetUrlState]; +type Accessor = '_a' | '_g'; +export type SetUrlState = ( + accessor: Accessor, + attribute: string | Dictionary, + value?: any +) => void; +export interface UrlState { + searchString: string; + setUrlState: SetUrlState; +} /** * Set of URL query parameters that require the rison serialization. @@ -30,7 +38,7 @@ function isRisonSerializationRequired(queryParam: string): boolean { return risonSerializedParams.has(queryParam); } -export function getUrlState(search: string): Dictionary { +export function parseUrlState(search: string): Dictionary { const urlState: Dictionary = {}; const parsedQueryString = parse(search, { sort: false }); @@ -56,14 +64,23 @@ export function getUrlState(search: string): Dictionary { // - `history.push()` is the successor of `save`. // - The exposed state and set call make use of the above and make sure that // different urlStates(e.g. `_a` / `_g`) don't overwrite each other. -export const useUrlState = (accessor: string): UrlState => { +// This uses a context to be able to maintain only one instance +// of the url state. It gets passed down with `UrlStateProvider` +// and can be used via `useUrlState`. +export const urlStateStore = createContext({ + searchString: '', + setUrlState: () => {}, +}); +const { Provider } = urlStateStore; +export const UrlStateProvider: FC = ({ children }) => { const history = useHistory(); - const { search } = useLocation(); + const { search: searchString } = useLocation(); - const setUrlState = useCallback( - (attribute: string | Dictionary, value?: any) => { - const urlState = getUrlState(search); - const parsedQueryString = parse(search, { sort: false }); + const setUrlState: SetUrlState = useCallback( + (accessor: Accessor, attribute: string | Dictionary, value?: any) => { + const prevSearchString = searchString; + const urlState = parseUrlState(prevSearchString); + const parsedQueryString = parse(prevSearchString, { sort: false }); if (!Object.prototype.hasOwnProperty.call(urlState, accessor)) { urlState[accessor] = {}; @@ -71,7 +88,7 @@ export const useUrlState = (accessor: string): UrlState => { if (typeof attribute === 'string') { if (isEqual(getNestedProperty(urlState, `${accessor}.${attribute}`), value)) { - return; + return prevSearchString; } urlState[accessor][attribute] = value; @@ -83,7 +100,10 @@ export const useUrlState = (accessor: string): UrlState => { } try { - const oldLocationSearch = stringify(parsedQueryString, { sort: false, encode: false }); + const oldLocationSearchString = stringify(parsedQueryString, { + sort: false, + encode: false, + }); Object.keys(urlState).forEach((a) => { if (isRisonSerializationRequired(a)) { @@ -92,20 +112,41 @@ export const useUrlState = (accessor: string): UrlState => { parsedQueryString[a] = urlState[a]; } }); - const newLocationSearch = stringify(parsedQueryString, { sort: false, encode: false }); + const newLocationSearchString = stringify(parsedQueryString, { + sort: false, + encode: false, + }); - if (oldLocationSearch !== newLocationSearch) { - history.push({ - search: stringify(parsedQueryString, { sort: false }), - }); + if (oldLocationSearchString !== newLocationSearchString) { + const newSearchString = stringify(parsedQueryString, { sort: false }); + history.push({ search: newSearchString }); } } catch (error) { // eslint-disable-next-line no-console console.error('Could not save url state', error); } }, - [search] + [searchString] ); - return [getUrlState(search)[accessor], setUrlState]; + return {children}; +}; + +export const useUrlState = (accessor: Accessor) => { + const { searchString, setUrlState: setUrlStateContext } = useContext(urlStateStore); + + const urlState = useMemo(() => { + const fullUrlState = parseUrlState(searchString); + if (typeof fullUrlState === 'object') { + return fullUrlState[accessor]; + } + return undefined; + }, [searchString]); + + const setUrlState = useCallback( + (attribute: string | Dictionary, value?: any) => + setUrlStateContext(accessor, attribute, value), + [accessor, setUrlStateContext] + ); + return [urlState, setUrlState]; }; diff --git a/x-pack/plugins/observability/public/application/index.tsx b/x-pack/plugins/observability/public/application/index.tsx index 21a9fabf445f1..5bc8d96656ed4 100644 --- a/x-pack/plugins/observability/public/application/index.tsx +++ b/x-pack/plugins/observability/public/application/index.tsx @@ -3,23 +3,64 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import { createHashHistory } from 'history'; +import React, { useEffect } from 'react'; import ReactDOM from 'react-dom'; -import { EuiThemeProvider } from '../../../../legacy/common/eui_styled_components'; +import { Route, Router, Switch } from 'react-router-dom'; +import { i18n } from '@kbn/i18n'; +import { RedirectAppLinks } from '../../../../../src/plugins/kibana_react/public'; import { AppMountParameters, CoreStart } from '../../../../../src/core/public'; -import { Home } from '../pages/home'; +import { EuiThemeProvider } from '../../../../legacy/common/eui_styled_components'; import { PluginContext } from '../context/plugin_context'; +import { useUrlParams } from '../hooks/use_url_params'; +import { routes } from '../routes'; +import { usePluginContext } from '../hooks/use_plugin_context'; + +const App = () => { + return ( + <> + + {Object.keys(routes).map((key) => { + const path = key as keyof typeof routes; + const route = routes[path]; + const Wrapper = () => { + const { core } = usePluginContext(); + useEffect(() => { + core.chrome.setBreadcrumbs([ + { + text: i18n.translate('xpack.observability.observability.breadcrumb.', { + defaultMessage: 'Observability', + }), + }, + ...route.breadcrumb, + ]); + }, [core]); + + const { query, path: pathParams } = useUrlParams(route.params); + return route.handler({ query, path: pathParams }); + }; + return ; + })} + + + ); +}; export const renderApp = (core: CoreStart, { element }: AppMountParameters) => { const i18nCore = core.i18n; const isDarkMode = core.uiSettings.get('theme:darkMode'); + const history = createHashHistory(); ReactDOM.render( - - - - - + + + + + + + + + , element ); diff --git a/x-pack/plugins/observability/public/components/app/chart_container/index.test.tsx b/x-pack/plugins/observability/public/components/app/chart_container/index.test.tsx new file mode 100644 index 0000000000000..d09d535a49340 --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/chart_container/index.test.tsx @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { render } from '@testing-library/react'; +import React from 'react'; +import { ChartContainer } from './'; + +describe('chart container', () => { + it('shows loading indicator', () => { + const component = render( + +
My amazing component
+
+ ); + expect(component.getByTestId('loading')).toBeInTheDocument(); + expect(component.queryByText('My amazing component')).not.toBeInTheDocument(); + }); + it("doesn't show loading indicator", () => { + const component = render( + +
My amazing component
+
+ ); + expect(component.queryByTestId('loading')).not.toBeInTheDocument(); + expect(component.getByText('My amazing component')).toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/observability/public/components/app/chart_container/index.tsx b/x-pack/plugins/observability/public/components/app/chart_container/index.tsx new file mode 100644 index 0000000000000..2a0c25773eae5 --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/chart_container/index.tsx @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { Chart } from '@elastic/charts'; +import { EuiLoadingChart } from '@elastic/eui'; +import { EuiLoadingChartSize } from '@elastic/eui/src/components/loading/loading_chart'; +import React from 'react'; + +interface Props { + isInitialLoad: boolean; + height?: number; + width?: number; + iconSize?: EuiLoadingChartSize; + children: React.ReactNode; +} + +const CHART_HEIGHT = 170; + +export const ChartContainer = ({ + isInitialLoad, + children, + iconSize = 'xl', + height = CHART_HEIGHT, +}: Props) => { + if (isInitialLoad) { + return ( +
+ +
+ ); + } + return {children}; +}; diff --git a/x-pack/plugins/observability/public/components/app/empty_section/index.test.tsx b/x-pack/plugins/observability/public/components/app/empty_section/index.test.tsx new file mode 100644 index 0000000000000..e04e8f050006a --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/empty_section/index.test.tsx @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; +import { ISection } from '../../../typings/section'; +import { render } from '../../../utils/test_helper'; +import { EmptySection } from './'; + +describe('EmptySection', () => { + it('renders without action button', () => { + const section: ISection = { + id: 'apm', + title: 'APM', + icon: 'logoAPM', + description: 'foo bar', + }; + const { getByText, queryAllByText } = render(); + + expect(getByText('APM')).toBeInTheDocument(); + expect(getByText('foo bar')).toBeInTheDocument(); + expect(queryAllByText('Install agent')).toEqual([]); + }); + it('renders with action button', () => { + const section: ISection = { + id: 'apm', + title: 'APM', + icon: 'logoAPM', + description: 'foo bar', + linkTitle: 'install agent', + href: 'https://www.elastic.co', + }; + const { getByText, getByTestId } = render(); + + expect(getByText('APM')).toBeInTheDocument(); + expect(getByText('foo bar')).toBeInTheDocument(); + const linkButton = getByTestId('empty-apm') as HTMLAnchorElement; + expect(linkButton.href).toEqual('https://www.elastic.co/'); + }); +}); diff --git a/x-pack/plugins/observability/public/components/app/empty_section/index.tsx b/x-pack/plugins/observability/public/components/app/empty_section/index.tsx new file mode 100644 index 0000000000000..e19bf1678bc01 --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/empty_section/index.tsx @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { EuiButton, EuiEmptyPrompt, EuiText } from '@elastic/eui'; +import React from 'react'; +import { ISection } from '../../../typings/section'; + +interface Props { + section: ISection; +} + +export const EmptySection = ({ section }: Props) => { + return ( + {section.title}} + titleSize="xs" + body={{section.description}} + actions={ + <> + {section.linkTitle && ( + + {section.linkTitle} + + )} + + } + /> + ); +}; diff --git a/x-pack/plugins/observability/public/components/app/header/index.test.tsx b/x-pack/plugins/observability/public/components/app/header/index.test.tsx new file mode 100644 index 0000000000000..59b6fbe9caf7a --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/header/index.test.tsx @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; +import { render } from '../../../utils/test_helper'; +import { Header } from './'; + +describe('Header', () => { + it('renders without add data button', () => { + const { getByText, queryAllByText, getByTestId } = render(
); + expect(getByTestId('observability-logo')).toBeInTheDocument(); + expect(getByText('Observability')).toBeInTheDocument(); + expect(queryAllByText('Add data')).toEqual([]); + }); + it('renders with add data button', () => { + const { getByText, getByTestId } = render(
); + expect(getByTestId('observability-logo')).toBeInTheDocument(); + expect(getByText('Observability')).toBeInTheDocument(); + expect(getByText('Add data')).toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/observability/public/components/app/header/index.tsx b/x-pack/plugins/observability/public/components/app/header/index.tsx new file mode 100644 index 0000000000000..1c6ce766d0901 --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/header/index.tsx @@ -0,0 +1,97 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + EuiBetaBadge, + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiSpacer, + EuiTitle, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import styled from 'styled-components'; +import { usePluginContext } from '../../../hooks/use_plugin_context'; + +const Container = styled.div<{ color: string }>` + background: ${(props) => props.color}; + border-bottom: ${(props) => props.theme.eui.euiBorderThin}; +`; + +const Wrapper = styled.div<{ restrictWidth?: number }>` + width: 100%; + max-width: ${(props) => `${props.restrictWidth}px`}; + margin: 0 auto; + overflow: hidden; + padding: ${(props) => (props.restrictWidth ? 0 : '0 24px')}; +`; + +interface Props { + color: string; + showAddData?: boolean; + restrictWidth?: number; + showGiveFeedback?: boolean; +} + +export const Header = ({ + color, + restrictWidth, + showAddData = false, + showGiveFeedback = false, +}: Props) => { + const { core } = usePluginContext(); + return ( + + + + + + + + + +

+ {i18n.translate('xpack.observability.home.title', { + defaultMessage: 'Observability', + })}{' '} + +

+
+
+ {showGiveFeedback && ( + + + {i18n.translate('xpack.observability.home.feedback', { + defaultMessage: 'Give us feedback', + })} + + + )} + {showAddData && ( + + + {i18n.translate('xpack.observability.home.addData', { defaultMessage: 'Add data' })} + + + )} +
+ +
+
+ ); +}; diff --git a/x-pack/plugins/observability/public/components/app/layout/with_header.tsx b/x-pack/plugins/observability/public/components/app/layout/with_header.tsx new file mode 100644 index 0000000000000..27b25f0056055 --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/layout/with_header.tsx @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiPage, EuiPageBody, EuiPageProps } from '@elastic/eui'; +import React from 'react'; +import styled from 'styled-components'; +import { Header } from '../header/index'; + +const getPaddingSize = (props: EuiPageProps) => (props.restrictWidth ? 0 : '24px'); + +const Page = styled(EuiPage)` + background: transparent; + padding-right: ${getPaddingSize}; + padding-left: ${getPaddingSize}; +`; + +const Container = styled.div<{ color?: string }>` + overflow-y: hidden; + min-height: calc(100vh - ${(props) => props.theme.eui.euiHeaderChildSize}); + background: ${(props) => props.color}; +`; + +interface Props { + headerColor: string; + bodyColor: string; + children?: React.ReactNode; + restrictWidth?: number; + showAddData?: boolean; + showGiveFeedback?: boolean; +} + +export const WithHeaderLayout = ({ + headerColor, + bodyColor, + children, + restrictWidth, + showAddData, + showGiveFeedback, +}: Props) => ( + +
+ + {children} + + +); diff --git a/x-pack/plugins/observability/public/components/app/news/index.scss b/x-pack/plugins/observability/public/components/app/news/index.scss new file mode 100644 index 0000000000000..1222fe489c732 --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/news/index.scss @@ -0,0 +1,3 @@ +.obsNewsFeed__itemImg{ + @include euiBottomShadowSmall; +} \ No newline at end of file diff --git a/x-pack/plugins/observability/public/components/app/news/index.test.tsx b/x-pack/plugins/observability/public/components/app/news/index.test.tsx new file mode 100644 index 0000000000000..cae6b4aec0c62 --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/news/index.test.tsx @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; +import { render } from '../../../utils/test_helper'; +import { News } from './'; + +describe('News', () => { + it('renders resources with all elements', () => { + const { getByText, getAllByText } = render(); + expect(getByText("What's new")).toBeInTheDocument(); + expect(getAllByText('Read full story')).not.toEqual([]); + }); +}); diff --git a/x-pack/plugins/observability/public/components/app/news/index.tsx b/x-pack/plugins/observability/public/components/app/news/index.tsx new file mode 100644 index 0000000000000..41a4074f47976 --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/news/index.tsx @@ -0,0 +1,97 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { + EuiFlexGroup, + EuiFlexItem, + EuiHorizontalRule, + EuiLink, + EuiText, + EuiTitle, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React, { useContext } from 'react'; +import { ThemeContext } from 'styled-components'; +import './index.scss'; +import { truncate } from 'lodash'; +import { news as newsMockData } from './mock/news.mock.data'; + +interface NewsItem { + title: string; + description: string; + link_url: string; + image_url: string; +} + +export const News = () => { + const newsItems: NewsItem[] = newsMockData; + return ( + + + +

+ {i18n.translate('xpack.observability.news.title', { + defaultMessage: "What's new", + })} +

+
+
+ {newsItems.map((item, index) => ( + + + + ))} +
+ ); +}; + +const limitString = (string: string, limit: number) => truncate(string, { length: limit }); + +const NewsItem = ({ item }: { item: NewsItem }) => { + const theme = useContext(ThemeContext); + + return ( + + + +

{item.title}

+
+
+ + + + + + + {limitString(item.description, 128)} + + + + + + {i18n.translate('xpack.observability.news.readFullStory', { + defaultMessage: 'Read full story', + })} + + + + + + + {item.title} + + + + +
+ ); +}; diff --git a/x-pack/plugins/observability/public/components/app/news/mock/news.mock.data.ts b/x-pack/plugins/observability/public/components/app/news/mock/news.mock.data.ts new file mode 100644 index 0000000000000..5c623bb9134eb --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/news/mock/news.mock.data.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const news = [ + { + title: 'Have SIEM questions?', + description: + 'Join our growing community of Elastic SIEM users to discuss the configuration and use of Elastic SIEM for threat detection and response.', + link_url: 'https://discuss.elastic.co/c/security/siem/?blade=securitysolutionfeed', + image_url: + 'https://aws1.discourse-cdn.com/elastic/original/3X/f/8/f8c3d0b9971cfcd0be349d973aa5799f71d280cc.png?blade=securitysolutionfeed', + }, + { + title: 'Elastic SIEM on-demand training course — free for a limited time', + description: + 'With this self-paced, on-demand course, you will learn how to leverage Elastic SIEM to drive your security operations and threat hunting. This course is designed for security analysts and practitioners who have used other SIEMs or are familiar with SIEM concepts.', + link_url: + 'https://training.elastic.co/elearning/security-analytics/elastic-siem-fundamentals-promo?blade=securitysolutionfeed', + image_url: + 'https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt50f58e0358ebea9d/5c30508693d9791a70cd73ad/illustration-specialization-course-page-security.svg?blade=securitysolutionfeed', + }, + { + title: 'New to Elastic SIEM? Take our on-demand training course', + description: + 'With this self-paced, on-demand course, you will learn how to leverage Elastic SIEM to drive your security operations and threat hunting. This course is designed for security analysts and practitioners who have used other SIEMs or are familiar with SIEM concepts.', + link_url: + 'https://www.elastic.co/training/specializations/security-analytics/elastic-siem-fundamentals?blade=securitysolutionfeed', + image_url: + 'https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt50f58e0358ebea9d/5c30508693d9791a70cd73ad/illustration-specialization-course-page-security.svg?blade=securitysolutionfeed', + }, +]; diff --git a/x-pack/plugins/observability/public/components/app/resources/index.test.tsx b/x-pack/plugins/observability/public/components/app/resources/index.test.tsx new file mode 100644 index 0000000000000..570aa3954424f --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/resources/index.test.tsx @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { render } from '@testing-library/react'; +import React from 'react'; +import { Resources } from './'; + +describe('Resources', () => { + it('renders resources with all elements', () => { + const { getByText } = render(); + expect(getByText('Documentation')).toBeInTheDocument(); + expect(getByText('Discuss forum')).toBeInTheDocument(); + expect(getByText('Observability fundamentals')).toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/observability/public/components/app/resources/index.tsx b/x-pack/plugins/observability/public/components/app/resources/index.tsx new file mode 100644 index 0000000000000..c330c358d022a --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/resources/index.tsx @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { EuiFlexGroup, EuiFlexItem, EuiListGroup, EuiTitle } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; + +const resources = [ + { + iconType: 'documents', + label: i18n.translate('xpack.observability.resources.documentation', { + defaultMessage: 'Documentation', + }), + href: 'https://www.elastic.co/guide/en/observability/current/observability-ui.html', + }, + { + iconType: 'editorComment', + label: i18n.translate('xpack.observability.resources.forum', { + defaultMessage: 'Discuss forum', + }), + href: 'https://discuss.elastic.co/c/observability/', + }, + { + iconType: 'training', + label: i18n.translate('xpack.observability.resources.training', { + defaultMessage: 'Observability fundamentals', + }), + href: 'https://www.elastic.co/training/observability-fundamentals', + }, +]; + +export const Resources = () => { + return ( + + + +

+ {i18n.translate('xpack.observability.resources.title', { + defaultMessage: 'Resources', + })} +

+
+
+ +
+ ); +}; diff --git a/x-pack/plugins/observability/public/components/app/section/alerts/index.tsx b/x-pack/plugins/observability/public/components/app/section/alerts/index.tsx new file mode 100644 index 0000000000000..4c80195d33ace --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/section/alerts/index.tsx @@ -0,0 +1,129 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { + EuiBadge, + EuiFlexGroup, + EuiFlexItem, + EuiHorizontalRule, + EuiIconTip, + EuiLink, + EuiText, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import moment from 'moment'; +import React, { useState } from 'react'; +import { EuiSelect } from '@elastic/eui'; +import { uniqBy } from 'lodash'; +import { Alert } from '../../../../../../alerts/common'; +import { usePluginContext } from '../../../../hooks/use_plugin_context'; +import { SectionContainer } from '..'; + +const ALL_TYPES = 'ALL_TYPES'; +const allTypes = { + value: ALL_TYPES, + text: i18n.translate('xpack.observability.overview.alert.allTypes', { + defaultMessage: 'All types', + }), +}; + +interface Props { + alerts: Alert[]; +} + +export const AlertsSection = ({ alerts }: Props) => { + const { core } = usePluginContext(); + const [filter, setFilter] = useState(ALL_TYPES); + + const filterOptions = uniqBy(alerts, (alert) => alert.consumer).map(({ consumer }) => ({ + value: consumer, + text: consumer, + })); + + return ( + + + + + + setFilter(e.target.value)} + prepend={i18n.translate('xpack.observability.overview.alert.view', { + defaultMessage: 'View', + })} + /> + + + + + + {alerts + .filter((alert) => filter === ALL_TYPES || alert.consumer === filter) + .map((alert, index) => { + const isLastElement = index === alerts.length - 1; + return ( + + + + {alert.name} + + + + + + {alert.alertTypeId} + + {alert.tags.map((tag, idx) => { + return ( + + {tag} + + ); + })} + + + + + + + Updated {moment.duration(moment().diff(alert.updatedAt)).humanize()} ago + + + {alert.muteAll && ( + + + + )} + + + {!isLastElement && } + + ); + })} + + + + ); +}; diff --git a/x-pack/plugins/observability/public/components/app/section/apm/index.test.tsx b/x-pack/plugins/observability/public/components/app/section/apm/index.test.tsx new file mode 100644 index 0000000000000..d4b8236e0ef49 --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/section/apm/index.test.tsx @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; +import * as fetcherHook from '../../../../hooks/use_fetcher'; +import { render } from '../../../../utils/test_helper'; +import { APMSection } from './'; +import { response } from './mock_data/apm.mock'; + +describe('APMSection', () => { + it('renders with transaction series and stats', () => { + jest.spyOn(fetcherHook, 'useFetcher').mockReturnValue({ + data: response, + status: fetcherHook.FETCH_STATUS.SUCCESS, + refetch: jest.fn(), + }); + const { getByText, queryAllByTestId } = render( + + ); + + expect(getByText('APM')).toBeInTheDocument(); + expect(getByText('View in app')).toBeInTheDocument(); + expect(getByText('Services 11')).toBeInTheDocument(); + expect(getByText('Transactions per minute 312.00k')).toBeInTheDocument(); + expect(queryAllByTestId('loading')).toEqual([]); + }); + it('shows loading state', () => { + jest.spyOn(fetcherHook, 'useFetcher').mockReturnValue({ + data: undefined, + status: fetcherHook.FETCH_STATUS.LOADING, + refetch: jest.fn(), + }); + const { getByText, queryAllByText, getByTestId } = render( + + ); + + expect(getByText('APM')).toBeInTheDocument(); + expect(getByTestId('loading')).toBeInTheDocument(); + expect(queryAllByText('View in app')).toEqual([]); + expect(queryAllByText('Services 11')).toEqual([]); + expect(queryAllByText('Transactions per minute 312.00k')).toEqual([]); + }); +}); diff --git a/x-pack/plugins/observability/public/components/app/section/apm/index.tsx b/x-pack/plugins/observability/public/components/app/section/apm/index.tsx new file mode 100644 index 0000000000000..697d4adfa0b75 --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/section/apm/index.tsx @@ -0,0 +1,112 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Axis, BarSeries, niceTimeFormatter, Position, ScaleType, Settings } from '@elastic/charts'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import numeral from '@elastic/numeral'; +import { i18n } from '@kbn/i18n'; +import moment from 'moment'; +import React, { useContext } from 'react'; +import { useHistory } from 'react-router-dom'; +import { ThemeContext } from 'styled-components'; +import { SectionContainer } from '../'; +import { getDataHandler } from '../../../../data_handler'; +import { useChartTheme } from '../../../../hooks/use_chart_theme'; +import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; +import { ChartContainer } from '../../chart_container'; +import { StyledStat } from '../../styled_stat'; +import { onBrushEnd } from '../helper'; + +interface Props { + startTime?: string; + endTime?: string; + bucketSize?: string; +} + +function formatTpm(value?: number) { + return numeral(value).format('0.00a'); +} + +export const APMSection = ({ startTime, endTime, bucketSize }: Props) => { + const theme = useContext(ThemeContext); + const history = useHistory(); + + const { data, status } = useFetcher(() => { + if (startTime && endTime && bucketSize) { + return getDataHandler('apm')?.fetchData({ startTime, endTime, bucketSize }); + } + }, [startTime, endTime, bucketSize]); + + const { title = 'APM', appLink, stats, series } = data || {}; + + const min = moment.utc(startTime).valueOf(); + const max = moment.utc(endTime).valueOf(); + + const formatter = niceTimeFormatter([min, max]); + + const isLoading = status === FETCH_STATUS.LOADING; + + const transactionsColor = theme.eui.euiColorVis1; + + return ( + + + + + + + + + + + onBrushEnd({ x, history })} + theme={useChartTheme()} + showLegend={false} + xDomain={{ min, max }} + /> + {series?.transactions.coordinates && ( + <> + + `${formatTpm(value)} tpm`} + /> + + + )} + + + ); +}; diff --git a/x-pack/plugins/observability/public/components/app/section/apm/mock_data/apm.mock.ts b/x-pack/plugins/observability/public/components/app/section/apm/mock_data/apm.mock.ts new file mode 100644 index 0000000000000..5857021b1537f --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/section/apm/mock_data/apm.mock.ts @@ -0,0 +1,168 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ApmFetchDataResponse } from '../../../../../typings'; + +export const response: ApmFetchDataResponse = { + title: 'APM', + + appLink: '/app/apm', + stats: { + services: { value: 11, type: 'number' }, + transactions: { value: 312000, type: 'number' }, + }, + series: { + transactions: { + coordinates: [ + { x: 1591365600000, y: 32 }, + { x: 1591366200000, y: 43 }, + { x: 1591366800000, y: 22 }, + { x: 1591367400000, y: 29 }, + { x: 1591368000000, y: 39 }, + { x: 1591368600000, y: 36 }, + { x: 1591369200000, y: 50 }, + { x: 1591369800000, y: 31 }, + { x: 1591370400000, y: 39 }, + { x: 1591371000000, y: 26 }, + { x: 1591371600000, y: 45 }, + { x: 1591372200000, y: 27 }, + { x: 1591372800000, y: 37 }, + { x: 1591373400000, y: 55 }, + { x: 1591374000000, y: 31 }, + { x: 1591374600000, y: 26 }, + { x: 1591375200000, y: 57 }, + { x: 1591375800000, y: 25 }, + { x: 1591376400000, y: 28 }, + { x: 1591377000000, y: 40 }, + { x: 1591377600000, y: 33 }, + { x: 1591378200000, y: 33 }, + { x: 1591378800000, y: 31 }, + { x: 1591379400000, y: 32 }, + { x: 1591380000000, y: 34 }, + { x: 1591380600000, y: 31 }, + { x: 1591381200000, y: 16 }, + { x: 1591381800000, y: 34 }, + { x: 1591382400000, y: 33 }, + { x: 1591383000000, y: 35 }, + { x: 1591383600000, y: 47 }, + { x: 1591384200000, y: 44 }, + { x: 1591384800000, y: 21 }, + { x: 1591385400000, y: 25 }, + { x: 1591386000000, y: 34 }, + { x: 1591386600000, y: 37 }, + { x: 1591387200000, y: 38 }, + { x: 1591387800000, y: 28 }, + { x: 1591388400000, y: 32 }, + { x: 1591389000000, y: 37 }, + { x: 1591389600000, y: 25 }, + { x: 1591390200000, y: 33 }, + { x: 1591390800000, y: 34 }, + { x: 1591391400000, y: 30 }, + { x: 1591392000000, y: 45 }, + { x: 1591392600000, y: 42 }, + { x: 1591393200000, y: 23 }, + { x: 1591393800000, y: 33 }, + { x: 1591394400000, y: 38 }, + { x: 1591395000000, y: 30 }, + { x: 1591395600000, y: 25 }, + { x: 1591396200000, y: 33 }, + { x: 1591396800000, y: 37 }, + { x: 1591397400000, y: 43 }, + { x: 1591398000000, y: 30 }, + { x: 1591398600000, y: 36 }, + { x: 1591399200000, y: 28 }, + { x: 1591399800000, y: 39 }, + { x: 1591400400000, y: 27 }, + { x: 1591401000000, y: 41 }, + { x: 1591401600000, y: 25 }, + { x: 1591402200000, y: 31 }, + { x: 1591402800000, y: 28 }, + { x: 1591403400000, y: 29 }, + { x: 1591404000000, y: 49 }, + { x: 1591404600000, y: 24 }, + { x: 1591405200000, y: 41 }, + { x: 1591405800000, y: 30 }, + { x: 1591406400000, y: 36 }, + { x: 1591407000000, y: 39 }, + { x: 1591407600000, y: 23 }, + { x: 1591408200000, y: 40 }, + { x: 1591408800000, y: 34 }, + { x: 1591409400000, y: 28 }, + { x: 1591410000000, y: 33 }, + { x: 1591410600000, y: 31 }, + { x: 1591411200000, y: 39 }, + { x: 1591411800000, y: 33 }, + { x: 1591412400000, y: 35 }, + { x: 1591413000000, y: 31 }, + { x: 1591413600000, y: 35 }, + { x: 1591414200000, y: 37 }, + { x: 1591414800000, y: 26 }, + { x: 1591415400000, y: 27 }, + { x: 1591416000000, y: 26 }, + { x: 1591416600000, y: 34 }, + { x: 1591417200000, y: 33 }, + { x: 1591417800000, y: 38 }, + { x: 1591418400000, y: 34 }, + { x: 1591419000000, y: 37 }, + { x: 1591419600000, y: 24 }, + { x: 1591420200000, y: 25 }, + { x: 1591420800000, y: 20 }, + { x: 1591421400000, y: 35 }, + { x: 1591422000000, y: 41 }, + { x: 1591422600000, y: 40 }, + { x: 1591423200000, y: 33 }, + { x: 1591423800000, y: 24 }, + { x: 1591424400000, y: 44 }, + { x: 1591425000000, y: 24 }, + { x: 1591425600000, y: 32 }, + { x: 1591426200000, y: 37 }, + { x: 1591426800000, y: 34 }, + { x: 1591427400000, y: 28 }, + { x: 1591428000000, y: 26 }, + { x: 1591428600000, y: 37 }, + { x: 1591429200000, y: 36 }, + { x: 1591429800000, y: 37 }, + { x: 1591430400000, y: 23 }, + { x: 1591431000000, y: 47 }, + { x: 1591431600000, y: 41 }, + { x: 1591432200000, y: 24 }, + { x: 1591432800000, y: 34 }, + { x: 1591433400000, y: 27 }, + { x: 1591434000000, y: 34 }, + { x: 1591434600000, y: 44 }, + { x: 1591435200000, y: 20 }, + { x: 1591435800000, y: 34 }, + { x: 1591436400000, y: 29 }, + { x: 1591437000000, y: 28 }, + { x: 1591437600000, y: 36 }, + { x: 1591438200000, y: 34 }, + { x: 1591438800000, y: 26 }, + { x: 1591439400000, y: 29 }, + { x: 1591440000000, y: 45 }, + { x: 1591440600000, y: 34 }, + { x: 1591441200000, y: 25 }, + { x: 1591441800000, y: 34 }, + { x: 1591442400000, y: 28 }, + { x: 1591443000000, y: 34 }, + { x: 1591443600000, y: 31 }, + { x: 1591444200000, y: 24 }, + { x: 1591444800000, y: 34 }, + { x: 1591445400000, y: 21 }, + { x: 1591446000000, y: 40 }, + { x: 1591446600000, y: 37 }, + { x: 1591447200000, y: 31 }, + { x: 1591447800000, y: 21 }, + { x: 1591448400000, y: 24 }, + { x: 1591449000000, y: 30 }, + { x: 1591449600000, y: 22 }, + { x: 1591450200000, y: 27 }, + { x: 1591450800000, y: 30 }, + { x: 1591451400000, y: 22 }, + { x: 1591452000000, y: 9 }, + ], + }, + }, +}; diff --git a/x-pack/plugins/observability/public/components/app/section/error_panel/index.tsx b/x-pack/plugins/observability/public/components/app/section/error_panel/index.tsx new file mode 100644 index 0000000000000..8f0781b8f0269 --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/section/error_panel/index.tsx @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; + +export const ErrorPanel = () => { + return ( + + + + {i18n.translate('xpack.observability.section.errorPanel', { + defaultMessage: 'An error happened when trying to fetch data. Please try again', + })} + + + + ); +}; diff --git a/x-pack/plugins/observability/public/components/app/section/helper.test.ts b/x-pack/plugins/observability/public/components/app/section/helper.test.ts new file mode 100644 index 0000000000000..6a8cd27753a8d --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/section/helper.test.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { onBrushEnd } from './helper'; +import { History } from 'history'; + +describe('Chart helper', () => { + describe('onBrushEnd', () => { + const history = ({ + push: jest.fn(), + location: { + search: '', + }, + } as unknown) as History; + it("doesn't push a new history when x is not defined", () => { + onBrushEnd({ x: undefined, history }); + expect(history.push).not.toBeCalled(); + }); + + it('pushes a new history with time range converted to ISO', () => { + onBrushEnd({ x: [1593409448167, 1593415727797], history }); + expect(history.push).toBeCalledWith({ + search: 'rangeFrom=2020-06-29T05:44:08.167Z&rangeTo=2020-06-29T07:28:47.797Z', + }); + }); + + it('pushes a new history keeping current search', () => { + history.location.search = '?foo=bar'; + onBrushEnd({ x: [1593409448167, 1593415727797], history }); + expect(history.push).toBeCalledWith({ + search: 'foo=bar&rangeFrom=2020-06-29T05:44:08.167Z&rangeTo=2020-06-29T07:28:47.797Z', + }); + }); + }); +}); diff --git a/x-pack/plugins/observability/public/components/app/section/helper.ts b/x-pack/plugins/observability/public/components/app/section/helper.ts new file mode 100644 index 0000000000000..81fa92cb87782 --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/section/helper.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { XYBrushArea } from '@elastic/charts'; +import { History } from 'history'; +import { fromQuery, toQuery } from '../../../utils/url'; + +export const onBrushEnd = ({ x, history }: { x: XYBrushArea['x']; history: History }) => { + if (x) { + const start = x[0]; + const end = x[1]; + + const currentSearch = toQuery(history.location.search); + const nextSearch = { + rangeFrom: new Date(start).toISOString(), + rangeTo: new Date(end).toISOString(), + }; + history.push({ + ...history.location, + search: fromQuery({ + ...currentSearch, + ...nextSearch, + }), + }); + } +}; diff --git a/x-pack/plugins/observability/public/components/app/section/index.test.tsx b/x-pack/plugins/observability/public/components/app/section/index.test.tsx new file mode 100644 index 0000000000000..49cb175d0c094 --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/section/index.test.tsx @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; +import { render } from '../../../utils/test_helper'; +import { SectionContainer } from './'; + +describe('SectionContainer', () => { + it('renders section without app link', () => { + const component = render( + +
I am a very nice component
+
+ ); + expect(component.getByText('I am a very nice component')).toBeInTheDocument(); + expect(component.getByText('Foo')).toBeInTheDocument(); + expect(component.queryAllByText('View in app')).toEqual([]); + }); + it('renders section with app link', () => { + const component = render( + +
I am a very nice component
+
+ ); + expect(component.getByText('I am a very nice component')).toBeInTheDocument(); + expect(component.getByText('Foo')).toBeInTheDocument(); + expect(component.getByText('View in app')).toBeInTheDocument(); + }); + it('renders section with error', () => { + const component = render( + +
I am a very nice component
+
+ ); + expect(component.queryByText('I am a very nice component')).not.toBeInTheDocument(); + expect(component.getByText('Foo')).toBeInTheDocument(); + expect( + component.getByText('An error happened when trying to fetch data. Please try again') + ).toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/observability/public/components/app/section/index.tsx b/x-pack/plugins/observability/public/components/app/section/index.tsx new file mode 100644 index 0000000000000..3556e8c01ab30 --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/section/index.tsx @@ -0,0 +1,62 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { EuiAccordion, EuiLink, EuiPanel, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { ErrorPanel } from './error_panel'; +import { usePluginContext } from '../../../hooks/use_plugin_context'; + +interface Props { + title: string; + hasError: boolean; + children: React.ReactNode; + minHeight?: number; + appLink?: string; + appLinkName?: string; +} + +export const SectionContainer = ({ title, appLink, children, hasError, appLinkName }: Props) => { + const { core } = usePluginContext(); + return ( + +
{title}
+ + } + extraAction={ + appLink && ( + + + {appLinkName + ? appLinkName + : i18n.translate('xpack.observability.chart.viewInAppLabel', { + defaultMessage: 'View in app', + })} + + + ) + } + > + <> + + + {hasError ? ( + + ) : ( + <> + + {children} + + )} + + +
+ ); +}; diff --git a/x-pack/plugins/observability/public/components/app/section/logs/index.tsx b/x-pack/plugins/observability/public/components/app/section/logs/index.tsx new file mode 100644 index 0000000000000..f3ba2ef6fa83a --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/section/logs/index.tsx @@ -0,0 +1,151 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Axis, BarSeries, niceTimeFormatter, Position, ScaleType, Settings } from '@elastic/charts'; +import { EuiFlexGroup, EuiFlexItem, euiPaletteColorBlind } from '@elastic/eui'; +import numeral from '@elastic/numeral'; +import { isEmpty } from 'lodash'; +import moment from 'moment'; +import React, { Fragment } from 'react'; +import { useHistory } from 'react-router-dom'; +import { EuiTitle } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { EuiSpacer } from '@elastic/eui'; +import { SectionContainer } from '../'; +import { getDataHandler } from '../../../../data_handler'; +import { useChartTheme } from '../../../../hooks/use_chart_theme'; +import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; +import { LogsFetchDataResponse } from '../../../../typings'; +import { formatStatValue } from '../../../../utils/format_stat_value'; +import { ChartContainer } from '../../chart_container'; +import { StyledStat } from '../../styled_stat'; +import { onBrushEnd } from '../helper'; + +interface Props { + startTime?: string; + endTime?: string; + bucketSize?: string; +} + +function getColorPerItem(series?: LogsFetchDataResponse['series']) { + if (!series) { + return {}; + } + const availableColors = euiPaletteColorBlind({ + rotations: Math.ceil(Object.keys(series).length / 10), + }); + const colorsPerItem = Object.keys(series).reduce((acc: Record, key, index) => { + acc[key] = availableColors[index]; + return acc; + }, {}); + + return colorsPerItem; +} + +export const LogsSection = ({ startTime, endTime, bucketSize }: Props) => { + const history = useHistory(); + + const { data, status } = useFetcher(() => { + if (startTime && endTime && bucketSize) { + return getDataHandler('infra_logs')?.fetchData({ startTime, endTime, bucketSize }); + } + }, [startTime, endTime, bucketSize]); + + const min = moment.utc(startTime).valueOf(); + const max = moment.utc(endTime).valueOf(); + + const formatter = niceTimeFormatter([min, max]); + + const { title, appLink, stats, series } = data || {}; + + const colorsPerItem = getColorPerItem(series); + + const isLoading = status === FETCH_STATUS.LOADING; + + return ( + + +

+ {i18n.translate('xpack.observability.overview.logs.subtitle', { + defaultMessage: 'Logs rate per minute', + })} +

+
+ + + {!stats || isEmpty(stats) ? ( + + + + ) : ( + Object.keys(stats).map((key) => { + const stat = stats[key]; + return ( + + + + ); + }) + )} + + + onBrushEnd({ x, history })} + theme={useChartTheme()} + showLegend + legendPosition={Position.Right} + xDomain={{ min, max }} + showLegendExtra + /> + {series && + Object.keys(series).map((key) => { + const serie = series[key]; + const chartData = serie.coordinates.map((coordinate) => ({ + ...coordinate, + g: serie.label, + })); + return ( + + + + numeral(d).format('0a')} + /> + + ); + })} + +
+ ); +}; diff --git a/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx b/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx new file mode 100644 index 0000000000000..6276e1ba1baca --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx @@ -0,0 +1,182 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { AreaSeries, ScaleType, Settings } from '@elastic/charts'; +import { EuiFlexGroup, EuiFlexItem, EuiProgress, EuiSpacer } from '@elastic/eui'; +import numeral from '@elastic/numeral'; +import { i18n } from '@kbn/i18n'; +import React, { useContext } from 'react'; +import styled, { ThemeContext } from 'styled-components'; +import { SectionContainer } from '../'; +import { getDataHandler } from '../../../../data_handler'; +import { useChartTheme } from '../../../../hooks/use_chart_theme'; +import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; +import { Series } from '../../../../typings'; +import { ChartContainer } from '../../chart_container'; +import { StyledStat } from '../../styled_stat'; + +interface Props { + startTime?: string; + endTime?: string; + bucketSize?: string; +} + +/** + * EuiProgress doesn't support custom color, when it does this component can be removed. + */ +const StyledProgress = styled.div<{ color?: string }>` + progress { + &.euiProgress--native { + &::-webkit-progress-value { + background-color: ${(props) => props.color}; + } + + &::-moz-progress-bar { + background-color: ${(props) => props.color}; + } + } + + &.euiProgress--indeterminate { + &:before { + background-color: ${(props) => props.color}; + } + } + } +`; + +export const MetricsSection = ({ startTime, endTime, bucketSize }: Props) => { + const theme = useContext(ThemeContext); + const { data, status } = useFetcher(() => { + if (startTime && endTime && bucketSize) { + return getDataHandler('infra_metrics')?.fetchData({ startTime, endTime, bucketSize }); + } + }, [startTime, endTime, bucketSize]); + + const isLoading = status === FETCH_STATUS.LOADING; + + const { title = 'Metrics', appLink, stats, series } = data || {}; + + const cpuColor = theme.eui.euiColorVis7; + const memoryColor = theme.eui.euiColorVis0; + const inboundTrafficColor = theme.eui.euiColorVis3; + const outboundTrafficColor = theme.eui.euiColorVis2; + + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +const AreaChart = ({ + serie, + isLoading, + color, +}: { + serie?: Series; + isLoading: boolean; + color: string; +}) => { + const chartTheme = useChartTheme(); + + return ( + + + {serie && ( + + )} + + ); +}; diff --git a/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx b/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx new file mode 100644 index 0000000000000..1f8ca6e61f132 --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx @@ -0,0 +1,179 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + Axis, + BarSeries, + niceTimeFormatter, + Position, + ScaleType, + Settings, + TickFormatter, +} from '@elastic/charts'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import numeral from '@elastic/numeral'; +import { i18n } from '@kbn/i18n'; +import moment from 'moment'; +import React, { useContext } from 'react'; +import { useHistory } from 'react-router-dom'; +import { ThemeContext } from 'styled-components'; +import { SectionContainer } from '../'; +import { getDataHandler } from '../../../../data_handler'; +import { useChartTheme } from '../../../../hooks/use_chart_theme'; +import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; +import { Series } from '../../../../typings'; +import { ChartContainer } from '../../chart_container'; +import { StyledStat } from '../../styled_stat'; +import { onBrushEnd } from '../helper'; + +interface Props { + startTime?: string; + endTime?: string; + bucketSize?: string; +} + +export const UptimeSection = ({ startTime, endTime, bucketSize }: Props) => { + const theme = useContext(ThemeContext); + const history = useHistory(); + + const { data, status } = useFetcher(() => { + if (startTime && endTime && bucketSize) { + return getDataHandler('uptime')?.fetchData({ startTime, endTime, bucketSize }); + } + }, [startTime, endTime, bucketSize]); + + const min = moment.utc(startTime).valueOf(); + const max = moment.utc(endTime).valueOf(); + const formatter = niceTimeFormatter([min, max]); + + const isLoading = status === FETCH_STATUS.LOADING; + + const { title = 'Uptime', appLink, stats, series } = data || {}; + + const downColor = theme.eui.euiColorVis2; + const upColor = theme.eui.euiColorLightShade; + + return ( + + + {/* Stats section */} + + + + + + + + + + + + {/* Chart section */} + + onBrushEnd({ x, history })} + theme={useChartTheme()} + showLegend={false} + legendPosition={Position.Right} + xDomain={{ min, max }} + /> + + + + + ); +}; + +const UptimeBarSeries = ({ + id, + label, + series, + color, + ticktFormatter, +}: { + id: string; + label: string; + series?: Series; + color: string; + ticktFormatter: TickFormatter; +}) => { + if (!series) { + return null; + } + const chartData = series.coordinates.map((coordinate) => ({ + ...coordinate, + g: label, + })); + return ( + <> + + + numeral(x).format('0a')} + /> + + ); +}; diff --git a/x-pack/plugins/observability/public/components/app/styled_stat/index.tsx b/x-pack/plugins/observability/public/components/app/styled_stat/index.tsx new file mode 100644 index 0000000000000..fe38df6484c29 --- /dev/null +++ b/x-pack/plugins/observability/public/components/app/styled_stat/index.tsx @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import styled from 'styled-components'; +import { EuiStat } from '@elastic/eui'; +import React from 'react'; +import { EuiStatProps } from '@elastic/eui/src/components/stat/stat'; + +const Stat = styled(EuiStat)` + .euiStat__title { + color: ${(props) => props.color}; + } +`; + +interface Props extends Partial { + children?: React.ReactNode; + color?: string; +} + +const EMPTY_VALUE = '--'; + +export const StyledStat = (props: Props) => { + const { description = EMPTY_VALUE, title = EMPTY_VALUE, ...rest } = props; + return ; +}; diff --git a/x-pack/plugins/observability/public/components/action_menu.tsx b/x-pack/plugins/observability/public/components/shared/action_menu/index.tsx similarity index 100% rename from x-pack/plugins/observability/public/components/action_menu.tsx rename to x-pack/plugins/observability/public/components/shared/action_menu/index.tsx diff --git a/x-pack/plugins/observability/public/components/shared/data_picker/index.tsx b/x-pack/plugins/observability/public/components/shared/data_picker/index.tsx new file mode 100644 index 0000000000000..cc77c1ed72b4a --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/data_picker/index.tsx @@ -0,0 +1,89 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiSuperDatePicker } from '@elastic/eui'; +import React from 'react'; +import { useHistory, useLocation } from 'react-router-dom'; +import { UI_SETTINGS, useKibanaUISettings } from '../../../hooks/use_kibana_ui_settings'; +import { fromQuery, toQuery } from '../../../utils/url'; + +export interface TimePickerTime { + from: string; + to: string; +} + +export interface TimePickerQuickRange extends TimePickerTime { + display: string; +} + +export interface TimePickerRefreshInterval { + pause: boolean; + value: number; +} + +interface Props { + rangeFrom: string; + rangeTo: string; + refreshPaused: boolean; + refreshInterval: number; +} + +export const DatePicker = ({ rangeFrom, rangeTo, refreshPaused, refreshInterval }: Props) => { + const location = useLocation(); + const history = useHistory(); + + const timePickerQuickRanges = useKibanaUISettings( + UI_SETTINGS.TIMEPICKER_QUICK_RANGES + ); + + const commonlyUsedRanges = timePickerQuickRanges.map(({ from, to, display }) => ({ + start: from, + end: to, + label: display, + })); + + function updateUrl(nextQuery: { + rangeFrom?: string; + rangeTo?: string; + refreshPaused?: boolean; + refreshInterval?: number; + }) { + history.push({ + ...location, + search: fromQuery({ + ...toQuery(location.search), + ...nextQuery, + }), + }); + } + + function onRefreshChange({ + isPaused, + refreshInterval: interval, + }: { + isPaused: boolean; + refreshInterval: number; + }) { + updateUrl({ refreshPaused: isPaused, refreshInterval: interval }); + } + + function onTimeChange({ start, end }: { start: string; end: string }) { + updateUrl({ rangeFrom: start, rangeTo: end }); + } + + return ( + + ); +}; diff --git a/x-pack/plugins/observability/public/data_handler.ts b/x-pack/plugins/observability/public/data_handler.ts index 39e702a332a8e..d7f8c471ad9aa 100644 --- a/x-pack/plugins/observability/public/data_handler.ts +++ b/x-pack/plugins/observability/public/data_handler.ts @@ -17,9 +17,20 @@ export function registerDataHandler({ dataHandlers[appName] = { fetchData, hasData }; } +export function unregisterDataHandler({ appName }: { appName: T }) { + delete dataHandlers[appName]; +} + export function getDataHandler(appName: T) { const dataHandler = dataHandlers[appName]; if (dataHandler) { return dataHandler as DataHandler; } } + +export async function fetchHasData() { + const apps: ObservabilityApp[] = ['apm', 'uptime', 'infra_logs', 'infra_metrics']; + const promises = apps.map((app) => getDataHandler(app)?.hasData()); + const [apm, uptime, logs, metrics] = await Promise.all(promises); + return { apm, uptime, infra_logs: logs, infra_metrics: metrics }; +} diff --git a/x-pack/plugins/observability/public/hooks/use_chart_theme.tsx b/x-pack/plugins/observability/public/hooks/use_chart_theme.tsx new file mode 100644 index 0000000000000..13f7159ba6043 --- /dev/null +++ b/x-pack/plugins/observability/public/hooks/use_chart_theme.tsx @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { EUI_CHARTS_THEME_DARK, EUI_CHARTS_THEME_LIGHT } from '@elastic/eui/dist/eui_charts_theme'; +import { useContext } from 'react'; +import { ThemeContext } from 'styled-components'; + +export function useChartTheme() { + const theme = useContext(ThemeContext); + return theme.darkMode ? EUI_CHARTS_THEME_DARK.theme : EUI_CHARTS_THEME_LIGHT.theme; +} diff --git a/x-pack/plugins/observability/public/hooks/use_fetcher.tsx b/x-pack/plugins/observability/public/hooks/use_fetcher.tsx new file mode 100644 index 0000000000000..88a8ad264e737 --- /dev/null +++ b/x-pack/plugins/observability/public/hooks/use_fetcher.tsx @@ -0,0 +1,84 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useEffect, useState, useMemo } from 'react'; + +export enum FETCH_STATUS { + LOADING = 'loading', + SUCCESS = 'success', + FAILURE = 'failure', + PENDING = 'pending', +} + +export interface FetcherResult { + data?: Data; + status: FETCH_STATUS; + error?: Error; +} + +// fetcher functions can return undefined OR a promise. Previously we had a more simple type +// but it led to issues when using object destructuring with default values +type InferResponseType = Exclude extends Promise + ? TResponseType + : unknown; + +export function useFetcher( + fn: () => TReturn, + fnDeps: any[], + options: { + preservePreviousData?: boolean; + } = {} +): FetcherResult> & { refetch: () => void } { + const { preservePreviousData = true } = options; + + const [result, setResult] = useState>>({ + data: undefined, + status: FETCH_STATUS.PENDING, + }); + const [counter, setCounter] = useState(0); + useEffect(() => { + async function doFetch() { + const promise = fn(); + if (!promise) { + return; + } + + setResult((prevResult) => ({ + data: preservePreviousData ? prevResult.data : undefined, + status: FETCH_STATUS.LOADING, + error: undefined, + })); + + try { + const data = await promise; + setResult({ + data, + status: FETCH_STATUS.SUCCESS, + error: undefined, + } as FetcherResult>); + } catch (e) { + setResult((prevResult) => ({ + data: preservePreviousData ? prevResult.data : undefined, + status: FETCH_STATUS.FAILURE, + error: e, + })); + } + } + + doFetch(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [counter, ...fnDeps]); + + return useMemo(() => { + return { + ...result, + refetch: () => { + // this will invalidate the deps to `useEffect` and will result in a new request + setCounter((count) => count + 1); + }, + }; + }, [result]); +} diff --git a/x-pack/plugins/observability/public/hooks/use_kibana_ui_settings.tsx b/x-pack/plugins/observability/public/hooks/use_kibana_ui_settings.tsx new file mode 100644 index 0000000000000..884d74db391ee --- /dev/null +++ b/x-pack/plugins/observability/public/hooks/use_kibana_ui_settings.tsx @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { usePluginContext } from './use_plugin_context'; +import { UI_SETTINGS } from '../../../../../src/plugins/data/public'; + +export { UI_SETTINGS }; + +type SettingKeys = keyof typeof UI_SETTINGS; +type SettingValues = typeof UI_SETTINGS[SettingKeys]; + +export function useKibanaUISettings(key: SettingValues): T { + const { core } = usePluginContext(); + return core.uiSettings.get(key); +} diff --git a/x-pack/plugins/observability/public/hooks/use_url_params.tsx b/x-pack/plugins/observability/public/hooks/use_url_params.tsx new file mode 100644 index 0000000000000..680a32fb49677 --- /dev/null +++ b/x-pack/plugins/observability/public/hooks/use_url_params.tsx @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import * as t from 'io-ts'; +import { useLocation, useParams } from 'react-router-dom'; +import { isLeft } from 'fp-ts/lib/Either'; +import { PathReporter } from 'io-ts/lib/PathReporter'; +import { Params } from '../routes'; + +function getQueryParams(location: ReturnType) { + const urlSearchParms = new URLSearchParams(location.search); + const queryParams: Record = {}; + urlSearchParms.forEach((value, key) => { + queryParams[key] = value; + }); + return queryParams; +} + +/** + * Extracts query and path params from the url and validate it against the type defined in the route file. + * It removes any aditional item which is not declared in the type. + * @param params + */ +export function useUrlParams(params: Params) { + const location = useLocation(); + const pathParams = useParams(); + const queryParams = getQueryParams(location); + + const rts = { + queryRt: params.query ? t.exact(params.query) : t.strict({}), + pathRt: params.path ? t.exact(params.path) : t.strict({}), + }; + + const queryResult = rts.queryRt.decode(queryParams); + const pathResult = rts.pathRt.decode(pathParams); + if (isLeft(queryResult)) { + // eslint-disable-next-line no-console + console.error(PathReporter.report(queryResult)[0]); + } + + if (isLeft(pathResult)) { + // eslint-disable-next-line no-console + console.error(PathReporter.report(pathResult)[0]); + } + + return { + query: isLeft(queryResult) ? {} : queryResult.right, + path: isLeft(pathResult) ? {} : pathResult.right, + }; +} diff --git a/x-pack/plugins/observability/public/index.ts b/x-pack/plugins/observability/public/index.ts index d2f1d246f79ec..03939736b64ae 100644 --- a/x-pack/plugins/observability/public/index.ts +++ b/x-pack/plugins/observability/public/index.ts @@ -15,7 +15,7 @@ export const plugin: PluginInitializer props.theme.eui.euiColorEmptyShade}; -`; - -const Title = styled.div` - background-color: ${(props) => props.theme.eui.euiPageBackgroundColor}; - border-bottom: ${(props) => props.theme.eui.euiBorderThin}; -`; - -const Page = styled.div` - width: 100%; - max-width: 1200px; - margin: 0 auto; - overflow: hidden; -} -`; - -const EuiCardWithoutPadding = styled(EuiCard)` - padding: 0; -`; - -export const Home = () => { - const { core } = usePluginContext(); - - useEffect(() => { - core.chrome.setBreadcrumbs([ - { - text: i18n.translate('xpack.observability.home.breadcrumb.observability', { - defaultMessage: 'Observability', - }), - }, - { - text: i18n.translate('xpack.observability.home.breadcrumb.gettingStarted', { - defaultMessage: 'Getting started', - }), - }, - ]); - }, [core]); - - return ( - - - <Page> - <EuiSpacer size="xxl" /> - <EuiFlexGroup> - <EuiFlexItem grow={false}> - <EuiIcon type="logoObservability" size="xxl" /> - </EuiFlexItem> - <EuiFlexItem> - <EuiTitle size="m"> - <h1> - {i18n.translate('xpack.observability.home.title', { - defaultMessage: 'Observability', - })} - </h1> - </EuiTitle> - </EuiFlexItem> - </EuiFlexGroup> - <EuiSpacer size="xxl" /> - </Page> - - - - - {/* title and description */} - - -

- {i18n.translate('xpack.observability.home.sectionTitle', { - defaultMessage: 'Unified visibility across your entire ecosystem', - })} -

-
- - - {i18n.translate('xpack.observability.home.sectionsubtitle', { - defaultMessage: - 'Monitor, analyze, and react to events happening anywhere in your environment by bringing logs, metrics, and traces together at scale in a single stack.', - })} - -
- - {/* Apps sections */} - - - - - - {appsSection.map((app) => ( - - } - title={ - -

{app.title}

-
- } - description={app.description} - /> -
- ))} -
-
- - - -
-
- - {/* Get started button */} - - - - - {i18n.translate('xpack.observability.home.getStatedButton', { - defaultMessage: 'Get started', - })} - - - - -
-
-
- ); +import React from 'react'; +import { useHistory } from 'react-router-dom'; +import { fetchHasData } from '../../data_handler'; +import { useFetcher } from '../../hooks/use_fetcher'; + +export const HomePage = () => { + const history = useHistory(); + const { data = {} } = useFetcher(() => fetchHasData(), []); + + const values = Object.values(data); + const hasSomeData = values.length ? values.some((hasData) => hasData) : null; + + if (hasSomeData === true) { + history.push({ pathname: '/overview' }); + } + if (hasSomeData === false) { + history.push({ pathname: '/landing' }); + } + + return <>; }; diff --git a/x-pack/plugins/observability/public/pages/home/section.ts b/x-pack/plugins/observability/public/pages/home/section.ts index d33571a16ccb7..8c87f17c16b3d 100644 --- a/x-pack/plugins/observability/public/pages/home/section.ts +++ b/x-pack/plugins/observability/public/pages/home/section.ts @@ -4,19 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ import { i18n } from '@kbn/i18n'; - -interface ISection { - id: string; - title: string; - icon: string; - description: string; - href?: string; - target?: '_blank'; -} +import { ISection } from '../../typings/section'; export const appsSection: ISection[] = [ { - id: 'logs', + id: 'infra_logs', title: i18n.translate('xpack.observability.section.apps.logs.title', { defaultMessage: 'Logs', }), @@ -25,6 +17,7 @@ export const appsSection: ISection[] = [ defaultMessage: 'Centralize logs from any source. Search, tail, automate anomaly detection, and visualize trends so you can take action quicker.', }), + href: 'https://www.elastic.co', }, { id: 'apm', @@ -36,9 +29,10 @@ export const appsSection: ISection[] = [ defaultMessage: 'Trace transactions through a distributed architecture and map your services’ interactions to easily spot performance bottlenecks.', }), + href: 'https://www.elastic.co', }, { - id: 'metrics', + id: 'infra_metrics', title: i18n.translate('xpack.observability.section.apps.metrics.title', { defaultMessage: 'Metrics', }), @@ -47,6 +41,7 @@ export const appsSection: ISection[] = [ defaultMessage: 'Analyze metrics from your infrastructure, apps, and services. Discover trends, forecast behavior, get alerts on anomalies, and more.', }), + href: 'https://www.elastic.co', }, { id: 'uptime', @@ -58,5 +53,6 @@ export const appsSection: ISection[] = [ defaultMessage: 'Proactively monitor the availability of your sites and services. Receive alerts and resolve issues faster to optimize your users’ experience.', }), + href: 'https://www.elastic.co', }, ]; diff --git a/x-pack/plugins/observability/public/pages/landing/index.tsx b/x-pack/plugins/observability/public/pages/landing/index.tsx new file mode 100644 index 0000000000000..b614095641250 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/landing/index.tsx @@ -0,0 +1,116 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + EuiButton, + EuiCard, + EuiFlexGrid, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiImage, + EuiSpacer, + EuiText, + EuiTitle, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React, { useContext } from 'react'; +import styled, { ThemeContext } from 'styled-components'; +import { WithHeaderLayout } from '../../components/app/layout/with_header'; +import { usePluginContext } from '../../hooks/use_plugin_context'; +import { appsSection } from '../home/section'; + +const EuiCardWithoutPadding = styled(EuiCard)` + padding: 0; +`; + +export const LandingPage = () => { + const { core } = usePluginContext(); + const theme = useContext(ThemeContext); + + return ( + + + {/* title and description */} + + +

+ {i18n.translate('xpack.observability.home.sectionTitle', { + defaultMessage: 'Unified visibility across your entire ecosystem', + })} +

+
+ + + {i18n.translate('xpack.observability.home.sectionsubtitle', { + defaultMessage: + 'Monitor, analyze, and react to events happening anywhere in your environment by bringing logs, metrics, and traces together at scale in a single stack.', + })} + +
+ + {/* Apps sections */} + + + + + + {appsSection.map((app) => ( + + } + title={ + +

{app.title}

+
+ } + description={app.description} + /> +
+ ))} +
+
+ + + +
+
+ + + + {/* Get started button */} + + + + + {i18n.translate('xpack.observability.home.getStatedButton', { + defaultMessage: 'Get started', + })} + + + + +
+
+ ); +}; diff --git a/x-pack/plugins/observability/public/pages/overview/empty_section.ts b/x-pack/plugins/observability/public/pages/overview/empty_section.ts new file mode 100644 index 0000000000000..61456bc88bd3e --- /dev/null +++ b/x-pack/plugins/observability/public/pages/overview/empty_section.ts @@ -0,0 +1,90 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { i18n } from '@kbn/i18n'; +import { AppMountContext } from 'kibana/public'; +import { ISection } from '../../typings/section'; + +export const getEmptySections = ({ core }: { core: AppMountContext['core'] }): ISection[] => { + return [ + { + id: 'infra_logs', + title: i18n.translate('xpack.observability.emptySection.apps.logs.title', { + defaultMessage: 'Logs', + }), + icon: 'logoLogging', + description: i18n.translate('xpack.observability.emptySection.apps.logs.description', { + defaultMessage: + 'Centralize logs from any source. Search, tail, automate anomaly detection, and visualize trends so you can take action quicker.', + }), + linkTitle: i18n.translate('xpack.observability.emptySection.apps.logs.link', { + defaultMessage: 'Install Filebeat', + }), + href: 'https://www.elastic.co', + }, + { + id: 'apm', + title: i18n.translate('xpack.observability.emptySection.apps.apm.title', { + defaultMessage: 'APM', + }), + icon: 'logoAPM', + description: i18n.translate('xpack.observability.emptySection.apps.apm.description', { + defaultMessage: + 'Trace transactions through a distributed architecture and map your services’ interactions to easily spot performance bottlenecks.', + }), + linkTitle: i18n.translate('xpack.observability.emptySection.apps.apm.link', { + defaultMessage: 'Install agent', + }), + href: 'https://www.elastic.co', + }, + { + id: 'infra_metrics', + title: i18n.translate('xpack.observability.emptySection.apps.metrics.title', { + defaultMessage: 'Metrics', + }), + icon: 'logoMetrics', + description: i18n.translate('xpack.observability.emptySection.apps.metrics.description', { + defaultMessage: + 'Analyze metrics from your infrastructure, apps, and services. Discover trends, forecast behavior, get alerts on anomalies, and more.', + }), + linkTitle: i18n.translate('xpack.observability.emptySection.apps.metrics.link', { + defaultMessage: 'Install metrics module', + }), + href: 'https://www.elastic.co', + }, + { + id: 'uptime', + title: i18n.translate('xpack.observability.emptySection.apps.uptime.title', { + defaultMessage: 'Uptime', + }), + icon: 'logoUptime', + description: i18n.translate('xpack.observability.emptySection.apps.uptime.description', { + defaultMessage: + 'Proactively monitor the availability of your sites and services. Receive alerts and resolve issues faster to optimize your users’ experience.', + }), + linkTitle: i18n.translate('xpack.observability.emptySection.apps.uptime.link', { + defaultMessage: 'Install Heartbeat', + }), + href: 'https://www.elastic.co', + }, + { + id: 'alert', + title: i18n.translate('xpack.observability.emptySection.apps.alert.title', { + defaultMessage: 'No alerts found.', + }), + icon: 'watchesApp', + description: i18n.translate('xpack.observability.emptySection.apps.alert.description', { + defaultMessage: + '503 errors stacking up. Applications not responding. CPU and RAM utilization jumping. See these warnings as they happen - not as part of the post-mortem.', + }), + linkTitle: i18n.translate('xpack.observability.emptySection.apps.alert.link', { + defaultMessage: 'Create alert', + }), + href: core.http.basePath.prepend( + '/app/management/insightsAndAlerting/triggersActions/alerts' + ), + }, + ]; +}; diff --git a/x-pack/plugins/observability/public/pages/overview/index.tsx b/x-pack/plugins/observability/public/pages/overview/index.tsx new file mode 100644 index 0000000000000..9caac7f9d86f4 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/overview/index.tsx @@ -0,0 +1,198 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { EuiFlexGrid, EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, EuiSpacer } from '@elastic/eui'; +import moment from 'moment'; +import React, { useContext } from 'react'; +import { ThemeContext } from 'styled-components'; +import { EmptySection } from '../../components/app/empty_section'; +import { WithHeaderLayout } from '../../components/app/layout/with_header'; +import { Resources } from '../../components/app/resources'; +import { AlertsSection } from '../../components/app/section/alerts'; +import { APMSection } from '../../components/app/section/apm'; +import { LogsSection } from '../../components/app/section/logs'; +import { MetricsSection } from '../../components/app/section/metrics'; +import { UptimeSection } from '../../components/app/section/uptime'; +import { DatePicker, TimePickerTime } from '../../components/shared/data_picker'; +import { fetchHasData } from '../../data_handler'; +import { FETCH_STATUS, useFetcher } from '../../hooks/use_fetcher'; +import { UI_SETTINGS, useKibanaUISettings } from '../../hooks/use_kibana_ui_settings'; +import { usePluginContext } from '../../hooks/use_plugin_context'; +import { RouteParams } from '../../routes'; +import { getObservabilityAlerts } from '../../services/get_observability_alerts'; +import { getParsedDate } from '../../utils/date'; +import { getBucketSize } from '../../utils/get_bucket_size'; +import { getEmptySections } from './empty_section'; +import { LoadingObservability } from './loading_observability'; + +interface Props { + routeParams: RouteParams<'/overview'>; +} + +function calculatetBucketSize({ startTime, endTime }: { startTime?: string; endTime?: string }) { + if (startTime && endTime) { + return getBucketSize({ + start: moment.utc(startTime).valueOf(), + end: moment.utc(endTime).valueOf(), + minInterval: '60s', + }); + } +} + +export const OverviewPage = ({ routeParams }: Props) => { + const { core } = usePluginContext(); + + const { data: alerts = [], status: alertStatus } = useFetcher(() => { + return getObservabilityAlerts({ core }); + }, []); + + const theme = useContext(ThemeContext); + const timePickerTime = useKibanaUISettings(UI_SETTINGS.TIMEPICKER_TIME_DEFAULTS); + + const result = useFetcher(() => fetchHasData(), []); + const hasData = result.data; + + if (!hasData) { + return ; + } + + const { + rangeFrom = timePickerTime.from, + rangeTo = timePickerTime.to, + refreshInterval = 10000, + refreshPaused = true, + } = routeParams.query; + + const startTime = getParsedDate(rangeFrom); + const endTime = getParsedDate(rangeTo, { roundUp: true }); + const bucketSize = calculatetBucketSize({ startTime, endTime }); + + const appEmptySections = getEmptySections({ core }).filter(({ id }) => { + if (id === 'alert') { + return alertStatus !== FETCH_STATUS.FAILURE && !alerts.length; + } + return !hasData[id]; + }); + + // Hides the data section when all 'hasData' is false or undefined + const showDataSections = Object.values(hasData).some((hasPluginData) => hasPluginData); + + return ( + + + + + + + + + + + + {/* Data sections */} + {showDataSections && ( + + {hasData.infra_logs && ( + + + + )} + {hasData.infra_metrics && ( + + + + )} + {hasData.apm && ( + + + + )} + {hasData.uptime && ( + + + + )} + + )} + + {/* Empty sections */} + {!!appEmptySections.length && ( + + + 2 ? 2 : 1 + } + gutterSize="s" + > + {appEmptySections.map((app) => { + return ( + + + + ); + })} + + + )} + + + {/* Alert section */} + {!!alerts.length && ( + + + + )} + + {/* Resources section */} + + + + + + + + + + ); +}; diff --git a/x-pack/plugins/observability/public/pages/overview/loading_observability.tsx b/x-pack/plugins/observability/public/pages/overview/loading_observability.tsx new file mode 100644 index 0000000000000..90e3104443e6b --- /dev/null +++ b/x-pack/plugins/observability/public/pages/overview/loading_observability.tsx @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React, { useContext } from 'react'; +import styled, { ThemeContext } from 'styled-components'; +import { EuiFlexItem } from '@elastic/eui'; +import { EuiPanel } from '@elastic/eui'; +import { EuiFlexGroup } from '@elastic/eui'; +import { EuiLoadingSpinner } from '@elastic/eui'; +import { EuiText } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { WithHeaderLayout } from '../../components/app/layout/with_header'; + +const CentralizedFlexGroup = styled(EuiFlexGroup)` + justify-content: center; + align-items: center; + // place the element in the center of the page + min-height: calc(100vh - ${(props) => props.theme.eui.euiHeaderChildSize}); +`; + +export const LoadingObservability = () => { + const theme = useContext(ThemeContext); + + return ( + + + + + + + + + + + {i18n.translate('xpack.observability.overview.loadingObservability', { + defaultMessage: 'Loading Observability', + })} + + + + + + + + ); +}; diff --git a/x-pack/plugins/observability/public/pages/overview/mock/alerts.mock.ts b/x-pack/plugins/observability/public/pages/overview/mock/alerts.mock.ts new file mode 100644 index 0000000000000..759b8b5fdae4f --- /dev/null +++ b/x-pack/plugins/observability/public/pages/overview/mock/alerts.mock.ts @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const alertsFetchData = async () => { + return Promise.resolve({ + data: [ + { + id: '1', + consumer: 'apm', + name: 'Error rate | opbeans-java', + alertTypeId: 'apm.error_rate', + tags: ['apm', 'service.name:opbeans-java'], + updatedAt: '2020-07-03T14:27:51.488Z', + muteAll: true, + }, + { + id: '2', + consumer: 'apm', + name: 'Transaction duration | opbeans-java', + alertTypeId: 'apm.transaction_duration', + tags: ['apm', 'service.name:opbeans-java'], + updatedAt: '2020-07-02T14:27:51.488Z', + muteAll: true, + }, + { + id: '3', + consumer: 'logs', + name: 'Logs obs test', + alertTypeId: 'logs.alert.document.count', + tags: ['logs', 'observability'], + updatedAt: '2020-06-30T14:27:51.488Z', + muteAll: true, + }, + { + id: '4', + consumer: 'metrics', + name: 'Metrics obs test', + alertTypeId: 'metrics.alert.inventory.threshold', + tags: ['metrics', 'observability'], + updatedAt: '2020-03-20T14:27:51.488Z', + muteAll: true, + }, + { + id: '5', + consumer: 'uptime', + name: 'Uptime obs test', + alertTypeId: 'xpack.uptime.alerts.monitorStatus', + tags: ['uptime', 'observability'], + updatedAt: '2020-03-25T17:27:51.488Z', + muteAll: true, + }, + ], + }); +}; diff --git a/x-pack/plugins/observability/public/pages/overview/mock/apm.mock.ts b/x-pack/plugins/observability/public/pages/overview/mock/apm.mock.ts new file mode 100644 index 0000000000000..7303b78cc0132 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/overview/mock/apm.mock.ts @@ -0,0 +1,627 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { ApmFetchDataResponse, FetchData } from '../../../typings'; + +export const fetchApmData: FetchData = () => { + return Promise.resolve(response); +}; + +const response: ApmFetchDataResponse = { + title: 'APM', + appLink: '/app/apm', + stats: { + services: { + type: 'number', + value: 7, + }, + transactions: { + type: 'number', + value: 125808, + }, + }, + series: { + transactions: { + coordinates: [ + { + x: 1593295200000, + y: 891, + }, + { + x: 1593297000000, + y: 902, + }, + { + x: 1593298800000, + y: 924, + }, + { + x: 1593300600000, + y: 944, + }, + { + x: 1593302400000, + y: 935, + }, + { + x: 1593304200000, + y: 915, + }, + { + x: 1593306000000, + y: 917, + }, + { + x: 1593307800000, + y: 941, + }, + { + x: 1593309600000, + y: 906, + }, + { + x: 1593311400000, + y: 939, + }, + { + x: 1593313200000, + y: 961, + }, + { + x: 1593315000000, + y: 911, + }, + { + x: 1593316800000, + y: 958, + }, + { + x: 1593318600000, + y: 861, + }, + { + x: 1593320400000, + y: 906, + }, + { + x: 1593322200000, + y: 899, + }, + { + x: 1593324000000, + y: 785, + }, + { + x: 1593325800000, + y: 952, + }, + { + x: 1593327600000, + y: 910, + }, + { + x: 1593329400000, + y: 869, + }, + { + x: 1593331200000, + y: 895, + }, + { + x: 1593333000000, + y: 924, + }, + { + x: 1593334800000, + y: 930, + }, + { + x: 1593336600000, + y: 947, + }, + { + x: 1593338400000, + y: 905, + }, + { + x: 1593340200000, + y: 963, + }, + { + x: 1593342000000, + y: 877, + }, + { + x: 1593343800000, + y: 839, + }, + { + x: 1593345600000, + y: 884, + }, + { + x: 1593347400000, + y: 934, + }, + { + x: 1593349200000, + y: 908, + }, + { + x: 1593351000000, + y: 982, + }, + { + x: 1593352800000, + y: 897, + }, + { + x: 1593354600000, + y: 903, + }, + { + x: 1593356400000, + y: 877, + }, + { + x: 1593358200000, + y: 893, + }, + { + x: 1593360000000, + y: 919, + }, + { + x: 1593361800000, + y: 844, + }, + { + x: 1593363600000, + y: 940, + }, + { + x: 1593365400000, + y: 951, + }, + { + x: 1593367200000, + y: 869, + }, + { + x: 1593369000000, + y: 901, + }, + { + x: 1593370800000, + y: 940, + }, + { + x: 1593372600000, + y: 942, + }, + { + x: 1593374400000, + y: 881, + }, + { + x: 1593376200000, + y: 935, + }, + { + x: 1593378000000, + y: 892, + }, + { + x: 1593379800000, + y: 861, + }, + { + x: 1593381600000, + y: 868, + }, + { + x: 1593383400000, + y: 990, + }, + { + x: 1593385200000, + y: 931, + }, + { + x: 1593387000000, + y: 898, + }, + { + x: 1593388800000, + y: 906, + }, + { + x: 1593390600000, + y: 928, + }, + { + x: 1593392400000, + y: 975, + }, + { + x: 1593394200000, + y: 842, + }, + { + x: 1593396000000, + y: 940, + }, + { + x: 1593397800000, + y: 922, + }, + { + x: 1593399600000, + y: 962, + }, + { + x: 1593401400000, + y: 940, + }, + { + x: 1593403200000, + y: 974, + }, + { + x: 1593405000000, + y: 887, + }, + { + x: 1593406800000, + y: 920, + }, + { + x: 1593408600000, + y: 854, + }, + { + x: 1593410400000, + y: 898, + }, + { + x: 1593412200000, + y: 952, + }, + { + x: 1593414000000, + y: 987, + }, + { + x: 1593415800000, + y: 932, + }, + { + x: 1593417600000, + y: 1009, + }, + { + x: 1593419400000, + y: 989, + }, + { + x: 1593421200000, + y: 939, + }, + { + x: 1593423000000, + y: 929, + }, + { + x: 1593424800000, + y: 929, + }, + { + x: 1593426600000, + y: 864, + }, + { + x: 1593428400000, + y: 895, + }, + { + x: 1593430200000, + y: 876, + }, + { + x: 1593432000000, + y: 68, + }, + { + x: 1593433800000, + y: 0, + }, + { + x: 1593435600000, + y: 0, + }, + { + x: 1593437400000, + y: 0, + }, + { + x: 1593439200000, + y: 0, + }, + { + x: 1593441000000, + y: 0, + }, + { + x: 1593442800000, + y: 700, + }, + { + x: 1593444600000, + y: 930, + }, + { + x: 1593446400000, + y: 953, + }, + { + x: 1593448200000, + y: 995, + }, + { + x: 1593450000000, + y: 883, + }, + { + x: 1593451800000, + y: 902, + }, + { + x: 1593453600000, + y: 988, + }, + { + x: 1593455400000, + y: 947, + }, + { + x: 1593457200000, + y: 889, + }, + { + x: 1593459000000, + y: 982, + }, + { + x: 1593460800000, + y: 919, + }, + { + x: 1593462600000, + y: 854, + }, + { + x: 1593464400000, + y: 894, + }, + { + x: 1593466200000, + y: 901, + }, + { + x: 1593468000000, + y: 970, + }, + { + x: 1593469800000, + y: 840, + }, + { + x: 1593471600000, + y: 857, + }, + { + x: 1593473400000, + y: 943, + }, + { + x: 1593475200000, + y: 825, + }, + { + x: 1593477000000, + y: 955, + }, + { + x: 1593478800000, + y: 959, + }, + { + x: 1593480600000, + y: 921, + }, + { + x: 1593482400000, + y: 924, + }, + { + x: 1593484200000, + y: 840, + }, + { + x: 1593486000000, + y: 943, + }, + { + x: 1593487800000, + y: 919, + }, + { + x: 1593489600000, + y: 882, + }, + { + x: 1593491400000, + y: 900, + }, + { + x: 1593493200000, + y: 930, + }, + { + x: 1593495000000, + y: 854, + }, + { + x: 1593496800000, + y: 905, + }, + { + x: 1593498600000, + y: 922, + }, + { + x: 1593500400000, + y: 863, + }, + { + x: 1593502200000, + y: 966, + }, + { + x: 1593504000000, + y: 910, + }, + { + x: 1593505800000, + y: 851, + }, + { + x: 1593507600000, + y: 867, + }, + { + x: 1593509400000, + y: 904, + }, + { + x: 1593511200000, + y: 913, + }, + { + x: 1593513000000, + y: 889, + }, + { + x: 1593514800000, + y: 907, + }, + { + x: 1593516600000, + y: 965, + }, + { + x: 1593518400000, + y: 868, + }, + { + x: 1593520200000, + y: 919, + }, + { + x: 1593522000000, + y: 945, + }, + { + x: 1593523800000, + y: 883, + }, + { + x: 1593525600000, + y: 902, + }, + { + x: 1593527400000, + y: 900, + }, + { + x: 1593529200000, + y: 829, + }, + { + x: 1593531000000, + y: 919, + }, + { + x: 1593532800000, + y: 942, + }, + { + x: 1593534600000, + y: 924, + }, + { + x: 1593536400000, + y: 958, + }, + { + x: 1593538200000, + y: 867, + }, + { + x: 1593540000000, + y: 844, + }, + { + x: 1593541800000, + y: 976, + }, + { + x: 1593543600000, + y: 937, + }, + { + x: 1593545400000, + y: 891, + }, + { + x: 1593547200000, + y: 936, + }, + { + x: 1593549000000, + y: 895, + }, + { + x: 1593550800000, + y: 850, + }, + { + x: 1593552600000, + y: 899, + }, + ], + }, + }, +}; + +export const emptyResponse: ApmFetchDataResponse = { + title: 'APM', + appLink: '/app/apm', + stats: { + services: { + type: 'number', + value: 0, + }, + transactions: { + type: 'number', + value: 0, + }, + }, + series: { + transactions: { + coordinates: [], + }, + }, +}; diff --git a/x-pack/plugins/observability/public/pages/overview/mock/logs.mock.ts b/x-pack/plugins/observability/public/pages/overview/mock/logs.mock.ts new file mode 100644 index 0000000000000..5bea1fbf19ace --- /dev/null +++ b/x-pack/plugins/observability/public/pages/overview/mock/logs.mock.ts @@ -0,0 +1,2326 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { FetchData, LogsFetchDataResponse } from '../../../typings'; + +export const fetchLogsData: FetchData = () => { + return Promise.resolve(response); +}; + +const response: LogsFetchDataResponse = { + title: 'Logs', + appLink: + "/app/logs/stream?logPosition=(end:'2020-06-30T21:30:00.000Z',start:'2020-06-27T22:00:00.000Z')", + stats: { + 'haproxy.log': { + type: 'number', + label: 'haproxy.log', + value: 145.84289044289045, + }, + 'nginx.access': { + type: 'number', + label: 'nginx.access', + value: 94.67039627039627, + }, + 'kibana.log': { + type: 'number', + label: 'kibana.log', + value: 11.018181818181818, + }, + 'nginx.error': { + type: 'number', + label: 'nginx.error', + value: 8.218181818181819, + }, + }, + series: { + 'haproxy.log': { + label: 'haproxy.log', + coordinates: [ + { + x: 1593295200000, + y: 146.83333333333334, + }, + { + x: 1593297000000, + y: 146.96666666666667, + }, + { + x: 1593298800000, + y: 146.96666666666667, + }, + { + x: 1593300600000, + y: 146.86666666666667, + }, + { + x: 1593302400000, + y: 146.96666666666667, + }, + { + x: 1593304200000, + y: 147.03333333333333, + }, + { + x: 1593306000000, + y: 147.16666666666666, + }, + { + x: 1593307800000, + y: 146.96666666666667, + }, + { + x: 1593309600000, + y: 146.96666666666667, + }, + { + x: 1593311400000, + y: 146.96666666666667, + }, + { + x: 1593313200000, + y: 147.03333333333333, + }, + { + x: 1593315000000, + y: 147.13333333333333, + }, + { + x: 1593316800000, + y: 146.96666666666667, + }, + { + x: 1593318600000, + y: 146.96666666666667, + }, + { + x: 1593320400000, + y: 146.93333333333334, + }, + { + x: 1593322200000, + y: 147.06666666666666, + }, + { + x: 1593324000000, + y: 146.9, + }, + { + x: 1593325800000, + y: 147.06666666666666, + }, + { + x: 1593327600000, + y: 147.06666666666666, + }, + { + x: 1593329400000, + y: 146.93333333333334, + }, + { + x: 1593331200000, + y: 146.86666666666667, + }, + { + x: 1593333000000, + y: 146.86666666666667, + }, + { + x: 1593334800000, + y: 147, + }, + { + x: 1593336600000, + y: 146.66666666666666, + }, + { + x: 1593338400000, + y: 146.83333333333334, + }, + { + x: 1593340200000, + y: 146.9, + }, + { + x: 1593342000000, + y: 146.96666666666667, + }, + { + x: 1593343800000, + y: 146.86666666666667, + }, + { + x: 1593345600000, + y: 146.83333333333334, + }, + { + x: 1593347400000, + y: 146.86666666666667, + }, + { + x: 1593349200000, + y: 146.93333333333334, + }, + { + x: 1593351000000, + y: 146.8, + }, + { + x: 1593352800000, + y: 146.83333333333334, + }, + { + x: 1593354600000, + y: 146.83333333333334, + }, + { + x: 1593356400000, + y: 146.73333333333332, + }, + { + x: 1593358200000, + y: 146.9, + }, + { + x: 1593360000000, + y: 146.73333333333332, + }, + { + x: 1593361800000, + y: 146.63333333333333, + }, + { + x: 1593363600000, + y: 146.6, + }, + { + x: 1593365400000, + y: 147.06666666666666, + }, + { + x: 1593367200000, + y: 147, + }, + { + x: 1593369000000, + y: 146.93333333333334, + }, + { + x: 1593370800000, + y: 146.73333333333332, + }, + { + x: 1593372600000, + y: 147.06666666666666, + }, + { + x: 1593374400000, + y: 147, + }, + { + x: 1593376200000, + y: 147.06666666666666, + }, + { + x: 1593378000000, + y: 147.2, + }, + { + x: 1593379800000, + y: 147.1, + }, + { + x: 1593381600000, + y: 147, + }, + { + x: 1593383400000, + y: 147.06666666666666, + }, + { + x: 1593385200000, + y: 147.13333333333333, + }, + { + x: 1593387000000, + y: 147.2, + }, + { + x: 1593388800000, + y: 146.96666666666667, + }, + { + x: 1593390600000, + y: 146.83333333333334, + }, + { + x: 1593392400000, + y: 146.8, + }, + { + x: 1593394200000, + y: 144.3, + }, + { + x: 1593396000000, + y: 147.3, + }, + { + x: 1593397800000, + y: 147.2, + }, + { + x: 1593399600000, + y: 147.33333333333334, + }, + { + x: 1593401400000, + y: 147.1, + }, + { + x: 1593403200000, + y: 147.13333333333333, + }, + { + x: 1593405000000, + y: 147.16666666666666, + }, + { + x: 1593406800000, + y: 147.1, + }, + { + x: 1593408600000, + y: 147.3, + }, + { + x: 1593410400000, + y: 147.26666666666668, + }, + { + x: 1593412200000, + y: 147.2, + }, + { + x: 1593414000000, + y: 147.03333333333333, + }, + { + x: 1593415800000, + y: 146.9, + }, + { + x: 1593417600000, + y: 146.96666666666667, + }, + { + x: 1593419400000, + y: 147.1, + }, + { + x: 1593421200000, + y: 147.13333333333333, + }, + { + x: 1593423000000, + y: 147.03333333333333, + }, + { + x: 1593424800000, + y: 141.36666666666667, + }, + { + x: 1593426600000, + y: 144.63333333333333, + }, + { + x: 1593428400000, + y: 153.66666666666666, + }, + { + x: 1593430200000, + y: 136.76666666666668, + }, + { + x: 1593432000000, + y: 123.43333333333334, + }, + { + x: 1593433800000, + y: 123.5, + }, + { + x: 1593435600000, + y: 123.26666666666667, + }, + { + x: 1593437400000, + y: 123.23333333333333, + }, + { + x: 1593439200000, + y: 123.13333333333334, + }, + { + x: 1593441000000, + y: 123.2, + }, + { + x: 1593442800000, + y: 144.23333333333332, + }, + { + x: 1593444600000, + y: 147.06666666666666, + }, + { + x: 1593446400000, + y: 146.9, + }, + { + x: 1593448200000, + y: 146.7, + }, + { + x: 1593450000000, + y: 146.8, + }, + { + x: 1593451800000, + y: 146.73333333333332, + }, + { + x: 1593453600000, + y: 146.7, + }, + { + x: 1593455400000, + y: 146.7, + }, + { + x: 1593457200000, + y: 146.56666666666666, + }, + { + x: 1593459000000, + y: 146.8, + }, + { + x: 1593460800000, + y: 146.8, + }, + { + x: 1593462600000, + y: 146.83333333333334, + }, + { + x: 1593464400000, + y: 146.7, + }, + { + x: 1593466200000, + y: 146.9, + }, + { + x: 1593468000000, + y: 147.03333333333333, + }, + { + x: 1593469800000, + y: 146.76666666666668, + }, + { + x: 1593471600000, + y: 146.7, + }, + { + x: 1593473400000, + y: 146.63333333333333, + }, + { + x: 1593475200000, + y: 146.93333333333334, + }, + { + x: 1593477000000, + y: 146.5, + }, + { + x: 1593478800000, + y: 146.76666666666668, + }, + { + x: 1593480600000, + y: 144.83333333333334, + }, + { + x: 1593482400000, + y: 146.96666666666667, + }, + { + x: 1593484200000, + y: 147.1, + }, + { + x: 1593486000000, + y: 147.1, + }, + { + x: 1593487800000, + y: 147.3, + }, + { + x: 1593489600000, + y: 147.1, + }, + { + x: 1593491400000, + y: 147.03333333333333, + }, + { + x: 1593493200000, + y: 147.2, + }, + { + x: 1593495000000, + y: 147.06666666666666, + }, + { + x: 1593496800000, + y: 147.1, + }, + { + x: 1593498600000, + y: 147.2, + }, + { + x: 1593500400000, + y: 147.06666666666666, + }, + { + x: 1593502200000, + y: 147.06666666666666, + }, + { + x: 1593504000000, + y: 147.06666666666666, + }, + { + x: 1593505800000, + y: 147.06666666666666, + }, + { + x: 1593507600000, + y: 146.96666666666667, + }, + { + x: 1593509400000, + y: 147.16666666666666, + }, + { + x: 1593511200000, + y: 147.03333333333333, + }, + { + x: 1593513000000, + y: 147, + }, + { + x: 1593514800000, + y: 147.03333333333333, + }, + { + x: 1593516600000, + y: 146.96666666666667, + }, + { + x: 1593518400000, + y: 146.63333333333333, + }, + { + x: 1593520200000, + y: 146.43333333333334, + }, + { + x: 1593522000000, + y: 147.13333333333333, + }, + { + x: 1593523800000, + y: 147.13333333333333, + }, + { + x: 1593525600000, + y: 146.93333333333334, + }, + { + x: 1593527400000, + y: 147, + }, + { + x: 1593529200000, + y: 147.03333333333333, + }, + { + x: 1593531000000, + y: 147.2, + }, + { + x: 1593532800000, + y: 147.13333333333333, + }, + { + x: 1593534600000, + y: 147.13333333333333, + }, + { + x: 1593536400000, + y: 147.13333333333333, + }, + { + x: 1593538200000, + y: 147.1, + }, + { + x: 1593540000000, + y: 147, + }, + { + x: 1593541800000, + y: 147.26666666666668, + }, + { + x: 1593543600000, + y: 146.73333333333332, + }, + { + x: 1593545400000, + y: 147.03333333333333, + }, + { + x: 1593547200000, + y: 147, + }, + { + x: 1593549000000, + y: 146.9, + }, + { + x: 1593550800000, + y: 147.03333333333333, + }, + ], + }, + 'nginx.access': { + label: 'nginx.access', + coordinates: [ + { + x: 1593295200000, + y: 94.06666666666666, + }, + { + x: 1593297000000, + y: 91.4, + }, + { + x: 1593298800000, + y: 95.03333333333333, + }, + { + x: 1593300600000, + y: 94.5, + }, + { + x: 1593302400000, + y: 94.06666666666666, + }, + { + x: 1593304200000, + y: 93.3, + }, + { + x: 1593306000000, + y: 91.16666666666667, + }, + { + x: 1593307800000, + y: 94.5, + }, + { + x: 1593309600000, + y: 93.53333333333333, + }, + { + x: 1593311400000, + y: 118.9, + }, + { + x: 1593313200000, + y: 110.66666666666667, + }, + { + x: 1593315000000, + y: 95.66666666666667, + }, + { + x: 1593316800000, + y: 99.53333333333333, + }, + { + x: 1593318600000, + y: 123.36666666666666, + }, + { + x: 1593320400000, + y: 94.13333333333334, + }, + { + x: 1593322200000, + y: 95.53333333333333, + }, + { + x: 1593324000000, + y: 93.93333333333334, + }, + { + x: 1593325800000, + y: 94.06666666666666, + }, + { + x: 1593327600000, + y: 118.16666666666667, + }, + { + x: 1593329400000, + y: 108.6, + }, + { + x: 1593331200000, + y: 93.53333333333333, + }, + { + x: 1593333000000, + y: 93.06666666666666, + }, + { + x: 1593334800000, + y: 93.76666666666667, + }, + { + x: 1593336600000, + y: 95.3, + }, + { + x: 1593338400000, + y: 96.4, + }, + { + x: 1593340200000, + y: 121.93333333333334, + }, + { + x: 1593342000000, + y: 134.43333333333334, + }, + { + x: 1593343800000, + y: 160.4, + }, + { + x: 1593345600000, + y: 129.7, + }, + { + x: 1593347400000, + y: 119.16666666666667, + }, + { + x: 1593349200000, + y: 133.06666666666666, + }, + { + x: 1593351000000, + y: 212.4, + }, + { + x: 1593352800000, + y: 95.36666666666666, + }, + { + x: 1593354600000, + y: 93.6, + }, + { + x: 1593356400000, + y: 93.4, + }, + { + x: 1593358200000, + y: 95.1, + }, + { + x: 1593360000000, + y: 94.36666666666666, + }, + { + x: 1593361800000, + y: 97.23333333333333, + }, + { + x: 1593363600000, + y: 94.03333333333333, + }, + { + x: 1593365400000, + y: 94.53333333333333, + }, + { + x: 1593367200000, + y: 93.56666666666666, + }, + { + x: 1593369000000, + y: 98.43333333333334, + }, + { + x: 1593370800000, + y: 92.3, + }, + { + x: 1593372600000, + y: 93.13333333333334, + }, + { + x: 1593374400000, + y: 93.16666666666667, + }, + { + x: 1593376200000, + y: 93.7, + }, + { + x: 1593378000000, + y: 94.46666666666667, + }, + { + x: 1593379800000, + y: 97.16666666666667, + }, + { + x: 1593381600000, + y: 94.36666666666666, + }, + { + x: 1593383400000, + y: 93.7, + }, + { + x: 1593385200000, + y: 93.4, + }, + { + x: 1593387000000, + y: 91.3, + }, + { + x: 1593388800000, + y: 92.66666666666667, + }, + { + x: 1593390600000, + y: 93.73333333333333, + }, + { + x: 1593392400000, + y: 94.33333333333333, + }, + { + x: 1593394200000, + y: 93.23333333333333, + }, + { + x: 1593396000000, + y: 93.9, + }, + { + x: 1593397800000, + y: 92.83333333333333, + }, + { + x: 1593399600000, + y: 93, + }, + { + x: 1593401400000, + y: 91.2, + }, + { + x: 1593403200000, + y: 91.96666666666667, + }, + { + x: 1593405000000, + y: 93.83333333333333, + }, + { + x: 1593406800000, + y: 93.16666666666667, + }, + { + x: 1593408600000, + y: 95.36666666666666, + }, + { + x: 1593410400000, + y: 92.5, + }, + { + x: 1593412200000, + y: 93.16666666666667, + }, + { + x: 1593414000000, + y: 92.8, + }, + { + x: 1593415800000, + y: 95.83333333333333, + }, + { + x: 1593417600000, + y: 96.96666666666667, + }, + { + x: 1593419400000, + y: 94.63333333333334, + }, + { + x: 1593421200000, + y: 98.7, + }, + { + x: 1593423000000, + y: 100.03333333333333, + }, + { + x: 1593424800000, + y: 108.66666666666667, + }, + { + x: 1593426600000, + y: 110.9, + }, + { + x: 1593428400000, + y: 88.56666666666666, + }, + { + x: 1593430200000, + y: 1, + }, + { + x: 1593442800000, + y: 74.53333333333333, + }, + { + x: 1593444600000, + y: 99.03333333333333, + }, + { + x: 1593446400000, + y: 98.03333333333333, + }, + { + x: 1593448200000, + y: 91.26666666666667, + }, + { + x: 1593450000000, + y: 107.76666666666667, + }, + { + x: 1593451800000, + y: 98.26666666666667, + }, + { + x: 1593453600000, + y: 99.46666666666667, + }, + { + x: 1593455400000, + y: 102.33333333333333, + }, + { + x: 1593457200000, + y: 108.13333333333334, + }, + { + x: 1593459000000, + y: 95.36666666666666, + }, + { + x: 1593460800000, + y: 98.23333333333333, + }, + { + x: 1593462600000, + y: 91.46666666666667, + }, + { + x: 1593464400000, + y: 115.63333333333334, + }, + { + x: 1593466200000, + y: 116.23333333333333, + }, + { + x: 1593468000000, + y: 91.66666666666667, + }, + { + x: 1593469800000, + y: 94.33333333333333, + }, + { + x: 1593471600000, + y: 96.43333333333334, + }, + { + x: 1593473400000, + y: 94.7, + }, + { + x: 1593475200000, + y: 93.76666666666667, + }, + { + x: 1593477000000, + y: 91.5, + }, + { + x: 1593478800000, + y: 91.9, + }, + { + x: 1593480600000, + y: 91.3, + }, + { + x: 1593482400000, + y: 98.3, + }, + { + x: 1593484200000, + y: 95.53333333333333, + }, + { + x: 1593486000000, + y: 95.66666666666667, + }, + { + x: 1593487800000, + y: 92.73333333333333, + }, + { + x: 1593489600000, + y: 93.6, + }, + { + x: 1593491400000, + y: 94.3, + }, + { + x: 1593493200000, + y: 93.13333333333334, + }, + { + x: 1593495000000, + y: 104.36666666666666, + }, + { + x: 1593496800000, + y: 107.26666666666667, + }, + { + x: 1593498600000, + y: 101.83333333333333, + }, + { + x: 1593500400000, + y: 105.46666666666667, + }, + { + x: 1593502200000, + y: 111.86666666666666, + }, + { + x: 1593504000000, + y: 111.56666666666666, + }, + { + x: 1593505800000, + y: 103.76666666666667, + }, + { + x: 1593507600000, + y: 93.9, + }, + { + x: 1593509400000, + y: 97.16666666666667, + }, + { + x: 1593511200000, + y: 93.03333333333333, + }, + { + x: 1593513000000, + y: 94.4, + }, + { + x: 1593514800000, + y: 94.76666666666667, + }, + { + x: 1593516600000, + y: 94.96666666666667, + }, + { + x: 1593518400000, + y: 101.3, + }, + { + x: 1593520200000, + y: 98.63333333333334, + }, + { + x: 1593522000000, + y: 94.8, + }, + { + x: 1593523800000, + y: 97.46666666666667, + }, + { + x: 1593525600000, + y: 95.86666666666666, + }, + { + x: 1593527400000, + y: 97.3, + }, + { + x: 1593529200000, + y: 96.1, + }, + { + x: 1593531000000, + y: 97.1, + }, + { + x: 1593532800000, + y: 97.56666666666666, + }, + { + x: 1593534600000, + y: 107.6, + }, + { + x: 1593536400000, + y: 97.46666666666667, + }, + { + x: 1593538200000, + y: 96.46666666666667, + }, + { + x: 1593540000000, + y: 93.83333333333333, + }, + { + x: 1593541800000, + y: 98.73333333333333, + }, + { + x: 1593543600000, + y: 99.86666666666666, + }, + { + x: 1593545400000, + y: 98.66666666666667, + }, + { + x: 1593547200000, + y: 102.8, + }, + { + x: 1593549000000, + y: 96.13333333333334, + }, + { + x: 1593550800000, + y: 94.53333333333333, + }, + ], + }, + 'kibana.log': { + label: 'kibana.log', + coordinates: [ + { + x: 1593295200000, + y: 11.8, + }, + { + x: 1593297000000, + y: 11.833333333333334, + }, + { + x: 1593298800000, + y: 12.1, + }, + { + x: 1593300600000, + y: 12.133333333333333, + }, + { + x: 1593302400000, + y: 11.2, + }, + { + x: 1593304200000, + y: 11.933333333333334, + }, + { + x: 1593306000000, + y: 11.466666666666667, + }, + { + x: 1593307800000, + y: 12.066666666666666, + }, + { + x: 1593309600000, + y: 11.9, + }, + { + x: 1593311400000, + y: 11.766666666666667, + }, + { + x: 1593313200000, + y: 12.066666666666666, + }, + { + x: 1593315000000, + y: 11.7, + }, + { + x: 1593316800000, + y: 11.6, + }, + { + x: 1593318600000, + y: 11.766666666666667, + }, + { + x: 1593320400000, + y: 11.633333333333333, + }, + { + x: 1593322200000, + y: 11.833333333333334, + }, + { + x: 1593324000000, + y: 11.8, + }, + { + x: 1593325800000, + y: 11.7, + }, + { + x: 1593327600000, + y: 11.666666666666666, + }, + { + x: 1593329400000, + y: 11.8, + }, + { + x: 1593331200000, + y: 11.966666666666667, + }, + { + x: 1593333000000, + y: 11.766666666666667, + }, + { + x: 1593334800000, + y: 11.766666666666667, + }, + { + x: 1593336600000, + y: 11.866666666666667, + }, + { + x: 1593338400000, + y: 11.433333333333334, + }, + { + x: 1593340200000, + y: 12.033333333333333, + }, + { + x: 1593342000000, + y: 12.1, + }, + { + x: 1593343800000, + y: 12.1, + }, + { + x: 1593345600000, + y: 11.8, + }, + { + x: 1593347400000, + y: 12.366666666666667, + }, + { + x: 1593349200000, + y: 12.033333333333333, + }, + { + x: 1593351000000, + y: 12, + }, + { + x: 1593352800000, + y: 11.8, + }, + { + x: 1593354600000, + y: 11.5, + }, + { + x: 1593356400000, + y: 12.1, + }, + { + x: 1593358200000, + y: 11.966666666666667, + }, + { + x: 1593360000000, + y: 11.9, + }, + { + x: 1593361800000, + y: 12.233333333333333, + }, + { + x: 1593363600000, + y: 11.533333333333333, + }, + { + x: 1593365400000, + y: 11.633333333333333, + }, + { + x: 1593367200000, + y: 11.866666666666667, + }, + { + x: 1593369000000, + y: 12, + }, + { + x: 1593370800000, + y: 11.7, + }, + { + x: 1593372600000, + y: 11.8, + }, + { + x: 1593374400000, + y: 11.4, + }, + { + x: 1593376200000, + y: 11.766666666666667, + }, + { + x: 1593378000000, + y: 12.033333333333333, + }, + { + x: 1593379800000, + y: 11.833333333333334, + }, + { + x: 1593381600000, + y: 11.9, + }, + { + x: 1593383400000, + y: 11.966666666666667, + }, + { + x: 1593385200000, + y: 11.8, + }, + { + x: 1593387000000, + y: 12, + }, + { + x: 1593388800000, + y: 11.933333333333334, + }, + { + x: 1593390600000, + y: 12.033333333333333, + }, + { + x: 1593392400000, + y: 12, + }, + { + x: 1593394200000, + y: 11.533333333333333, + }, + { + x: 1593396000000, + y: 11.4, + }, + { + x: 1593397800000, + y: 11.666666666666666, + }, + { + x: 1593399600000, + y: 11.633333333333333, + }, + { + x: 1593401400000, + y: 11.166666666666666, + }, + { + x: 1593403200000, + y: 11.3, + }, + { + x: 1593405000000, + y: 11.2, + }, + { + x: 1593406800000, + y: 10.966666666666667, + }, + { + x: 1593408600000, + y: 11.5, + }, + { + x: 1593410400000, + y: 11.1, + }, + { + x: 1593412200000, + y: 11.2, + }, + { + x: 1593414000000, + y: 11.4, + }, + { + x: 1593415800000, + y: 10.8, + }, + { + x: 1593417600000, + y: 11.066666666666666, + }, + { + x: 1593419400000, + y: 11.8, + }, + { + x: 1593421200000, + y: 11.266666666666667, + }, + { + x: 1593423000000, + y: 11.333333333333334, + }, + { + x: 1593424800000, + y: 11.233333333333333, + }, + { + x: 1593426600000, + y: 11.5, + }, + { + x: 1593428400000, + y: 8.2, + }, + { + x: 1593442800000, + y: 8.2, + }, + { + x: 1593444600000, + y: 11.4, + }, + { + x: 1593446400000, + y: 10.733333333333333, + }, + { + x: 1593448200000, + y: 10.833333333333334, + }, + { + x: 1593450000000, + y: 11.3, + }, + { + x: 1593451800000, + y: 11.633333333333333, + }, + { + x: 1593453600000, + y: 11.266666666666667, + }, + { + x: 1593455400000, + y: 11.3, + }, + { + x: 1593457200000, + y: 11.333333333333334, + }, + { + x: 1593459000000, + y: 11.133333333333333, + }, + { + x: 1593460800000, + y: 10.933333333333334, + }, + { + x: 1593462600000, + y: 11.2, + }, + { + x: 1593464400000, + y: 11.166666666666666, + }, + { + x: 1593466200000, + y: 11.766666666666667, + }, + { + x: 1593468000000, + y: 11.433333333333334, + }, + { + x: 1593469800000, + y: 10.8, + }, + { + x: 1593471600000, + y: 11.266666666666667, + }, + { + x: 1593473400000, + y: 11.333333333333334, + }, + { + x: 1593475200000, + y: 11.133333333333333, + }, + { + x: 1593477000000, + y: 11.133333333333333, + }, + { + x: 1593478800000, + y: 10.9, + }, + { + x: 1593480600000, + y: 11.3, + }, + { + x: 1593482400000, + y: 12.166666666666666, + }, + { + x: 1593484200000, + y: 11.433333333333334, + }, + { + x: 1593486000000, + y: 12.133333333333333, + }, + { + x: 1593487800000, + y: 11.666666666666666, + }, + { + x: 1593489600000, + y: 11.533333333333333, + }, + { + x: 1593491400000, + y: 11.833333333333334, + }, + { + x: 1593493200000, + y: 11.766666666666667, + }, + { + x: 1593495000000, + y: 11.9, + }, + { + x: 1593496800000, + y: 11.433333333333334, + }, + { + x: 1593498600000, + y: 12, + }, + { + x: 1593500400000, + y: 12.1, + }, + { + x: 1593502200000, + y: 11.6, + }, + { + x: 1593504000000, + y: 12, + }, + { + x: 1593505800000, + y: 12.233333333333333, + }, + { + x: 1593507600000, + y: 11.633333333333333, + }, + { + x: 1593509400000, + y: 11.2, + }, + { + x: 1593511200000, + y: 11.766666666666667, + }, + { + x: 1593513000000, + y: 11.9, + }, + { + x: 1593514800000, + y: 11.366666666666667, + }, + { + x: 1593516600000, + y: 11.833333333333334, + }, + { + x: 1593518400000, + y: 11.5, + }, + { + x: 1593520200000, + y: 12, + }, + { + x: 1593522000000, + y: 12.033333333333333, + }, + { + x: 1593523800000, + y: 11.733333333333333, + }, + { + x: 1593525600000, + y: 11.566666666666666, + }, + { + x: 1593527400000, + y: 11.6, + }, + { + x: 1593529200000, + y: 11.333333333333334, + }, + { + x: 1593531000000, + y: 11.833333333333334, + }, + { + x: 1593532800000, + y: 11.233333333333333, + }, + { + x: 1593534600000, + y: 11.833333333333334, + }, + { + x: 1593536400000, + y: 11.266666666666667, + }, + { + x: 1593538200000, + y: 12, + }, + { + x: 1593540000000, + y: 11.633333333333333, + }, + { + x: 1593541800000, + y: 11.9, + }, + { + x: 1593543600000, + y: 11.966666666666667, + }, + { + x: 1593545400000, + y: 11.5, + }, + { + x: 1593547200000, + y: 11.466666666666667, + }, + { + x: 1593549000000, + y: 11.4, + }, + { + x: 1593550800000, + y: 11.833333333333334, + }, + ], + }, + 'nginx.error': { + label: 'nginx.error', + coordinates: [ + { + x: 1593295200000, + y: 9.266666666666667, + }, + { + x: 1593297000000, + y: 8.833333333333334, + }, + { + x: 1593298800000, + y: 9.033333333333333, + }, + { + x: 1593300600000, + y: 8.933333333333334, + }, + { + x: 1593302400000, + y: 8.9, + }, + { + x: 1593304200000, + y: 9.6, + }, + { + x: 1593306000000, + y: 9.066666666666666, + }, + { + x: 1593307800000, + y: 8.966666666666667, + }, + { + x: 1593309600000, + y: 8.933333333333334, + }, + { + x: 1593311400000, + y: 8.5, + }, + { + x: 1593313200000, + y: 8.133333333333333, + }, + { + x: 1593315000000, + y: 8.233333333333333, + }, + { + x: 1593316800000, + y: 8.433333333333334, + }, + { + x: 1593318600000, + y: 8.4, + }, + { + x: 1593320400000, + y: 9.266666666666667, + }, + { + x: 1593322200000, + y: 8.566666666666666, + }, + { + x: 1593324000000, + y: 8.966666666666667, + }, + { + x: 1593325800000, + y: 8.833333333333334, + }, + { + x: 1593327600000, + y: 7.5, + }, + { + x: 1593329400000, + y: 8.033333333333333, + }, + { + x: 1593331200000, + y: 8.633333333333333, + }, + { + x: 1593333000000, + y: 8.5, + }, + { + x: 1593334800000, + y: 8.866666666666667, + }, + { + x: 1593336600000, + y: 8.3, + }, + { + x: 1593338400000, + y: 8.966666666666667, + }, + { + x: 1593340200000, + y: 8.2, + }, + { + x: 1593342000000, + y: 7.566666666666666, + }, + { + x: 1593343800000, + y: 7.5, + }, + { + x: 1593345600000, + y: 7.933333333333334, + }, + { + x: 1593347400000, + y: 7.866666666666666, + }, + { + x: 1593349200000, + y: 7.566666666666666, + }, + { + x: 1593351000000, + y: 7.533333333333333, + }, + { + x: 1593352800000, + y: 8.866666666666667, + }, + { + x: 1593354600000, + y: 8.566666666666666, + }, + { + x: 1593356400000, + y: 8.233333333333333, + }, + { + x: 1593358200000, + y: 8.9, + }, + { + x: 1593360000000, + y: 8.533333333333333, + }, + { + x: 1593361800000, + y: 8.733333333333333, + }, + { + x: 1593363600000, + y: 9.333333333333334, + }, + { + x: 1593365400000, + y: 9.133333333333333, + }, + { + x: 1593367200000, + y: 9.166666666666666, + }, + { + x: 1593369000000, + y: 9.266666666666667, + }, + { + x: 1593370800000, + y: 8.966666666666667, + }, + { + x: 1593372600000, + y: 9.2, + }, + { + x: 1593374400000, + y: 9.433333333333334, + }, + { + x: 1593376200000, + y: 9.166666666666666, + }, + { + x: 1593378000000, + y: 9.266666666666667, + }, + { + x: 1593379800000, + y: 9.5, + }, + { + x: 1593381600000, + y: 9.333333333333334, + }, + { + x: 1593383400000, + y: 8.8, + }, + { + x: 1593385200000, + y: 8.733333333333333, + }, + { + x: 1593387000000, + y: 8.633333333333333, + }, + { + x: 1593388800000, + y: 8.9, + }, + { + x: 1593390600000, + y: 8.533333333333333, + }, + { + x: 1593392400000, + y: 9.3, + }, + { + x: 1593394200000, + y: 9.266666666666667, + }, + { + x: 1593396000000, + y: 8.966666666666667, + }, + { + x: 1593397800000, + y: 8.666666666666666, + }, + { + x: 1593399600000, + y: 9.166666666666666, + }, + { + x: 1593401400000, + y: 8.733333333333333, + }, + { + x: 1593403200000, + y: 8.866666666666667, + }, + { + x: 1593405000000, + y: 8.633333333333333, + }, + { + x: 1593406800000, + y: 8.8, + }, + { + x: 1593408600000, + y: 8.466666666666667, + }, + { + x: 1593410400000, + y: 8.966666666666667, + }, + { + x: 1593412200000, + y: 8.166666666666666, + }, + { + x: 1593414000000, + y: 8.7, + }, + { + x: 1593415800000, + y: 8.333333333333334, + }, + { + x: 1593417600000, + y: 8.666666666666666, + }, + { + x: 1593419400000, + y: 8.533333333333333, + }, + { + x: 1593421200000, + y: 8.233333333333333, + }, + { + x: 1593423000000, + y: 8.3, + }, + { + x: 1593424800000, + y: 7.7, + }, + { + x: 1593426600000, + y: 7.7, + }, + { + x: 1593428400000, + y: 6.133333333333334, + }, + { + x: 1593430200000, + y: 0.4666666666666667, + }, + { + x: 1593442800000, + y: 7.233333333333333, + }, + { + x: 1593444600000, + y: 8.333333333333334, + }, + { + x: 1593446400000, + y: 8.666666666666666, + }, + { + x: 1593448200000, + y: 8.466666666666667, + }, + { + x: 1593450000000, + y: 8.666666666666666, + }, + { + x: 1593451800000, + y: 8.5, + }, + { + x: 1593453600000, + y: 8.6, + }, + { + x: 1593455400000, + y: 8.5, + }, + { + x: 1593457200000, + y: 8.6, + }, + { + x: 1593459000000, + y: 8.866666666666667, + }, + { + x: 1593460800000, + y: 9.166666666666666, + }, + { + x: 1593462600000, + y: 8.4, + }, + { + x: 1593464400000, + y: 8.533333333333333, + }, + { + x: 1593466200000, + y: 8.066666666666666, + }, + { + x: 1593468000000, + y: 8.666666666666666, + }, + { + x: 1593469800000, + y: 8.966666666666667, + }, + { + x: 1593471600000, + y: 8.4, + }, + { + x: 1593473400000, + y: 8.833333333333334, + }, + { + x: 1593475200000, + y: 8.533333333333333, + }, + { + x: 1593477000000, + y: 8.066666666666666, + }, + { + x: 1593478800000, + y: 8.533333333333333, + }, + { + x: 1593480600000, + y: 8.633333333333333, + }, + { + x: 1593482400000, + y: 8.933333333333334, + }, + { + x: 1593484200000, + y: 8.833333333333334, + }, + { + x: 1593486000000, + y: 8.4, + }, + { + x: 1593487800000, + y: 8.633333333333333, + }, + { + x: 1593489600000, + y: 9.333333333333334, + }, + { + x: 1593491400000, + y: 9.366666666666667, + }, + { + x: 1593493200000, + y: 8.333333333333334, + }, + { + x: 1593495000000, + y: 9.266666666666667, + }, + { + x: 1593496800000, + y: 8.2, + }, + { + x: 1593498600000, + y: 8.4, + }, + { + x: 1593500400000, + y: 8.433333333333334, + }, + { + x: 1593502200000, + y: 7.633333333333334, + }, + { + x: 1593504000000, + y: 7.766666666666667, + }, + { + x: 1593505800000, + y: 8.4, + }, + { + x: 1593507600000, + y: 8.3, + }, + { + x: 1593509400000, + y: 8.833333333333334, + }, + { + x: 1593511200000, + y: 8.433333333333334, + }, + { + x: 1593513000000, + y: 8.766666666666667, + }, + { + x: 1593514800000, + y: 9.066666666666666, + }, + { + x: 1593516600000, + y: 8.4, + }, + { + x: 1593518400000, + y: 8.4, + }, + { + x: 1593520200000, + y: 8.8, + }, + { + x: 1593522000000, + y: 8.466666666666667, + }, + { + x: 1593523800000, + y: 8.633333333333333, + }, + { + x: 1593525600000, + y: 9.133333333333333, + }, + { + x: 1593527400000, + y: 8.7, + }, + { + x: 1593529200000, + y: 8.566666666666666, + }, + { + x: 1593531000000, + y: 9.033333333333333, + }, + { + x: 1593532800000, + y: 8.9, + }, + { + x: 1593534600000, + y: 8.7, + }, + { + x: 1593536400000, + y: 8.7, + }, + { + x: 1593538200000, + y: 8.8, + }, + { + x: 1593540000000, + y: 9.166666666666666, + }, + { + x: 1593541800000, + y: 9.033333333333333, + }, + { + x: 1593543600000, + y: 8.733333333333333, + }, + { + x: 1593545400000, + y: 9.2, + }, + { + x: 1593547200000, + y: 8.933333333333334, + }, + { + x: 1593549000000, + y: 9.2, + }, + { + x: 1593550800000, + y: 9.333333333333334, + }, + ], + }, + sample_web_logs: { + label: 'sample_web_logs', + coordinates: [ + { + x: 1593430200000, + y: 0.5666666666666667, + }, + { + x: 1593432000000, + y: 0.36666666666666664, + }, + { + x: 1593433800000, + y: 0.5666666666666667, + }, + { + x: 1593435600000, + y: 0.4666666666666667, + }, + { + x: 1593437400000, + y: 0.36666666666666664, + }, + { + x: 1593439200000, + y: 0.3, + }, + { + x: 1593441000000, + y: 0.13333333333333333, + }, + ], + }, + 'postgresql.log': { + label: 'postgresql.log', + coordinates: [ + { + x: 1593439200000, + y: 0.1, + }, + { + x: 1593441000000, + y: 0.1, + }, + ], + }, + }, +}; + +export const emptyResponse: LogsFetchDataResponse = { + title: 'Logs', + appLink: '/app/logs', + stats: {}, + series: {}, +}; diff --git a/x-pack/plugins/observability/public/pages/overview/mock/metrics.mock.ts b/x-pack/plugins/observability/public/pages/overview/mock/metrics.mock.ts new file mode 100644 index 0000000000000..37233b4f6342c --- /dev/null +++ b/x-pack/plugins/observability/public/pages/overview/mock/metrics.mock.ts @@ -0,0 +1,133 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { MetricsFetchDataResponse, FetchData } from '../../../typings'; + +export const fetchMetricsData: FetchData = () => { + return Promise.resolve(response); +}; + +const response: MetricsFetchDataResponse = { + title: 'Metrics', + appLink: '/app/apm', + stats: { + hosts: { value: 11, type: 'number' }, + cpu: { value: 0.8, type: 'percent' }, + memory: { value: 0.362, type: 'percent' }, + inboundTraffic: { value: 1024, type: 'bytesPerSecond' }, + outboundTraffic: { value: 1024, type: 'bytesPerSecond' }, + }, + series: { + outboundTraffic: { + coordinates: [ + { + x: 1589805437549, + y: 331514, + }, + { + x: 1590047357549, + y: 319208, + }, + { + x: 1590289277549, + y: 309648, + }, + { + x: 1590531197549, + y: 280568, + }, + { + x: 1590773117549, + y: 337180, + }, + { + x: 1591015037549, + y: 122468, + }, + { + x: 1591256957549, + y: 184164, + }, + { + x: 1591498877549, + y: 316323, + }, + { + x: 1591740797549, + y: 307351, + }, + { + x: 1591982717549, + y: 290262, + }, + ], + }, + inboundTraffic: { + coordinates: [ + { + x: 1589805437549, + y: 331514, + }, + { + x: 1590047357549, + y: 319208, + }, + { + x: 1590289277549, + y: 309648, + }, + { + x: 1590531197549, + y: 280568, + }, + { + x: 1590773117549, + y: 337180, + }, + { + x: 1591015037549, + y: 122468, + }, + { + x: 1591256957549, + y: 184164, + }, + { + x: 1591498877549, + y: 316323, + }, + { + x: 1591740797549, + y: 307351, + }, + { + x: 1591982717549, + y: 290262, + }, + ], + }, + }, +}; + +export const emptyResponse: MetricsFetchDataResponse = { + title: 'Metrics', + appLink: '/app/apm', + stats: { + hosts: { value: 0, type: 'number' }, + cpu: { value: 0, type: 'percent' }, + memory: { value: 0, type: 'percent' }, + inboundTraffic: { value: 0, type: 'bytesPerSecond' }, + outboundTraffic: { value: 0, type: 'bytesPerSecond' }, + }, + series: { + outboundTraffic: { + coordinates: [], + }, + inboundTraffic: { + coordinates: [], + }, + }, +}; diff --git a/x-pack/plugins/observability/public/pages/overview/mock/uptime.mock.ts b/x-pack/plugins/observability/public/pages/overview/mock/uptime.mock.ts new file mode 100644 index 0000000000000..ab5874f8bfcd4 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/overview/mock/uptime.mock.ts @@ -0,0 +1,1218 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { UptimeFetchDataResponse, FetchData } from '../../../typings'; + +export const fetchUptimeData: FetchData = () => { + return Promise.resolve(response); +}; + +const response: UptimeFetchDataResponse = { + title: 'Uptime', + appLink: '/app/uptime#/', + stats: { + monitors: { + type: 'number', + value: 26, + }, + up: { + type: 'number', + value: 20, + }, + down: { + type: 'number', + value: 6, + }, + }, + series: { + up: { + coordinates: [ + { + x: 1593295200000, + y: 1170, + }, + { + x: 1593297000000, + y: 1170, + }, + { + x: 1593298800000, + y: 1170, + }, + { + x: 1593300600000, + y: 1170, + }, + { + x: 1593302400000, + y: 1170, + }, + { + x: 1593304200000, + y: 1170, + }, + { + x: 1593306000000, + y: 1170, + }, + { + x: 1593307800000, + y: 1170, + }, + { + x: 1593309600000, + y: 1170, + }, + { + x: 1593311400000, + y: 1170, + }, + { + x: 1593313200000, + y: 1170, + }, + { + x: 1593315000000, + y: 1170, + }, + { + x: 1593316800000, + y: 1170, + }, + { + x: 1593318600000, + y: 1170, + }, + { + x: 1593320400000, + y: 1170, + }, + { + x: 1593322200000, + y: 1170, + }, + { + x: 1593324000000, + y: 1170, + }, + { + x: 1593325800000, + y: 1170, + }, + { + x: 1593327600000, + y: 1170, + }, + { + x: 1593329400000, + y: 1170, + }, + { + x: 1593331200000, + y: 1170, + }, + { + x: 1593333000000, + y: 1170, + }, + { + x: 1593334800000, + y: 1170, + }, + { + x: 1593336600000, + y: 1170, + }, + { + x: 1593338400000, + y: 1170, + }, + { + x: 1593340200000, + y: 1170, + }, + { + x: 1593342000000, + y: 1170, + }, + { + x: 1593343800000, + y: 1170, + }, + { + x: 1593345600000, + y: 1170, + }, + { + x: 1593347400000, + y: 1170, + }, + { + x: 1593349200000, + y: 1170, + }, + { + x: 1593351000000, + y: 1170, + }, + { + x: 1593352800000, + y: 1170, + }, + { + x: 1593354600000, + y: 1170, + }, + { + x: 1593356400000, + y: 1170, + }, + { + x: 1593358200000, + y: 1170, + }, + { + x: 1593360000000, + y: 1170, + }, + { + x: 1593361800000, + y: 1170, + }, + { + x: 1593363600000, + y: 1170, + }, + { + x: 1593365400000, + y: 1170, + }, + { + x: 1593367200000, + y: 1170, + }, + { + x: 1593369000000, + y: 1170, + }, + { + x: 1593370800000, + y: 1170, + }, + { + x: 1593372600000, + y: 1170, + }, + { + x: 1593374400000, + y: 1169, + }, + { + x: 1593376200000, + y: 1170, + }, + { + x: 1593378000000, + y: 1170, + }, + { + x: 1593379800000, + y: 1170, + }, + { + x: 1593381600000, + y: 1170, + }, + { + x: 1593383400000, + y: 1170, + }, + { + x: 1593385200000, + y: 1170, + }, + { + x: 1593387000000, + y: 1170, + }, + { + x: 1593388800000, + y: 1170, + }, + { + x: 1593390600000, + y: 1170, + }, + { + x: 1593392400000, + y: 1170, + }, + { + x: 1593394200000, + y: 1239, + }, + { + x: 1593396000000, + y: 1170, + }, + { + x: 1593397800000, + y: 1170, + }, + { + x: 1593399600000, + y: 1170, + }, + { + x: 1593401400000, + y: 1170, + }, + { + x: 1593403200000, + y: 1170, + }, + { + x: 1593405000000, + y: 1170, + }, + { + x: 1593406800000, + y: 1170, + }, + { + x: 1593408600000, + y: 1170, + }, + { + x: 1593410400000, + y: 1170, + }, + { + x: 1593412200000, + y: 1170, + }, + { + x: 1593414000000, + y: 1170, + }, + { + x: 1593415800000, + y: 1170, + }, + { + x: 1593417600000, + y: 1170, + }, + { + x: 1593419400000, + y: 1170, + }, + { + x: 1593421200000, + y: 1170, + }, + { + x: 1593423000000, + y: 1170, + }, + { + x: 1593424800000, + y: 1166, + }, + { + x: 1593426600000, + y: 1206, + }, + { + x: 1593428400000, + y: 1143, + }, + { + x: 1593430200000, + y: 1170, + }, + { + x: 1593432000000, + y: 1170, + }, + { + x: 1593433800000, + y: 1170, + }, + { + x: 1593435600000, + y: 1170, + }, + { + x: 1593437400000, + y: 1170, + }, + { + x: 1593439200000, + y: 1170, + }, + { + x: 1593441000000, + y: 1170, + }, + { + x: 1593442800000, + y: 1170, + }, + { + x: 1593444600000, + y: 1170, + }, + { + x: 1593446400000, + y: 1170, + }, + { + x: 1593448200000, + y: 1170, + }, + { + x: 1593450000000, + y: 1170, + }, + { + x: 1593451800000, + y: 1170, + }, + { + x: 1593453600000, + y: 1170, + }, + { + x: 1593455400000, + y: 1170, + }, + { + x: 1593457200000, + y: 1170, + }, + { + x: 1593459000000, + y: 1170, + }, + { + x: 1593460800000, + y: 1170, + }, + { + x: 1593462600000, + y: 1170, + }, + { + x: 1593464400000, + y: 1170, + }, + { + x: 1593466200000, + y: 1170, + }, + { + x: 1593468000000, + y: 1170, + }, + { + x: 1593469800000, + y: 1170, + }, + { + x: 1593471600000, + y: 1170, + }, + { + x: 1593473400000, + y: 1170, + }, + { + x: 1593475200000, + y: 1170, + }, + { + x: 1593477000000, + y: 1170, + }, + { + x: 1593478800000, + y: 1170, + }, + { + x: 1593480600000, + y: 1201, + }, + { + x: 1593482400000, + y: 1139, + }, + { + x: 1593484200000, + y: 1140, + }, + { + x: 1593486000000, + y: 1140, + }, + { + x: 1593487800000, + y: 1140, + }, + { + x: 1593489600000, + y: 1140, + }, + { + x: 1593491400000, + y: 1140, + }, + { + x: 1593493200000, + y: 1140, + }, + { + x: 1593495000000, + y: 1140, + }, + { + x: 1593496800000, + y: 1140, + }, + { + x: 1593498600000, + y: 1140, + }, + { + x: 1593500400000, + y: 1140, + }, + { + x: 1593502200000, + y: 1140, + }, + { + x: 1593504000000, + y: 1140, + }, + { + x: 1593505800000, + y: 1140, + }, + { + x: 1593507600000, + y: 1140, + }, + { + x: 1593509400000, + y: 1140, + }, + { + x: 1593511200000, + y: 1140, + }, + { + x: 1593513000000, + y: 1140, + }, + { + x: 1593514800000, + y: 1140, + }, + { + x: 1593516600000, + y: 1140, + }, + { + x: 1593518400000, + y: 1140, + }, + { + x: 1593520200000, + y: 1140, + }, + { + x: 1593522000000, + y: 1140, + }, + { + x: 1593523800000, + y: 1140, + }, + { + x: 1593525600000, + y: 1140, + }, + { + x: 1593527400000, + y: 1140, + }, + { + x: 1593529200000, + y: 1140, + }, + { + x: 1593531000000, + y: 1140, + }, + { + x: 1593532800000, + y: 1140, + }, + { + x: 1593534600000, + y: 1140, + }, + { + x: 1593536400000, + y: 1140, + }, + { + x: 1593538200000, + y: 1140, + }, + { + x: 1593540000000, + y: 1140, + }, + { + x: 1593541800000, + y: 1139, + }, + { + x: 1593543600000, + y: 1140, + }, + { + x: 1593545400000, + y: 1140, + }, + { + x: 1593547200000, + y: 1140, + }, + { + x: 1593549000000, + y: 1140, + }, + { + x: 1593550800000, + y: 1140, + }, + { + x: 1593552600000, + y: 1140, + }, + ], + }, + down: { + coordinates: [ + { + x: 1593295200000, + y: 234, + }, + { + x: 1593297000000, + y: 234, + }, + { + x: 1593298800000, + y: 234, + }, + { + x: 1593300600000, + y: 234, + }, + { + x: 1593302400000, + y: 234, + }, + { + x: 1593304200000, + y: 234, + }, + { + x: 1593306000000, + y: 234, + }, + { + x: 1593307800000, + y: 234, + }, + { + x: 1593309600000, + y: 234, + }, + { + x: 1593311400000, + y: 234, + }, + { + x: 1593313200000, + y: 234, + }, + { + x: 1593315000000, + y: 234, + }, + { + x: 1593316800000, + y: 234, + }, + { + x: 1593318600000, + y: 234, + }, + { + x: 1593320400000, + y: 234, + }, + { + x: 1593322200000, + y: 234, + }, + { + x: 1593324000000, + y: 234, + }, + { + x: 1593325800000, + y: 234, + }, + { + x: 1593327600000, + y: 234, + }, + { + x: 1593329400000, + y: 234, + }, + { + x: 1593331200000, + y: 234, + }, + { + x: 1593333000000, + y: 234, + }, + { + x: 1593334800000, + y: 234, + }, + { + x: 1593336600000, + y: 234, + }, + { + x: 1593338400000, + y: 234, + }, + { + x: 1593340200000, + y: 234, + }, + { + x: 1593342000000, + y: 234, + }, + { + x: 1593343800000, + y: 234, + }, + { + x: 1593345600000, + y: 234, + }, + { + x: 1593347400000, + y: 234, + }, + { + x: 1593349200000, + y: 234, + }, + { + x: 1593351000000, + y: 234, + }, + { + x: 1593352800000, + y: 234, + }, + { + x: 1593354600000, + y: 234, + }, + { + x: 1593356400000, + y: 234, + }, + { + x: 1593358200000, + y: 234, + }, + { + x: 1593360000000, + y: 234, + }, + { + x: 1593361800000, + y: 234, + }, + { + x: 1593363600000, + y: 234, + }, + { + x: 1593365400000, + y: 234, + }, + { + x: 1593367200000, + y: 234, + }, + { + x: 1593369000000, + y: 234, + }, + { + x: 1593370800000, + y: 234, + }, + { + x: 1593372600000, + y: 234, + }, + { + x: 1593374400000, + y: 235, + }, + { + x: 1593376200000, + y: 234, + }, + { + x: 1593378000000, + y: 234, + }, + { + x: 1593379800000, + y: 234, + }, + { + x: 1593381600000, + y: 234, + }, + { + x: 1593383400000, + y: 234, + }, + { + x: 1593385200000, + y: 234, + }, + { + x: 1593387000000, + y: 234, + }, + { + x: 1593388800000, + y: 234, + }, + { + x: 1593390600000, + y: 234, + }, + { + x: 1593392400000, + y: 234, + }, + { + x: 1593394200000, + y: 246, + }, + { + x: 1593396000000, + y: 234, + }, + { + x: 1593397800000, + y: 234, + }, + { + x: 1593399600000, + y: 234, + }, + { + x: 1593401400000, + y: 234, + }, + { + x: 1593403200000, + y: 234, + }, + { + x: 1593405000000, + y: 234, + }, + { + x: 1593406800000, + y: 234, + }, + { + x: 1593408600000, + y: 234, + }, + { + x: 1593410400000, + y: 234, + }, + { + x: 1593412200000, + y: 234, + }, + { + x: 1593414000000, + y: 234, + }, + { + x: 1593415800000, + y: 234, + }, + { + x: 1593417600000, + y: 234, + }, + { + x: 1593419400000, + y: 234, + }, + { + x: 1593421200000, + y: 234, + }, + { + x: 1593423000000, + y: 234, + }, + { + x: 1593424800000, + y: 240, + }, + { + x: 1593426600000, + y: 254, + }, + { + x: 1593428400000, + y: 231, + }, + { + x: 1593430200000, + y: 234, + }, + { + x: 1593432000000, + y: 234, + }, + { + x: 1593433800000, + y: 234, + }, + { + x: 1593435600000, + y: 234, + }, + { + x: 1593437400000, + y: 234, + }, + { + x: 1593439200000, + y: 234, + }, + { + x: 1593441000000, + y: 234, + }, + { + x: 1593442800000, + y: 234, + }, + { + x: 1593444600000, + y: 234, + }, + { + x: 1593446400000, + y: 234, + }, + { + x: 1593448200000, + y: 234, + }, + { + x: 1593450000000, + y: 234, + }, + { + x: 1593451800000, + y: 234, + }, + { + x: 1593453600000, + y: 234, + }, + { + x: 1593455400000, + y: 234, + }, + { + x: 1593457200000, + y: 234, + }, + { + x: 1593459000000, + y: 234, + }, + { + x: 1593460800000, + y: 234, + }, + { + x: 1593462600000, + y: 234, + }, + { + x: 1593464400000, + y: 234, + }, + { + x: 1593466200000, + y: 234, + }, + { + x: 1593468000000, + y: 234, + }, + { + x: 1593469800000, + y: 234, + }, + { + x: 1593471600000, + y: 234, + }, + { + x: 1593473400000, + y: 234, + }, + { + x: 1593475200000, + y: 234, + }, + { + x: 1593477000000, + y: 234, + }, + { + x: 1593478800000, + y: 234, + }, + { + x: 1593480600000, + y: 254, + }, + { + x: 1593482400000, + y: 265, + }, + { + x: 1593484200000, + y: 264, + }, + { + x: 1593486000000, + y: 264, + }, + { + x: 1593487800000, + y: 264, + }, + { + x: 1593489600000, + y: 264, + }, + { + x: 1593491400000, + y: 264, + }, + { + x: 1593493200000, + y: 264, + }, + { + x: 1593495000000, + y: 264, + }, + { + x: 1593496800000, + y: 264, + }, + { + x: 1593498600000, + y: 264, + }, + { + x: 1593500400000, + y: 264, + }, + { + x: 1593502200000, + y: 264, + }, + { + x: 1593504000000, + y: 264, + }, + { + x: 1593505800000, + y: 264, + }, + { + x: 1593507600000, + y: 264, + }, + { + x: 1593509400000, + y: 264, + }, + { + x: 1593511200000, + y: 264, + }, + { + x: 1593513000000, + y: 264, + }, + { + x: 1593514800000, + y: 264, + }, + { + x: 1593516600000, + y: 264, + }, + { + x: 1593518400000, + y: 264, + }, + { + x: 1593520200000, + y: 264, + }, + { + x: 1593522000000, + y: 264, + }, + { + x: 1593523800000, + y: 264, + }, + { + x: 1593525600000, + y: 264, + }, + { + x: 1593527400000, + y: 264, + }, + { + x: 1593529200000, + y: 264, + }, + { + x: 1593531000000, + y: 264, + }, + { + x: 1593532800000, + y: 264, + }, + { + x: 1593534600000, + y: 264, + }, + { + x: 1593536400000, + y: 264, + }, + { + x: 1593538200000, + y: 264, + }, + { + x: 1593540000000, + y: 264, + }, + { + x: 1593541800000, + y: 265, + }, + { + x: 1593543600000, + y: 264, + }, + { + x: 1593545400000, + y: 264, + }, + { + x: 1593547200000, + y: 264, + }, + { + x: 1593549000000, + y: 264, + }, + { + x: 1593550800000, + y: 264, + }, + { + x: 1593552600000, + y: 264, + }, + ], + }, + }, +}; + +export const emptyResponse: UptimeFetchDataResponse = { + title: 'Uptime', + appLink: '/app/uptime#/', + stats: { + monitors: { + type: 'number', + value: 0, + }, + up: { + type: 'number', + value: 0, + }, + down: { + type: 'number', + value: 0, + }, + }, + series: { + up: { + coordinates: [], + }, + down: { + coordinates: [], + }, + }, +}; diff --git a/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx b/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx new file mode 100644 index 0000000000000..b88614b22e81a --- /dev/null +++ b/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx @@ -0,0 +1,534 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { storiesOf } from '@storybook/react'; +import { AppMountContext } from 'kibana/public'; +import React from 'react'; +import { MemoryRouter } from 'react-router-dom'; +import { UI_SETTINGS } from '../../../../../../src/plugins/data/public'; +import { PluginContext } from '../../context/plugin_context'; +import { registerDataHandler, unregisterDataHandler } from '../../data_handler'; +import { emptyResponse as emptyAPMResponse, fetchApmData } from './mock/apm.mock'; +import { fetchLogsData, emptyResponse as emptyLogsResponse } from './mock/logs.mock'; +import { fetchMetricsData, emptyResponse as emptyMetricsResponse } from './mock/metrics.mock'; +import { fetchUptimeData, emptyResponse as emptyUptimeResponse } from './mock/uptime.mock'; +import { EuiThemeProvider } from '../../typings'; +import { OverviewPage } from './'; +import { alertsFetchData } from './mock/alerts.mock'; + +const core = { + http: { + basePath: { + prepend: (link) => `http://localhost:5601${link}`, + }, + }, + uiSettings: { + get: (key: string) => { + const euiSettings = { + [UI_SETTINGS.TIMEPICKER_TIME_DEFAULTS]: { + from: 'now-15m', + to: 'now', + }, + [UI_SETTINGS.TIMEPICKER_REFRESH_INTERVAL_DEFAULTS]: { + pause: true, + value: 1000, + }, + [UI_SETTINGS.TIMEPICKER_QUICK_RANGES]: [ + { + from: 'now/d', + to: 'now/d', + display: 'Today', + }, + { + from: 'now/w', + to: 'now/w', + display: 'This week', + }, + { + from: 'now-15m', + to: 'now', + display: 'Last 15 minutes', + }, + { + from: 'now-30m', + to: 'now', + display: 'Last 30 minutes', + }, + { + from: 'now-1h', + to: 'now', + display: 'Last 1 hour', + }, + { + from: 'now-24h', + to: 'now', + display: 'Last 24 hours', + }, + { + from: 'now-7d', + to: 'now', + display: 'Last 7 days', + }, + { + from: 'now-30d', + to: 'now', + display: 'Last 30 days', + }, + { + from: 'now-90d', + to: 'now', + display: 'Last 90 days', + }, + { + from: 'now-1y', + to: 'now', + display: 'Last 1 year', + }, + ], + }; + // @ts-expect-error + return euiSettings[key]; + }, + }, +} as AppMountContext['core']; + +const coreWithAlerts = ({ + ...core, + http: { + ...core.http, + get: alertsFetchData, + }, +} as unknown) as AppMountContext['core']; + +function unregisterAll() { + unregisterDataHandler({ appName: 'apm' }); + unregisterDataHandler({ appName: 'infra_logs' }); + unregisterDataHandler({ appName: 'infra_metrics' }); + unregisterDataHandler({ appName: 'uptime' }); +} + +storiesOf('app/Overview', module) + .addDecorator((storyFn) => ( + + + {storyFn()}) + + + )) + .add('Empty state', () => { + unregisterAll(); + registerDataHandler({ + appName: 'apm', + fetchData: fetchApmData, + hasData: async () => false, + }); + registerDataHandler({ + appName: 'infra_logs', + fetchData: fetchLogsData, + hasData: async () => false, + }); + registerDataHandler({ + appName: 'infra_metrics', + fetchData: fetchMetricsData, + hasData: async () => false, + }); + registerDataHandler({ + appName: 'uptime', + fetchData: fetchUptimeData, + hasData: async () => false, + }); + + return ; + }); + +storiesOf('app/Overview', module) + .addDecorator((storyFn) => ( + + + {storyFn()}) + + + )) + .add('single panel', () => { + unregisterAll(); + registerDataHandler({ + appName: 'infra_logs', + fetchData: fetchLogsData, + hasData: async () => true, + }); + return ( + + ); + }); + +storiesOf('app/Overview', module) + .addDecorator((storyFn) => ( + + + {storyFn()}) + + + )) + .add('logs and metrics', () => { + unregisterAll(); + registerDataHandler({ + appName: 'infra_logs', + fetchData: fetchLogsData, + hasData: async () => true, + }); + registerDataHandler({ + appName: 'infra_metrics', + fetchData: fetchMetricsData, + hasData: async () => true, + }); + return ( + + ); + }); + +storiesOf('app/Overview', module) + .addDecorator((storyFn) => ( + + + {storyFn()}) + + + )) + .add('logs, metrics and alerts', () => { + unregisterAll(); + registerDataHandler({ + appName: 'infra_logs', + fetchData: fetchLogsData, + hasData: async () => true, + }); + registerDataHandler({ + appName: 'infra_metrics', + fetchData: fetchMetricsData, + hasData: async () => true, + }); + return ( + + ); + }); + +storiesOf('app/Overview', module) + .addDecorator((storyFn) => ( + + + {storyFn()}) + + + )) + .add('logs, metrics, APM and alerts', () => { + unregisterAll(); + registerDataHandler({ + appName: 'infra_logs', + fetchData: fetchLogsData, + hasData: async () => true, + }); + registerDataHandler({ + appName: 'infra_metrics', + fetchData: fetchMetricsData, + hasData: async () => true, + }); + registerDataHandler({ + appName: 'apm', + fetchData: fetchApmData, + hasData: async () => true, + }); + return ( + + ); + }); + +storiesOf('app/Overview', module) + .addDecorator((storyFn) => ( + + + {storyFn()}) + + + )) + .add('logs, metrics, APM and Uptime', () => { + unregisterAll(); + registerDataHandler({ + appName: 'apm', + fetchData: fetchApmData, + hasData: async () => true, + }); + registerDataHandler({ + appName: 'infra_logs', + fetchData: fetchLogsData, + hasData: async () => true, + }); + registerDataHandler({ + appName: 'infra_metrics', + fetchData: fetchMetricsData, + hasData: async () => true, + }); + registerDataHandler({ + appName: 'uptime', + fetchData: fetchUptimeData, + hasData: async () => true, + }); + return ( + + ); + }); + +storiesOf('app/Overview', module) + .addDecorator((storyFn) => ( + + + {storyFn()}) + + + )) + .add('logs, metrics, APM, Uptime and Alerts', () => { + unregisterAll(); + registerDataHandler({ + appName: 'apm', + fetchData: fetchApmData, + hasData: async () => true, + }); + registerDataHandler({ + appName: 'infra_logs', + fetchData: fetchLogsData, + hasData: async () => true, + }); + registerDataHandler({ + appName: 'infra_metrics', + fetchData: fetchMetricsData, + hasData: async () => true, + }); + registerDataHandler({ + appName: 'uptime', + fetchData: fetchUptimeData, + hasData: async () => true, + }); + return ( + + ); + }); + +storiesOf('app/Overview', module) + .addDecorator((storyFn) => ( + + + {storyFn()}) + + + )) + .add('no data', () => { + unregisterAll(); + registerDataHandler({ + appName: 'apm', + fetchData: async () => emptyAPMResponse, + hasData: async () => true, + }); + registerDataHandler({ + appName: 'infra_logs', + fetchData: async () => emptyLogsResponse, + hasData: async () => true, + }); + registerDataHandler({ + appName: 'infra_metrics', + fetchData: async () => emptyMetricsResponse, + hasData: async () => true, + }); + registerDataHandler({ + appName: 'uptime', + fetchData: async () => emptyUptimeResponse, + hasData: async () => true, + }); + return ( + + ); + }); + +const coreAlertsThrowsError = ({ + ...core, + http: { + ...core.http, + get: async () => { + throw new Error('Error fetching Alerts data'); + }, + }, +} as unknown) as AppMountContext['core']; +storiesOf('app/Overview', module) + .addDecorator((storyFn) => ( + + + {storyFn()}) + + + )) + .add('fetch data with error', () => { + unregisterAll(); + registerDataHandler({ + appName: 'apm', + fetchData: async () => { + throw new Error('Error fetching APM data'); + }, + hasData: async () => true, + }); + registerDataHandler({ + appName: 'infra_logs', + fetchData: async () => { + throw new Error('Error fetching Logs data'); + }, + hasData: async () => true, + }); + registerDataHandler({ + appName: 'infra_metrics', + fetchData: async () => { + throw new Error('Error fetching Metric data'); + }, + hasData: async () => true, + }); + registerDataHandler({ + appName: 'uptime', + fetchData: async () => { + throw new Error('Error fetching Uptime data'); + }, + hasData: async () => true, + }); + return ( + + ); + }); + +storiesOf('app/Overview', module) + .addDecorator((storyFn) => ( + + + {storyFn()}) + + + )) + .add('hasData with error and alerts', () => { + unregisterAll(); + registerDataHandler({ + appName: 'apm', + fetchData: fetchApmData, + // @ts-ignore thows an error instead + hasData: async () => { + new Error('Error has data'); + }, + }); + registerDataHandler({ + appName: 'infra_logs', + fetchData: fetchLogsData, + // @ts-ignore thows an error instead + hasData: async () => { + new Error('Error has data'); + }, + }); + registerDataHandler({ + appName: 'infra_metrics', + fetchData: fetchMetricsData, + // @ts-ignore thows an error instead + hasData: async () => { + new Error('Error has data'); + }, + }); + registerDataHandler({ + appName: 'uptime', + fetchData: fetchUptimeData, + // @ts-ignore thows an error instead + hasData: async () => { + new Error('Error has data'); + }, + }); + return ( + + ); + }); +storiesOf('app/Overview', module) + .addDecorator((storyFn) => ( + + + {storyFn()}) + + + )) + .add('hasData with error', () => { + unregisterAll(); + registerDataHandler({ + appName: 'apm', + fetchData: fetchApmData, + // @ts-ignore thows an error instead + hasData: async () => { + new Error('Error has data'); + }, + }); + registerDataHandler({ + appName: 'infra_logs', + fetchData: fetchLogsData, + // @ts-ignore thows an error instead + hasData: async () => { + new Error('Error has data'); + }, + }); + registerDataHandler({ + appName: 'infra_metrics', + fetchData: fetchMetricsData, + // @ts-ignore thows an error instead + hasData: async () => { + new Error('Error has data'); + }, + }); + registerDataHandler({ + appName: 'uptime', + fetchData: fetchUptimeData, + // @ts-ignore thows an error instead + hasData: async () => { + new Error('Error has data'); + }, + }); + return ( + + ); + }); diff --git a/x-pack/plugins/observability/public/routes/index.tsx b/x-pack/plugins/observability/public/routes/index.tsx new file mode 100644 index 0000000000000..10f9b4dc42723 --- /dev/null +++ b/x-pack/plugins/observability/public/routes/index.tsx @@ -0,0 +1,71 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; +import * as t from 'io-ts'; +import { i18n } from '@kbn/i18n'; +import { HomePage } from '../pages/home'; +import { LandingPage } from '../pages/landing'; +import { OverviewPage } from '../pages/overview'; +import { jsonRt } from './json_rt'; + +export type RouteParams = DecodeParams; + +type DecodeParams = { + [key in keyof TParams]: TParams[key] extends t.Any ? t.TypeOf : never; +}; + +export interface Params { + query?: t.HasProps; + path?: t.HasProps; +} +export const routes = { + '/': { + handler: () => { + return ; + }, + params: {}, + breadcrumb: [ + { + text: i18n.translate('xpack.observability.home.breadcrumb', { + defaultMessage: 'Overview', + }), + }, + ], + }, + '/landing': { + handler: () => { + return ; + }, + params: {}, + breadcrumb: [ + { + text: i18n.translate('xpack.observability.landing.breadcrumb', { + defaultMessage: 'Getting started', + }), + }, + ], + }, + '/overview': { + handler: ({ query }: any) => { + return ; + }, + params: { + query: t.partial({ + rangeFrom: t.string, + rangeTo: t.string, + refreshPaused: jsonRt.pipe(t.boolean), + refreshInterval: jsonRt.pipe(t.number), + }), + }, + breadcrumb: [ + { + text: i18n.translate('xpack.observability.overview.breadcrumb', { + defaultMessage: 'Overview', + }), + }, + ], + }, +}; diff --git a/x-pack/plugins/observability/public/routes/json_rt.ts b/x-pack/plugins/observability/public/routes/json_rt.ts new file mode 100644 index 0000000000000..fcc73547a686b --- /dev/null +++ b/x-pack/plugins/observability/public/routes/json_rt.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import * as t from 'io-ts'; +import { either } from 'fp-ts/lib/Either'; + +export const jsonRt = new t.Type( + 'JSON', + t.any.is, + (input, context) => + either.chain(t.string.validate(input, context), (str) => { + try { + return t.success(JSON.parse(str)); + } catch (e) { + return t.failure(input, context); + } + }), + (a) => JSON.stringify(a) +); diff --git a/x-pack/plugins/observability/public/services/get_observability_alerts.test.ts b/x-pack/plugins/observability/public/services/get_observability_alerts.test.ts new file mode 100644 index 0000000000000..dd3f476fe7d53 --- /dev/null +++ b/x-pack/plugins/observability/public/services/get_observability_alerts.test.ts @@ -0,0 +1,91 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { AppMountContext } from 'kibana/public'; +import { getObservabilityAlerts } from './get_observability_alerts'; + +describe('getObservabilityAlerts', () => { + it('Returns empty array when api throws exception', async () => { + const core = ({ + http: { + get: async () => { + throw new Error('Boom'); + }, + }, + } as unknown) as AppMountContext['core']; + + const alerts = await getObservabilityAlerts({ core }); + expect(alerts).toEqual([]); + }); + + it('Returns empty array when api return undefined', async () => { + const core = ({ + http: { + get: async () => { + return { + data: undefined, + }; + }, + }, + } as unknown) as AppMountContext['core']; + + const alerts = await getObservabilityAlerts({ core }); + expect(alerts).toEqual([]); + }); + + it('Shows alerts from Observability', async () => { + const core = ({ + http: { + get: async () => { + return { + data: [ + { + id: 1, + consumer: 'siem', + }, + { + id: 2, + consumer: 'apm', + }, + { + id: 3, + consumer: 'uptime', + }, + { + id: 4, + consumer: 'logs', + }, + { + id: 5, + consumer: 'metrics', + }, + ], + }; + }, + }, + } as unknown) as AppMountContext['core']; + + const alerts = await getObservabilityAlerts({ core }); + expect(alerts).toEqual([ + { + id: 2, + consumer: 'apm', + }, + { + id: 3, + consumer: 'uptime', + }, + { + id: 4, + consumer: 'logs', + }, + { + id: 5, + consumer: 'metrics', + }, + ]); + }); +}); diff --git a/x-pack/plugins/observability/public/services/get_observability_alerts.ts b/x-pack/plugins/observability/public/services/get_observability_alerts.ts new file mode 100644 index 0000000000000..1bbabbad2834a --- /dev/null +++ b/x-pack/plugins/observability/public/services/get_observability_alerts.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { AppMountContext } from 'kibana/public'; +import { Alert } from '../../../alerts/common'; + +export async function getObservabilityAlerts({ core }: { core: AppMountContext['core'] }) { + try { + const { data = [] }: { data: Alert[] } = await core.http.get('/api/alerts/_find', { + query: { + page: 1, + per_page: 20, + }, + }); + + return data.filter(({ consumer }) => { + return ( + consumer === 'apm' || consumer === 'uptime' || consumer === 'logs' || consumer === 'metrics' + ); + }); + } catch (e) { + return []; + } +} diff --git a/x-pack/plugins/observability/public/typings/fetch_overview_data/index.ts b/x-pack/plugins/observability/public/typings/fetch_overview_data/index.ts index e57dfebb36419..2dafd70896cc5 100644 --- a/x-pack/plugins/observability/public/typings/fetch_overview_data/index.ts +++ b/x-pack/plugins/observability/public/typings/fetch_overview_data/index.ts @@ -6,11 +6,9 @@ import { ObservabilityApp } from '../../../typings/common'; -interface Stat { +export interface Stat { type: 'number' | 'percent' | 'bytesPerSecond'; - label: string; value: number; - color?: string; } export interface Coordinates { @@ -18,10 +16,8 @@ export interface Coordinates { y?: number; } -interface Series { - label: string; +export interface Series { coordinates: Coordinates[]; - color?: string; } export interface FetchDataParams { @@ -50,8 +46,8 @@ export interface FetchDataResponse { } export interface LogsFetchDataResponse extends FetchDataResponse { - stats: Record; - series: Record; + stats: Record; + series: Record; } export interface MetricsFetchDataResponse extends FetchDataResponse { diff --git a/x-pack/plugins/observability/public/typings/section/index.ts b/x-pack/plugins/observability/public/typings/section/index.ts new file mode 100644 index 0000000000000..f336b6b981687 --- /dev/null +++ b/x-pack/plugins/observability/public/typings/section/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ObservabilityApp } from '../../../typings/common'; + +export interface ISection { + id: ObservabilityApp | 'alert'; + title: string; + icon: string; + description: string; + href?: string; + linkTitle?: string; + target?: '_blank'; +} diff --git a/x-pack/plugins/observability/public/utils/date.ts b/x-pack/plugins/observability/public/utils/date.ts new file mode 100644 index 0000000000000..fc0bbdae20cb9 --- /dev/null +++ b/x-pack/plugins/observability/public/utils/date.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import datemath from '@elastic/datemath'; + +export function getParsedDate(range?: string, opts = {}) { + if (range) { + const parsed = datemath.parse(range, opts); + if (parsed) { + return parsed.toISOString(); + } + } +} diff --git a/x-pack/plugins/observability/public/utils/format_stat_value.test.ts b/x-pack/plugins/observability/public/utils/format_stat_value.test.ts new file mode 100644 index 0000000000000..6643692e02dd4 --- /dev/null +++ b/x-pack/plugins/observability/public/utils/format_stat_value.test.ts @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { formatStatValue } from './format_stat_value'; +import { Stat } from '../typings'; + +describe('formatStatValue', () => { + it('formats value as number', () => { + const stat = { + type: 'number', + label: 'numeral stat', + value: 1000, + } as Stat; + expect(formatStatValue(stat)).toEqual('1k'); + }); + it('formats value as bytes', () => { + expect( + formatStatValue({ + type: 'bytesPerSecond', + label: 'bytes stat', + value: 1, + } as Stat) + ).toEqual('1.0B/s'); + expect( + formatStatValue({ + type: 'bytesPerSecond', + label: 'bytes stat', + value: 1048576, + } as Stat) + ).toEqual('1.0MB/s'); + expect( + formatStatValue({ + type: 'bytesPerSecond', + label: 'bytes stat', + value: 1073741824, + } as Stat) + ).toEqual('1.0GB/s'); + }); + it('formats value as percent', () => { + const stat = { + type: 'percent', + label: 'percent stat', + value: 0.841, + } as Stat; + expect(formatStatValue(stat)).toEqual('84.1%'); + }); +}); diff --git a/x-pack/plugins/observability/public/utils/format_stat_value.ts b/x-pack/plugins/observability/public/utils/format_stat_value.ts new file mode 100644 index 0000000000000..c200d94d5699e --- /dev/null +++ b/x-pack/plugins/observability/public/utils/format_stat_value.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import numeral from '@elastic/numeral'; +import { Stat } from '../typings'; + +export function formatStatValue(stat: Stat) { + const { value, type } = stat; + switch (type) { + case 'bytesPerSecond': + return `${numeral(value).format('0.0b')}/s`; + case 'number': + return numeral(value).format('0a'); + case 'percent': + return numeral(value).format('0.0%'); + } +} diff --git a/x-pack/plugins/observability/public/utils/get_bucket_size/calculate_auto.js b/x-pack/plugins/observability/public/utils/get_bucket_size/calculate_auto.js new file mode 100644 index 0000000000000..1608003641596 --- /dev/null +++ b/x-pack/plugins/observability/public/utils/get_bucket_size/calculate_auto.js @@ -0,0 +1,77 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import moment from 'moment'; +const d = moment.duration; + +const roundingRules = [ + [d(500, 'ms'), d(100, 'ms')], + [d(5, 'second'), d(1, 'second')], + [d(7.5, 'second'), d(5, 'second')], + [d(15, 'second'), d(10, 'second')], + [d(45, 'second'), d(30, 'second')], + [d(3, 'minute'), d(1, 'minute')], + [d(9, 'minute'), d(5, 'minute')], + [d(20, 'minute'), d(10, 'minute')], + [d(45, 'minute'), d(30, 'minute')], + [d(2, 'hour'), d(1, 'hour')], + [d(6, 'hour'), d(3, 'hour')], + [d(24, 'hour'), d(12, 'hour')], + [d(1, 'week'), d(1, 'd')], + [d(3, 'week'), d(1, 'week')], + [d(1, 'year'), d(1, 'month')], + [Infinity, d(1, 'year')], +]; + +const revRoundingRules = roundingRules.slice(0).reverse(); + +function find(rules, check, last) { + function pick(buckets, duration) { + const target = duration / buckets; + let lastResp = null; + + for (let i = 0; i < rules.length; i++) { + const rule = rules[i]; + const resp = check(rule[0], rule[1], target); + + if (resp == null) { + if (!last) continue; + if (lastResp) return lastResp; + break; + } + + if (!last) return resp; + lastResp = resp; + } + + // fallback to just a number of milliseconds, ensure ms is >= 1 + const ms = Math.max(Math.floor(target), 1); + return moment.duration(ms, 'ms'); + } + + return (buckets, duration) => { + const interval = pick(buckets, duration); + if (interval) return moment.duration(interval._data); + }; +} + +export const calculateAuto = { + near: find( + revRoundingRules, + function near(bound, interval, target) { + if (bound > target) return interval; + }, + true + ), + + lessThan: find(revRoundingRules, function lessThan(_bound, interval, target) { + if (interval < target) return interval; + }), + + atLeast: find(revRoundingRules, function atLeast(_bound, interval, target) { + if (interval <= target) return interval; + }), +}; diff --git a/x-pack/plugins/observability/public/utils/get_bucket_size/index.test.ts b/x-pack/plugins/observability/public/utils/get_bucket_size/index.test.ts new file mode 100644 index 0000000000000..39c4aedaa6013 --- /dev/null +++ b/x-pack/plugins/observability/public/utils/get_bucket_size/index.test.ts @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { getBucketSize } from './index'; +import moment from 'moment'; + +describe('getBuckets', () => { + describe("minInterval 'auto'", () => { + it('last 15 minutes', () => { + const start = moment().subtract(15, 'minutes').valueOf(); + const end = moment.now(); + expect(getBucketSize({ start, end, minInterval: 'auto' })).toEqual({ + bucketSize: 10, + intervalString: '10s', + }); + }); + it('last 1 hour', () => { + const start = moment().subtract(1, 'hour').valueOf(); + const end = moment.now(); + expect(getBucketSize({ start, end, minInterval: 'auto' })).toEqual({ + bucketSize: 30, + intervalString: '30s', + }); + }); + it('last 1 week', () => { + const start = moment().subtract(1, 'week').valueOf(); + const end = moment.now(); + expect(getBucketSize({ start, end, minInterval: 'auto' })).toEqual({ + bucketSize: 3600, + intervalString: '3600s', + }); + }); + it('last 30 days', () => { + const start = moment().subtract(30, 'days').valueOf(); + const end = moment.now(); + expect(getBucketSize({ start, end, minInterval: 'auto' })).toEqual({ + bucketSize: 43200, + intervalString: '43200s', + }); + }); + it('last 1 year', () => { + const start = moment().subtract(1, 'year').valueOf(); + const end = moment.now(); + expect(getBucketSize({ start, end, minInterval: 'auto' })).toEqual({ + bucketSize: 86400, + intervalString: '86400s', + }); + }); + }); + describe("minInterval '30s'", () => { + it('last 15 minutes', () => { + const start = moment().subtract(15, 'minutes').valueOf(); + const end = moment.now(); + expect(getBucketSize({ start, end, minInterval: '30s' })).toEqual({ + bucketSize: 30, + intervalString: '30s', + }); + }); + it('last 1 year', () => { + const start = moment().subtract(1, 'year').valueOf(); + const end = moment.now(); + expect(getBucketSize({ start, end, minInterval: '30s' })).toEqual({ + bucketSize: 86400, + intervalString: '86400s', + }); + }); + }); +}); diff --git a/x-pack/plugins/observability/public/utils/get_bucket_size/index.ts b/x-pack/plugins/observability/public/utils/get_bucket_size/index.ts new file mode 100644 index 0000000000000..5673b890adf33 --- /dev/null +++ b/x-pack/plugins/observability/public/utils/get_bucket_size/index.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import moment from 'moment'; +// @ts-ignore +import { calculateAuto } from './calculate_auto'; +import { unitToSeconds } from './unit_to_seconds'; + +export function getBucketSize({ + start, + end, + minInterval, +}: { + start: number; + end: number; + minInterval: string; +}) { + const duration = moment.duration(end - start, 'ms'); + const bucketSize = Math.max(calculateAuto.near(100, duration).asSeconds(), 1); + const intervalString = `${bucketSize}s`; + const matches = minInterval && minInterval.match(/^([\d]+)([shmdwMy]|ms)$/); + const minBucketSize = matches ? Number(matches[1]) * unitToSeconds(matches[2]) : 0; + + if (bucketSize < minBucketSize) { + return { + bucketSize: minBucketSize, + intervalString: minInterval, + }; + } + + return { bucketSize, intervalString }; +} diff --git a/x-pack/plugins/observability/public/utils/get_bucket_size/unit_to_seconds.ts b/x-pack/plugins/observability/public/utils/get_bucket_size/unit_to_seconds.ts new file mode 100644 index 0000000000000..657726d988495 --- /dev/null +++ b/x-pack/plugins/observability/public/utils/get_bucket_size/unit_to_seconds.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import moment, { unitOfTime as UnitOfTIme } from 'moment'; + +function getDurationAsSeconds(value: number, unitOfTime: UnitOfTIme.Base) { + return moment.duration(value, unitOfTime).asSeconds(); +} + +const units = { + ms: getDurationAsSeconds(1, 'millisecond'), + s: getDurationAsSeconds(1, 'second'), + m: getDurationAsSeconds(1, 'minute'), + h: getDurationAsSeconds(1, 'hour'), + d: getDurationAsSeconds(1, 'day'), + w: getDurationAsSeconds(1, 'week'), + M: getDurationAsSeconds(1, 'month'), + y: getDurationAsSeconds(1, 'year'), +}; + +export function unitToSeconds(unit: string) { + return units[unit as keyof typeof units]; +} diff --git a/x-pack/plugins/observability/public/utils/test_helper.tsx b/x-pack/plugins/observability/public/utils/test_helper.tsx new file mode 100644 index 0000000000000..2a290f2b24d6b --- /dev/null +++ b/x-pack/plugins/observability/public/utils/test_helper.tsx @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; +import { render as testLibRender } from '@testing-library/react'; +import { AppMountContext } from 'kibana/public'; +import { PluginContext } from '../context/plugin_context'; +import { EuiThemeProvider } from '../typings'; + +export const core = ({ + http: { + basePath: { + prepend: jest.fn(), + }, + }, +} as unknown) as AppMountContext['core']; + +export const render = (component: React.ReactNode) => { + return testLibRender( + + {component} + + ); +}; diff --git a/x-pack/plugins/observability/public/utils/url.ts b/x-pack/plugins/observability/public/utils/url.ts new file mode 100644 index 0000000000000..962ab8233a8f5 --- /dev/null +++ b/x-pack/plugins/observability/public/utils/url.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { parse, stringify } from 'query-string'; +import { url } from '../../../../../src/plugins/kibana_utils/public'; + +export function toQuery(search?: string) { + return search ? parse(search.slice(1), { sort: false }) : {}; +} + +export function fromQuery(query: Record) { + const encodedQuery = url.encodeQuery(query, (value) => + encodeURIComponent(value).replace(/%3A/g, ':') + ); + + return stringify(encodedQuery, { sort: false, encode: false }); +} diff --git a/x-pack/plugins/observability/scripts/storybook.js b/x-pack/plugins/observability/scripts/storybook.js new file mode 100644 index 0000000000000..e9db98e2adf6b --- /dev/null +++ b/x-pack/plugins/observability/scripts/storybook.js @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { join } from 'path'; + +// eslint-disable-next-line +require('@kbn/storybook').runStorybookCli({ + name: 'observability', + storyGlobs: [ + join(__dirname, '..', 'public', 'components', '**', '*.stories.tsx'), + join(__dirname, '..', 'public', 'pages', '**', '*.stories.tsx'), + ], +}); diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts index a34a76361f799..bf9cf7486810d 100644 --- a/x-pack/plugins/security_solution/common/constants.ts +++ b/x-pack/plugins/security_solution/common/constants.ts @@ -51,7 +51,7 @@ export const APP_HOSTS_PATH = `${APP_PATH}/hosts`; export const APP_NETWORK_PATH = `${APP_PATH}/network`; export const APP_TIMELINES_PATH = `${APP_PATH}/timelines`; export const APP_CASES_PATH = `${APP_PATH}/cases`; -export const APP_MANAGEMENT_PATH = `${APP_PATH}/management`; +export const APP_MANAGEMENT_PATH = `${APP_PATH}/administration`; /** The comma-delimited list of Elasticsearch indices from which the SIEM app collects events */ export const DEFAULT_INDEX_PATTERN = [ diff --git a/x-pack/plugins/security_solution/cypress/integration/navigation.spec.ts b/x-pack/plugins/security_solution/cypress/integration/navigation.spec.ts index ea3a78c77152a..7864160d5bca0 100644 --- a/x-pack/plugins/security_solution/cypress/integration/navigation.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/navigation.spec.ts @@ -21,7 +21,7 @@ import { CASES_URL, HOSTS_URL, KIBANA_HOME, - MANAGEMENT_URL, + ADMINISTRATION_URL, NETWORK_URL, OVERVIEW_URL, TIMELINES_URL, @@ -31,7 +31,7 @@ import { ALERTS_PAGE, CASES_PAGE, HOSTS_PAGE, - MANAGEMENT_PAGE, + ADMINISTRATION_PAGE, NETWORK_PAGE, OVERVIEW_PAGE, TIMELINES_PAGE, @@ -72,9 +72,9 @@ describe('top-level navigation common to all pages in the Security app', () => { cy.url().should('include', CASES_URL); }); - it('navigates to the Management page', () => { + it('navigates to the Administration page', () => { navigateFromHeaderTo(MANAGEMENT); - cy.url().should('include', MANAGEMENT_URL); + cy.url().should('include', ADMINISTRATION_URL); }); }); @@ -115,8 +115,8 @@ describe('Kibana navigation to all pages in the Security app ', () => { cy.url().should('include', CASES_URL); }); - it('navigates to the Management page', () => { - navigateFromKibanaCollapsibleTo(MANAGEMENT_PAGE); - cy.url().should('include', MANAGEMENT_URL); + it('navigates to the Administration page', () => { + navigateFromKibanaCollapsibleTo(ADMINISTRATION_PAGE); + cy.url().should('include', ADMINISTRATION_URL); }); }); diff --git a/x-pack/plugins/security_solution/cypress/screens/kibana_navigation.ts b/x-pack/plugins/security_solution/cypress/screens/kibana_navigation.ts index 2f7956ce370bc..eeec19fa3dd1e 100644 --- a/x-pack/plugins/security_solution/cypress/screens/kibana_navigation.ts +++ b/x-pack/plugins/security_solution/cypress/screens/kibana_navigation.ts @@ -12,8 +12,8 @@ export const HOSTS_PAGE = '[data-test-subj="collapsibleNavGroup-security"] [titl export const KIBANA_NAVIGATION_TOGGLE = '[data-test-subj="toggleNavButton"]'; -export const MANAGEMENT_PAGE = - '[data-test-subj="collapsibleNavGroup-security"] [title="Management"]'; +export const ADMINISTRATION_PAGE = + '[data-test-subj="collapsibleNavGroup-security"] [title="Administration"]'; export const NETWORK_PAGE = '[data-test-subj="collapsibleNavGroup-security"] [title="Network"]'; diff --git a/x-pack/plugins/security_solution/cypress/urls/navigation.ts b/x-pack/plugins/security_solution/cypress/urls/navigation.ts index 9da9abf388e4d..e53dac157eed7 100644 --- a/x-pack/plugins/security_solution/cypress/urls/navigation.ts +++ b/x-pack/plugins/security_solution/cypress/urls/navigation.ts @@ -16,7 +16,7 @@ export const HOSTS_PAGE_TAB_URLS = { uncommonProcesses: '/app/security/hosts/uncommonProcesses', }; export const KIBANA_HOME = '/app/home#/'; -export const MANAGEMENT_URL = '/app/security/management'; +export const ADMINISTRATION_URL = '/app/security/administration'; export const NETWORK_URL = '/app/security/network'; export const OVERVIEW_URL = '/app/security/overview'; export const TIMELINES_URL = '/app/security/timelines'; diff --git a/x-pack/plugins/security_solution/public/app/home/home_navigations.tsx b/x-pack/plugins/security_solution/public/app/home/home_navigations.tsx index 88e9d4179a971..d7acda4988570 100644 --- a/x-pack/plugins/security_solution/public/app/home/home_navigations.tsx +++ b/x-pack/plugins/security_solution/public/app/home/home_navigations.tsx @@ -27,7 +27,7 @@ export const navTabs: SiemNavTab = { }, [SecurityPageName.alerts]: { id: SecurityPageName.alerts, - name: i18n.Alerts, + name: i18n.ALERTS, href: APP_ALERTS_PATH, disabled: false, urlKey: 'alerts', @@ -63,7 +63,7 @@ export const navTabs: SiemNavTab = { }, [SecurityPageName.management]: { id: SecurityPageName.management, - name: i18n.MANAGEMENT, + name: i18n.ADMINISTRATION, href: APP_MANAGEMENT_PATH, disabled: false, urlKey: SecurityPageName.management, diff --git a/x-pack/plugins/security_solution/public/app/home/translations.ts b/x-pack/plugins/security_solution/public/app/home/translations.ts index f5a08e6395f1f..bee1dfe333851 100644 --- a/x-pack/plugins/security_solution/public/app/home/translations.ts +++ b/x-pack/plugins/security_solution/public/app/home/translations.ts @@ -25,7 +25,7 @@ export const DETECTION_ENGINE = i18n.translate( } ); -export const Alerts = i18n.translate('xpack.securitySolution.navigation.alerts', { +export const ALERTS = i18n.translate('xpack.securitySolution.navigation.alerts', { defaultMessage: 'Alerts', }); @@ -37,6 +37,6 @@ export const CASE = i18n.translate('xpack.securitySolution.navigation.case', { defaultMessage: 'Cases', }); -export const MANAGEMENT = i18n.translate('xpack.securitySolution.navigation.management', { - defaultMessage: 'Management', +export const ADMINISTRATION = i18n.translate('xpack.securitySolution.navigation.administration', { + defaultMessage: 'Administration', }); diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/index.test.tsx index 10f8b11b4d9c5..2ad83d37576b1 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/index.test.tsx @@ -108,9 +108,9 @@ describe('SIEM Navigation', () => { }, management: { disabled: false, - href: '/app/security/management', + href: '/app/security/administration', id: 'management', - name: 'Management', + name: 'Administration', urlKey: 'management', }, hosts: { @@ -220,9 +220,9 @@ describe('SIEM Navigation', () => { }, management: { disabled: false, - href: '/app/security/management', + href: '/app/security/administration', id: 'management', - name: 'Management', + name: 'Administration', urlKey: 'management', }, network: { diff --git a/x-pack/plugins/security_solution/public/management/common/constants.ts b/x-pack/plugins/security_solution/public/management/common/constants.ts index 0fad1273c7279..4bc586bdee8a9 100644 --- a/x-pack/plugins/security_solution/public/management/common/constants.ts +++ b/x-pack/plugins/security_solution/public/management/common/constants.ts @@ -10,7 +10,7 @@ import { SecurityPageName } from '../../app/types'; // --[ ROUTING ]--------------------------------------------------------------------------- export const MANAGEMENT_APP_ID = `${APP_ID}:${SecurityPageName.management}`; export const MANAGEMENT_ROUTING_ROOT_PATH = ''; -export const MANAGEMENT_ROUTING_ENDPOINTS_PATH = `${MANAGEMENT_ROUTING_ROOT_PATH}/:tabName(${ManagementSubTab.endpoints})`; +export const MANAGEMENT_ROUTING_HOSTS_PATH = `${MANAGEMENT_ROUTING_ROOT_PATH}/:tabName(${ManagementSubTab.hosts})`; export const MANAGEMENT_ROUTING_POLICIES_PATH = `${MANAGEMENT_ROUTING_ROOT_PATH}/:tabName(${ManagementSubTab.policies})`; export const MANAGEMENT_ROUTING_POLICY_DETAILS_PATH = `${MANAGEMENT_ROUTING_ROOT_PATH}/:tabName(${ManagementSubTab.policies})/:policyId`; @@ -21,5 +21,5 @@ export const MANAGEMENT_STORE_GLOBAL_NAMESPACE: ManagementStoreGlobalNamespace = export const MANAGEMENT_STORE_POLICY_LIST_NAMESPACE = 'policyList'; /** Namespace within the Management state where policy details state is maintained */ export const MANAGEMENT_STORE_POLICY_DETAILS_NAMESPACE = 'policyDetails'; -/** Namespace within the Management state where endpoints state is maintained */ -export const MANAGEMENT_STORE_ENDPOINTS_NAMESPACE = 'endpoints'; +/** Namespace within the Management state where hosts state is maintained */ +export const MANAGEMENT_STORE_HOSTS_NAMESPACE = 'hosts'; diff --git a/x-pack/plugins/security_solution/public/management/common/routing.ts b/x-pack/plugins/security_solution/public/management/common/routing.ts index 92eb7717318d3..5add6b753a7a9 100644 --- a/x-pack/plugins/security_solution/public/management/common/routing.ts +++ b/x-pack/plugins/security_solution/public/management/common/routing.ts @@ -10,7 +10,7 @@ import { generatePath } from 'react-router-dom'; import querystring from 'querystring'; import { - MANAGEMENT_ROUTING_ENDPOINTS_PATH, + MANAGEMENT_ROUTING_HOSTS_PATH, MANAGEMENT_ROUTING_POLICIES_PATH, MANAGEMENT_ROUTING_POLICY_DETAILS_PATH, } from './constants'; @@ -32,11 +32,11 @@ const querystringStringify: ( ) => string = querystring.stringify; /** Make `selected_host` required */ -type EndpointDetailsUrlProps = Omit & +type HostDetailsUrlProps = Omit & Required>; -export const getEndpointListPath = ( - props: { name: 'default' | 'endpointList' } & HostIndexUIQueryParams, +export const getHostListPath = ( + props: { name: 'default' | 'hostList' } & HostIndexUIQueryParams, search?: string ) => { const { name, ...queryParams } = props; @@ -45,29 +45,27 @@ export const getEndpointListPath = ( ); const urlSearch = `${urlQueryParams && !isEmpty(search) ? '&' : ''}${search ?? ''}`; - if (name === 'endpointList') { - return `${generatePath(MANAGEMENT_ROUTING_ENDPOINTS_PATH, { - tabName: ManagementSubTab.endpoints, + if (name === 'hostList') { + return `${generatePath(MANAGEMENT_ROUTING_HOSTS_PATH, { + tabName: ManagementSubTab.hosts, })}${appendSearch(`${urlQueryParams ? `${urlQueryParams}${urlSearch}` : urlSearch}`)}`; } return `${appendSearch(`${urlQueryParams ? `${urlQueryParams}${urlSearch}` : urlSearch}`)}`; }; -export const getEndpointDetailsPath = ( - props: { name: 'endpointDetails' | 'endpointPolicyResponse' } & EndpointDetailsUrlProps, +export const getHostDetailsPath = ( + props: { name: 'hostDetails' | 'hostPolicyResponse' } & HostDetailsUrlProps, search?: string ) => { const { name, ...queryParams } = props; - queryParams.show = (props.name === 'endpointPolicyResponse' + queryParams.show = (props.name === 'hostPolicyResponse' ? 'policy_response' : '') as HostIndexUIQueryParams['show']; - const urlQueryParams = querystringStringify( - queryParams - ); + const urlQueryParams = querystringStringify(queryParams); const urlSearch = `${urlQueryParams && !isEmpty(search) ? '&' : ''}${search ?? ''}`; - return `${generatePath(MANAGEMENT_ROUTING_ENDPOINTS_PATH, { - tabName: ManagementSubTab.endpoints, + return `${generatePath(MANAGEMENT_ROUTING_HOSTS_PATH, { + tabName: ManagementSubTab.hosts, })}${appendSearch(`${urlQueryParams ? `${urlQueryParams}${urlSearch}` : urlSearch}`)}`; }; diff --git a/x-pack/plugins/security_solution/public/management/components/management_empty_state.tsx b/x-pack/plugins/security_solution/public/management/components/management_empty_state.tsx index 5dd47d4e88028..c3d6cb48e4dae 100644 --- a/x-pack/plugins/security_solution/public/management/components/management_empty_state.tsx +++ b/x-pack/plugins/security_solution/public/management/components/management_empty_state.tsx @@ -103,7 +103,7 @@ const PolicyEmptyState = React.memo<{ ); }); -const EndpointsEmptyState = React.memo<{ +const HostsEmptyState = React.memo<{ loading: boolean; onActionClick: (event: MouseEvent) => void; actionDisabled: boolean; @@ -113,14 +113,14 @@ const EndpointsEmptyState = React.memo<{ const policySteps = useMemo( () => [ { - title: i18n.translate('xpack.securitySolution.endpoint.endpointList.stepOneTitle', { + title: i18n.translate('xpack.securitySolution.endpoint.hostList.stepOneTitle', { defaultMessage: 'Select a policy you created from the list below.', }), children: ( <> @@ -138,7 +138,7 @@ const EndpointsEmptyState = React.memo<{ return loading ? ( @@ -146,7 +146,7 @@ const EndpointsEmptyState = React.memo<{ list ) : ( ); @@ -156,14 +156,14 @@ const EndpointsEmptyState = React.memo<{ ), }, { - title: i18n.translate('xpack.securitySolution.endpoint.endpointList.stepTwoTitle', { + title: i18n.translate('xpack.securitySolution.endpoint.hostList.stepTwoTitle', { defaultMessage: 'Head over to Ingest to deploy your Agent with Endpoint Security enabled.', }), children: ( @@ -178,18 +178,18 @@ const EndpointsEmptyState = React.memo<{ loading={loading} onActionClick={onActionClick} actionDisabled={actionDisabled} - dataTestSubj="emptyEndpointsTable" + dataTestSubj="emptyHostsTable" steps={policySteps} headerComponent={ } bodyComponent={ } /> @@ -271,7 +271,7 @@ const ManagementEmptyState = React.memo<{ ); PolicyEmptyState.displayName = 'PolicyEmptyState'; -EndpointsEmptyState.displayName = 'EndpointsEmptyState'; +HostsEmptyState.displayName = 'HostsEmptyState'; ManagementEmptyState.displayName = 'ManagementEmptyState'; -export { PolicyEmptyState, EndpointsEmptyState, ManagementEmptyState }; +export { PolicyEmptyState, HostsEmptyState, ManagementEmptyState }; diff --git a/x-pack/plugins/security_solution/public/management/components/management_page_view.tsx b/x-pack/plugins/security_solution/public/management/components/management_page_view.tsx index c3dbb93b369a9..8495628709d2a 100644 --- a/x-pack/plugins/security_solution/public/management/components/management_page_view.tsx +++ b/x-pack/plugins/security_solution/public/management/components/management_page_view.tsx @@ -11,7 +11,7 @@ import { PageView, PageViewProps } from '../../common/components/endpoint/page_v import { ManagementSubTab } from '../types'; import { SecurityPageName } from '../../app/types'; import { useFormatUrl } from '../../common/components/link_to'; -import { getEndpointListPath, getPoliciesPath } from '../common/routing'; +import { getHostListPath, getPoliciesPath } from '../common/routing'; import { useNavigateByRouterEventHandler } from '../../common/hooks/endpoint/use_navigate_by_router_event_handler'; export const ManagementPageView = memo>((options) => { @@ -19,7 +19,7 @@ export const ManagementPageView = memo>((options) => const { tabName } = useParams<{ tabName: ManagementSubTab }>(); const goToEndpoint = useNavigateByRouterEventHandler( - getEndpointListPath({ name: 'endpointList' }, search) + getHostListPath({ name: 'hostList' }, search) ); const goToPolicies = useNavigateByRouterEventHandler(getPoliciesPath(search)); @@ -31,11 +31,11 @@ export const ManagementPageView = memo>((options) => return [ { name: i18n.translate('xpack.securitySolution.managementTabs.endpoints', { - defaultMessage: 'Endpoints', + defaultMessage: 'Hosts', }), - id: ManagementSubTab.endpoints, - isSelected: tabName === ManagementSubTab.endpoints, - href: formatUrl(getEndpointListPath({ name: 'endpointList' })), + id: ManagementSubTab.hosts, + isSelected: tabName === ManagementSubTab.hosts, + href: formatUrl(getHostListPath({ name: 'hostList' })), onClick: goToEndpoint, }, { diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/index.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/index.tsx index ff7f522b9bc52..a970edd4d30f4 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/index.tsx @@ -7,19 +7,19 @@ import { Switch, Route } from 'react-router-dom'; import React, { memo } from 'react'; import { HostList } from './view'; -import { MANAGEMENT_ROUTING_ENDPOINTS_PATH } from '../../common/constants'; +import { MANAGEMENT_ROUTING_HOSTS_PATH } from '../../common/constants'; import { NotFoundPage } from '../../../app/404'; /** - * Provides the routing container for the endpoints related views + * Provides the routing container for the hosts related views */ -export const EndpointsContainer = memo(() => { +export const HostsContainer = memo(() => { return ( - + ); }); -EndpointsContainer.displayName = 'EndpointsContainer'; +HostsContainer.displayName = 'HostsContainer'; diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/host_pagination.test.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/host_pagination.test.ts index ae2ce9facc837..533b14e50f3dd 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/host_pagination.test.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/host_pagination.test.ts @@ -24,7 +24,7 @@ import { MiddlewareActionSpyHelper, createSpyMiddleware, } from '../../../../common/store/test_utils'; -import { getEndpointListPath } from '../../../common/routing'; +import { getHostListPath } from '../../../common/routing'; describe('host list pagination: ', () => { let fakeCoreStart: jest.Mocked; @@ -56,7 +56,7 @@ describe('host list pagination: ', () => { queryParams = () => uiQueryParams(store.getState()); historyPush = (nextQueryParams: HostIndexUIQueryParams): void => { - return history.push(getEndpointListPath({ name: 'endpointList', ...nextQueryParams })); + return history.push(getHostListPath({ name: 'hostList', ...nextQueryParams })); }; }); @@ -70,7 +70,7 @@ describe('host list pagination: ', () => { type: 'userChangedUrl', payload: { ...history.location, - pathname: getEndpointListPath({ name: 'endpointList' }), + pathname: getHostListPath({ name: 'hostList' }), }, }); await waitForAction('serverReturnedHostList'); diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.test.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.test.ts index e62c53e061a33..1c5c4fbac51ba 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.test.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.test.ts @@ -21,7 +21,7 @@ import { listData } from './selectors'; import { HostState } from '../types'; import { hostListReducer } from './reducer'; import { hostMiddlewareFactory } from './middleware'; -import { getEndpointListPath } from '../../../common/routing'; +import { getHostListPath } from '../../../common/routing'; describe('host list middleware', () => { let fakeCoreStart: jest.Mocked; @@ -60,7 +60,7 @@ describe('host list middleware', () => { type: 'userChangedUrl', payload: { ...history.location, - pathname: getEndpointListPath({ name: 'endpointList' }), + pathname: getHostListPath({ name: 'hostList' }), }, }); await waitForAction('serverReturnedHostList'); diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts index e75d2129f61a5..4f47eaf565d8c 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts @@ -15,7 +15,7 @@ import { HostPolicyResponseActionStatus, } from '../../../../../common/endpoint/types'; import { HostState, HostIndexUIQueryParams } from '../types'; -import { MANAGEMENT_ROUTING_ENDPOINTS_PATH } from '../../../common/constants'; +import { MANAGEMENT_ROUTING_HOSTS_PATH } from '../../../common/constants'; const PAGE_SIZES = Object.freeze([10, 20, 50]); @@ -114,7 +114,7 @@ export const policyResponseError = (state: Immutable) => state.policy export const isOnHostPage = (state: Immutable) => { return ( matchPath(state.location?.pathname ?? '', { - path: MANAGEMENT_ROUTING_ENDPOINTS_PATH, + path: MANAGEMENT_ROUTING_HOSTS_PATH, exact: true, }) !== null ); diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/host_details.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/host_details.tsx index 66abf993770a7..10ea271139e49 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/host_details.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/host_details.tsx @@ -26,7 +26,7 @@ import { POLICY_STATUS_TO_HEALTH_COLOR } from '../host_constants'; import { FormattedDateAndTime } from '../../../../../common/components/endpoint/formatted_date_time'; import { useNavigateByRouterEventHandler } from '../../../../../common/hooks/endpoint/use_navigate_by_router_event_handler'; import { LinkToApp } from '../../../../../common/components/endpoint/link_to_app'; -import { getEndpointDetailsPath, getPolicyDetailPath } from '../../../../common/routing'; +import { getHostDetailsPath, getPolicyDetailPath } from '../../../../common/routing'; import { SecurityPageName } from '../../../../../app/types'; import { useFormatUrl } from '../../../../../common/components/link_to'; import { AgentDetailsReassignConfigAction } from '../../../../../../../ingest_manager/public'; @@ -84,14 +84,14 @@ export const HostDetails = memo(({ details }: { details: HostMetadata }) => { const { selected_host, show, ...currentUrlParams } = queryParams; return [ formatUrl( - getEndpointDetailsPath({ - name: 'endpointPolicyResponse', + getHostDetailsPath({ + name: 'hostPolicyResponse', ...currentUrlParams, selected_host: details.host.id, }) ), - getEndpointDetailsPath({ - name: 'endpointPolicyResponse', + getHostDetailsPath({ + name: 'hostPolicyResponse', ...currentUrlParams, selected_host: details.host.id, }), @@ -108,7 +108,7 @@ export const HostDetails = memo(({ details }: { details: HostMetadata }) => { onDoneNavigateTo: [ 'securitySolution:management', { - path: getEndpointDetailsPath({ name: 'endpointDetails', selected_host: details.host.id }), + path: getHostDetailsPath({ name: 'hostDetails', selected_host: details.host.id }), }, ], }, @@ -200,8 +200,8 @@ export const HostDetails = memo(({ details }: { details: HostMetadata }) => { description: details.host.hostname, }, { - title: i18n.translate('xpack.securitySolution.endpoint.host.details.sensorVersion', { - defaultMessage: 'Sensor Version', + title: i18n.translate('xpack.securitySolution.endpoint.host.details.endpointVersion', { + defaultMessage: 'Endpoint Version', }), description: details.agent.version, }, diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/index.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/index.tsx index 3d44b73858e90..e29d796325bd6 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/index.tsx @@ -38,7 +38,7 @@ import { PolicyResponse } from './policy_response'; import { HostMetadata } from '../../../../../../common/endpoint/types'; import { FlyoutSubHeader, FlyoutSubHeaderProps } from './components/flyout_sub_header'; import { useNavigateByRouterEventHandler } from '../../../../../common/hooks/endpoint/use_navigate_by_router_event_handler'; -import { getEndpointListPath } from '../../../../common/routing'; +import { getHostListPath } from '../../../../common/routing'; import { SecurityPageName } from '../../../../../app/types'; import { useFormatUrl } from '../../../../../common/components/link_to'; @@ -122,14 +122,14 @@ const PolicyResponseFlyoutPanel = memo<{ const [detailsUri, detailsRoutePath] = useMemo( () => [ formatUrl( - getEndpointListPath({ - name: 'endpointList', + getHostListPath({ + name: 'hostList', ...queryParams, selected_host: hostMeta.host.id, }) ), - getEndpointListPath({ - name: 'endpointList', + getHostListPath({ + name: 'hostList', ...queryParams, selected_host: hostMeta.host.id, }), diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks.ts index b048a8f69b5d2..d11335df875e9 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks.ts @@ -9,14 +9,14 @@ import { useMemo } from 'react'; import { useKibana } from '../../../../common/lib/kibana'; import { HostState } from '../types'; import { - MANAGEMENT_STORE_ENDPOINTS_NAMESPACE, + MANAGEMENT_STORE_HOSTS_NAMESPACE, MANAGEMENT_STORE_GLOBAL_NAMESPACE, } from '../../../common/constants'; import { State } from '../../../../common/store'; export function useHostSelector(selector: (state: HostState) => TSelected) { return useSelector(function (state: State) { return selector( - state[MANAGEMENT_STORE_GLOBAL_NAMESPACE][MANAGEMENT_STORE_ENDPOINTS_NAMESPACE] as HostState + state[MANAGEMENT_STORE_GLOBAL_NAMESPACE][MANAGEMENT_STORE_HOSTS_NAMESPACE] as HostState ); }); } diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx index 9766cd6abd2b1..996b987ea2be3 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx @@ -44,7 +44,7 @@ describe('when on the hosts page', () => { it('should show the empty state when there are no hosts or polices', async () => { const renderResult = render(); - // Initially, there are no endpoints or policies, so we prompt to add policies first. + // Initially, there are no hosts or policies, so we prompt to add policies first. const table = await renderResult.findByTestId('emptyPolicyTable'); expect(table).not.toBeNull(); }); @@ -79,8 +79,8 @@ describe('when on the hosts page', () => { it('should show the no hosts empty state', async () => { const renderResult = render(); - const emptyEndpointsTable = await renderResult.findByTestId('emptyEndpointsTable'); - expect(emptyEndpointsTable).not.toBeNull(); + const emptyHostsTable = await renderResult.findByTestId('emptyHostsTable'); + expect(emptyHostsTable).not.toBeNull(); }); it('should display the onboarding steps', async () => { @@ -335,7 +335,7 @@ describe('when on the hosts page', () => { const policyStatusLink = await renderResult.findByTestId('policyStatusValue'); expect(policyStatusLink).not.toBeNull(); expect(policyStatusLink.getAttribute('href')).toEqual( - '/endpoints?page_index=0&page_size=10&selected_host=1&show=policy_response' + '/hosts?page_index=0&page_size=10&selected_host=1&show=policy_response' ); }); @@ -549,7 +549,7 @@ describe('when on the hosts page', () => { const subHeaderBackLink = await renderResult.findByTestId('flyoutSubHeaderBackButton'); expect(subHeaderBackLink.textContent).toBe('Endpoint Details'); expect(subHeaderBackLink.getAttribute('href')).toBe( - '/endpoints?page_index=0&page_size=10&selected_host=1' + '/hosts?page_index=0&page_size=10&selected_host=1' ); }); diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx index d49335ca8de2c..492c75607a255 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx @@ -10,6 +10,8 @@ import { EuiBasicTable, EuiBasicTableColumn, EuiText, + EuiTitle, + EuiSpacer, EuiLink, EuiHealth, EuiToolTip, @@ -33,7 +35,7 @@ import { CreateStructuredSelector } from '../../../../common/store'; import { Immutable, HostInfo } from '../../../../../common/endpoint/types'; import { SpyRoute } from '../../../../common/utils/route/spy_routes'; import { ManagementPageView } from '../../../components/management_page_view'; -import { PolicyEmptyState, EndpointsEmptyState } from '../../../components/management_empty_state'; +import { PolicyEmptyState, HostsEmptyState } from '../../../components/management_empty_state'; import { FormattedDate } from '../../../../common/components/formatted_date'; import { useNavigateToAppEventHandler } from '../../../../common/hooks/endpoint/use_navigate_to_app_event_handler'; import { @@ -41,11 +43,7 @@ import { AgentConfigDetailsDeployAgentAction, } from '../../../../../../ingest_manager/public'; import { SecurityPageName } from '../../../../app/types'; -import { - getEndpointListPath, - getEndpointDetailsPath, - getPolicyDetailPath, -} from '../../../common/routing'; +import { getHostListPath, getHostDetailsPath, getPolicyDetailPath } from '../../../common/routing'; import { useFormatUrl } from '../../../../common/components/link_to'; import { HostAction } from '../store/action'; @@ -107,8 +105,8 @@ export const HostList = () => { const { index, size } = page; // FIXME: PT: if host details is open, table is not displaying correct number of rows history.push( - getEndpointListPath({ - name: 'endpointList', + getHostListPath({ + name: 'hostList', ...queryParams, page_index: JSON.stringify(index), page_size: JSON.stringify(size), @@ -127,12 +125,12 @@ export const HostList = () => { state: { onCancelNavigateTo: [ 'securitySolution:management', - { path: getEndpointListPath({ name: 'endpointList' }) }, + { path: getHostListPath({ name: 'hostList' }) }, ], - onCancelUrl: formatUrl(getEndpointListPath({ name: 'endpointList' })), + onCancelUrl: formatUrl(getHostListPath({ name: 'hostList' })), onSaveNavigateTo: [ 'securitySolution:management', - { path: getEndpointListPath({ name: 'endpointList' }) }, + { path: getHostListPath({ name: 'hostList' }) }, ], }, } @@ -145,7 +143,7 @@ export const HostList = () => { state: { onDoneNavigateTo: [ 'securitySolution:management', - { path: getEndpointListPath({ name: 'endpointList' }) }, + { path: getHostListPath({ name: 'hostList' }) }, ], }, }); @@ -191,10 +189,10 @@ export const HostList = () => { defaultMessage: 'Hostname', }), render: ({ hostname, id }: HostInfo['metadata']['host']) => { - const toRoutePath = getEndpointDetailsPath( + const toRoutePath = getHostDetailsPath( { ...queryParams, - name: 'endpointDetails', + name: 'hostDetails', selected_host: id, }, search @@ -259,8 +257,8 @@ export const HostList = () => { }), // eslint-disable-next-line react/display-name render: (policy: HostInfo['metadata']['Endpoint']['policy']['applied'], item: HostInfo) => { - const toRoutePath = getEndpointDetailsPath({ - name: 'endpointPolicyResponse', + const toRoutePath = getHostDetailsPath({ + name: 'hostPolicyResponse', selected_host: item.metadata.host.id, }); const toRouteUrl = formatUrl(toRoutePath); @@ -341,7 +339,7 @@ export const HostList = () => { ); } else if (!policyItemsLoading && policyItems && policyItems.length > 0) { return ( - { + +

+ +

+
+ + +

+ +

+
+ + } > {hasSelectedHost && } {listData && listData.length > 0 && ( diff --git a/x-pack/plugins/security_solution/public/management/pages/index.tsx b/x-pack/plugins/security_solution/public/management/pages/index.tsx index 0e81b75d651ba..2cf07b9b4382e 100644 --- a/x-pack/plugins/security_solution/public/management/pages/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/index.tsx @@ -9,25 +9,25 @@ import { useHistory, Route, Switch } from 'react-router-dom'; import { PolicyContainer } from './policy'; import { - MANAGEMENT_ROUTING_ENDPOINTS_PATH, + MANAGEMENT_ROUTING_HOSTS_PATH, MANAGEMENT_ROUTING_POLICIES_PATH, MANAGEMENT_ROUTING_ROOT_PATH, } from '../common/constants'; import { NotFoundPage } from '../../app/404'; -import { EndpointsContainer } from './endpoint_hosts'; -import { getEndpointListPath } from '../common/routing'; +import { HostsContainer } from './endpoint_hosts'; +import { getHostListPath } from '../common/routing'; export const ManagementContainer = memo(() => { const history = useHistory(); return ( - + { - history.replace(getEndpointListPath({ name: 'endpointList' })); + history.replace(getHostListPath({ name: 'hostList' })); return null; }} /> diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.tsx index 447a70ef998a9..aa7e867e89d6a 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.tsx @@ -8,6 +8,8 @@ import React, { useCallback, useEffect, useMemo, CSSProperties, useState } from import { EuiBasicTable, EuiText, + EuiTitle, + EuiSpacer, EuiFlexGroup, EuiFlexItem, EuiTableFieldDataColumnType, @@ -20,7 +22,6 @@ import { EuiOverlayMask, EuiConfirmModal, EuiCallOut, - EuiSpacer, EuiButton, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -391,9 +392,27 @@ export const PolicyList = React.memo(() => { + +

+ +

+
+ + +

+ +

+
+ + } headerRight={ const policyDetailsSelector = (state: State) => state[MANAGEMENT_STORE_GLOBAL_NAMESPACE][MANAGEMENT_STORE_POLICY_DETAILS_NAMESPACE]; const endpointsSelector = (state: State) => - state[MANAGEMENT_STORE_GLOBAL_NAMESPACE][MANAGEMENT_STORE_ENDPOINTS_NAMESPACE]; + state[MANAGEMENT_STORE_GLOBAL_NAMESPACE][MANAGEMENT_STORE_HOSTS_NAMESPACE]; export const managementMiddlewareFactory: SecuritySubPluginMiddlewareFactory = ( coreStart, diff --git a/x-pack/plugins/security_solution/public/management/store/reducer.ts b/x-pack/plugins/security_solution/public/management/store/reducer.ts index 2ed3dfe86d2f8..f3c470fb1e8a3 100644 --- a/x-pack/plugins/security_solution/public/management/store/reducer.ts +++ b/x-pack/plugins/security_solution/public/management/store/reducer.ts @@ -14,7 +14,7 @@ import { initialPolicyListState, } from '../pages/policy/store/policy_list/reducer'; import { - MANAGEMENT_STORE_ENDPOINTS_NAMESPACE, + MANAGEMENT_STORE_HOSTS_NAMESPACE, MANAGEMENT_STORE_POLICY_DETAILS_NAMESPACE, MANAGEMENT_STORE_POLICY_LIST_NAMESPACE, } from '../common/constants'; @@ -31,7 +31,7 @@ const immutableCombineReducers: ImmutableCombineReducers = combineReducers; export const mockManagementState: Immutable = { [MANAGEMENT_STORE_POLICY_LIST_NAMESPACE]: initialPolicyListState(), [MANAGEMENT_STORE_POLICY_DETAILS_NAMESPACE]: initialPolicyDetailsState(), - [MANAGEMENT_STORE_ENDPOINTS_NAMESPACE]: initialHostListState, + [MANAGEMENT_STORE_HOSTS_NAMESPACE]: initialHostListState, }; /** @@ -40,5 +40,5 @@ export const mockManagementState: Immutable = { export const managementReducer = immutableCombineReducers({ [MANAGEMENT_STORE_POLICY_LIST_NAMESPACE]: policyListReducer, [MANAGEMENT_STORE_POLICY_DETAILS_NAMESPACE]: policyDetailsReducer, - [MANAGEMENT_STORE_ENDPOINTS_NAMESPACE]: hostListReducer, + [MANAGEMENT_STORE_HOSTS_NAMESPACE]: hostListReducer, }); diff --git a/x-pack/plugins/security_solution/public/management/types.ts b/x-pack/plugins/security_solution/public/management/types.ts index 854e9faa0204d..cb21a236ddd7e 100644 --- a/x-pack/plugins/security_solution/public/management/types.ts +++ b/x-pack/plugins/security_solution/public/management/types.ts @@ -18,14 +18,14 @@ export type ManagementStoreGlobalNamespace = 'management'; export type ManagementState = CombinedState<{ policyList: PolicyListState; policyDetails: PolicyDetailsState; - endpoints: HostState; + hosts: HostState; }>; /** * The management list of sub-tabs. Changes to these will impact the Router routes. */ export enum ManagementSubTab { - endpoints = 'endpoints', + hosts = 'hosts', policies = 'policy', } diff --git a/x-pack/plugins/security_solution/public/overview/components/endpoint_notice/index.tsx b/x-pack/plugins/security_solution/public/overview/components/endpoint_notice/index.tsx index ee048f0d61212..3758bd10bfc8f 100644 --- a/x-pack/plugins/security_solution/public/overview/components/endpoint_notice/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/endpoint_notice/index.tsx @@ -7,13 +7,13 @@ import React, { memo } from 'react'; import { EuiCallOut, EuiButton, EuiButtonEmpty } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { getEndpointListPath } from '../../../management/common/routing'; +import { getHostListPath } from '../../../management/common/routing'; import { useNavigateToAppEventHandler } from '../../../common/hooks/endpoint/use_navigate_to_app_event_handler'; import { useManagementFormatUrl } from '../../../management/components/hooks/use_management_format_url'; import { MANAGEMENT_APP_ID } from '../../../management/common/constants'; export const EndpointNotice = memo<{ onDismiss: () => void }>(({ onDismiss }) => { - const endpointsPath = getEndpointListPath({ name: 'endpointList' }); + const endpointsPath = getHostListPath({ name: 'hostList' }); const endpointsLink = useManagementFormatUrl(endpointsPath); const handleGetStartedClick = useNavigateToAppEventHandler(MANAGEMENT_APP_ID, { path: endpointsPath, diff --git a/x-pack/plugins/security_solution/public/plugin.tsx b/x-pack/plugins/security_solution/public/plugin.tsx index 18072c25e6dde..2b7fc160110f5 100644 --- a/x-pack/plugins/security_solution/public/plugin.tsx +++ b/x-pack/plugins/security_solution/public/plugin.tsx @@ -48,6 +48,15 @@ import { ConfigureEndpointPackageConfig } from './management/pages/policy/view/i import { State, createStore, createInitialState } from './common/store'; import { SecurityPageName } from './app/types'; import { manageOldSiemRoutes } from './helpers'; +import { + OVERVIEW, + HOSTS, + NETWORK, + TIMELINES, + ALERTS, + CASE, + ADMINISTRATION, +} from './app/home/translations'; export class Plugin implements IPlugin { private kibanaVersion: string; @@ -95,10 +104,12 @@ export class Plugin implements IPlugin { + mount: async () => { const [{ application }] = await core.getStartServices(); application.navigateToApp(`${APP_ID}:${SecurityPageName.overview}`, { replace: true }); return () => true; @@ -107,9 +118,7 @@ export class Plugin implements IPlugin = ( activeDescendantId: null, selectedDescendantId: null, processEntityIdOfSelectedDescendant: null, - panelToDisplay: null, }, action ) => { @@ -39,11 +38,6 @@ const uiReducer: Reducer = ( selectedDescendantId: action.payload.nodeId, processEntityIdOfSelectedDescendant: action.payload.selectedProcessId, }; - } else if (action.type === 'appDisplayedDifferentPanel') { - return { - ...uiState, - panelToDisplay: action.payload, - }; } else if ( action.type === 'userBroughtProcessIntoView' || action.type === 'appDetectedNewIdFromQueryParams' diff --git a/x-pack/plugins/security_solution/public/resolver/store/selectors.ts b/x-pack/plugins/security_solution/public/resolver/store/selectors.ts index e54193ab394a5..2bc254d118d33 100644 --- a/x-pack/plugins/security_solution/public/resolver/store/selectors.ts +++ b/x-pack/plugins/security_solution/public/resolver/store/selectors.ts @@ -127,11 +127,6 @@ export const uiSelectedDescendantProcessId = composeSelectors( uiSelectors.selectedDescendantProcessId ); -/** - * The current panel to display - */ -export const currentPanelView = composeSelectors(uiStateSelector, uiSelectors.currentPanelView); - /** * Returns the camera state from within ResolverState */ diff --git a/x-pack/plugins/security_solution/public/resolver/store/ui/selectors.ts b/x-pack/plugins/security_solution/public/resolver/store/ui/selectors.ts index bddc7d34abf1c..494d8884329c6 100644 --- a/x-pack/plugins/security_solution/public/resolver/store/ui/selectors.ts +++ b/x-pack/plugins/security_solution/public/resolver/store/ui/selectors.ts @@ -39,8 +39,3 @@ export const selectedDescendantProcessId = createSelector( return processEntityIdOfSelectedDescendant; } ); - -// Select the current panel to be displayed -export const currentPanelView = (uiState: ResolverUIState) => { - return uiState.panelToDisplay; -}; diff --git a/x-pack/plugins/security_solution/public/resolver/types.ts b/x-pack/plugins/security_solution/public/resolver/types.ts index 5dd9a944b88ea..2025762a0605c 100644 --- a/x-pack/plugins/security_solution/public/resolver/types.ts +++ b/x-pack/plugins/security_solution/public/resolver/types.ts @@ -45,10 +45,6 @@ export interface ResolverUIState { * The entity_id of the process for the resolver's currently selected descendant. */ readonly processEntityIdOfSelectedDescendant: string | null; - /** - * Which panel the ui should display - */ - readonly panelToDisplay: string | null; } /** diff --git a/x-pack/plugins/security_solution/public/resolver/view/panel.tsx b/x-pack/plugins/security_solution/public/resolver/view/panel.tsx index 2a2e7e87394a9..f4fe4fe520c92 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/panel.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/panel.tsx @@ -4,17 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { - memo, - useCallback, - useMemo, - useContext, - useLayoutEffect, - useState, - useEffect, -} from 'react'; +import React, { memo, useCallback, useMemo, useContext, useLayoutEffect, useState } from 'react'; import { useSelector } from 'react-redux'; -import { useHistory } from 'react-router-dom'; +import { useHistory, useLocation } from 'react-router-dom'; // eslint-disable-next-line import/no-nodejs-modules import querystring from 'querystring'; import { EuiPanel } from '@elastic/eui'; @@ -48,7 +40,7 @@ import { CrumbInfo } from './panels/panel_content_utilities'; */ const PanelContent = memo(function PanelContent() { const history = useHistory(); - const urlSearch = history.location.search; + const urlSearch = useLocation().search; const dispatch = useResolverDispatch(); const { timestamp } = useContext(SideEffectContext); @@ -205,21 +197,12 @@ const PanelContent = memo(function PanelContent() { return 'processListWithCounts'; }, [uiSelectedEvent, crumbEvent, crumbId, graphableProcessEntityIds]); - useEffect(() => { - // dispatch `appDisplayedDifferentPanel` to sync state with which panel gets displayed - dispatch({ - type: 'appDisplayedDifferentPanel', - payload: panelToShow, - }); - }, [panelToShow, dispatch]); - - const currentPanelView = useSelector(selectors.currentPanelView); const terminatedProcesses = useSelector(selectors.terminatedProcesses); const processEntityId = uiSelectedEvent ? event.entityId(uiSelectedEvent) : undefined; const isProcessTerminated = processEntityId ? terminatedProcesses.has(processEntityId) : false; const panelInstance = useMemo(() => { - if (currentPanelView === 'processDetails') { + if (panelToShow === 'processDetails') { return ( sum + val, 0); @@ -278,7 +261,7 @@ const PanelContent = memo(function PanelContent() { crumbId, pushToQueryParams, relatedStatsForIdFromParams, - currentPanelView, + panelToShow, isProcessTerminated, ]); diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_utilities.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_utilities.tsx index 56f88ccb13115..517b847855647 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_utilities.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_utilities.tsx @@ -82,10 +82,13 @@ const invalidDateText = i18n.translate( } ); /** - * @param {ConstructorParameters[0]} timestamp To be passed through Date->Intl.DateTimeFormat * @returns {string} A nicely formatted string for a date */ -export function formatDate(timestamp: ConstructorParameters[0]) { +export function formatDate( + /** To be passed through Date->Intl.DateTimeFormat */ timestamp: ConstructorParameters< + typeof Date + >[0] +): string { const date = new Date(timestamp); if (isFinite(date.getTime())) { return formatter.format(date); diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/translations.ts b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/translations.ts index 7b07548af67ae..8d55d00b50e13 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/translations.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/translations.ts @@ -298,6 +298,7 @@ export const IMPORT_FAILED_DETAILED = (id: string, statusCode: number, message: export const TEMPLATE_CALL_OUT_MESSAGE = i18n.translate( 'xpack.securitySolution.timelines.components.templateCallOutMessageTitle', { - defaultMessage: 'Now you can add timeline templates and link it to rules.', + defaultMessage: + 'Prebuit detection rules are now packaged with Timeline templates. You can also create your own Timeline templates and associate them with any rule.', } ); diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/create_timelines_route.test.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/create_timelines_route.test.ts index f5345c3dce222..84a18cb1573dd 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/create_timelines_route.test.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/create_timelines_route.test.ts @@ -28,7 +28,7 @@ import { import { CREATE_TEMPLATE_TIMELINE_ERROR_MESSAGE, CREATE_TIMELINE_ERROR_MESSAGE, -} from './utils/create_timelines'; +} from './utils/failure_cases'; describe('create timelines', () => { let server: ReturnType; diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/import_timelines_route.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/import_timelines_route.ts index fb4991d7d1e7d..0f4e8f3204e2b 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/import_timelines_route.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/import_timelines_route.ts @@ -46,7 +46,7 @@ import { createTimelines } from './utils/create_timelines'; import { TimelineStatus } from '../../../../common/types/timeline'; const CHUNK_PARSED_OBJECT_SIZE = 10; -const DEFAULT_IMPORT_ERROR = `Something went wrong, there's something we didn't handle properly, please help us improve by providing the file you try to import on https://discuss.elastic.co/c/security/siem`; +const DEFAULT_IMPORT_ERROR = `Something has gone wrong. We didn't handle something properly. To help us fix this, please upload your file to https://discuss.elastic.co/c/security/siem.`; export const importTimelinesRoute = ( router: IRouter, diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/create_timelines.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/create_timelines.ts index abe298566341c..67965469e1a9f 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/create_timelines.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/create_timelines.ts @@ -13,11 +13,6 @@ import { SavedTimeline, TimelineSavedObject } from '../../../../../common/types/ import { SavedNote } from '../../../../../common/types/timeline/note'; import { NoteResult, ResponseTimeline } from '../../../../graphql/types'; -export const CREATE_TIMELINE_ERROR_MESSAGE = - 'UPDATE timeline with POST is not allowed, please use PATCH instead'; -export const CREATE_TEMPLATE_TIMELINE_ERROR_MESSAGE = - 'UPDATE template timeline with POST is not allowed, please use PATCH instead'; - export const saveTimelines = ( frameworkRequest: FrameworkRequest, timeline: SavedTimeline, diff --git a/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/failure_cases.ts b/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/failure_cases.ts index 60ba5389280c4..5e7a73ca18d0e 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/failure_cases.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/routes/utils/failure_cases.ts @@ -11,27 +11,31 @@ import { } from '../../../../../common/types/timeline'; export const UPDATE_TIMELINE_ERROR_MESSAGE = - 'CREATE timeline with PATCH is not allowed, please use POST instead'; + 'You cannot create new timelines with PATCH. Use POST instead.'; export const UPDATE_TEMPLATE_TIMELINE_ERROR_MESSAGE = - "CREATE template timeline with PATCH is not allowed, please use POST instead (Given template timeline doesn't exist)"; + 'You cannot create new Timeline templates with PATCH. Use POST instead (templateTimelineId does not exist).'; export const NO_MATCH_VERSION_ERROR_MESSAGE = - 'TimelineVersion conflict: The given version doesn not match with existing timeline'; + 'Timeline template version conflict. The provided templateTimelineVersion does not match the current template.'; export const NO_MATCH_ID_ERROR_MESSAGE = - "Timeline id doesn't match with existing template timeline"; -export const TEMPLATE_TIMELINE_VERSION_CONFLICT_MESSAGE = 'Template timelineVersion conflict'; + 'There are no Timeline templates that match the provided templateTimelineId.'; +export const TEMPLATE_TIMELINE_VERSION_CONFLICT_MESSAGE = + 'To update existing Timeline templates, you must increment the templateTimelineVersion value.'; export const CREATE_TIMELINE_ERROR_MESSAGE = - 'UPDATE timeline with POST is not allowed, please use PATCH instead'; + 'You cannot update timelines with POST. Use PATCH instead.'; export const CREATE_TEMPLATE_TIMELINE_ERROR_MESSAGE = - 'UPDATE template timeline with POST is not allowed, please use PATCH instead'; -export const EMPTY_TITLE_ERROR_MESSAGE = 'Title cannot be empty'; -export const UPDATE_STATUS_ERROR_MESSAGE = 'Update an immutable timeline is is not allowed'; + 'You cannot update Timeline templates with POST. Use PATCH instead.'; +export const EMPTY_TITLE_ERROR_MESSAGE = 'The title field cannot be empty.'; +export const UPDATE_STATUS_ERROR_MESSAGE = + 'You are not allowed to set the status field value to immutable.'; export const CREATE_TEMPLATE_TIMELINE_WITHOUT_VERSION_ERROR_MESSAGE = - 'Create template timeline without a valid templateTimelineVersion is not allowed. Please start from 1 to create a new template timeline'; -export const CREATE_WITH_INVALID_STATUS_ERROR_MESSAGE = 'Cannot create a draft timeline'; -export const NOT_ALLOW_UPDATE_STATUS_ERROR_MESSAGE = 'Update status is not allowed'; -export const NOT_ALLOW_UPDATE_TIMELINE_TYPE_ERROR_MESSAGE = 'Update timelineType is not allowed'; + 'You must provide a valid templateTimelineVersion value. Use 1 for new Timeline templates.'; +export const CREATE_WITH_INVALID_STATUS_ERROR_MESSAGE = + 'You are not allowed to set the status field value to draft.'; +export const NOT_ALLOW_UPDATE_STATUS_ERROR_MESSAGE = 'You are not allowed to set the status field.'; +export const NOT_ALLOW_UPDATE_TIMELINE_TYPE_ERROR_MESSAGE = + 'You cannot convert a Timeline template to a timeline, or a timeline to a Timeline template.'; export const UPDAT_TIMELINE_VIA_IMPORT_NOT_ALLOWED_ERROR_MESSAGE = - 'Update timeline via import is not allowed'; + 'You cannot update a timeline via imports. Use the UI to modify existing timelines.'; const isUpdatingStatus = ( isHandlingTemplateTimeline: boolean, diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 72d21400540fd..5fc5bf604351e 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -8737,30 +8737,25 @@ "xpack.licenseMgmt.licenseDashboard.licenseStatus.permanentActiveLicenseStatusDescription": "ご使用のライセンスには有効期限がありません。", "xpack.licenseMgmt.licenseDashboard.requestTrialExtension.extendTrialButtonLabel": "トライアルを延長", "xpack.licenseMgmt.licenseDashboard.requestTrialExtension.extendYourTrialTitle": "トライアルの延長", - "xpack.licenseMgmt.licenseDashboard.requestTrialExtension.howToContinueUsingPluginsDescription": "機械学習、高度なセキュリティ、その他の素晴らしい {platinumLicenseFeaturesLinkText} の使用を続けるには、今すぐ延長をお申し込みください。", - "xpack.licenseMgmt.licenseDashboard.requestTrialExtension.platinumLicenseFeaturesLinkText": "プラチナ機能", + "xpack.licenseMgmt.licenseDashboard.requestTrialExtension.howToContinueUsingPluginsDescription": "機械学習、高度なセキュリティ、その他の素晴らしい {subscriptionFeaturesLinkText} の使用を続けるには、今すぐ延長をお申し込みください。", "xpack.licenseMgmt.licenseDashboard.revertToBasic.acknowledgeModal.revertToBasicButtonLabel": "ベーシックに戻す", "xpack.licenseMgmt.licenseDashboard.revertToBasic.acknowledgeModalTitle": "ベーシックライセンスに戻す", "xpack.licenseMgmt.licenseDashboard.revertToBasic.confirmModal.cancelButtonLabel": "キャンセル", "xpack.licenseMgmt.licenseDashboard.revertToBasic.confirmModal.confirmButtonLabel": "確認", "xpack.licenseMgmt.licenseDashboard.revertToBasic.confirmModalTitle": "ベーシックライセンスに戻す確認", - "xpack.licenseMgmt.licenseDashboard.revertToBasic.platinumLicenseFeaturesLinkText": "プラチナ機能", - "xpack.licenseMgmt.licenseDashboard.revertToBasic.revertToFreeFeaturesDescription": "無料の機能に戻すと、セキュリティ、機械学習、その他 {platinumLicenseFeaturesLinkText} が利用できなくなります。", + "xpack.licenseMgmt.licenseDashboard.revertToBasic.revertToFreeFeaturesDescription": "無料の機能に戻すと、セキュリティ、機械学習、その他 {subscriptionFeaturesLinkText} が利用できなくなります。", "xpack.licenseMgmt.licenseDashboard.startTrial.confirmModal.cancelButtonLabel": "キャンセル", "xpack.licenseMgmt.licenseDashboard.startTrial.confirmModal.startTrialButtonLabel": "トライアルを開始", - "xpack.licenseMgmt.licenseDashboard.startTrial.confirmModalDescription": "このトライアルは、Elastic Stack の {platinumLicenseFeaturesLinkText} のフルセットが使えます。次の機能に直ちにアクセスできるようになります:", + "xpack.licenseMgmt.licenseDashboard.startTrial.confirmModalDescription": "このトライアルは、Elastic Stack の {subscriptionFeaturesLinkText} のフルセットが使えます。次の機能に直ちにアクセスできるようになります:", "xpack.licenseMgmt.licenseDashboard.startTrial.confirmModalDescription.alertingFeatureTitle": "アラート", "xpack.licenseMgmt.licenseDashboard.startTrial.confirmModalDescription.dataBaseConnectivityFeatureTitle": "{sqlDataBase} の {jdbcStandard} および {odbcStandard} 接続", "xpack.licenseMgmt.licenseDashboard.startTrial.confirmModalDescription.graphCapabilitiesFeatureTitle": "グラフ機能", "xpack.licenseMgmt.licenseDashboard.startTrial.confirmModalDescription.mashingLearningFeatureTitle": "機械学習", - "xpack.licenseMgmt.licenseDashboard.startTrial.confirmModalDescription.platinumLicenseFeaturesLinkText": "プラチナ機能", "xpack.licenseMgmt.licenseDashboard.startTrial.confirmModalDescription.securityDocumentationLinkText": "ドキュメンテーション", "xpack.licenseMgmt.licenseDashboard.startTrial.confirmModalDescription.securityFeaturesConfigurationDescription": "認証 ({authenticationTypeList})、フィールドとドキュメントレベルのセキュリティ、監査などの高度なセキュリティ機能には構成が必要です。手順は {securityDocumentationLinkText} をご覧ください。", "xpack.licenseMgmt.licenseDashboard.startTrial.confirmModalDescription.termsAndConditionsDescription": "このトライアルを開始することで、これらの {termsAndConditionsLinkText} が適用されることに同意したものとみなされます。", "xpack.licenseMgmt.licenseDashboard.startTrial.confirmModalDescription.termsAndConditionsLinkText": "諸条件", "xpack.licenseMgmt.licenseDashboard.startTrial.confirmModalTitle": "30 日間の無料トライアルの開始", - "xpack.licenseMgmt.licenseDashboard.startTrial.platinumFeaturesExperienceDescription": "機械学習、高度なセキュリティ、その他 {platinumLicenseFeaturesLinkText} をご体験ください。", - "xpack.licenseMgmt.licenseDashboard.startTrial.platinumLicenseFeaturesLinkText": "プラチナ機能", "xpack.licenseMgmt.licenseDashboard.startTrial.startTrialButtonLabel": "トライアルを開始", "xpack.licenseMgmt.licenseDashboard.startTrialTitle": "30 日間のトライアルの開始", "xpack.licenseMgmt.managementSectionDisplayName": "ライセンス管理", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 2fa281c8042ee..856a3ec852ffe 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -8741,30 +8741,25 @@ "xpack.licenseMgmt.licenseDashboard.licenseStatus.permanentActiveLicenseStatusDescription": "您的许可永不会过期。", "xpack.licenseMgmt.licenseDashboard.requestTrialExtension.extendTrialButtonLabel": "延期试用", "xpack.licenseMgmt.licenseDashboard.requestTrialExtension.extendYourTrialTitle": "延期您的试用", - "xpack.licenseMgmt.licenseDashboard.requestTrialExtension.howToContinueUsingPluginsDescription": "如果您想继续使用 Machine Learning、高级安全性以及我们其他超卓的{platinumLicenseFeaturesLinkText},请立即申请延期。", - "xpack.licenseMgmt.licenseDashboard.requestTrialExtension.platinumLicenseFeaturesLinkText": "白金级功能", + "xpack.licenseMgmt.licenseDashboard.requestTrialExtension.howToContinueUsingPluginsDescription": "如果您想继续使用 Machine Learning、高级安全性以及我们其他超卓的{subscriptionFeaturesLinkText},请立即申请延期。", "xpack.licenseMgmt.licenseDashboard.revertToBasic.acknowledgeModal.revertToBasicButtonLabel": "恢复为基础级", "xpack.licenseMgmt.licenseDashboard.revertToBasic.acknowledgeModalTitle": "恢复为基础级许可", "xpack.licenseMgmt.licenseDashboard.revertToBasic.confirmModal.cancelButtonLabel": "取消", "xpack.licenseMgmt.licenseDashboard.revertToBasic.confirmModal.confirmButtonLabel": "确认", "xpack.licenseMgmt.licenseDashboard.revertToBasic.confirmModalTitle": "确认恢复为基础级许可", - "xpack.licenseMgmt.licenseDashboard.revertToBasic.platinumLicenseFeaturesLinkText": "白金级功能", - "xpack.licenseMgmt.licenseDashboard.revertToBasic.revertToFreeFeaturesDescription": "您将恢复到我们的免费功能,并失去对 Machine Learning、高级安全性和其他{platinumLicenseFeaturesLinkText}的访问权限。", + "xpack.licenseMgmt.licenseDashboard.revertToBasic.revertToFreeFeaturesDescription": "您将恢复到我们的免费功能,并失去对 Machine Learning、高级安全性和其他{subscriptionFeaturesLinkText}的访问权限。", "xpack.licenseMgmt.licenseDashboard.startTrial.confirmModal.cancelButtonLabel": "取消", "xpack.licenseMgmt.licenseDashboard.startTrial.confirmModal.startTrialButtonLabel": "开始我的试用", - "xpack.licenseMgmt.licenseDashboard.startTrial.confirmModalDescription": "此试用具有 Elastic Stack 的全套{platinumLicenseFeaturesLinkText}您立即可以访问:", + "xpack.licenseMgmt.licenseDashboard.startTrial.confirmModalDescription": "此试用具有 Elastic Stack 的全套{subscriptionFeaturesLinkText}您立即可以访问:", "xpack.licenseMgmt.licenseDashboard.startTrial.confirmModalDescription.alertingFeatureTitle": "告警", "xpack.licenseMgmt.licenseDashboard.startTrial.confirmModalDescription.dataBaseConnectivityFeatureTitle": "{sqlDataBase} 的 {jdbcStandard} 和 {odbcStandard} 连接性", "xpack.licenseMgmt.licenseDashboard.startTrial.confirmModalDescription.graphCapabilitiesFeatureTitle": "图表功能", "xpack.licenseMgmt.licenseDashboard.startTrial.confirmModalDescription.mashingLearningFeatureTitle": "Machine Learning", - "xpack.licenseMgmt.licenseDashboard.startTrial.confirmModalDescription.platinumLicenseFeaturesLinkText": "白金级功能", "xpack.licenseMgmt.licenseDashboard.startTrial.confirmModalDescription.securityDocumentationLinkText": "文档", "xpack.licenseMgmt.licenseDashboard.startTrial.confirmModalDescription.securityFeaturesConfigurationDescription": "诸如身份验证 ({authenticationTypeList})、字段级和文档级安全以及审计等高级安全功能需要配置。有关说明,请参阅 {securityDocumentationLinkText}。", "xpack.licenseMgmt.licenseDashboard.startTrial.confirmModalDescription.termsAndConditionsDescription": "通过开始此试用,您同意其受这些{termsAndConditionsLinkText}约束。", "xpack.licenseMgmt.licenseDashboard.startTrial.confirmModalDescription.termsAndConditionsLinkText": "条款和条件", "xpack.licenseMgmt.licenseDashboard.startTrial.confirmModalTitle": "立即开始为期 30 天的免费试用", - "xpack.licenseMgmt.licenseDashboard.startTrial.platinumFeaturesExperienceDescription": "体验 Machine Learning、高级安全性以及我们所有其他{platinumLicenseFeaturesLinkText}能帮您做什么。", - "xpack.licenseMgmt.licenseDashboard.startTrial.platinumLicenseFeaturesLinkText": "白金级功能", "xpack.licenseMgmt.licenseDashboard.startTrial.startTrialButtonLabel": "开始试用", "xpack.licenseMgmt.licenseDashboard.startTrialTitle": "开始为期 30 天的试用", "xpack.licenseMgmt.managementSectionDisplayName": "许可管理", diff --git a/x-pack/plugins/uptime/public/apps/uptime_overview_fetcher.ts b/x-pack/plugins/uptime/public/apps/uptime_overview_fetcher.ts index bede391537ec5..89720b275c63d 100644 --- a/x-pack/plugins/uptime/public/apps/uptime_overview_fetcher.ts +++ b/x-pack/plugins/uptime/public/apps/uptime_overview_fetcher.ts @@ -29,29 +29,24 @@ export async function fetchUptimeOverviewData({ stats: { monitors: { type: 'number', - label: 'Monitors', value: snapshot.total, }, up: { type: 'number', - label: 'Up', value: snapshot.up, }, down: { type: 'number', - label: 'Down', value: snapshot.down, }, }, series: { up: { - label: 'Up', coordinates: pings.histogram.map((p) => { return { x: p.x!, y: p.upCount || 0 }; }), }, down: { - label: 'Down', coordinates: pings.histogram.map((p) => { return { x: p.x!, y: p.downCount || 0 }; }), diff --git a/x-pack/test/api_integration/apis/management/index_management/data_streams.ts b/x-pack/test/api_integration/apis/management/index_management/data_streams.ts index 74ab59f2ffdc6..afd1faadcde5f 100644 --- a/x-pack/test/api_integration/apis/management/index_management/data_streams.ts +++ b/x-pack/test/api_integration/apis/management/index_management/data_streams.ts @@ -53,7 +53,8 @@ export default function ({ getService }: FtrProviderContext) { await deleteComposableIndexTemplate(name); }; - describe('Data streams', function () { + // Failing ES Promotion: https://github.com/elastic/kibana/issues/71018 + describe.skip('Data streams', function () { describe('Get', () => { const testDataStreamName = 'test-data-stream'; diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/find_statuses.ts b/x-pack/test/detection_engine_api_integration/basic/tests/find_statuses.ts index cc6fa53939f60..c88b094879ac8 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/find_statuses.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/find_statuses.ts @@ -22,7 +22,8 @@ export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); const es = getService('es'); - describe('find_statuses', () => { + // FLAKY: https://github.com/elastic/kibana/issues/69632 + describe.skip('find_statuses', () => { beforeEach(async () => { await createSignalsIndex(supertest); }); diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_list.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_list.ts index 7531c406a4118..6971d9f523e7e 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_list.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_list.ts @@ -13,14 +13,14 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const esArchiver = getService('esArchiver'); const testSubjects = getService('testSubjects'); - describe('endpoint list', function () { + describe('host list', function () { this.tags('ciGroup7'); const sleep = (ms = 100) => new Promise((resolve) => setTimeout(resolve, ms)); describe('when there is data,', () => { before(async () => { await esArchiver.load('endpoint/metadata/api_feature', { useCreate: true }); - await pageObjects.endpoint.navigateToEndpointList(); + await pageObjects.endpoint.navigateToHostList(); }); after(async () => { await deleteMetadataStream(getService); @@ -28,7 +28,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { it('finds page title', async () => { const title = await testSubjects.getVisibleText('pageViewHeaderLeftTitle'); - expect(title).to.equal('Endpoints'); + expect(title).to.equal('Hosts'); }); it('displays table data', async () => { @@ -129,7 +129,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { // This set of tests fails the flyout does not open in the before() and will be fixed in soon describe.skip('has a url with a host id', () => { before(async () => { - await pageObjects.endpoint.navigateToEndpointList( + await pageObjects.endpoint.navigateToHostList( 'selected_host=fc0ff548-feba-41b6-8367-65e8790d0eaf' ); }); @@ -178,7 +178,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { before(async () => { // clear out the data and reload the page await deleteMetadataStream(getService); - await pageObjects.endpoint.navigateToEndpointList(); + await pageObjects.endpoint.navigateToHostList(); }); it('displays empty Policy Table page.', async () => { await testSubjects.existOrFail('emptyPolicyTable'); diff --git a/x-pack/test/security_solution_endpoint/config.ts b/x-pack/test/security_solution_endpoint/config.ts index f02a6bdcd51ed..2d94163fa1018 100644 --- a/x-pack/test/security_solution_endpoint/config.ts +++ b/x-pack/test/security_solution_endpoint/config.ts @@ -23,7 +23,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { apps: { ...xpackFunctionalConfig.get('apps'), ['securitySolutionManagement']: { - pathname: '/app/security/management', + pathname: '/app/security/administration', }, }, kbnTestServer: { diff --git a/x-pack/test/security_solution_endpoint/page_objects/endpoint_page.ts b/x-pack/test/security_solution_endpoint/page_objects/endpoint_page.ts index 7339903d74a0b..ae4320fc5395f 100644 --- a/x-pack/test/security_solution_endpoint/page_objects/endpoint_page.ts +++ b/x-pack/test/security_solution_endpoint/page_objects/endpoint_page.ts @@ -14,12 +14,12 @@ export function EndpointPageProvider({ getService, getPageObjects }: FtrProvider return { /** - * Navigate to the Endpoints list page + * Navigate to the Hosts list page */ - async navigateToEndpointList(searchParams?: string) { + async navigateToHostList(searchParams?: string) { await pageObjects.common.navigateToUrlWithBrowserHistory( 'securitySolutionManagement', - `/endpoints${searchParams ? `?${searchParams}` : ''}` + `/hosts${searchParams ? `?${searchParams}` : ''}` ); await pageObjects.header.waitUntilLoadingHasFinished(); },