diff --git a/.bazelrc.common b/.bazelrc.common index 20a41c4cde9a0d..115c0214b1a533 100644 --- a/.bazelrc.common +++ b/.bazelrc.common @@ -10,12 +10,13 @@ build --experimental_guard_against_concurrent_changes run --experimental_guard_against_concurrent_changes test --experimental_guard_against_concurrent_changes +query --experimental_guard_against_concurrent_changes ## Cache action outputs on disk so they persist across output_base and bazel shutdown (eg. changing branches) -build --disk_cache=~/.bazel-cache/disk-cache +common --disk_cache=~/.bazel-cache/disk-cache ## Bazel repo cache settings -build --repository_cache=~/.bazel-cache/repository-cache +common --repository_cache=~/.bazel-cache/repository-cache # Bazel will create symlinks from the workspace directory to output artifacts. # Build results will be placed in a directory called "bazel-bin" @@ -35,13 +36,16 @@ build --experimental_inprocess_symlink_creation # Incompatible flags to run with build --incompatible_no_implicit_file_export build --incompatible_restrict_string_escapes +query --incompatible_no_implicit_file_export +query --incompatible_restrict_string_escapes # Log configs ## different from default common --color=yes -build --show_task_finish -build --noshow_progress +common --noshow_progress +common --show_task_finish build --noshow_loading_progress +query --noshow_loading_progress build --show_result=0 # Specifies desired output mode for running tests. @@ -82,7 +86,7 @@ test:debug --test_output=streamed --test_strategy=exclusive --test_timeout=9999 run:debug --define=VERBOSE_LOGS=1 -- --node_options=--inspect-brk # The following option will change the build output of certain rules such as terser and may not be desirable in all cases # It will also output both the repo cache and action cache to a folder inside the repo -build:debug --compilation_mode=dbg --show_result=1 +build:debug --compilation_mode=dbg --show_result=0 --noshow_loading_progress --noshow_progress --show_task_finish # Turn off legacy external runfiles # This prevents accidentally depending on this feature, which Bazel will remove. diff --git a/.editorconfig b/.editorconfig index 7564b3596f0433..ec8a51f2314bea 100644 --- a/.editorconfig +++ b/.editorconfig @@ -12,6 +12,6 @@ insert_final_newline = true [package.json] insert_final_newline = false -[*.{md,asciidoc}] +[*.{md,mdx,asciidoc}] trim_trailing_whitespace = false insert_final_newline = false diff --git a/WORKSPACE.bazel b/WORKSPACE.bazel index e74c646eedeaf8..bd4d8801b0d4e2 100644 --- a/WORKSPACE.bazel +++ b/WORKSPACE.bazel @@ -52,9 +52,6 @@ node_repositories( # NOTE: FORCE_COLOR env var forces colors on non tty mode yarn_install( name = "npm", - environment = { - "FORCE_COLOR": "True", - }, package_json = "//:package.json", yarn_lock = "//:yarn.lock", data = [ diff --git a/docs/concepts/images/add-filter-popup.png b/docs/concepts/images/add-filter-popup.png new file mode 100644 index 00000000000000..f1b5b1ff3f6ca6 Binary files /dev/null and b/docs/concepts/images/add-filter-popup.png differ diff --git a/docs/concepts/images/global-search.png b/docs/concepts/images/global-search.png new file mode 100644 index 00000000000000..a867477938219a Binary files /dev/null and b/docs/concepts/images/global-search.png differ diff --git a/docs/concepts/images/refresh-every.png b/docs/concepts/images/refresh-every.png new file mode 100644 index 00000000000000..a0930a6c56a653 Binary files /dev/null and b/docs/concepts/images/refresh-every.png differ diff --git a/docs/concepts/images/save-icon.png b/docs/concepts/images/save-icon.png new file mode 100644 index 00000000000000..959c7ef8e1bb97 Binary files /dev/null and b/docs/concepts/images/save-icon.png differ diff --git a/docs/concepts/images/top-bar.png b/docs/concepts/images/top-bar.png new file mode 100755 index 00000000000000..73c872c29e6ae9 Binary files /dev/null and b/docs/concepts/images/top-bar.png differ diff --git a/docs/concepts/index.asciidoc b/docs/concepts/index.asciidoc new file mode 100644 index 00000000000000..70b8a5265ce8ad --- /dev/null +++ b/docs/concepts/index.asciidoc @@ -0,0 +1,149 @@ +[[kibana-concepts-analysts]] +== {kib} concepts for analysts +**_Learn the shared concepts for analyzing and visualizing your data_** + +As an analyst, you will use a combination of {kib} apps to analyze and +visualize your data. {kib} contains both general-purpose apps and apps for the +https://www.elastic.co/guide/en/enterprise-search/current/index.html[*Enterprise Search*], +{observability-guide}/observability-introduction.html[*Elastic Observability*], +and {security-guide}/es-overview.html[*Elastic Security*] solutions. +These apps share a common set of concepts. + +[float] +=== Three things to know about {es} + +You don't need to know everything about {es} to use {kib}, but the most important concepts follow: + +* *{es} makes JSON documents searchable and aggregatable.* The documents are +stored in an {ref}/documents-indices.html[index] or {ref}/data-streams.html[data stream], which represent one type of data. + +* **_Searchable_ means that you can filter the documents for conditions.** +For example, you can filter for data "within the last 7 days" or data that "contains the word {kib}". +{kib} provides many ways for you to construct filters, which are also called queries or search terms. + +* **_Aggregatable_ means that you can extract summaries from matching documents.** +The simplest aggregation is *count*, and it is frequently used in combination +with the *date histogram*, to see count over time. The *terms* aggregation shows the most frequent values. + +[float] +=== Finding your apps and objects + +{kib} offers a <> on every page that you can use to find any app or saved object. +Open the search bar using the keyboard shortcut Ctrl+/ on Windows and Linux, Command+/ on MacOS. + +[role="screenshot"] +image:concepts/images/global-search.png["Global search showing matches to apps and saved objects for the word visualize"] + +[float] +=== Accessing data with index patterns + +{kib} requires an index pattern to tell it which {es} data you want to access, +and whether the data is time-based. An index pattern can point to one or more {es} +data streams, indices, or index aliases by name. +For example, `logs-elasticsearch-prod-*` is an index pattern, +and it is time-based with a time field of `@timestamp`. The time field is not editable. + +Index patterns are typically created by an administrator when sending data to {es}. +You can <> in *Stack Management*, or by using a script +that accesses the {kib} API. + +{kib} uses the index pattern to show you a list of fields, such as +`event.duration`. You can customize the display name and format for each field. +For example, you can tell Kibana to display `event.duration` in seconds. +{kib} has <> for strings, +dates, geopoints, +and numbers. + +[float] +=== Searching your data + +{kib} provides you several ways to build search queries, +which will reduce the number of document matches that you get from {es}. +Each app in {kib} provides a time filter, and most apps also include semi-structured search and extra filters. + +[role="screenshot"] +image:concepts/images/top-bar.png["Time filter, semi-structured search, and filters in a {kib} app"] + +If you frequently use any of the search options, you can click the +save icon +image:concepts/images/save-icon.png["save icon"] next to the +semi-structured search to save or load a previously saved query. +The saved query will always contain the semi-structured search query, +and can optionally contain the time filter and extra filters. + +[float] +==== Time filter + +The <> limits the time range of data displayed. +In most cases, the time filter applies to the time field in the index pattern, +but some apps allow you to use a different time field. + +Using the time filter, you can configure a refresh rate to periodically +resubmit your searches. You can also click *Refresh* to resubmit the search. +This might be useful if you use {kib} to monitor the underlying data. + +[role="screenshot"] +image:concepts/images/refresh-every.png["section of time filter where you can configure a refresh rate"] + + +[float] +==== Semi-structured search + +Combine free text search with field-based search using the Kibana Query Language (KQL). +Type a search term to match across all fields, or start typing a field name to +get suggestions for field names and operators that you can use to build a structured query. +The semi-structured search will filter documents for matches, and only return matching documents. + +Following are some example KQL queries. For more detailed examples, refer to <>. + +[cols=2*] +|=== +| Exact phrase query +| `http.response.body.content.text:"quick brown fox"` + +| Terms query +| http.response.status_code:400 401 404 + +| Boolean query +| `response:200 or extension:php` + +| Range query +| `account_number >= 100 and items_sold <= 200` + +| Wildcard query +| `machine.os:win*` +|=== + +[float] +==== Additional filters with AND + +Structured filters are a more interactive way to create {es} queries, +and are commonly used when building dashboards that are shared by multiple analysts. +Each filter can be disabled, inverted, or pinned across all apps. +The structured filters are the only way to use the {es} Query DSL in JSON form, +or to target a specific index pattern for filtering. Each of the structured +filters is combined with AND logic on the rest of the query. + +[role="screenshot"] +image:concepts/images/add-filter-popup.png["Add filter popup"] + +[float] +=== Saving objects +{kib} lets you save objects for your own future use or for sharing with others. +Each <> type has different abilities. For example, you can save +your search queries made with *Discover*, which lets you: + +* Share a link to your search +* Download the full search results in CSV form +* Start an aggregated visualization using the same search query +* Embed the *Discover* search results into a dashboard +* Embed the *Discover* search results into a Canvas workpad + +For organization, every saved object can have a name, <>, and type. +Use the global search to quickly open a saved object. + +[float] +=== What's next? + +* Try the {kib} <>, which shows you how to put these concepts into action. +* Go to <> for instructions on searching your data. diff --git a/docs/concepts/save-query.asciidoc b/docs/concepts/save-query.asciidoc new file mode 100644 index 00000000000000..4f049d121bbef5 --- /dev/null +++ b/docs/concepts/save-query.asciidoc @@ -0,0 +1,39 @@ +[[save-load-delete-query]] +== Save a query +A saved query is a collection of query text and filters that you can +reuse in any app with a query bar, like <> and <>. Save a query when you want to: + +* Retrieve results from the same query at a later time without having to reenter the query text, add the filters or set the time filter +* View the results of the same query in multiple apps +* Share your query + +Saved queries don't include information specific to *Discover*, +such as the currently selected columns in the document table, the sort order, and the index pattern. +To save your current view of *Discover* for later retrieval and reuse, +create a <> instead. + +NOTE:: + +If you have insufficient privileges to save queries, the *Save current query* +button isn't visible in the saved query management popover. +For more information, see <> + +. Click *#* in the query bar. +. In the popover, click *Save current query*. ++ +[role="screenshot"] +image::discover/images/saved-query-management-component-all-privileges.png["Example of the saved query management popover with a list of saved queries with write access",width="80%"] ++ +. Enter a name, a description, and then select the filter options. +By default, filters are automatically included, but the time filter is not. ++ +[role="screenshot"] +image::discover/images/saved-query-save-form-default-filters.png["Example of the saved query management save form with the filters option included and the time filter option excluded",width="80%"] +. Click *Save*. +. To load a saved query into *Discover* or *Dashboard*, open the *Saved search* popover, and select the query. +. To manage your saved queries, use these actions in the popover: ++ +* Save as new: Save changes to the current query. +* Clear. Clear a query that is currently loaded in an app. +* Delete. You can’t recover a deleted query. +. To import and export saved queries, go to <>. diff --git a/docs/developer/getting-started/index.asciidoc b/docs/developer/getting-started/index.asciidoc index 5a16dac66c822d..d5fe7ebf470382 100644 --- a/docs/developer/getting-started/index.asciidoc +++ b/docs/developer/getting-started/index.asciidoc @@ -66,7 +66,8 @@ yarn kbn bootstrap --force-install (You can also run `yarn kbn` to see the other available commands. For more info about this tool, see -{kib-repo}tree/{branch}/packages/kbn-pm[{kib-repo}tree/{branch}/packages/kbn-pm].) +{kib-repo}tree/{branch}/packages/kbn-pm[{kib-repo}tree/{branch}/packages/kbn-pm]. If you want more +information about how to actively develop over packages please read <>) When switching branches which use different versions of npm packages you may need to run: @@ -169,3 +170,5 @@ include::debugging.asciidoc[leveloffset=+1] include::building-kibana.asciidoc[leveloffset=+1] include::development-plugin-resources.asciidoc[leveloffset=+1] + +include::monorepo-packages.asciidoc[leveloffset=+1] diff --git a/docs/developer/getting-started/monorepo-packages.asciidoc b/docs/developer/getting-started/monorepo-packages.asciidoc new file mode 100644 index 00000000000000..fc78729be5a692 --- /dev/null +++ b/docs/developer/getting-started/monorepo-packages.asciidoc @@ -0,0 +1,68 @@ +[[monorepo-packages]] +== {kib} Monorepo Packages + +Currently {kib} works as a monorepo composed by a core, plugins and packages. +The latest are located in a folder called `packages` and are pieces of software that +composes a set of features that can be isolated and reused across the entire repository. +They are also supposed to be able to imported just like any other `node_module`. + +Previously we relied solely on `@kbn/pm` to manage the development tools of those packages, but we are +now in the middle of migrating those responsibilities into Bazel. Every package already migrated +will contain in its root folder a `BUILD.bazel` file and other `build` and `watching` strategies should be used. + +Remember that any time you need to make sure the monorepo is ready to be used just run: + +[source,bash] +---- +yarn kbn bootstrap +---- + +[discrete] +=== Building Non Bazel Packages + +Non Bazel packages can be built independently with + +[source,bash] +---- +yarn kbn run build -i PACKAGE_NAME +---- + +[discrete] +=== Watching Non Bazel Packages + +Non Bazel packages can be watched independently with + +[source,bash] +---- +yarn kbn watch -i PACKAGE_NAME +---- + +[discrete] +=== Building Bazel Packages + +Bazel packages are built as a whole for now. You can use: + +[source,bash] +---- +yarn kbn build-bazel +---- + +[discrete] +=== Watching Bazel Packages + +Bazel packages are watched as a whole for now. You can use: + +[source,bash] +---- +yarn kbn watch-bazel +---- + + +[discrete] +=== List of Already Migrated Packages to Bazel + +- @elastic/datemath +- @kbn/apm-utils +- @kbn/config-schema +- @kbn/tinymath + diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index 0c40c2a8c4db94..353a77527d1d53 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -216,14 +216,27 @@ which also contains the timelion APIs and backend, look at the vis_type_timelion |<> -|An API for: - -- creating custom functionality (`actions`) -- creating custom user interaction events (`triggers`) -- attaching and detaching `actions` to `triggers`. -- emitting `trigger` events -- executing `actions` attached to a given `trigger`. -- exposing a context menu for the user to choose the appropriate action when there are multiple actions attached to a single trigger. +|UI Actions plugins provides API to manage *triggers* and *actions*. + +*Trigger* is an abstract description of user's intent to perform an action +(like user clicking on a value inside chart). It allows us to do runtime +binding between code from different plugins. For, example one such +trigger is when somebody applies filters on dashboard; another one is when +somebody opens a Dashboard panel context menu. + +*Actions* are pieces of code that execute in response to a trigger. For example, +to the dashboard filtering trigger multiple actions can be attached. Once a user +filters on the dashboard all possible actions are displayed to the user in a +popup menu and the user has to chose one. + +In general this plugin provides: + +- Creating custom functionality (actions). +- Creating custom user interaction events (triggers). +- Attaching and detaching actions to triggers. +- Emitting trigger events. +- Executing actions attached to a given trigger. +- Exposing a context menu for the user to choose the appropriate action when there are multiple actions attached to a single trigger. |{kib-repo}blob/{branch}/src/plugins/url_forwarding/README.md[urlForwarding] diff --git a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md index 860f7c3c748924..535bd8f11236df 100644 --- a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md +++ b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md @@ -88,6 +88,7 @@ readonly links: { readonly top_hits: string; }; readonly runtimeFields: { + readonly overview: string; readonly mapping: string; }; readonly scriptedFields: { @@ -107,6 +108,7 @@ readonly links: { }; readonly addData: string; readonly kibana: string; + readonly upgradeAssistant: string; readonly elasticsearch: Record; readonly siem: { readonly guide: string; @@ -114,9 +116,10 @@ readonly links: { }; readonly query: { readonly eql: string; + readonly kueryQuerySyntax: string; readonly luceneQuerySyntax: string; + readonly percolate: string; readonly queryDsl: string; - readonly kueryQuerySyntax: string; }; readonly date: { readonly dateMath: string; @@ -127,6 +130,7 @@ readonly links: { readonly transforms: Record; readonly visualize: Record; readonly apis: Readonly<{ + bulkIndexAlias: string; createIndex: string; createSnapshotLifecyclePolicy: string; createRoleMapping: string; @@ -143,6 +147,7 @@ readonly links: { painlessExecuteAPIContexts: string; putComponentTemplateMetadata: string; putSnapshotLifecyclePolicy: string; + putIndexTemplateV1: string; putWatch: string; simulatePipeline: string; updateTransform: string; diff --git a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md index a9cb6729b214e6..11814e7ca8b771 100644 --- a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md +++ b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md @@ -17,5 +17,5 @@ export interface DocLinksStart | --- | --- | --- | | [DOC\_LINK\_VERSION](./kibana-plugin-core-public.doclinksstart.doc_link_version.md) | string | | | [ELASTIC\_WEBSITE\_URL](./kibana-plugin-core-public.doclinksstart.elastic_website_url.md) | string | | -| [links](./kibana-plugin-core-public.doclinksstart.links.md) | {
readonly dashboard: {
readonly guide: string;
readonly drilldowns: string;
readonly drilldownsTriggerPicker: string;
readonly urlDrilldownTemplateSyntax: string;
readonly urlDrilldownVariables: string;
};
readonly discover: Record<string, string>;
readonly filebeat: {
readonly base: string;
readonly installation: string;
readonly configuration: string;
readonly elasticsearchOutput: string;
readonly elasticsearchModule: string;
readonly startup: string;
readonly exportedFields: string;
};
readonly auditbeat: {
readonly base: string;
};
readonly metricbeat: {
readonly base: string;
readonly configure: string;
readonly httpEndpoint: string;
readonly install: string;
readonly start: string;
};
readonly enterpriseSearch: {
readonly base: string;
readonly appSearchBase: string;
readonly workplaceSearchBase: string;
};
readonly heartbeat: {
readonly base: string;
};
readonly logstash: {
readonly base: string;
};
readonly functionbeat: {
readonly base: string;
};
readonly winlogbeat: {
readonly base: string;
};
readonly aggs: {
readonly composite: string;
readonly composite_missing_bucket: string;
readonly date_histogram: string;
readonly date_range: string;
readonly date_format_pattern: string;
readonly filter: string;
readonly filters: string;
readonly geohash_grid: string;
readonly histogram: string;
readonly ip_range: string;
readonly range: string;
readonly significant_terms: string;
readonly terms: string;
readonly avg: string;
readonly avg_bucket: string;
readonly max_bucket: string;
readonly min_bucket: string;
readonly sum_bucket: string;
readonly cardinality: string;
readonly count: string;
readonly cumulative_sum: string;
readonly derivative: string;
readonly geo_bounds: string;
readonly geo_centroid: string;
readonly max: string;
readonly median: string;
readonly min: string;
readonly moving_avg: string;
readonly percentile_ranks: string;
readonly serial_diff: string;
readonly std_dev: string;
readonly sum: string;
readonly top_hits: string;
};
readonly runtimeFields: {
readonly mapping: string;
};
readonly scriptedFields: {
readonly scriptFields: string;
readonly scriptAggs: string;
readonly painless: string;
readonly painlessApi: string;
readonly painlessLangSpec: string;
readonly painlessSyntax: string;
readonly painlessWalkthrough: string;
readonly luceneExpressions: string;
};
readonly indexPatterns: {
readonly introduction: string;
readonly fieldFormattersNumber: string;
readonly fieldFormattersString: string;
};
readonly addData: string;
readonly kibana: string;
readonly elasticsearch: Record<string, string>;
readonly siem: {
readonly guide: string;
readonly gettingStarted: string;
};
readonly query: {
readonly eql: string;
readonly luceneQuerySyntax: string;
readonly queryDsl: string;
readonly kueryQuerySyntax: string;
};
readonly date: {
readonly dateMath: string;
readonly dateMathIndexNames: string;
};
readonly management: Record<string, string>;
readonly ml: Record<string, string>;
readonly transforms: Record<string, string>;
readonly visualize: Record<string, string>;
readonly apis: Readonly<{
createIndex: string;
createSnapshotLifecyclePolicy: string;
createRoleMapping: string;
createRoleMappingTemplates: string;
createApiKey: string;
createPipeline: string;
createTransformRequest: string;
cronExpressions: string;
executeWatchActionModes: string;
indexExists: string;
openIndex: string;
putComponentTemplate: string;
painlessExecute: string;
painlessExecuteAPIContexts: string;
putComponentTemplateMetadata: string;
putSnapshotLifecyclePolicy: string;
putWatch: string;
simulatePipeline: string;
updateTransform: string;
}>;
readonly observability: Record<string, string>;
readonly alerting: Record<string, string>;
readonly maps: Record<string, string>;
readonly monitoring: Record<string, string>;
readonly security: Readonly<{
apiKeyServiceSettings: string;
clusterPrivileges: string;
elasticsearchSettings: string;
elasticsearchEnableSecurity: string;
indicesPrivileges: string;
kibanaTLS: string;
kibanaPrivileges: string;
mappingRoles: string;
mappingRolesFieldRules: string;
runAsPrivilege: string;
}>;
readonly watcher: Record<string, string>;
readonly ccs: Record<string, string>;
readonly plugins: Record<string, string>;
readonly snapshotRestore: Record<string, string>;
readonly ingest: Record<string, string>;
} | | +| [links](./kibana-plugin-core-public.doclinksstart.links.md) | {
readonly dashboard: {
readonly guide: string;
readonly drilldowns: string;
readonly drilldownsTriggerPicker: string;
readonly urlDrilldownTemplateSyntax: string;
readonly urlDrilldownVariables: string;
};
readonly discover: Record<string, string>;
readonly filebeat: {
readonly base: string;
readonly installation: string;
readonly configuration: string;
readonly elasticsearchOutput: string;
readonly elasticsearchModule: string;
readonly startup: string;
readonly exportedFields: string;
};
readonly auditbeat: {
readonly base: string;
};
readonly metricbeat: {
readonly base: string;
readonly configure: string;
readonly httpEndpoint: string;
readonly install: string;
readonly start: string;
};
readonly enterpriseSearch: {
readonly base: string;
readonly appSearchBase: string;
readonly workplaceSearchBase: string;
};
readonly heartbeat: {
readonly base: string;
};
readonly logstash: {
readonly base: string;
};
readonly functionbeat: {
readonly base: string;
};
readonly winlogbeat: {
readonly base: string;
};
readonly aggs: {
readonly composite: string;
readonly composite_missing_bucket: string;
readonly date_histogram: string;
readonly date_range: string;
readonly date_format_pattern: string;
readonly filter: string;
readonly filters: string;
readonly geohash_grid: string;
readonly histogram: string;
readonly ip_range: string;
readonly range: string;
readonly significant_terms: string;
readonly terms: string;
readonly avg: string;
readonly avg_bucket: string;
readonly max_bucket: string;
readonly min_bucket: string;
readonly sum_bucket: string;
readonly cardinality: string;
readonly count: string;
readonly cumulative_sum: string;
readonly derivative: string;
readonly geo_bounds: string;
readonly geo_centroid: string;
readonly max: string;
readonly median: string;
readonly min: string;
readonly moving_avg: string;
readonly percentile_ranks: string;
readonly serial_diff: string;
readonly std_dev: string;
readonly sum: string;
readonly top_hits: string;
};
readonly runtimeFields: {
readonly overview: string;
readonly mapping: string;
};
readonly scriptedFields: {
readonly scriptFields: string;
readonly scriptAggs: string;
readonly painless: string;
readonly painlessApi: string;
readonly painlessLangSpec: string;
readonly painlessSyntax: string;
readonly painlessWalkthrough: string;
readonly luceneExpressions: string;
};
readonly indexPatterns: {
readonly introduction: string;
readonly fieldFormattersNumber: string;
readonly fieldFormattersString: string;
};
readonly addData: string;
readonly kibana: string;
readonly elasticsearch: Record<string, string>;
readonly siem: {
readonly guide: string;
readonly gettingStarted: string;
};
readonly query: {
readonly eql: string;
readonly kueryQuerySyntax: string;
readonly luceneQuerySyntax: string;
readonly percolate: string;
readonly queryDsl: string;
};
readonly date: {
readonly dateMath: string;
readonly dateMathIndexNames: string;
};
readonly management: Record<string, string>;
readonly ml: Record<string, string>;
readonly transforms: Record<string, string>;
readonly visualize: Record<string, string>;
readonly apis: Readonly<{
bulkIndexAlias: string;
createIndex: string;
createSnapshotLifecyclePolicy: string;
createRoleMapping: string;
createRoleMappingTemplates: string;
createApiKey: string;
createPipeline: string;
createTransformRequest: string;
cronExpressions: string;
executeWatchActionModes: string;
indexExists: string;
openIndex: string;
putComponentTemplate: string;
painlessExecute: string;
painlessExecuteAPIContexts: string;
putComponentTemplateMetadata: string;
putSnapshotLifecyclePolicy: string;
putIndexTemplateV1: string;
putWatch: string;
simulatePipeline: string;
updateTransform: string;
}>;
readonly observability: Record<string, string>;
readonly alerting: Record<string, string>;
readonly maps: Record<string, string>;
readonly monitoring: Record<string, string>;
readonly security: Readonly<{
apiKeyServiceSettings: string;
clusterPrivileges: string;
elasticsearchSettings: string;
elasticsearchEnableSecurity: string;
indicesPrivileges: string;
kibanaTLS: string;
kibanaPrivileges: string;
mappingRoles: string;
mappingRolesFieldRules: string;
runAsPrivilege: string;
}>;
readonly watcher: Record<string, string>;
readonly ccs: Record<string, string>;
readonly plugins: Record<string, string>;
readonly snapshotRestore: Record<string, string>;
readonly ingest: Record<string, string>;
} | | diff --git a/docs/development/core/public/kibana-plugin-core-public.domaindeprecationdetails.domainid.md b/docs/development/core/public/kibana-plugin-core-public.domaindeprecationdetails.domainid.md new file mode 100644 index 00000000000000..b6d1f9386be8fb --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.domaindeprecationdetails.domainid.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [DomainDeprecationDetails](./kibana-plugin-core-public.domaindeprecationdetails.md) > [domainId](./kibana-plugin-core-public.domaindeprecationdetails.domainid.md) + +## DomainDeprecationDetails.domainId property + +Signature: + +```typescript +domainId: string; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.domaindeprecationdetails.md b/docs/development/core/public/kibana-plugin-core-public.domaindeprecationdetails.md new file mode 100644 index 00000000000000..93d715a11c5036 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.domaindeprecationdetails.md @@ -0,0 +1,18 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [DomainDeprecationDetails](./kibana-plugin-core-public.domaindeprecationdetails.md) + +## DomainDeprecationDetails interface + +Signature: + +```typescript +export interface DomainDeprecationDetails extends DeprecationsDetails +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [domainId](./kibana-plugin-core-public.domaindeprecationdetails.domainid.md) | string | | + diff --git a/docs/development/core/public/kibana-plugin-core-public.md b/docs/development/core/public/kibana-plugin-core-public.md index 32f17d5488f66c..39e554f5492ac2 100644 --- a/docs/development/core/public/kibana-plugin-core-public.md +++ b/docs/development/core/public/kibana-plugin-core-public.md @@ -61,6 +61,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [CoreStart](./kibana-plugin-core-public.corestart.md) | Core services exposed to the Plugin start lifecycle | | [DeprecationsServiceStart](./kibana-plugin-core-public.deprecationsservicestart.md) | DeprecationsService provides methods to fetch domain deprecation details from the Kibana server. | | [DocLinksStart](./kibana-plugin-core-public.doclinksstart.md) | | +| [DomainDeprecationDetails](./kibana-plugin-core-public.domaindeprecationdetails.md) | | | [ErrorToastOptions](./kibana-plugin-core-public.errortoastoptions.md) | Options available for [IToasts](./kibana-plugin-core-public.itoasts.md) error APIs. | | [FatalErrorInfo](./kibana-plugin-core-public.fatalerrorinfo.md) | Represents the message and stack of a fatal Error | | [FatalErrorsSetup](./kibana-plugin-core-public.fatalerrorssetup.md) | FatalErrors stop the Kibana Public Core and displays a fatal error screen with details about the Kibana build and the error. | diff --git a/docs/user/alerting/defining-rules.asciidoc b/docs/user/alerting/defining-rules.asciidoc index 63839cf465e983..05885f1af13ba2 100644 --- a/docs/user/alerting/defining-rules.asciidoc +++ b/docs/user/alerting/defining-rules.asciidoc @@ -28,8 +28,8 @@ Name:: The name of the rule. While this name does not have to be unique, th Tags:: A list of tag names that can be applied to a rule. Tags can help you organize and find rules, because tags appear in the rule listing in the management UI which is searchable by tag. Check every:: This value determines how frequently the rule conditions below are checked. Note that the timing of background rule checks are not guaranteed, particularly for intervals of less than 10 seconds. See <> for more information. Notify:: This value limits how often actions are repeated when an alert remains active across rule checks. See <> for more information. + -- **Only on status change**: Actions are not repeated when an alert remains active across checks. Actions run only when the rule status changes. -- **Every time rule is active**: Actions are repeated when an alert remains active across checks. +- **Only on status change**: Actions are not repeated when an alert remains active across checks. Actions run only when the alert status changes. +- **Every time alert is active**: Actions are repeated when an alert remains active across checks. - **On a custom action interval**: Actions are suppressed for the throttle interval, but repeat when an alert remains active across checks for a duration longer than the throttle interval. diff --git a/docs/user/index.asciidoc b/docs/user/index.asciidoc index d7e15258bf29bb..81ded1e54d8fdb 100644 --- a/docs/user/index.asciidoc +++ b/docs/user/index.asciidoc @@ -2,6 +2,8 @@ include::introduction.asciidoc[] include::whats-new.asciidoc[] +include::{kib-repo-dir}/concepts/index.asciidoc[] + include::{kib-repo-dir}/getting-started/quick-start-guide.asciidoc[] include::setup.asciidoc[] diff --git a/docs/user/monitoring/kibana-alerts.asciidoc b/docs/user/monitoring/kibana-alerts.asciidoc index bbc9c41c6ca5a6..2944921edd2eea 100644 --- a/docs/user/monitoring/kibana-alerts.asciidoc +++ b/docs/user/monitoring/kibana-alerts.asciidoc @@ -81,8 +81,8 @@ by running checks on a schedule time of 1 minute with a re-notify interval of 6 [[kibana-alerts-large-shard-size]] == Large shard size -This alert is triggered if a large (primary) shard size is found on any of the -specified index patterns. The trigger condition is met if an index's shard size is +This alert is triggered if a large average shard size (across associated primaries) is found on any of the +specified index patterns. The trigger condition is met if an index's average shard size is 55gb or higher in the last 5 minutes. The alert is grouped across all indices that match the default pattern of `*` by running checks on a schedule time of 1 minute with a re-notify interval of 12 hours. diff --git a/docs/user/security/api-keys/images/api-key-invalidate.png b/docs/user/security/api-keys/images/api-key-invalidate.png deleted file mode 100755 index c925679ab24bc6..00000000000000 Binary files a/docs/user/security/api-keys/images/api-key-invalidate.png and /dev/null differ diff --git a/docs/user/security/api-keys/images/api-keys.png b/docs/user/security/api-keys/images/api-keys.png old mode 100755 new mode 100644 index df74f245676d9c..ec4111a4432534 Binary files a/docs/user/security/api-keys/images/api-keys.png and b/docs/user/security/api-keys/images/api-keys.png differ diff --git a/docs/user/security/api-keys/images/create-api-key.png b/docs/user/security/api-keys/images/create-api-key.png new file mode 100644 index 00000000000000..c763dcbfa53f86 Binary files /dev/null and b/docs/user/security/api-keys/images/create-api-key.png differ diff --git a/docs/user/security/api-keys/index.asciidoc b/docs/user/security/api-keys/index.asciidoc index 3edd1f8f9c63d4..6b92ab3c6656a0 100644 --- a/docs/user/security/api-keys/index.asciidoc +++ b/docs/user/security/api-keys/index.asciidoc @@ -4,7 +4,7 @@ API keys enable you to create secondary credentials so that you can send -requests on behalf of the user. Secondary credentials have +requests on behalf of a user. Secondary credentials have the same or lower access rights. For example, if you extract data from an {es} cluster on a daily @@ -14,8 +14,7 @@ and then put the API credentials into a cron job. Or, you might create API keys to automate ingestion of new data from remote sources, without a live user interaction. -You can create API keys from the {kib} Console. To view and invalidate -API keys, open the main menu, then click *Stack Management > API Keys*. +To manage API keys, open the main menu, then click *Stack Management > API Keys*. [role="screenshot"] image:user/security/api-keys/images/api-keys.png["API Keys UI"] @@ -46,37 +45,15 @@ cluster privileges to use API keys in {kib}. To manage roles, open the main menu [float] [[create-api-key]] === Create an API key -You can {ref}/security-api-create-api-key.html[create an API key] from -the {kib} Console. This example shows how to create an API key -to authenticate to a <>. - -[source,js] -POST /_security/api_key -{ - "name": "kibana_api_key" -} - -This creates an API key with the -name `kibana_api_key`. API key -names must be globally unique. -An expiration date is optional and follows -{ref}/common-options.html#time-units[{es} time unit format]. -When an expiration is not provided, the API key does not expire. - -The response should look something like this: - -[source,js] -{ - "id" : "XFcbCnIBnbwqt2o79G4q", - "name" : "kibana_api_key", - "api_key" : "FD6P5UA4QCWlZZQhYF3YGw" -} - -Now, you can use the API key to request {kib} roles. You'll need to send a request with a -`Authorization` header with a value having the prefix `ApiKey` followed by the credentials, -where credentials is the base64 encoding of `id` and `api_key` joined by a colon. For example: - -[source,js] + +To create an API key, open the main menu, then click *Stack Management > API Keys > Create API key*. + +[role="screenshot"] +image:user/security/api-keys/images/create-api-key.png["Create API Key UI"] + +Once created, you can copy the API key (Base64 encoded) and use it to send requests to {es} on your behalf. For example: + +[source,bash] curl --location --request GET 'http://localhost:5601/api/security/role' \ --header 'Content-Type: application/json;charset=UTF-8' \ --header 'kbn-xsrf: true' \ @@ -84,20 +61,16 @@ curl --location --request GET 'http://localhost:5601/api/security/role' \ [float] [[view-api-keys]] -=== View and invalidate API keys -The *API Keys* feature in Kibana lists your API keys, including the name, date created, -and expiration date. If an API key expires, its status changes from `Active` to `Expired`. +=== View and delete API keys + +The *API Keys* feature in Kibana lists your API keys, including the name, date created, and status. If an API key expires, its status changes from `Active` to `Expired`. If you have `manage_security` or `manage_api_key` permissions, you can view the API keys of all users, and see which API key was created by which user in which realm. If you have only the `manage_own_api_key` permission, you see only a list of your own keys. -You can invalidate API keys individually or in bulk. -Invalidated keys are deleted in batch after seven days. - -[role="screenshot"] -image:user/security/api-keys/images/api-key-invalidate.png["API Keys invalidate"] +You can delete API keys individually or in bulk. You cannot modify an API key. If you need additional privileges, you must create a new key with the desired configuration and invalidate the old key. diff --git a/package.json b/package.json index 9bddca46654674..ff7f76df4aee55 100644 --- a/package.json +++ b/package.json @@ -102,7 +102,7 @@ "@elastic/datemath": "link:bazel-bin/packages/elastic-datemath/npm_module", "@elastic/elasticsearch": "npm:@elastic/elasticsearch-canary@^8.0.0-canary.4", "@elastic/ems-client": "7.12.0", - "@elastic/eui": "31.10.0", + "@elastic/eui": "32.0.4", "@elastic/filesaver": "1.1.2", "@elastic/good": "^9.0.1-kibana3", "@elastic/maki": "6.3.0", @@ -125,9 +125,9 @@ "@kbn/ace": "link:packages/kbn-ace", "@kbn/analytics": "link:packages/kbn-analytics", "@kbn/apm-config-loader": "link:packages/kbn-apm-config-loader", - "@kbn/apm-utils": "link:packages/kbn-apm-utils", + "@kbn/apm-utils": "link:bazel-bin/packages/kbn-apm-utils/npm_module", "@kbn/config": "link:packages/kbn-config", - "@kbn/config-schema": "link:packages/kbn-config-schema", + "@kbn/config-schema": "link:bazel-bin/packages/kbn-config-schema/npm_module", "@kbn/crypto": "link:packages/kbn-crypto", "@kbn/i18n": "link:packages/kbn-i18n", "@kbn/interpreter": "link:packages/kbn-interpreter", @@ -138,7 +138,7 @@ "@kbn/server-http-tools": "link:packages/kbn-server-http-tools", "@kbn/server-route-repository": "link:packages/kbn-server-route-repository", "@kbn/std": "link:packages/kbn-std", - "@kbn/tinymath": "link:packages/kbn-tinymath", + "@kbn/tinymath": "link:bazel-bin/packages/kbn-tinymath/npm_module", "@kbn/ui-framework": "link:packages/kbn-ui-framework", "@kbn/ui-shared-deps": "link:packages/kbn-ui-shared-deps", "@kbn/utility-types": "link:packages/kbn-utility-types", diff --git a/packages/BUILD.bazel b/packages/BUILD.bazel index 31894fcb1bb5db..182013c356bb0b 100644 --- a/packages/BUILD.bazel +++ b/packages/BUILD.bazel @@ -3,6 +3,9 @@ filegroup( name = "build", srcs = [ - "//packages/elastic-datemath:build" + "//packages/elastic-datemath:build", + "//packages/kbn-apm-utils:build", + "//packages/kbn-config-schema:build", + "//packages/kbn-tinymath:build", ], ) diff --git a/packages/elastic-datemath/BUILD.bazel b/packages/elastic-datemath/BUILD.bazel index 6a80556d4eed51..bc0c1412ef5f15 100644 --- a/packages/elastic-datemath/BUILD.bazel +++ b/packages/elastic-datemath/BUILD.bazel @@ -4,15 +4,15 @@ load("@build_bazel_rules_nodejs//:index.bzl", "js_library", "pkg_npm") PKG_BASE_NAME = "elastic-datemath" PKG_REQUIRE_NAME = "@elastic/datemath" -SOURCE_FILES = [ +SOURCE_FILES = glob([ "src/index.ts", -] +]) SRCS = SOURCE_FILES filegroup( name = "srcs", - srcs = glob(SOURCE_FILES), + srcs = SRCS, ) NPM_MODULE_EXTRA_FILES = [ @@ -40,6 +40,7 @@ ts_config( ts_project( name = "tsc", + args = ['--pretty'], srcs = SRCS, deps = DEPS, declaration = True, diff --git a/packages/elastic-datemath/tsconfig.json b/packages/elastic-datemath/tsconfig.json index d0fa806ed411b4..6e7219c7a82455 100644 --- a/packages/elastic-datemath/tsconfig.json +++ b/packages/elastic-datemath/tsconfig.json @@ -3,6 +3,7 @@ "compilerOptions": { "declaration": true, "declarationMap": true, + "incremental": true, "outDir": "target", "rootDir": "src", "sourceMap": true, diff --git a/packages/kbn-apm-utils/BUILD.bazel b/packages/kbn-apm-utils/BUILD.bazel new file mode 100644 index 00000000000000..63adf2b77b5163 --- /dev/null +++ b/packages/kbn-apm-utils/BUILD.bazel @@ -0,0 +1,76 @@ +load("@npm//@bazel/typescript:index.bzl", "ts_config", "ts_project") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library", "pkg_npm") + +PKG_BASE_NAME = "kbn-apm-utils" +PKG_REQUIRE_NAME = "@kbn/apm-utils" + +SOURCE_FILES = glob([ + "src/index.ts", +]) + +SRCS = SOURCE_FILES + +filegroup( + name = "srcs", + srcs = SRCS, +) + +NPM_MODULE_EXTRA_FILES = [ + "package.json", +] + +SRC_DEPS = [ + "@npm//elastic-apm-node", +] + +TYPES_DEPS = [ + "@npm//@types/node", +] + +DEPS = SRC_DEPS + TYPES_DEPS + +ts_config( + name = "tsconfig", + src = "tsconfig.json", + deps = [ + "//:tsconfig.base.json", + ], +) + +ts_project( + name = "tsc", + args = ['--pretty'], + srcs = SRCS, + deps = DEPS, + declaration = True, + declaration_map = True, + incremental = True, + out_dir = "target", + source_map = True, + root_dir = "src", + tsconfig = ":tsconfig", +) + +js_library( + name = PKG_BASE_NAME, + srcs = [], + deps = [":tsc"] + DEPS, + package_name = PKG_REQUIRE_NAME, + visibility = ["//visibility:public"], +) + +pkg_npm( + name = "npm_module", + srcs = NPM_MODULE_EXTRA_FILES, + deps = [ + ":%s" % PKG_BASE_NAME, + ] +) + +filegroup( + name = "build", + srcs = [ + ":npm_module", + ], + visibility = ["//visibility:public"], +) diff --git a/packages/kbn-apm-utils/package.json b/packages/kbn-apm-utils/package.json index d414b94cb39789..04b8e2ed831b39 100644 --- a/packages/kbn-apm-utils/package.json +++ b/packages/kbn-apm-utils/package.json @@ -4,10 +4,5 @@ "types": "./target/index.d.ts", "version": "1.0.0", "license": "SSPL-1.0 OR Elastic License 2.0", - "private": true, - "scripts": { - "build": "../../node_modules/.bin/tsc", - "kbn:bootstrap": "yarn build", - "kbn:watch": "yarn build --watch" - } + "private": true } diff --git a/packages/kbn-apm-utils/tsconfig.json b/packages/kbn-apm-utils/tsconfig.json index e08769aab65436..3ce240059486a7 100644 --- a/packages/kbn-apm-utils/tsconfig.json +++ b/packages/kbn-apm-utils/tsconfig.json @@ -1,11 +1,11 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { - "incremental": false, - "outDir": "./target", - "stripInternal": false, "declaration": true, "declarationMap": true, + "incremental": true, + "outDir": "target", + "rootDir": "src", "sourceMap": true, "sourceRoot": "../../../../packages/kbn-apm-utils/src", "types": [ diff --git a/packages/kbn-cli-dev-mode/package.json b/packages/kbn-cli-dev-mode/package.json index 2ee9831e960842..1ea319ef3601c9 100644 --- a/packages/kbn-cli-dev-mode/package.json +++ b/packages/kbn-cli-dev-mode/package.json @@ -15,7 +15,6 @@ }, "dependencies": { "@kbn/config": "link:../kbn-config", - "@kbn/config-schema": "link:../kbn-config-schema", "@kbn/logging": "link:../kbn-logging", "@kbn/server-http-tools": "link:../kbn-server-http-tools", "@kbn/optimizer": "link:../kbn-optimizer", diff --git a/packages/kbn-config-schema/BUILD.bazel b/packages/kbn-config-schema/BUILD.bazel new file mode 100644 index 00000000000000..5dcbd9e5a802a2 --- /dev/null +++ b/packages/kbn-config-schema/BUILD.bazel @@ -0,0 +1,86 @@ +load("@npm//@bazel/typescript:index.bzl", "ts_config", "ts_project") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library", "pkg_npm") + +PKG_BASE_NAME = "kbn-config-schema" +PKG_REQUIRE_NAME = "@kbn/config-schema" + +SOURCE_FILES = glob([ + "src/**/*.ts", + "types/joi.d.ts" +]) + +SRCS = SOURCE_FILES + +filegroup( + name = "srcs", + srcs = SRCS, +) + +NPM_MODULE_EXTRA_FILES = [ + "package.json", + "README.md", +] + +SRC_DEPS = [ + "@npm//joi", + "@npm//lodash", + "@npm//moment", + "@npm//tsd", + "@npm//type-detect", +] + +TYPES_DEPS = [ + "@npm//@types/jest", + "@npm//@types/joi", + "@npm//@types/lodash", + "@npm//@types/node", + "@npm//@types/type-detect", +] + +DEPS = SRC_DEPS + TYPES_DEPS + +ts_config( + name = "tsconfig", + src = "tsconfig.json", + deps = [ + "//:tsconfig.base.json", + ], +) + +ts_project( + name = "tsc", + args = ['--pretty'], + srcs = SRCS, + deps = DEPS, + declaration = True, + declaration_map = True, + incremental = True, + out_dir = "target", + source_map = True, + root_dir = "src", + tsconfig = ":tsconfig", +) + +js_library( + name = PKG_BASE_NAME, + srcs = [], + deps = [":tsc"] + DEPS, + package_name = PKG_REQUIRE_NAME, + visibility = ["//visibility:public"], +) + +pkg_npm( + name = "npm_module", + srcs = NPM_MODULE_EXTRA_FILES, + deps = [ + ":%s" % PKG_BASE_NAME, + ] +) + +filegroup( + name = "build", + srcs = [ + ":npm_module", + ], + visibility = ["//visibility:public"], +) diff --git a/packages/kbn-config-schema/package.json b/packages/kbn-config-schema/package.json index a47dee88db5887..85b52f5d75533d 100644 --- a/packages/kbn-config-schema/package.json +++ b/packages/kbn-config-schema/package.json @@ -1,12 +1,8 @@ { "name": "@kbn/config-schema", - "main": "./target/out/index.js", - "types": "./target/types/index.d.ts", + "main": "./target/index.js", + "types": "./target/index.d.ts", "version": "1.0.0", "license": "SSPL-1.0 OR Elastic License 2.0", - "private": true, - "scripts": { - "build": "../../node_modules/.bin/tsc", - "kbn:bootstrap": "yarn build" - } + "private": true } \ No newline at end of file diff --git a/packages/kbn-config-schema/tsconfig.json b/packages/kbn-config-schema/tsconfig.json index d33683acded162..5490f37a943fc9 100644 --- a/packages/kbn-config-schema/tsconfig.json +++ b/packages/kbn-config-schema/tsconfig.json @@ -1,14 +1,14 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { - "incremental": false, - "outDir": "./target/out", - "declarationDir": "./target/types", - "stripInternal": true, "declaration": true, "declarationMap": true, + "incremental": true, + "outDir": "target", + "rootDir": "src", "sourceMap": true, "sourceRoot": "../../../../../packages/kbn-config-schema/src", + "stripInternal": true, "types": [ "jest", "node" diff --git a/packages/kbn-config/package.json b/packages/kbn-config/package.json index e71175034787aa..8093b6ac0d211d 100644 --- a/packages/kbn-config/package.json +++ b/packages/kbn-config/package.json @@ -11,7 +11,6 @@ }, "dependencies": { "@elastic/safer-lodash-set": "link:../elastic-safer-lodash-set", - "@kbn/config-schema": "link:../kbn-config-schema", "@kbn/logging": "link:../kbn-logging", "@kbn/std": "link:../kbn-std" }, diff --git a/packages/kbn-legacy-logging/package.json b/packages/kbn-legacy-logging/package.json index 96edeccad6658a..9450fd39607ea9 100644 --- a/packages/kbn-legacy-logging/package.json +++ b/packages/kbn-legacy-logging/package.json @@ -11,7 +11,6 @@ "kbn:watch": "yarn build --watch" }, "dependencies": { - "@kbn/utils": "link:../kbn-utils", - "@kbn/config-schema": "link:../kbn-config-schema" + "@kbn/utils": "link:../kbn-utils" } } diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index 249183d4b1e316..e114e3e9300168 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -100,7 +100,7 @@ pageLoadAssetSize: watcher: 43598 runtimeFields: 41752 stackAlerts: 29684 - presentationUtil: 28545 + presentationUtil: 49767 spacesOss: 18817 indexPatternFieldEditor: 90489 osquery: 107090 diff --git a/packages/kbn-plugin-generator/src/render_template.ts b/packages/kbn-plugin-generator/src/render_template.ts index 282a547318d288..1a9716f1f1ba5a 100644 --- a/packages/kbn-plugin-generator/src/render_template.ts +++ b/packages/kbn-plugin-generator/src/render_template.ts @@ -84,7 +84,7 @@ export async function renderTemplates({ answers.ui ? [] : 'public/**/*', answers.ui && !answers.internal ? [] : ['translations/**/*', 'i18nrc.json'], answers.server ? [] : 'server/**/*', - !answers.internal ? [] : ['eslintrc.js', 'tsconfig.json', 'package.json', '.gitignore'] + !answers.internal ? [] : ['.eslintrc.js', 'tsconfig.json', 'package.json', '.gitignore'] ) ), diff --git a/packages/kbn-pm/dist/index.js b/packages/kbn-pm/dist/index.js index 7c5d0390d9fba2..e6cdd52686656a 100644 --- a/packages/kbn-pm/dist/index.js +++ b/packages/kbn-pm/dist/index.js @@ -94,7 +94,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _cli__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "run", function() { return _cli__WEBPACK_IMPORTED_MODULE_0__["run"]; }); -/* harmony import */ var _production__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(563); +/* harmony import */ var _production__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(565); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "buildBazelProductionProjects", function() { return _production__WEBPACK_IMPORTED_MODULE_1__["buildBazelProductionProjects"]; }); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "buildNonBazelProductionProjects", function() { return _production__WEBPACK_IMPORTED_MODULE_1__["buildNonBazelProductionProjects"]; }); @@ -108,7 +108,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _utils_package_json__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(251); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "transformDependencies", function() { return _utils_package_json__WEBPACK_IMPORTED_MODULE_4__["transformDependencies"]; }); -/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(562); +/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(564); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "getProjectPaths", function() { return _config__WEBPACK_IMPORTED_MODULE_5__["getProjectPaths"]; }); /* @@ -141,7 +141,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _kbn_dev_utils_tooling_log__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(5); /* harmony import */ var _kbn_dev_utils_tooling_log__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(_kbn_dev_utils_tooling_log__WEBPACK_IMPORTED_MODULE_3__); /* harmony import */ var _commands__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(128); -/* harmony import */ var _run__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(514); +/* harmony import */ var _run__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(516); /* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(246); /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one @@ -8833,10 +8833,12 @@ exports.ToolingLogCollectingWriter = ToolingLogCollectingWriter; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "commands", function() { return commands; }); /* harmony import */ var _bootstrap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(129); -/* harmony import */ var _clean__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(478); -/* harmony import */ var _reset__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(510); -/* harmony import */ var _run__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(511); -/* harmony import */ var _watch__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(512); +/* harmony import */ var _build_bazel__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(478); +/* harmony import */ var _clean__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(479); +/* harmony import */ var _reset__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(511); +/* harmony import */ var _run__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(512); +/* harmony import */ var _watch__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(513); +/* harmony import */ var _watch_bazel__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(515); /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License @@ -8849,12 +8851,16 @@ __webpack_require__.r(__webpack_exports__); + + const commands = { bootstrap: _bootstrap__WEBPACK_IMPORTED_MODULE_0__["BootstrapCommand"], - clean: _clean__WEBPACK_IMPORTED_MODULE_1__["CleanCommand"], - reset: _reset__WEBPACK_IMPORTED_MODULE_2__["ResetCommand"], - run: _run__WEBPACK_IMPORTED_MODULE_3__["RunCommand"], - watch: _watch__WEBPACK_IMPORTED_MODULE_4__["WatchCommand"] + 'build-bazel': _build_bazel__WEBPACK_IMPORTED_MODULE_1__["BuildBazelCommand"], + clean: _clean__WEBPACK_IMPORTED_MODULE_2__["CleanCommand"], + reset: _reset__WEBPACK_IMPORTED_MODULE_3__["ResetCommand"], + run: _run__WEBPACK_IMPORTED_MODULE_4__["RunCommand"], + watch: _watch__WEBPACK_IMPORTED_MODULE_5__["WatchCommand"], + 'watch-bazel': _watch_bazel__WEBPACK_IMPORTED_MODULE_6__["WatchBazelCommand"] }; /***/ }), @@ -8933,7 +8939,7 @@ const BootstrapCommand = { await Object(_utils_bazel__WEBPACK_IMPORTED_MODULE_9__["runBazel"])(['run', '@nodejs//:yarn'], runOffline); } - await Object(_utils_bazel__WEBPACK_IMPORTED_MODULE_9__["runBazel"])(['build', '//packages:build'], runOffline); // Install monorepo npm dependencies outside of the Bazel managed ones + await Object(_utils_bazel__WEBPACK_IMPORTED_MODULE_9__["runBazel"])(['build', '//packages:build', '--show_result=1'], runOffline); // Install monorepo npm dependencies outside of the Bazel managed ones for (const batch of batchedNonBazelProjects) { for (const project of batch) { @@ -29748,15 +29754,14 @@ var gitHosts = __webpack_require__(284) var GitHost = module.exports = __webpack_require__(285) var protocolToRepresentationMap = { - 'git+ssh': 'sshurl', - 'git+https': 'https', - 'ssh': 'sshurl', - 'git': 'git' + 'git+ssh:': 'sshurl', + 'git+https:': 'https', + 'ssh:': 'sshurl', + 'git:': 'git' } function protocolToRepresentation (protocol) { - if (protocol.substr(-1) === ':') protocol = protocol.slice(0, -1) - return protocolToRepresentationMap[protocol] || protocol + return protocolToRepresentationMap[protocol] || protocol.slice(0, -1) } var authProtocols = { @@ -29770,6 +29775,7 @@ var authProtocols = { var cache = {} module.exports.fromUrl = function (giturl, opts) { + if (typeof giturl !== 'string') return var key = giturl + JSON.stringify(opts || {}) if (!(key in cache)) { @@ -29785,13 +29791,13 @@ function fromUrl (giturl, opts) { isGitHubShorthand(giturl) ? 'github:' + giturl : giturl ) var parsed = parseGitUrl(url) - var shortcutMatch = url.match(new RegExp('^([^:]+):(?:(?:[^@:]+(?:[^@]+)?@)?([^/]*))[/](.+?)(?:[.]git)?($|#)')) + var shortcutMatch = url.match(/^([^:]+):(?:[^@]+@)?(?:([^/]*)\/)?([^#]+)/) var matches = Object.keys(gitHosts).map(function (gitHostName) { try { var gitHostInfo = gitHosts[gitHostName] var auth = null if (parsed.auth && authProtocols[parsed.protocol]) { - auth = decodeURIComponent(parsed.auth) + auth = parsed.auth } var committish = parsed.hash ? decodeURIComponent(parsed.hash.substr(1)) : null var user = null @@ -29799,22 +29805,27 @@ function fromUrl (giturl, opts) { var defaultRepresentation = null if (shortcutMatch && shortcutMatch[1] === gitHostName) { user = shortcutMatch[2] && decodeURIComponent(shortcutMatch[2]) - project = decodeURIComponent(shortcutMatch[3]) + project = decodeURIComponent(shortcutMatch[3].replace(/\.git$/, '')) defaultRepresentation = 'shortcut' } else { - if (parsed.host !== gitHostInfo.domain) return + if (parsed.host && parsed.host !== gitHostInfo.domain && parsed.host.replace(/^www[.]/, '') !== gitHostInfo.domain) return if (!gitHostInfo.protocols_re.test(parsed.protocol)) return if (!parsed.path) return var pathmatch = gitHostInfo.pathmatch var matched = parsed.path.match(pathmatch) if (!matched) return - if (matched[1] != null) user = decodeURIComponent(matched[1].replace(/^:/, '')) - if (matched[2] != null) project = decodeURIComponent(matched[2]) + /* istanbul ignore else */ + if (matched[1] !== null && matched[1] !== undefined) { + user = decodeURIComponent(matched[1].replace(/^:/, '')) + } + project = decodeURIComponent(matched[2]) defaultRepresentation = protocolToRepresentation(parsed.protocol) } return new GitHost(gitHostName, user, auth, project, committish, defaultRepresentation, opts) } catch (ex) { - if (!(ex instanceof URIError)) throw ex + /* istanbul ignore else */ + if (ex instanceof URIError) { + } else throw ex } }).filter(function (gitHostInfo) { return gitHostInfo }) if (matches.length !== 1) return @@ -29844,9 +29855,31 @@ function fixupUnqualifiedGist (giturl) { } function parseGitUrl (giturl) { - if (typeof giturl !== 'string') giturl = '' + giturl var matched = giturl.match(/^([^@]+)@([^:/]+):[/]?((?:[^/]+[/])?[^/]+?)(?:[.]git)?(#.*)?$/) - if (!matched) return url.parse(giturl) + if (!matched) { + var legacy = url.parse(giturl) + // If we don't have url.URL, then sorry, this is just not fixable. + // This affects Node <= 6.12. + if (legacy.auth && typeof url.URL === 'function') { + // git urls can be in the form of scp-style/ssh-connect strings, like + // git+ssh://user@host.com:some/path, which the legacy url parser + // supports, but WhatWG url.URL class does not. However, the legacy + // parser de-urlencodes the username and password, so something like + // https://user%3An%40me:p%40ss%3Aword@x.com/ becomes + // https://user:n@me:p@ss:word@x.com/ which is all kinds of wrong. + // Pull off just the auth and host, so we dont' get the confusing + // scp-style URL, then pass that to the WhatWG parser to get the + // auth properly escaped. + var authmatch = giturl.match(/[^@]+@[^:/]+/) + /* istanbul ignore else - this should be impossible */ + if (authmatch) { + var whatwg = new url.URL(authmatch[0]) + legacy.auth = whatwg.username || '' + if (whatwg.password) legacy.auth += ':' + whatwg.password + } + } + return legacy + } return { protocol: 'git+ssh:', slashes: true, @@ -29888,7 +29921,7 @@ var gitHosts = module.exports = { 'filetemplate': 'https://{auth@}raw.githubusercontent.com/{user}/{project}/{committish}/{path}', 'bugstemplate': 'https://{domain}/{user}/{project}/issues', 'gittemplate': 'git://{auth@}{domain}/{user}/{project}.git{#committish}', - 'tarballtemplate': 'https://{domain}/{user}/{project}/archive/{committish}.tar.gz' + 'tarballtemplate': 'https://codeload.{domain}/{user}/{project}/tar.gz/{committish}' }, bitbucket: { 'protocols': [ 'git+ssh', 'git+https', 'ssh', 'https' ], @@ -29900,25 +29933,30 @@ var gitHosts = module.exports = { 'protocols': [ 'git+ssh', 'git+https', 'ssh', 'https' ], 'domain': 'gitlab.com', 'treepath': 'tree', - 'docstemplate': 'https://{domain}/{user}/{project}{/tree/committish}#README', 'bugstemplate': 'https://{domain}/{user}/{project}/issues', - 'tarballtemplate': 'https://{domain}/{user}/{project}/repository/archive.tar.gz?ref={committish}' + 'httpstemplate': 'git+https://{auth@}{domain}/{user}/{projectPath}.git{#committish}', + 'tarballtemplate': 'https://{domain}/{user}/{project}/repository/archive.tar.gz?ref={committish}', + 'pathmatch': /^[/]([^/]+)[/]((?!.*(\/-\/|\/repository\/archive\.tar\.gz\?=.*|\/repository\/[^/]+\/archive.tar.gz$)).*?)(?:[.]git|[/])?$/ }, gist: { 'protocols': [ 'git', 'git+ssh', 'git+https', 'ssh', 'https' ], 'domain': 'gist.github.com', - 'pathmatch': /^[/](?:([^/]+)[/])?([a-z0-9]+)(?:[.]git)?$/, + 'pathmatch': /^[/](?:([^/]+)[/])?([a-z0-9]{32,})(?:[.]git)?$/, 'filetemplate': 'https://gist.githubusercontent.com/{user}/{project}/raw{/committish}/{path}', 'bugstemplate': 'https://{domain}/{project}', 'gittemplate': 'git://{domain}/{project}.git{#committish}', 'sshtemplate': 'git@{domain}:/{project}.git{#committish}', 'sshurltemplate': 'git+ssh://git@{domain}/{project}.git{#committish}', 'browsetemplate': 'https://{domain}/{project}{/committish}', + 'browsefiletemplate': 'https://{domain}/{project}{/committish}{#path}', 'docstemplate': 'https://{domain}/{project}{/committish}', 'httpstemplate': 'git+https://{domain}/{project}.git{#committish}', 'shortcuttemplate': '{type}:{project}{#committish}', 'pathtemplate': '{project}{#committish}', - 'tarballtemplate': 'https://{domain}/{user}/{project}/archive/{committish}.tar.gz' + 'tarballtemplate': 'https://codeload.github.com/gist/{project}/tar.gz/{committish}', + 'hashformat': function (fragment) { + return 'file-' + formatHashFragment(fragment) + } } } @@ -29926,12 +29964,14 @@ var gitHostDefaults = { 'sshtemplate': 'git@{domain}:{user}/{project}.git{#committish}', 'sshurltemplate': 'git+ssh://git@{domain}/{user}/{project}.git{#committish}', 'browsetemplate': 'https://{domain}/{user}/{project}{/tree/committish}', + 'browsefiletemplate': 'https://{domain}/{user}/{project}/{treepath}/{committish}/{path}{#fragment}', 'docstemplate': 'https://{domain}/{user}/{project}{/tree/committish}#readme', 'httpstemplate': 'git+https://{auth@}{domain}/{user}/{project}.git{#committish}', 'filetemplate': 'https://{domain}/{user}/{project}/raw/{committish}/{path}', 'shortcuttemplate': '{type}:{user}/{project}{#committish}', 'pathtemplate': '{user}/{project}{#committish}', - 'pathmatch': /^[/]([^/]+)[/]([^/]+?)(?:[.]git|[/])?$/ + 'pathmatch': /^[/]([^/]+)[/]([^/]+?)(?:[.]git|[/])?$/, + 'hashformat': formatHashFragment } Object.keys(gitHosts).forEach(function (name) { @@ -29945,6 +29985,10 @@ Object.keys(gitHosts).forEach(function (name) { }).join('|') + '):$') }) +function formatHashFragment (fragment) { + return fragment.toLowerCase().replace(/^\W+|\/|\W+$/g, '').replace(/\W+/g, '-') +} + /***/ }), /* 285 */ @@ -29953,9 +29997,25 @@ Object.keys(gitHosts).forEach(function (name) { "use strict"; var gitHosts = __webpack_require__(284) -var extend = Object.assign || __webpack_require__(112)._extend +/* eslint-disable node/no-deprecated-api */ -var GitHost = module.exports = function (type, user, auth, project, committish, defaultRepresentation, opts) { +// copy-pasta util._extend from node's source, to avoid pulling +// the whole util module into peoples' webpack bundles. +/* istanbul ignore next */ +var extend = Object.assign || function _extend (target, source) { + // Don't do anything if source isn't an object + if (source === null || typeof source !== 'object') return target + + var keys = Object.keys(source) + var i = keys.length + while (i--) { + target[keys[i]] = source[keys[i]] + } + return target +} + +module.exports = GitHost +function GitHost (type, user, auth, project, committish, defaultRepresentation, opts) { var gitHostInfo = this gitHostInfo.type = type Object.keys(gitHosts[type]).forEach(function (key) { @@ -29968,7 +30028,6 @@ var GitHost = module.exports = function (type, user, auth, project, committish, gitHostInfo.default = defaultRepresentation gitHostInfo.opts = opts || {} } -GitHost.prototype = {} GitHost.prototype.hash = function () { return this.committish ? '#' + this.committish : '' @@ -29977,27 +30036,43 @@ GitHost.prototype.hash = function () { GitHost.prototype._fill = function (template, opts) { if (!template) return var vars = extend({}, opts) + vars.path = vars.path ? vars.path.replace(/^[/]+/g, '') : '' opts = extend(extend({}, this.opts), opts) var self = this Object.keys(this).forEach(function (key) { if (self[key] != null && vars[key] == null) vars[key] = self[key] }) var rawAuth = vars.auth - var rawComittish = vars.committish + var rawcommittish = vars.committish + var rawFragment = vars.fragment + var rawPath = vars.path + var rawProject = vars.project Object.keys(vars).forEach(function (key) { - vars[key] = encodeURIComponent(vars[key]) + var value = vars[key] + if ((key === 'path' || key === 'project') && typeof value === 'string') { + vars[key] = value.split('/').map(function (pathComponent) { + return encodeURIComponent(pathComponent) + }).join('/') + } else { + vars[key] = encodeURIComponent(value) + } }) vars['auth@'] = rawAuth ? rawAuth + '@' : '' + vars['#fragment'] = rawFragment ? '#' + this.hashformat(rawFragment) : '' + vars.fragment = vars.fragment ? vars.fragment : '' + vars['#path'] = rawPath ? '#' + this.hashformat(rawPath) : '' + vars['/path'] = vars.path ? '/' + vars.path : '' + vars.projectPath = rawProject.split('/').map(encodeURIComponent).join('/') if (opts.noCommittish) { vars['#committish'] = '' vars['/tree/committish'] = '' - vars['/comittish'] = '' - vars.comittish = '' + vars['/committish'] = '' + vars.committish = '' } else { - vars['#committish'] = rawComittish ? '#' + rawComittish : '' + vars['#committish'] = rawcommittish ? '#' + rawcommittish : '' vars['/tree/committish'] = vars.committish - ? '/' + vars.treepath + '/' + vars.committish - : '' + ? '/' + vars.treepath + '/' + vars.committish + : '' vars['/committish'] = vars.committish ? '/' + vars.committish : '' vars.committish = vars.committish || 'master' } @@ -30020,8 +30095,19 @@ GitHost.prototype.sshurl = function (opts) { return this._fill(this.sshurltemplate, opts) } -GitHost.prototype.browse = function (opts) { - return this._fill(this.browsetemplate, opts) +GitHost.prototype.browse = function (P, F, opts) { + if (typeof P === 'string') { + if (typeof F !== 'string') { + opts = F + F = null + } + return this._fill(this.browsefiletemplate, extend({ + fragment: F, + path: P + }, opts)) + } else { + return this._fill(this.browsetemplate, P) + } } GitHost.prototype.docs = function (opts) { @@ -30048,14 +30134,13 @@ GitHost.prototype.path = function (opts) { return this._fill(this.pathtemplate, opts) } -GitHost.prototype.tarball = function (opts) { +GitHost.prototype.tarball = function (opts_) { + var opts = extend({}, opts_, { noCommittish: false }) return this._fill(this.tarballtemplate, opts) } GitHost.prototype.file = function (P, opts) { - return this._fill(this.filetemplate, extend({ - path: P.replace(/^[/]+/g, '') - }, opts)) + return this._fill(this.filetemplate, extend({ path: P }, opts)) } GitHost.prototype.getDefaultRepresentation = function () { @@ -30063,7 +30148,8 @@ GitHost.prototype.getDefaultRepresentation = function () { } GitHost.prototype.toString = function (opts) { - return (this[this.default] || this.sshurl).call(this, opts) + if (this.default && typeof this[this.default] === 'function') return this[this.default](opts) + return this.sshurl(opts) } @@ -48141,6 +48227,8 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _run__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(376); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "runBazel", function() { return _run__WEBPACK_IMPORTED_MODULE_3__["runBazel"]; }); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "runIBazel", function() { return _run__WEBPACK_IMPORTED_MODULE_3__["runIBazel"]; }); + /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License @@ -48363,6 +48451,7 @@ async function installBazelTools(repoRootPath) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "runBazel", function() { return runBazel; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "runIBazel", function() { return runIBazel; }); /* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(113); /* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(chalk__WEBPACK_IMPORTED_MODULE_0__); /* harmony import */ var rxjs__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(8); @@ -48371,6 +48460,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _kbn_dev_utils_stdio__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(_kbn_dev_utils_stdio__WEBPACK_IMPORTED_MODULE_3__); /* harmony import */ var _child_process__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(319); /* harmony import */ var _log__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(246); +/* harmony import */ var _errors__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(249); function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } @@ -48390,7 +48480,9 @@ function _defineProperty(obj, key, value) { if (key in obj) { Object.definePrope -async function runBazel(bazelArgs, offline = false, runOpts = {}) { + + +async function runBazelCommandWithRunner(bazelCommandRunner, bazelArgs, offline = false, runOpts = {}) { // Force logs to pipe in order to control the output of them const bazelOpts = _objectSpread(_objectSpread({}, runOpts), {}, { stdio: 'pipe' @@ -48400,17 +48492,29 @@ async function runBazel(bazelArgs, offline = false, runOpts = {}) { bazelArgs.push('--config=offline'); } - const bazelProc = Object(_child_process__WEBPACK_IMPORTED_MODULE_4__["spawn"])('bazel', bazelArgs, bazelOpts); + const bazelProc = Object(_child_process__WEBPACK_IMPORTED_MODULE_4__["spawn"])(bazelCommandRunner, bazelArgs, bazelOpts); const bazelLogs$ = new rxjs__WEBPACK_IMPORTED_MODULE_1__["Subject"](); // Bazel outputs machine readable output into stdout and human readable output goes to stderr. // Therefore we need to get both. In order to get errors we need to parse the actual text line - const bazelLogSubscription = rxjs__WEBPACK_IMPORTED_MODULE_1__["merge"](Object(_kbn_dev_utils_stdio__WEBPACK_IMPORTED_MODULE_3__["observeLines"])(bazelProc.stdout).pipe(Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_2__["tap"])(line => _log__WEBPACK_IMPORTED_MODULE_5__["log"].info(`${chalk__WEBPACK_IMPORTED_MODULE_0___default.a.cyan('[bazel]')} ${line}`))), Object(_kbn_dev_utils_stdio__WEBPACK_IMPORTED_MODULE_3__["observeLines"])(bazelProc.stderr).pipe(Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_2__["tap"])(line => _log__WEBPACK_IMPORTED_MODULE_5__["log"].info(`${chalk__WEBPACK_IMPORTED_MODULE_0___default.a.cyan('[bazel]')} ${line}`)))).subscribe(bazelLogs$); // Wait for process and logs to finish, unsubscribing in the end + const bazelLogSubscription = rxjs__WEBPACK_IMPORTED_MODULE_1__["merge"](Object(_kbn_dev_utils_stdio__WEBPACK_IMPORTED_MODULE_3__["observeLines"])(bazelProc.stdout).pipe(Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_2__["tap"])(line => _log__WEBPACK_IMPORTED_MODULE_5__["log"].info(`${chalk__WEBPACK_IMPORTED_MODULE_0___default.a.cyan(`[${bazelCommandRunner}]`)} ${line}`))), Object(_kbn_dev_utils_stdio__WEBPACK_IMPORTED_MODULE_3__["observeLines"])(bazelProc.stderr).pipe(Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_2__["tap"])(line => _log__WEBPACK_IMPORTED_MODULE_5__["log"].info(`${chalk__WEBPACK_IMPORTED_MODULE_0___default.a.cyan(`[${bazelCommandRunner}]`)} ${line}`)))).subscribe(bazelLogs$); // Wait for process and logs to finish, unsubscribing in the end + + try { + await bazelProc; + } catch { + throw new _errors__WEBPACK_IMPORTED_MODULE_6__["CliError"](`The bazel command that was running failed to complete.`); + } - await bazelProc; await bazelLogs$.toPromise(); await bazelLogSubscription.unsubscribe(); } +async function runBazel(bazelArgs, offline = false, runOpts = {}) { + await runBazelCommandWithRunner('bazel', bazelArgs, offline, runOpts); +} +async function runIBazel(bazelArgs, offline = false, runOpts = {}) { + await runBazelCommandWithRunner('ibazel', bazelArgs, offline, runOpts); +} + /***/ }), /* 377 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { @@ -54550,6 +54654,36 @@ exports.observeReadable = observeReadable; /* 478 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "BuildBazelCommand", function() { return BuildBazelCommand; }); +/* harmony import */ var _utils_bazel__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(372); +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +const BuildBazelCommand = { + description: 'Runs a build in the Bazel built packages', + name: 'build-bazel', + + async run(projects, projectGraph, { + options + }) { + const runOffline = (options === null || options === void 0 ? void 0 : options.offline) === true; // Call bazel with the target to build all available packages + + await Object(_utils_bazel__WEBPACK_IMPORTED_MODULE_0__["runBazel"])(['build', '//packages:build', '--show_result=1'], runOffline); + } + +}; + +/***/ }), +/* 479 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "CleanCommand", function() { return CleanCommand; }); @@ -54557,7 +54691,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var dedent__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(dedent__WEBPACK_IMPORTED_MODULE_0__); /* harmony import */ var del__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(143); /* harmony import */ var del__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(del__WEBPACK_IMPORTED_MODULE_1__); -/* harmony import */ var ora__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(479); +/* harmony import */ var ora__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(480); /* harmony import */ var ora__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(ora__WEBPACK_IMPORTED_MODULE_2__); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(4); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_3__); @@ -54660,20 +54794,20 @@ const CleanCommand = { }; /***/ }), -/* 479 */ +/* 480 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const readline = __webpack_require__(480); -const chalk = __webpack_require__(481); -const cliCursor = __webpack_require__(488); -const cliSpinners = __webpack_require__(490); -const logSymbols = __webpack_require__(492); -const stripAnsi = __webpack_require__(502); -const wcwidth = __webpack_require__(504); -const isInteractive = __webpack_require__(508); -const MuteStream = __webpack_require__(509); +const readline = __webpack_require__(481); +const chalk = __webpack_require__(482); +const cliCursor = __webpack_require__(489); +const cliSpinners = __webpack_require__(491); +const logSymbols = __webpack_require__(493); +const stripAnsi = __webpack_require__(503); +const wcwidth = __webpack_require__(505); +const isInteractive = __webpack_require__(509); +const MuteStream = __webpack_require__(510); const TEXT = Symbol('text'); const PREFIX_TEXT = Symbol('prefixText'); @@ -55026,23 +55160,23 @@ module.exports.promise = (action, options) => { /***/ }), -/* 480 */ +/* 481 */ /***/ (function(module, exports) { module.exports = require("readline"); /***/ }), -/* 481 */ +/* 482 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const ansiStyles = __webpack_require__(482); +const ansiStyles = __webpack_require__(483); const {stdout: stdoutColor, stderr: stderrColor} = __webpack_require__(120); const { stringReplaceAll, stringEncaseCRLFWithFirstIndex -} = __webpack_require__(486); +} = __webpack_require__(487); // `supportsColor.level` → `ansiStyles.color[name]` mapping const levelMapping = [ @@ -55243,7 +55377,7 @@ const chalkTag = (chalk, ...strings) => { } if (template === undefined) { - template = __webpack_require__(487); + template = __webpack_require__(488); } return template(chalk, parts.join('')); @@ -55272,7 +55406,7 @@ module.exports = chalk; /***/ }), -/* 482 */ +/* 483 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -55318,7 +55452,7 @@ const setLazyProperty = (object, property, get) => { let colorConvert; const makeDynamicStyles = (wrap, targetSpace, identity, isBackground) => { if (colorConvert === undefined) { - colorConvert = __webpack_require__(483); + colorConvert = __webpack_require__(484); } const offset = isBackground ? 10 : 0; @@ -55443,11 +55577,11 @@ Object.defineProperty(module, 'exports', { /* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(115)(module))) /***/ }), -/* 483 */ +/* 484 */ /***/ (function(module, exports, __webpack_require__) { -const conversions = __webpack_require__(484); -const route = __webpack_require__(485); +const conversions = __webpack_require__(485); +const route = __webpack_require__(486); const convert = {}; @@ -55530,7 +55664,7 @@ module.exports = convert; /***/ }), -/* 484 */ +/* 485 */ /***/ (function(module, exports, __webpack_require__) { /* MIT license */ @@ -56375,10 +56509,10 @@ convert.rgb.gray = function (rgb) { /***/ }), -/* 485 */ +/* 486 */ /***/ (function(module, exports, __webpack_require__) { -const conversions = __webpack_require__(484); +const conversions = __webpack_require__(485); /* This function routes a model to all other models. @@ -56478,7 +56612,7 @@ module.exports = function (fromModel) { /***/ }), -/* 486 */ +/* 487 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -56524,7 +56658,7 @@ module.exports = { /***/ }), -/* 487 */ +/* 488 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -56665,12 +56799,12 @@ module.exports = (chalk, temporary) => { /***/ }), -/* 488 */ +/* 489 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const restoreCursor = __webpack_require__(489); +const restoreCursor = __webpack_require__(490); let isHidden = false; @@ -56707,7 +56841,7 @@ exports.toggle = (force, writableStream) => { /***/ }), -/* 489 */ +/* 490 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -56723,13 +56857,13 @@ module.exports = onetime(() => { /***/ }), -/* 490 */ +/* 491 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const spinners = Object.assign({}, __webpack_require__(491)); +const spinners = Object.assign({}, __webpack_require__(492)); const spinnersList = Object.keys(spinners); @@ -56747,18 +56881,18 @@ module.exports.default = spinners; /***/ }), -/* 491 */ +/* 492 */ /***/ (function(module) { module.exports = JSON.parse("{\"dots\":{\"interval\":80,\"frames\":[\"⠋\",\"⠙\",\"⠹\",\"⠸\",\"⠼\",\"⠴\",\"⠦\",\"⠧\",\"⠇\",\"⠏\"]},\"dots2\":{\"interval\":80,\"frames\":[\"⣾\",\"⣽\",\"⣻\",\"⢿\",\"⡿\",\"⣟\",\"⣯\",\"⣷\"]},\"dots3\":{\"interval\":80,\"frames\":[\"⠋\",\"⠙\",\"⠚\",\"⠞\",\"⠖\",\"⠦\",\"⠴\",\"⠲\",\"⠳\",\"⠓\"]},\"dots4\":{\"interval\":80,\"frames\":[\"⠄\",\"⠆\",\"⠇\",\"⠋\",\"⠙\",\"⠸\",\"⠰\",\"⠠\",\"⠰\",\"⠸\",\"⠙\",\"⠋\",\"⠇\",\"⠆\"]},\"dots5\":{\"interval\":80,\"frames\":[\"⠋\",\"⠙\",\"⠚\",\"⠒\",\"⠂\",\"⠂\",\"⠒\",\"⠲\",\"⠴\",\"⠦\",\"⠖\",\"⠒\",\"⠐\",\"⠐\",\"⠒\",\"⠓\",\"⠋\"]},\"dots6\":{\"interval\":80,\"frames\":[\"⠁\",\"⠉\",\"⠙\",\"⠚\",\"⠒\",\"⠂\",\"⠂\",\"⠒\",\"⠲\",\"⠴\",\"⠤\",\"⠄\",\"⠄\",\"⠤\",\"⠴\",\"⠲\",\"⠒\",\"⠂\",\"⠂\",\"⠒\",\"⠚\",\"⠙\",\"⠉\",\"⠁\"]},\"dots7\":{\"interval\":80,\"frames\":[\"⠈\",\"⠉\",\"⠋\",\"⠓\",\"⠒\",\"⠐\",\"⠐\",\"⠒\",\"⠖\",\"⠦\",\"⠤\",\"⠠\",\"⠠\",\"⠤\",\"⠦\",\"⠖\",\"⠒\",\"⠐\",\"⠐\",\"⠒\",\"⠓\",\"⠋\",\"⠉\",\"⠈\"]},\"dots8\":{\"interval\":80,\"frames\":[\"⠁\",\"⠁\",\"⠉\",\"⠙\",\"⠚\",\"⠒\",\"⠂\",\"⠂\",\"⠒\",\"⠲\",\"⠴\",\"⠤\",\"⠄\",\"⠄\",\"⠤\",\"⠠\",\"⠠\",\"⠤\",\"⠦\",\"⠖\",\"⠒\",\"⠐\",\"⠐\",\"⠒\",\"⠓\",\"⠋\",\"⠉\",\"⠈\",\"⠈\"]},\"dots9\":{\"interval\":80,\"frames\":[\"⢹\",\"⢺\",\"⢼\",\"⣸\",\"⣇\",\"⡧\",\"⡗\",\"⡏\"]},\"dots10\":{\"interval\":80,\"frames\":[\"⢄\",\"⢂\",\"⢁\",\"⡁\",\"⡈\",\"⡐\",\"⡠\"]},\"dots11\":{\"interval\":100,\"frames\":[\"⠁\",\"⠂\",\"⠄\",\"⡀\",\"⢀\",\"⠠\",\"⠐\",\"⠈\"]},\"dots12\":{\"interval\":80,\"frames\":[\"⢀⠀\",\"⡀⠀\",\"⠄⠀\",\"⢂⠀\",\"⡂⠀\",\"⠅⠀\",\"⢃⠀\",\"⡃⠀\",\"⠍⠀\",\"⢋⠀\",\"⡋⠀\",\"⠍⠁\",\"⢋⠁\",\"⡋⠁\",\"⠍⠉\",\"⠋⠉\",\"⠋⠉\",\"⠉⠙\",\"⠉⠙\",\"⠉⠩\",\"⠈⢙\",\"⠈⡙\",\"⢈⠩\",\"⡀⢙\",\"⠄⡙\",\"⢂⠩\",\"⡂⢘\",\"⠅⡘\",\"⢃⠨\",\"⡃⢐\",\"⠍⡐\",\"⢋⠠\",\"⡋⢀\",\"⠍⡁\",\"⢋⠁\",\"⡋⠁\",\"⠍⠉\",\"⠋⠉\",\"⠋⠉\",\"⠉⠙\",\"⠉⠙\",\"⠉⠩\",\"⠈⢙\",\"⠈⡙\",\"⠈⠩\",\"⠀⢙\",\"⠀⡙\",\"⠀⠩\",\"⠀⢘\",\"⠀⡘\",\"⠀⠨\",\"⠀⢐\",\"⠀⡐\",\"⠀⠠\",\"⠀⢀\",\"⠀⡀\"]},\"dots8Bit\":{\"interval\":80,\"frames\":[\"⠀\",\"⠁\",\"⠂\",\"⠃\",\"⠄\",\"⠅\",\"⠆\",\"⠇\",\"⡀\",\"⡁\",\"⡂\",\"⡃\",\"⡄\",\"⡅\",\"⡆\",\"⡇\",\"⠈\",\"⠉\",\"⠊\",\"⠋\",\"⠌\",\"⠍\",\"⠎\",\"⠏\",\"⡈\",\"⡉\",\"⡊\",\"⡋\",\"⡌\",\"⡍\",\"⡎\",\"⡏\",\"⠐\",\"⠑\",\"⠒\",\"⠓\",\"⠔\",\"⠕\",\"⠖\",\"⠗\",\"⡐\",\"⡑\",\"⡒\",\"⡓\",\"⡔\",\"⡕\",\"⡖\",\"⡗\",\"⠘\",\"⠙\",\"⠚\",\"⠛\",\"⠜\",\"⠝\",\"⠞\",\"⠟\",\"⡘\",\"⡙\",\"⡚\",\"⡛\",\"⡜\",\"⡝\",\"⡞\",\"⡟\",\"⠠\",\"⠡\",\"⠢\",\"⠣\",\"⠤\",\"⠥\",\"⠦\",\"⠧\",\"⡠\",\"⡡\",\"⡢\",\"⡣\",\"⡤\",\"⡥\",\"⡦\",\"⡧\",\"⠨\",\"⠩\",\"⠪\",\"⠫\",\"⠬\",\"⠭\",\"⠮\",\"⠯\",\"⡨\",\"⡩\",\"⡪\",\"⡫\",\"⡬\",\"⡭\",\"⡮\",\"⡯\",\"⠰\",\"⠱\",\"⠲\",\"⠳\",\"⠴\",\"⠵\",\"⠶\",\"⠷\",\"⡰\",\"⡱\",\"⡲\",\"⡳\",\"⡴\",\"⡵\",\"⡶\",\"⡷\",\"⠸\",\"⠹\",\"⠺\",\"⠻\",\"⠼\",\"⠽\",\"⠾\",\"⠿\",\"⡸\",\"⡹\",\"⡺\",\"⡻\",\"⡼\",\"⡽\",\"⡾\",\"⡿\",\"⢀\",\"⢁\",\"⢂\",\"⢃\",\"⢄\",\"⢅\",\"⢆\",\"⢇\",\"⣀\",\"⣁\",\"⣂\",\"⣃\",\"⣄\",\"⣅\",\"⣆\",\"⣇\",\"⢈\",\"⢉\",\"⢊\",\"⢋\",\"⢌\",\"⢍\",\"⢎\",\"⢏\",\"⣈\",\"⣉\",\"⣊\",\"⣋\",\"⣌\",\"⣍\",\"⣎\",\"⣏\",\"⢐\",\"⢑\",\"⢒\",\"⢓\",\"⢔\",\"⢕\",\"⢖\",\"⢗\",\"⣐\",\"⣑\",\"⣒\",\"⣓\",\"⣔\",\"⣕\",\"⣖\",\"⣗\",\"⢘\",\"⢙\",\"⢚\",\"⢛\",\"⢜\",\"⢝\",\"⢞\",\"⢟\",\"⣘\",\"⣙\",\"⣚\",\"⣛\",\"⣜\",\"⣝\",\"⣞\",\"⣟\",\"⢠\",\"⢡\",\"⢢\",\"⢣\",\"⢤\",\"⢥\",\"⢦\",\"⢧\",\"⣠\",\"⣡\",\"⣢\",\"⣣\",\"⣤\",\"⣥\",\"⣦\",\"⣧\",\"⢨\",\"⢩\",\"⢪\",\"⢫\",\"⢬\",\"⢭\",\"⢮\",\"⢯\",\"⣨\",\"⣩\",\"⣪\",\"⣫\",\"⣬\",\"⣭\",\"⣮\",\"⣯\",\"⢰\",\"⢱\",\"⢲\",\"⢳\",\"⢴\",\"⢵\",\"⢶\",\"⢷\",\"⣰\",\"⣱\",\"⣲\",\"⣳\",\"⣴\",\"⣵\",\"⣶\",\"⣷\",\"⢸\",\"⢹\",\"⢺\",\"⢻\",\"⢼\",\"⢽\",\"⢾\",\"⢿\",\"⣸\",\"⣹\",\"⣺\",\"⣻\",\"⣼\",\"⣽\",\"⣾\",\"⣿\"]},\"line\":{\"interval\":130,\"frames\":[\"-\",\"\\\\\",\"|\",\"/\"]},\"line2\":{\"interval\":100,\"frames\":[\"⠂\",\"-\",\"–\",\"—\",\"–\",\"-\"]},\"pipe\":{\"interval\":100,\"frames\":[\"┤\",\"┘\",\"┴\",\"└\",\"├\",\"┌\",\"┬\",\"┐\"]},\"simpleDots\":{\"interval\":400,\"frames\":[\". \",\".. \",\"...\",\" \"]},\"simpleDotsScrolling\":{\"interval\":200,\"frames\":[\". \",\".. \",\"...\",\" ..\",\" .\",\" \"]},\"star\":{\"interval\":70,\"frames\":[\"✶\",\"✸\",\"✹\",\"✺\",\"✹\",\"✷\"]},\"star2\":{\"interval\":80,\"frames\":[\"+\",\"x\",\"*\"]},\"flip\":{\"interval\":70,\"frames\":[\"_\",\"_\",\"_\",\"-\",\"`\",\"`\",\"'\",\"´\",\"-\",\"_\",\"_\",\"_\"]},\"hamburger\":{\"interval\":100,\"frames\":[\"☱\",\"☲\",\"☴\"]},\"growVertical\":{\"interval\":120,\"frames\":[\"▁\",\"▃\",\"▄\",\"▅\",\"▆\",\"▇\",\"▆\",\"▅\",\"▄\",\"▃\"]},\"growHorizontal\":{\"interval\":120,\"frames\":[\"▏\",\"▎\",\"▍\",\"▌\",\"▋\",\"▊\",\"▉\",\"▊\",\"▋\",\"▌\",\"▍\",\"▎\"]},\"balloon\":{\"interval\":140,\"frames\":[\" \",\".\",\"o\",\"O\",\"@\",\"*\",\" \"]},\"balloon2\":{\"interval\":120,\"frames\":[\".\",\"o\",\"O\",\"°\",\"O\",\"o\",\".\"]},\"noise\":{\"interval\":100,\"frames\":[\"▓\",\"▒\",\"░\"]},\"bounce\":{\"interval\":120,\"frames\":[\"⠁\",\"⠂\",\"⠄\",\"⠂\"]},\"boxBounce\":{\"interval\":120,\"frames\":[\"▖\",\"▘\",\"▝\",\"▗\"]},\"boxBounce2\":{\"interval\":100,\"frames\":[\"▌\",\"▀\",\"▐\",\"▄\"]},\"triangle\":{\"interval\":50,\"frames\":[\"◢\",\"◣\",\"◤\",\"◥\"]},\"arc\":{\"interval\":100,\"frames\":[\"◜\",\"◠\",\"◝\",\"◞\",\"◡\",\"◟\"]},\"circle\":{\"interval\":120,\"frames\":[\"◡\",\"⊙\",\"◠\"]},\"squareCorners\":{\"interval\":180,\"frames\":[\"◰\",\"◳\",\"◲\",\"◱\"]},\"circleQuarters\":{\"interval\":120,\"frames\":[\"◴\",\"◷\",\"◶\",\"◵\"]},\"circleHalves\":{\"interval\":50,\"frames\":[\"◐\",\"◓\",\"◑\",\"◒\"]},\"squish\":{\"interval\":100,\"frames\":[\"╫\",\"╪\"]},\"toggle\":{\"interval\":250,\"frames\":[\"⊶\",\"⊷\"]},\"toggle2\":{\"interval\":80,\"frames\":[\"▫\",\"▪\"]},\"toggle3\":{\"interval\":120,\"frames\":[\"□\",\"■\"]},\"toggle4\":{\"interval\":100,\"frames\":[\"■\",\"□\",\"▪\",\"▫\"]},\"toggle5\":{\"interval\":100,\"frames\":[\"▮\",\"▯\"]},\"toggle6\":{\"interval\":300,\"frames\":[\"ဝ\",\"၀\"]},\"toggle7\":{\"interval\":80,\"frames\":[\"⦾\",\"⦿\"]},\"toggle8\":{\"interval\":100,\"frames\":[\"◍\",\"◌\"]},\"toggle9\":{\"interval\":100,\"frames\":[\"◉\",\"◎\"]},\"toggle10\":{\"interval\":100,\"frames\":[\"㊂\",\"㊀\",\"㊁\"]},\"toggle11\":{\"interval\":50,\"frames\":[\"⧇\",\"⧆\"]},\"toggle12\":{\"interval\":120,\"frames\":[\"☗\",\"☖\"]},\"toggle13\":{\"interval\":80,\"frames\":[\"=\",\"*\",\"-\"]},\"arrow\":{\"interval\":100,\"frames\":[\"←\",\"↖\",\"↑\",\"↗\",\"→\",\"↘\",\"↓\",\"↙\"]},\"arrow2\":{\"interval\":80,\"frames\":[\"⬆️ \",\"↗️ \",\"➡️ \",\"↘️ \",\"⬇️ \",\"↙️ \",\"⬅️ \",\"↖️ \"]},\"arrow3\":{\"interval\":120,\"frames\":[\"▹▹▹▹▹\",\"▸▹▹▹▹\",\"▹▸▹▹▹\",\"▹▹▸▹▹\",\"▹▹▹▸▹\",\"▹▹▹▹▸\"]},\"bouncingBar\":{\"interval\":80,\"frames\":[\"[ ]\",\"[= ]\",\"[== ]\",\"[=== ]\",\"[ ===]\",\"[ ==]\",\"[ =]\",\"[ ]\",\"[ =]\",\"[ ==]\",\"[ ===]\",\"[====]\",\"[=== ]\",\"[== ]\",\"[= ]\"]},\"bouncingBall\":{\"interval\":80,\"frames\":[\"( ● )\",\"( ● )\",\"( ● )\",\"( ● )\",\"( ●)\",\"( ● )\",\"( ● )\",\"( ● )\",\"( ● )\",\"(● )\"]},\"smiley\":{\"interval\":200,\"frames\":[\"😄 \",\"😝 \"]},\"monkey\":{\"interval\":300,\"frames\":[\"🙈 \",\"🙈 \",\"🙉 \",\"🙊 \"]},\"hearts\":{\"interval\":100,\"frames\":[\"💛 \",\"💙 \",\"💜 \",\"💚 \",\"❤️ \"]},\"clock\":{\"interval\":100,\"frames\":[\"🕛 \",\"🕐 \",\"🕑 \",\"🕒 \",\"🕓 \",\"🕔 \",\"🕕 \",\"🕖 \",\"🕗 \",\"🕘 \",\"🕙 \",\"🕚 \"]},\"earth\":{\"interval\":180,\"frames\":[\"🌍 \",\"🌎 \",\"🌏 \"]},\"material\":{\"interval\":17,\"frames\":[\"█▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"██▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"███▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"████▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"██████▁▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"██████▁▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"███████▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"████████▁▁▁▁▁▁▁▁▁▁▁▁\",\"█████████▁▁▁▁▁▁▁▁▁▁▁\",\"█████████▁▁▁▁▁▁▁▁▁▁▁\",\"██████████▁▁▁▁▁▁▁▁▁▁\",\"███████████▁▁▁▁▁▁▁▁▁\",\"█████████████▁▁▁▁▁▁▁\",\"██████████████▁▁▁▁▁▁\",\"██████████████▁▁▁▁▁▁\",\"▁██████████████▁▁▁▁▁\",\"▁██████████████▁▁▁▁▁\",\"▁██████████████▁▁▁▁▁\",\"▁▁██████████████▁▁▁▁\",\"▁▁▁██████████████▁▁▁\",\"▁▁▁▁█████████████▁▁▁\",\"▁▁▁▁██████████████▁▁\",\"▁▁▁▁██████████████▁▁\",\"▁▁▁▁▁██████████████▁\",\"▁▁▁▁▁██████████████▁\",\"▁▁▁▁▁██████████████▁\",\"▁▁▁▁▁▁██████████████\",\"▁▁▁▁▁▁██████████████\",\"▁▁▁▁▁▁▁█████████████\",\"▁▁▁▁▁▁▁█████████████\",\"▁▁▁▁▁▁▁▁████████████\",\"▁▁▁▁▁▁▁▁████████████\",\"▁▁▁▁▁▁▁▁▁███████████\",\"▁▁▁▁▁▁▁▁▁███████████\",\"▁▁▁▁▁▁▁▁▁▁██████████\",\"▁▁▁▁▁▁▁▁▁▁██████████\",\"▁▁▁▁▁▁▁▁▁▁▁▁████████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁███████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁██████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█████\",\"█▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████\",\"██▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███\",\"██▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███\",\"███▁▁▁▁▁▁▁▁▁▁▁▁▁▁███\",\"████▁▁▁▁▁▁▁▁▁▁▁▁▁▁██\",\"█████▁▁▁▁▁▁▁▁▁▁▁▁▁▁█\",\"█████▁▁▁▁▁▁▁▁▁▁▁▁▁▁█\",\"██████▁▁▁▁▁▁▁▁▁▁▁▁▁█\",\"████████▁▁▁▁▁▁▁▁▁▁▁▁\",\"█████████▁▁▁▁▁▁▁▁▁▁▁\",\"█████████▁▁▁▁▁▁▁▁▁▁▁\",\"█████████▁▁▁▁▁▁▁▁▁▁▁\",\"█████████▁▁▁▁▁▁▁▁▁▁▁\",\"███████████▁▁▁▁▁▁▁▁▁\",\"████████████▁▁▁▁▁▁▁▁\",\"████████████▁▁▁▁▁▁▁▁\",\"██████████████▁▁▁▁▁▁\",\"██████████████▁▁▁▁▁▁\",\"▁██████████████▁▁▁▁▁\",\"▁██████████████▁▁▁▁▁\",\"▁▁▁█████████████▁▁▁▁\",\"▁▁▁▁▁████████████▁▁▁\",\"▁▁▁▁▁████████████▁▁▁\",\"▁▁▁▁▁▁███████████▁▁▁\",\"▁▁▁▁▁▁▁▁█████████▁▁▁\",\"▁▁▁▁▁▁▁▁█████████▁▁▁\",\"▁▁▁▁▁▁▁▁▁█████████▁▁\",\"▁▁▁▁▁▁▁▁▁█████████▁▁\",\"▁▁▁▁▁▁▁▁▁▁█████████▁\",\"▁▁▁▁▁▁▁▁▁▁▁████████▁\",\"▁▁▁▁▁▁▁▁▁▁▁████████▁\",\"▁▁▁▁▁▁▁▁▁▁▁▁███████▁\",\"▁▁▁▁▁▁▁▁▁▁▁▁███████▁\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁███████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁███████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁██\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁██\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁██\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁\"]},\"moon\":{\"interval\":80,\"frames\":[\"🌑 \",\"🌒 \",\"🌓 \",\"🌔 \",\"🌕 \",\"🌖 \",\"🌗 \",\"🌘 \"]},\"runner\":{\"interval\":140,\"frames\":[\"🚶 \",\"🏃 \"]},\"pong\":{\"interval\":80,\"frames\":[\"▐⠂ ▌\",\"▐⠈ ▌\",\"▐ ⠂ ▌\",\"▐ ⠠ ▌\",\"▐ ⡀ ▌\",\"▐ ⠠ ▌\",\"▐ ⠂ ▌\",\"▐ ⠈ ▌\",\"▐ ⠂ ▌\",\"▐ ⠠ ▌\",\"▐ ⡀ ▌\",\"▐ ⠠ ▌\",\"▐ ⠂ ▌\",\"▐ ⠈ ▌\",\"▐ ⠂▌\",\"▐ ⠠▌\",\"▐ ⡀▌\",\"▐ ⠠ ▌\",\"▐ ⠂ ▌\",\"▐ ⠈ ▌\",\"▐ ⠂ ▌\",\"▐ ⠠ ▌\",\"▐ ⡀ ▌\",\"▐ ⠠ ▌\",\"▐ ⠂ ▌\",\"▐ ⠈ ▌\",\"▐ ⠂ ▌\",\"▐ ⠠ ▌\",\"▐ ⡀ ▌\",\"▐⠠ ▌\"]},\"shark\":{\"interval\":120,\"frames\":[\"▐|\\\\____________▌\",\"▐_|\\\\___________▌\",\"▐__|\\\\__________▌\",\"▐___|\\\\_________▌\",\"▐____|\\\\________▌\",\"▐_____|\\\\_______▌\",\"▐______|\\\\______▌\",\"▐_______|\\\\_____▌\",\"▐________|\\\\____▌\",\"▐_________|\\\\___▌\",\"▐__________|\\\\__▌\",\"▐___________|\\\\_▌\",\"▐____________|\\\\▌\",\"▐____________/|▌\",\"▐___________/|_▌\",\"▐__________/|__▌\",\"▐_________/|___▌\",\"▐________/|____▌\",\"▐_______/|_____▌\",\"▐______/|______▌\",\"▐_____/|_______▌\",\"▐____/|________▌\",\"▐___/|_________▌\",\"▐__/|__________▌\",\"▐_/|___________▌\",\"▐/|____________▌\"]},\"dqpb\":{\"interval\":100,\"frames\":[\"d\",\"q\",\"p\",\"b\"]},\"weather\":{\"interval\":100,\"frames\":[\"☀️ \",\"☀️ \",\"☀️ \",\"🌤 \",\"⛅️ \",\"🌥 \",\"☁️ \",\"🌧 \",\"🌨 \",\"🌧 \",\"🌨 \",\"🌧 \",\"🌨 \",\"⛈ \",\"🌨 \",\"🌧 \",\"🌨 \",\"☁️ \",\"🌥 \",\"⛅️ \",\"🌤 \",\"☀️ \",\"☀️ \"]},\"christmas\":{\"interval\":400,\"frames\":[\"🌲\",\"🎄\"]},\"grenade\":{\"interval\":80,\"frames\":[\"، \",\"′ \",\" ´ \",\" ‾ \",\" ⸌\",\" ⸊\",\" |\",\" ⁎\",\" ⁕\",\" ෴ \",\" ⁓\",\" \",\" \",\" \"]},\"point\":{\"interval\":125,\"frames\":[\"∙∙∙\",\"●∙∙\",\"∙●∙\",\"∙∙●\",\"∙∙∙\"]},\"layer\":{\"interval\":150,\"frames\":[\"-\",\"=\",\"≡\"]},\"betaWave\":{\"interval\":80,\"frames\":[\"ρββββββ\",\"βρβββββ\",\"ββρββββ\",\"βββρβββ\",\"ββββρββ\",\"βββββρβ\",\"ββββββρ\"]},\"aesthetic\":{\"interval\":80,\"frames\":[\"▰▱▱▱▱▱▱\",\"▰▰▱▱▱▱▱\",\"▰▰▰▱▱▱▱\",\"▰▰▰▰▱▱▱\",\"▰▰▰▰▰▱▱\",\"▰▰▰▰▰▰▱\",\"▰▰▰▰▰▰▰\",\"▰▱▱▱▱▱▱\"]}}"); /***/ }), -/* 492 */ +/* 493 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const chalk = __webpack_require__(493); +const chalk = __webpack_require__(494); const isSupported = process.platform !== 'win32' || process.env.CI || process.env.TERM === 'xterm-256color'; @@ -56780,16 +56914,16 @@ module.exports = isSupported ? main : fallbacks; /***/ }), -/* 493 */ +/* 494 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const escapeStringRegexp = __webpack_require__(265); -const ansiStyles = __webpack_require__(494); -const stdoutColor = __webpack_require__(499).stdout; +const ansiStyles = __webpack_require__(495); +const stdoutColor = __webpack_require__(500).stdout; -const template = __webpack_require__(501); +const template = __webpack_require__(502); const isSimpleWindowsTerm = process.platform === 'win32' && !(process.env.TERM || '').toLowerCase().startsWith('xterm'); @@ -57015,12 +57149,12 @@ module.exports.default = module.exports; // For TypeScript /***/ }), -/* 494 */ +/* 495 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; /* WEBPACK VAR INJECTION */(function(module) { -const colorConvert = __webpack_require__(495); +const colorConvert = __webpack_require__(496); const wrapAnsi16 = (fn, offset) => function () { const code = fn.apply(colorConvert, arguments); @@ -57188,11 +57322,11 @@ Object.defineProperty(module, 'exports', { /* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(115)(module))) /***/ }), -/* 495 */ +/* 496 */ /***/ (function(module, exports, __webpack_require__) { -var conversions = __webpack_require__(496); -var route = __webpack_require__(498); +var conversions = __webpack_require__(497); +var route = __webpack_require__(499); var convert = {}; @@ -57272,11 +57406,11 @@ module.exports = convert; /***/ }), -/* 496 */ +/* 497 */ /***/ (function(module, exports, __webpack_require__) { /* MIT license */ -var cssKeywords = __webpack_require__(497); +var cssKeywords = __webpack_require__(498); // NOTE: conversions should only return primitive values (i.e. arrays, or // values that give correct `typeof` results). @@ -58146,7 +58280,7 @@ convert.rgb.gray = function (rgb) { /***/ }), -/* 497 */ +/* 498 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -58305,10 +58439,10 @@ module.exports = { /***/ }), -/* 498 */ +/* 499 */ /***/ (function(module, exports, __webpack_require__) { -var conversions = __webpack_require__(496); +var conversions = __webpack_require__(497); /* this function routes a model to all other models. @@ -58408,13 +58542,13 @@ module.exports = function (fromModel) { /***/ }), -/* 499 */ +/* 500 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const os = __webpack_require__(121); -const hasFlag = __webpack_require__(500); +const hasFlag = __webpack_require__(501); const env = process.env; @@ -58546,7 +58680,7 @@ module.exports = { /***/ }), -/* 500 */ +/* 501 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -58561,7 +58695,7 @@ module.exports = (flag, argv) => { /***/ }), -/* 501 */ +/* 502 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -58696,18 +58830,18 @@ module.exports = (chalk, tmp) => { /***/ }), -/* 502 */ +/* 503 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const ansiRegex = __webpack_require__(503); +const ansiRegex = __webpack_require__(504); module.exports = string => typeof string === 'string' ? string.replace(ansiRegex(), '') : string; /***/ }), -/* 503 */ +/* 504 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -58724,14 +58858,14 @@ module.exports = ({onlyFirst = false} = {}) => { /***/ }), -/* 504 */ +/* 505 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var defaults = __webpack_require__(505) -var combining = __webpack_require__(507) +var defaults = __webpack_require__(506) +var combining = __webpack_require__(508) var DEFAULTS = { nul: 0, @@ -58830,10 +58964,10 @@ function bisearch(ucs) { /***/ }), -/* 505 */ +/* 506 */ /***/ (function(module, exports, __webpack_require__) { -var clone = __webpack_require__(506); +var clone = __webpack_require__(507); module.exports = function(options, defaults) { options = options || {}; @@ -58848,7 +58982,7 @@ module.exports = function(options, defaults) { }; /***/ }), -/* 506 */ +/* 507 */ /***/ (function(module, exports, __webpack_require__) { var clone = (function() { @@ -59020,7 +59154,7 @@ if ( true && module.exports) { /***/ }), -/* 507 */ +/* 508 */ /***/ (function(module, exports) { module.exports = [ @@ -59076,7 +59210,7 @@ module.exports = [ /***/ }), -/* 508 */ +/* 509 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -59092,7 +59226,7 @@ module.exports = ({stream = process.stdout} = {}) => { /***/ }), -/* 509 */ +/* 510 */ /***/ (function(module, exports, __webpack_require__) { var Stream = __webpack_require__(138) @@ -59243,7 +59377,7 @@ MuteStream.prototype.close = proxy('close') /***/ }), -/* 510 */ +/* 511 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -59253,7 +59387,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var dedent__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(dedent__WEBPACK_IMPORTED_MODULE_0__); /* harmony import */ var del__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(143); /* harmony import */ var del__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(del__WEBPACK_IMPORTED_MODULE_1__); -/* harmony import */ var ora__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(479); +/* harmony import */ var ora__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(480); /* harmony import */ var ora__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(ora__WEBPACK_IMPORTED_MODULE_2__); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(4); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_3__); @@ -59362,16 +59496,18 @@ const ResetCommand = { }; /***/ }), -/* 511 */ +/* 512 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "RunCommand", function() { return RunCommand; }); -/* harmony import */ var _utils_errors__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(249); -/* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(246); -/* harmony import */ var _utils_parallelize__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(247); -/* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(248); +/* harmony import */ var dedent__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2); +/* harmony import */ var dedent__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(dedent__WEBPACK_IMPORTED_MODULE_0__); +/* harmony import */ var _utils_errors__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(249); +/* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(246); +/* harmony import */ var _utils_parallelize__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(247); +/* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(248); /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License @@ -59383,53 +59519,61 @@ __webpack_require__.r(__webpack_exports__); + const RunCommand = { - description: 'Run script defined in package.json in each package that contains that script.', + description: 'Run script defined in package.json in each package that contains that script (only works on packages not using Bazel yet)', name: 'run', async run(projects, projectGraph, { extraArgs, options }) { - const batchedProjects = Object(_utils_projects__WEBPACK_IMPORTED_MODULE_3__["topologicallyBatchProjects"])(projects, projectGraph); + _utils_log__WEBPACK_IMPORTED_MODULE_2__["log"].warning(dedent__WEBPACK_IMPORTED_MODULE_0___default.a` + We are migrating packages into the Bazel build system and we will no longer support running npm scripts on + packages using 'yarn kbn run' on Bazel built packages. If the package you are trying to act on contains a + BUILD.bazel file please just use 'yarn kbn build-bazel' to build it or 'yarn kbn watch-bazel' to watch it + `); + const batchedProjects = Object(_utils_projects__WEBPACK_IMPORTED_MODULE_4__["topologicallyBatchProjects"])(projects, projectGraph); if (extraArgs.length === 0) { - throw new _utils_errors__WEBPACK_IMPORTED_MODULE_0__["CliError"]('No script specified'); + throw new _utils_errors__WEBPACK_IMPORTED_MODULE_1__["CliError"]('No script specified'); } const scriptName = extraArgs[0]; const scriptArgs = extraArgs.slice(1); - await Object(_utils_parallelize__WEBPACK_IMPORTED_MODULE_2__["parallelizeBatches"])(batchedProjects, async project => { + await Object(_utils_parallelize__WEBPACK_IMPORTED_MODULE_3__["parallelizeBatches"])(batchedProjects, async project => { if (!project.hasScript(scriptName)) { if (!!options['skip-missing']) { return; } - throw new _utils_errors__WEBPACK_IMPORTED_MODULE_0__["CliError"](`[${project.name}] no "${scriptName}" script defined. To skip packages without the "${scriptName}" script pass --skip-missing`); + throw new _utils_errors__WEBPACK_IMPORTED_MODULE_1__["CliError"](`[${project.name}] no "${scriptName}" script defined. To skip packages without the "${scriptName}" script pass --skip-missing`); } - _utils_log__WEBPACK_IMPORTED_MODULE_1__["log"].info(`[${project.name}] running "${scriptName}" script`); + _utils_log__WEBPACK_IMPORTED_MODULE_2__["log"].info(`[${project.name}] running "${scriptName}" script`); await project.runScriptStreaming(scriptName, { args: scriptArgs }); - _utils_log__WEBPACK_IMPORTED_MODULE_1__["log"].success(`[${project.name}] complete`); + _utils_log__WEBPACK_IMPORTED_MODULE_2__["log"].success(`[${project.name}] complete`); }); } }; /***/ }), -/* 512 */ +/* 513 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "WatchCommand", function() { return WatchCommand; }); -/* harmony import */ var _utils_errors__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(249); -/* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(246); -/* harmony import */ var _utils_parallelize__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(247); -/* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(248); -/* harmony import */ var _utils_watch__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(513); +/* harmony import */ var dedent__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2); +/* harmony import */ var dedent__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(dedent__WEBPACK_IMPORTED_MODULE_0__); +/* harmony import */ var _utils_errors__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(249); +/* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(246); +/* harmony import */ var _utils_parallelize__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(247); +/* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(248); +/* harmony import */ var _utils_watch__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(514); /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License @@ -59443,6 +59587,7 @@ __webpack_require__.r(__webpack_exports__); + /** * Name of the script in the package/project package.json file to run during `kbn watch`. */ @@ -59464,10 +59609,14 @@ const kibanaProjectName = 'kibana'; */ const WatchCommand = { - description: 'Runs `kbn:watch` script for every project.', + description: 'Runs `kbn:watch` script for every project (only works on packages not using Bazel yet)', name: 'watch', async run(projects, projectGraph) { + _utils_log__WEBPACK_IMPORTED_MODULE_2__["log"].warning(dedent__WEBPACK_IMPORTED_MODULE_0___default.a` + We are migrating packages into the Bazel build system. If the package you are trying to watch + contains a BUILD.bazel file please just use 'yarn kbn watch-bazel' + `); const projectsToWatch = new Map(); for (const project of projects.values()) { @@ -59478,33 +59627,33 @@ const WatchCommand = { } if (projectsToWatch.size === 0) { - throw new _utils_errors__WEBPACK_IMPORTED_MODULE_0__["CliError"](`There are no projects to watch found. Make sure that projects define 'kbn:watch' script in 'package.json'.`); + throw new _utils_errors__WEBPACK_IMPORTED_MODULE_1__["CliError"](`There are no projects to watch found. Make sure that projects define 'kbn:watch' script in 'package.json'.`); } const projectNames = Array.from(projectsToWatch.keys()); - _utils_log__WEBPACK_IMPORTED_MODULE_1__["log"].info(`Running ${watchScriptName} scripts for [${projectNames.join(', ')}].`); // Kibana should always be run the last, so we don't rely on automatic + _utils_log__WEBPACK_IMPORTED_MODULE_2__["log"].info(`Running ${watchScriptName} scripts for [${projectNames.join(', ')}].`); // Kibana should always be run the last, so we don't rely on automatic // topological batching and push it to the last one-entry batch manually. const shouldWatchKibanaProject = projectsToWatch.delete(kibanaProjectName); - const batchedProjects = Object(_utils_projects__WEBPACK_IMPORTED_MODULE_3__["topologicallyBatchProjects"])(projectsToWatch, projectGraph); + const batchedProjects = Object(_utils_projects__WEBPACK_IMPORTED_MODULE_4__["topologicallyBatchProjects"])(projectsToWatch, projectGraph); if (shouldWatchKibanaProject) { batchedProjects.push([projects.get(kibanaProjectName)]); } - await Object(_utils_parallelize__WEBPACK_IMPORTED_MODULE_2__["parallelizeBatches"])(batchedProjects, async pkg => { - const completionHint = await Object(_utils_watch__WEBPACK_IMPORTED_MODULE_4__["waitUntilWatchIsReady"])(pkg.runScriptStreaming(watchScriptName, { + await Object(_utils_parallelize__WEBPACK_IMPORTED_MODULE_3__["parallelizeBatches"])(batchedProjects, async pkg => { + const completionHint = await Object(_utils_watch__WEBPACK_IMPORTED_MODULE_5__["waitUntilWatchIsReady"])(pkg.runScriptStreaming(watchScriptName, { debug: false }).stdout // TypeScript note: As long as the proc stdio[1] is 'pipe', then stdout will not be null ); - _utils_log__WEBPACK_IMPORTED_MODULE_1__["log"].success(`[${pkg.name}] Initial build completed (${completionHint}).`); + _utils_log__WEBPACK_IMPORTED_MODULE_2__["log"].success(`[${pkg.name}] Initial build completed (${completionHint}).`); }); } }; /***/ }), -/* 513 */ +/* 514 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -59567,19 +59716,52 @@ function waitUntilWatchIsReady(stream, opts = {}) { } /***/ }), -/* 514 */ +/* 515 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "WatchBazelCommand", function() { return WatchBazelCommand; }); +/* harmony import */ var _utils_bazel__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(372); +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +const WatchBazelCommand = { + description: 'Runs a build in the Bazel built packages and keeps watching them for changes', + name: 'watch-bazel', + + async run(projects, projectGraph, { + options + }) { + const runOffline = (options === null || options === void 0 ? void 0 : options.offline) === true; // Call bazel with the target to build all available packages and run it through iBazel to watch it for changes + // + // Note: --run_output=false arg will disable the iBazel notifications about gazelle and buildozer when running it + // Can also be solved by adding a root `.bazel_fix_commands.json` but its not needed at the moment + + await Object(_utils_bazel__WEBPACK_IMPORTED_MODULE_0__["runIBazel"])(['--run_output=false', 'build', '//packages:build'], runOffline); + } + +}; + +/***/ }), +/* 516 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "runCommand", function() { return runCommand; }); -/* harmony import */ var _kbn_dev_utils_ci_stats_reporter__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(515); +/* harmony import */ var _kbn_dev_utils_ci_stats_reporter__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(517); /* harmony import */ var _kbn_dev_utils_ci_stats_reporter__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_kbn_dev_utils_ci_stats_reporter__WEBPACK_IMPORTED_MODULE_0__); /* harmony import */ var _utils_errors__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(249); /* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(246); /* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(248); /* harmony import */ var _utils_projects_tree__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(371); -/* harmony import */ var _utils_kibana__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(558); +/* harmony import */ var _utils_kibana__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(560); function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } @@ -59697,7 +59879,7 @@ function toArray(value) { } /***/ }), -/* 515 */ +/* 517 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -59716,8 +59898,8 @@ const util_1 = __webpack_require__(112); const os_1 = tslib_1.__importDefault(__webpack_require__(121)); const fs_1 = tslib_1.__importDefault(__webpack_require__(134)); const path_1 = tslib_1.__importDefault(__webpack_require__(4)); -const axios_1 = tslib_1.__importDefault(__webpack_require__(516)); -const ci_stats_config_1 = __webpack_require__(556); +const axios_1 = tslib_1.__importDefault(__webpack_require__(518)); +const ci_stats_config_1 = __webpack_require__(558); const BASE_URL = 'https://ci-stats.kibana.dev'; class CiStatsReporter { constructor(config, log) { @@ -59805,7 +59987,7 @@ class CiStatsReporter { // specify the module id in a way that will keep webpack from bundling extra code into @kbn/pm const hideFromWebpack = ['@', 'kbn/utils']; // eslint-disable-next-line @typescript-eslint/no-var-requires - const { kibanaPackageJson } = __webpack_require__(557)(hideFromWebpack.join('')); + const { kibanaPackageJson } = __webpack_require__(559)(hideFromWebpack.join('')); return kibanaPackageJson.branch; } /** @@ -59817,7 +59999,7 @@ class CiStatsReporter { // specify the module id in a way that will keep webpack from bundling extra code into @kbn/pm const hideFromWebpack = ['@', 'kbn/utils']; // eslint-disable-next-line @typescript-eslint/no-var-requires - const { REPO_ROOT } = __webpack_require__(557)(hideFromWebpack.join('')); + const { REPO_ROOT } = __webpack_require__(559)(hideFromWebpack.join('')); try { return fs_1.default.readFileSync(path_1.default.resolve(REPO_ROOT, 'data/uuid'), 'utf-8').trim(); } @@ -59880,23 +60062,23 @@ exports.CiStatsReporter = CiStatsReporter; //# sourceMappingURL=ci_stats_reporter.js.map /***/ }), -/* 516 */ +/* 518 */ /***/ (function(module, exports, __webpack_require__) { -module.exports = __webpack_require__(517); +module.exports = __webpack_require__(519); /***/ }), -/* 517 */ +/* 519 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var utils = __webpack_require__(518); -var bind = __webpack_require__(519); -var Axios = __webpack_require__(520); -var mergeConfig = __webpack_require__(551); -var defaults = __webpack_require__(526); +var utils = __webpack_require__(520); +var bind = __webpack_require__(521); +var Axios = __webpack_require__(522); +var mergeConfig = __webpack_require__(553); +var defaults = __webpack_require__(528); /** * Create an instance of Axios @@ -59929,18 +60111,18 @@ axios.create = function create(instanceConfig) { }; // Expose Cancel & CancelToken -axios.Cancel = __webpack_require__(552); -axios.CancelToken = __webpack_require__(553); -axios.isCancel = __webpack_require__(525); +axios.Cancel = __webpack_require__(554); +axios.CancelToken = __webpack_require__(555); +axios.isCancel = __webpack_require__(527); // Expose all/spread axios.all = function all(promises) { return Promise.all(promises); }; -axios.spread = __webpack_require__(554); +axios.spread = __webpack_require__(556); // Expose isAxiosError -axios.isAxiosError = __webpack_require__(555); +axios.isAxiosError = __webpack_require__(557); module.exports = axios; @@ -59949,13 +60131,13 @@ module.exports.default = axios; /***/ }), -/* 518 */ +/* 520 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var bind = __webpack_require__(519); +var bind = __webpack_require__(521); /*global toString:true*/ @@ -60307,7 +60489,7 @@ module.exports = { /***/ }), -/* 519 */ +/* 521 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -60325,17 +60507,17 @@ module.exports = function bind(fn, thisArg) { /***/ }), -/* 520 */ +/* 522 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var utils = __webpack_require__(518); -var buildURL = __webpack_require__(521); -var InterceptorManager = __webpack_require__(522); -var dispatchRequest = __webpack_require__(523); -var mergeConfig = __webpack_require__(551); +var utils = __webpack_require__(520); +var buildURL = __webpack_require__(523); +var InterceptorManager = __webpack_require__(524); +var dispatchRequest = __webpack_require__(525); +var mergeConfig = __webpack_require__(553); /** * Create a new instance of Axios @@ -60427,13 +60609,13 @@ module.exports = Axios; /***/ }), -/* 521 */ +/* 523 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var utils = __webpack_require__(518); +var utils = __webpack_require__(520); function encode(val) { return encodeURIComponent(val). @@ -60504,13 +60686,13 @@ module.exports = function buildURL(url, params, paramsSerializer) { /***/ }), -/* 522 */ +/* 524 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var utils = __webpack_require__(518); +var utils = __webpack_require__(520); function InterceptorManager() { this.handlers = []; @@ -60563,16 +60745,16 @@ module.exports = InterceptorManager; /***/ }), -/* 523 */ +/* 525 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var utils = __webpack_require__(518); -var transformData = __webpack_require__(524); -var isCancel = __webpack_require__(525); -var defaults = __webpack_require__(526); +var utils = __webpack_require__(520); +var transformData = __webpack_require__(526); +var isCancel = __webpack_require__(527); +var defaults = __webpack_require__(528); /** * Throws a `Cancel` if cancellation has been requested. @@ -60649,13 +60831,13 @@ module.exports = function dispatchRequest(config) { /***/ }), -/* 524 */ +/* 526 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var utils = __webpack_require__(518); +var utils = __webpack_require__(520); /** * Transform the data for a request or a response @@ -60676,7 +60858,7 @@ module.exports = function transformData(data, headers, fns) { /***/ }), -/* 525 */ +/* 527 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -60688,14 +60870,14 @@ module.exports = function isCancel(value) { /***/ }), -/* 526 */ +/* 528 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var utils = __webpack_require__(518); -var normalizeHeaderName = __webpack_require__(527); +var utils = __webpack_require__(520); +var normalizeHeaderName = __webpack_require__(529); var DEFAULT_CONTENT_TYPE = { 'Content-Type': 'application/x-www-form-urlencoded' @@ -60711,10 +60893,10 @@ function getDefaultAdapter() { var adapter; if (typeof XMLHttpRequest !== 'undefined') { // For browsers use XHR adapter - adapter = __webpack_require__(528); + adapter = __webpack_require__(530); } else if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') { // For node use HTTP adapter - adapter = __webpack_require__(538); + adapter = __webpack_require__(540); } return adapter; } @@ -60793,13 +60975,13 @@ module.exports = defaults; /***/ }), -/* 527 */ +/* 529 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var utils = __webpack_require__(518); +var utils = __webpack_require__(520); module.exports = function normalizeHeaderName(headers, normalizedName) { utils.forEach(headers, function processHeader(value, name) { @@ -60812,20 +60994,20 @@ module.exports = function normalizeHeaderName(headers, normalizedName) { /***/ }), -/* 528 */ +/* 530 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var utils = __webpack_require__(518); -var settle = __webpack_require__(529); -var cookies = __webpack_require__(532); -var buildURL = __webpack_require__(521); -var buildFullPath = __webpack_require__(533); -var parseHeaders = __webpack_require__(536); -var isURLSameOrigin = __webpack_require__(537); -var createError = __webpack_require__(530); +var utils = __webpack_require__(520); +var settle = __webpack_require__(531); +var cookies = __webpack_require__(534); +var buildURL = __webpack_require__(523); +var buildFullPath = __webpack_require__(535); +var parseHeaders = __webpack_require__(538); +var isURLSameOrigin = __webpack_require__(539); +var createError = __webpack_require__(532); module.exports = function xhrAdapter(config) { return new Promise(function dispatchXhrRequest(resolve, reject) { @@ -60998,13 +61180,13 @@ module.exports = function xhrAdapter(config) { /***/ }), -/* 529 */ +/* 531 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var createError = __webpack_require__(530); +var createError = __webpack_require__(532); /** * Resolve or reject a Promise based on response status. @@ -61030,13 +61212,13 @@ module.exports = function settle(resolve, reject, response) { /***/ }), -/* 530 */ +/* 532 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var enhanceError = __webpack_require__(531); +var enhanceError = __webpack_require__(533); /** * Create an Error with the specified message, config, error code, request and response. @@ -61055,7 +61237,7 @@ module.exports = function createError(message, config, code, request, response) /***/ }), -/* 531 */ +/* 533 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -61104,13 +61286,13 @@ module.exports = function enhanceError(error, config, code, request, response) { /***/ }), -/* 532 */ +/* 534 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var utils = __webpack_require__(518); +var utils = __webpack_require__(520); module.exports = ( utils.isStandardBrowserEnv() ? @@ -61164,14 +61346,14 @@ module.exports = ( /***/ }), -/* 533 */ +/* 535 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isAbsoluteURL = __webpack_require__(534); -var combineURLs = __webpack_require__(535); +var isAbsoluteURL = __webpack_require__(536); +var combineURLs = __webpack_require__(537); /** * Creates a new URL by combining the baseURL with the requestedURL, @@ -61191,7 +61373,7 @@ module.exports = function buildFullPath(baseURL, requestedURL) { /***/ }), -/* 534 */ +/* 536 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -61212,7 +61394,7 @@ module.exports = function isAbsoluteURL(url) { /***/ }), -/* 535 */ +/* 537 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -61233,13 +61415,13 @@ module.exports = function combineURLs(baseURL, relativeURL) { /***/ }), -/* 536 */ +/* 538 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var utils = __webpack_require__(518); +var utils = __webpack_require__(520); // Headers whose duplicates are ignored by node // c.f. https://nodejs.org/api/http.html#http_message_headers @@ -61293,13 +61475,13 @@ module.exports = function parseHeaders(headers) { /***/ }), -/* 537 */ +/* 539 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var utils = __webpack_require__(518); +var utils = __webpack_require__(520); module.exports = ( utils.isStandardBrowserEnv() ? @@ -61368,25 +61550,25 @@ module.exports = ( /***/ }), -/* 538 */ +/* 540 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var utils = __webpack_require__(518); -var settle = __webpack_require__(529); -var buildFullPath = __webpack_require__(533); -var buildURL = __webpack_require__(521); -var http = __webpack_require__(539); -var https = __webpack_require__(540); -var httpFollow = __webpack_require__(541).http; -var httpsFollow = __webpack_require__(541).https; +var utils = __webpack_require__(520); +var settle = __webpack_require__(531); +var buildFullPath = __webpack_require__(535); +var buildURL = __webpack_require__(523); +var http = __webpack_require__(541); +var https = __webpack_require__(542); +var httpFollow = __webpack_require__(543).http; +var httpsFollow = __webpack_require__(543).https; var url = __webpack_require__(283); -var zlib = __webpack_require__(549); -var pkg = __webpack_require__(550); -var createError = __webpack_require__(530); -var enhanceError = __webpack_require__(531); +var zlib = __webpack_require__(551); +var pkg = __webpack_require__(552); +var createError = __webpack_require__(532); +var enhanceError = __webpack_require__(533); var isHttps = /https:?/; @@ -61678,28 +61860,28 @@ module.exports = function httpAdapter(config) { /***/ }), -/* 539 */ +/* 541 */ /***/ (function(module, exports) { module.exports = require("http"); /***/ }), -/* 540 */ +/* 542 */ /***/ (function(module, exports) { module.exports = require("https"); /***/ }), -/* 541 */ +/* 543 */ /***/ (function(module, exports, __webpack_require__) { var url = __webpack_require__(283); var URL = url.URL; -var http = __webpack_require__(539); -var https = __webpack_require__(540); +var http = __webpack_require__(541); +var https = __webpack_require__(542); var Writable = __webpack_require__(138).Writable; var assert = __webpack_require__(140); -var debug = __webpack_require__(542); +var debug = __webpack_require__(544); // Create handlers that pass events from native requests var eventHandlers = Object.create(null); @@ -62194,13 +62376,13 @@ module.exports.wrap = wrap; /***/ }), -/* 542 */ +/* 544 */ /***/ (function(module, exports, __webpack_require__) { var debug; try { /* eslint global-require: off */ - debug = __webpack_require__(543)("follow-redirects"); + debug = __webpack_require__(545)("follow-redirects"); } catch (error) { debug = function () { /* */ }; @@ -62209,7 +62391,7 @@ module.exports = debug; /***/ }), -/* 543 */ +/* 545 */ /***/ (function(module, exports, __webpack_require__) { /** @@ -62218,14 +62400,14 @@ module.exports = debug; */ if (typeof process !== 'undefined' && process.type === 'renderer') { - module.exports = __webpack_require__(544); + module.exports = __webpack_require__(546); } else { - module.exports = __webpack_require__(547); + module.exports = __webpack_require__(549); } /***/ }), -/* 544 */ +/* 546 */ /***/ (function(module, exports, __webpack_require__) { /** @@ -62234,7 +62416,7 @@ if (typeof process !== 'undefined' && process.type === 'renderer') { * Expose `debug()` as the module. */ -exports = module.exports = __webpack_require__(545); +exports = module.exports = __webpack_require__(547); exports.log = log; exports.formatArgs = formatArgs; exports.save = save; @@ -62416,7 +62598,7 @@ function localstorage() { /***/ }), -/* 545 */ +/* 547 */ /***/ (function(module, exports, __webpack_require__) { @@ -62432,7 +62614,7 @@ exports.coerce = coerce; exports.disable = disable; exports.enable = enable; exports.enabled = enabled; -exports.humanize = __webpack_require__(546); +exports.humanize = __webpack_require__(548); /** * The currently active debug mode names, and names to skip. @@ -62624,7 +62806,7 @@ function coerce(val) { /***/ }), -/* 546 */ +/* 548 */ /***/ (function(module, exports) { /** @@ -62782,7 +62964,7 @@ function plural(ms, n, name) { /***/ }), -/* 547 */ +/* 549 */ /***/ (function(module, exports, __webpack_require__) { /** @@ -62798,7 +62980,7 @@ var util = __webpack_require__(112); * Expose `debug()` as the module. */ -exports = module.exports = __webpack_require__(545); +exports = module.exports = __webpack_require__(547); exports.init = init; exports.log = log; exports.formatArgs = formatArgs; @@ -62977,7 +63159,7 @@ function createWritableStdioStream (fd) { case 'PIPE': case 'TCP': - var net = __webpack_require__(548); + var net = __webpack_require__(550); stream = new net.Socket({ fd: fd, readable: false, @@ -63036,31 +63218,31 @@ exports.enable(load()); /***/ }), -/* 548 */ +/* 550 */ /***/ (function(module, exports) { module.exports = require("net"); /***/ }), -/* 549 */ +/* 551 */ /***/ (function(module, exports) { module.exports = require("zlib"); /***/ }), -/* 550 */ +/* 552 */ /***/ (function(module) { module.exports = JSON.parse("{\"name\":\"axios\",\"version\":\"0.21.1\",\"description\":\"Promise based HTTP client for the browser and node.js\",\"main\":\"index.js\",\"scripts\":{\"test\":\"grunt test && bundlesize\",\"start\":\"node ./sandbox/server.js\",\"build\":\"NODE_ENV=production grunt build\",\"preversion\":\"npm test\",\"version\":\"npm run build && grunt version && git add -A dist && git add CHANGELOG.md bower.json package.json\",\"postversion\":\"git push && git push --tags\",\"examples\":\"node ./examples/server.js\",\"coveralls\":\"cat coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js\",\"fix\":\"eslint --fix lib/**/*.js\"},\"repository\":{\"type\":\"git\",\"url\":\"https://github.com/axios/axios.git\"},\"keywords\":[\"xhr\",\"http\",\"ajax\",\"promise\",\"node\"],\"author\":\"Matt Zabriskie\",\"license\":\"MIT\",\"bugs\":{\"url\":\"https://github.com/axios/axios/issues\"},\"homepage\":\"https://github.com/axios/axios\",\"devDependencies\":{\"bundlesize\":\"^0.17.0\",\"coveralls\":\"^3.0.0\",\"es6-promise\":\"^4.2.4\",\"grunt\":\"^1.0.2\",\"grunt-banner\":\"^0.6.0\",\"grunt-cli\":\"^1.2.0\",\"grunt-contrib-clean\":\"^1.1.0\",\"grunt-contrib-watch\":\"^1.0.0\",\"grunt-eslint\":\"^20.1.0\",\"grunt-karma\":\"^2.0.0\",\"grunt-mocha-test\":\"^0.13.3\",\"grunt-ts\":\"^6.0.0-beta.19\",\"grunt-webpack\":\"^1.0.18\",\"istanbul-instrumenter-loader\":\"^1.0.0\",\"jasmine-core\":\"^2.4.1\",\"karma\":\"^1.3.0\",\"karma-chrome-launcher\":\"^2.2.0\",\"karma-coverage\":\"^1.1.1\",\"karma-firefox-launcher\":\"^1.1.0\",\"karma-jasmine\":\"^1.1.1\",\"karma-jasmine-ajax\":\"^0.1.13\",\"karma-opera-launcher\":\"^1.0.0\",\"karma-safari-launcher\":\"^1.0.0\",\"karma-sauce-launcher\":\"^1.2.0\",\"karma-sinon\":\"^1.0.5\",\"karma-sourcemap-loader\":\"^0.3.7\",\"karma-webpack\":\"^1.7.0\",\"load-grunt-tasks\":\"^3.5.2\",\"minimist\":\"^1.2.0\",\"mocha\":\"^5.2.0\",\"sinon\":\"^4.5.0\",\"typescript\":\"^2.8.1\",\"url-search-params\":\"^0.10.0\",\"webpack\":\"^1.13.1\",\"webpack-dev-server\":\"^1.14.1\"},\"browser\":{\"./lib/adapters/http.js\":\"./lib/adapters/xhr.js\"},\"jsdelivr\":\"dist/axios.min.js\",\"unpkg\":\"dist/axios.min.js\",\"typings\":\"./index.d.ts\",\"dependencies\":{\"follow-redirects\":\"^1.10.0\"},\"bundlesize\":[{\"path\":\"./dist/axios.min.js\",\"threshold\":\"5kB\"}]}"); /***/ }), -/* 551 */ +/* 553 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var utils = __webpack_require__(518); +var utils = __webpack_require__(520); /** * Config-specific merge-function which creates a new config-object @@ -63148,7 +63330,7 @@ module.exports = function mergeConfig(config1, config2) { /***/ }), -/* 552 */ +/* 554 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63174,13 +63356,13 @@ module.exports = Cancel; /***/ }), -/* 553 */ +/* 555 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var Cancel = __webpack_require__(552); +var Cancel = __webpack_require__(554); /** * A `CancelToken` is an object that can be used to request cancellation of an operation. @@ -63238,7 +63420,7 @@ module.exports = CancelToken; /***/ }), -/* 554 */ +/* 556 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63272,7 +63454,7 @@ module.exports = function spread(callback) { /***/ }), -/* 555 */ +/* 557 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63290,7 +63472,7 @@ module.exports = function isAxiosError(payload) { /***/ }), -/* 556 */ +/* 558 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63340,7 +63522,7 @@ exports.parseConfig = parseConfig; //# sourceMappingURL=ci_stats_config.js.map /***/ }), -/* 557 */ +/* 559 */ /***/ (function(module, exports) { function webpackEmptyContext(req) { @@ -63351,10 +63533,10 @@ function webpackEmptyContext(req) { webpackEmptyContext.keys = function() { return []; }; webpackEmptyContext.resolve = webpackEmptyContext; module.exports = webpackEmptyContext; -webpackEmptyContext.id = 557; +webpackEmptyContext.id = 559; /***/ }), -/* 558 */ +/* 560 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -63364,13 +63546,13 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_0__); /* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(134); /* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(fs__WEBPACK_IMPORTED_MODULE_1__); -/* harmony import */ var multimatch__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(559); +/* harmony import */ var multimatch__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(561); /* harmony import */ var multimatch__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(multimatch__WEBPACK_IMPORTED_MODULE_2__); /* harmony import */ var is_path_inside__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(239); /* harmony import */ var is_path_inside__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(is_path_inside__WEBPACK_IMPORTED_MODULE_3__); /* harmony import */ var _yarn_lock__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(366); /* harmony import */ var _projects__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(248); -/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(562); +/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(564); function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } @@ -63534,15 +63716,15 @@ class Kibana { } /***/ }), -/* 559 */ +/* 561 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const minimatch = __webpack_require__(150); const arrayUnion = __webpack_require__(145); -const arrayDiffer = __webpack_require__(560); -const arrify = __webpack_require__(561); +const arrayDiffer = __webpack_require__(562); +const arrify = __webpack_require__(563); module.exports = (list, patterns, options = {}) => { list = arrify(list); @@ -63566,7 +63748,7 @@ module.exports = (list, patterns, options = {}) => { /***/ }), -/* 560 */ +/* 562 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63581,7 +63763,7 @@ module.exports = arrayDiffer; /***/ }), -/* 561 */ +/* 563 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63611,7 +63793,7 @@ module.exports = arrify; /***/ }), -/* 562 */ +/* 564 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -63670,15 +63852,15 @@ function getProjectPaths({ } /***/ }), -/* 563 */ +/* 565 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); -/* harmony import */ var _build_bazel_production_projects__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(564); +/* harmony import */ var _build_bazel_production_projects__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(566); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "buildBazelProductionProjects", function() { return _build_bazel_production_projects__WEBPACK_IMPORTED_MODULE_0__["buildBazelProductionProjects"]; }); -/* harmony import */ var _build_non_bazel_production_projects__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(812); +/* harmony import */ var _build_non_bazel_production_projects__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(814); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "buildNonBazelProductionProjects", function() { return _build_non_bazel_production_projects__WEBPACK_IMPORTED_MODULE_1__["buildNonBazelProductionProjects"]; }); /* @@ -63692,19 +63874,19 @@ __webpack_require__.r(__webpack_exports__); /***/ }), -/* 564 */ +/* 566 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "buildBazelProductionProjects", function() { return buildBazelProductionProjects; }); -/* harmony import */ var cpy__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(565); +/* harmony import */ var cpy__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(567); /* harmony import */ var cpy__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(cpy__WEBPACK_IMPORTED_MODULE_0__); -/* harmony import */ var globby__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(774); +/* harmony import */ var globby__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(776); /* harmony import */ var globby__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(globby__WEBPACK_IMPORTED_MODULE_1__); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(4); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_2__); -/* harmony import */ var _build_non_bazel_production_projects__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(812); +/* harmony import */ var _build_non_bazel_production_projects__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(814); /* harmony import */ var _utils_bazel__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(372); /* harmony import */ var _utils_fs__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(131); /* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(246); @@ -63799,7 +63981,7 @@ async function applyCorrectPermissions(project, kibanaRoot, buildRoot) { } /***/ }), -/* 565 */ +/* 567 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63807,14 +63989,14 @@ async function applyCorrectPermissions(project, kibanaRoot, buildRoot) { const EventEmitter = __webpack_require__(156); const path = __webpack_require__(4); const os = __webpack_require__(121); -const pMap = __webpack_require__(566); -const arrify = __webpack_require__(561); -const globby = __webpack_require__(569); -const hasGlob = __webpack_require__(758); -const cpFile = __webpack_require__(760); -const junk = __webpack_require__(770); -const pFilter = __webpack_require__(771); -const CpyError = __webpack_require__(773); +const pMap = __webpack_require__(568); +const arrify = __webpack_require__(563); +const globby = __webpack_require__(571); +const hasGlob = __webpack_require__(760); +const cpFile = __webpack_require__(762); +const junk = __webpack_require__(772); +const pFilter = __webpack_require__(773); +const CpyError = __webpack_require__(775); const defaultOptions = { ignoreJunk: true @@ -63965,12 +64147,12 @@ module.exports = (source, destination, { /***/ }), -/* 566 */ +/* 568 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const AggregateError = __webpack_require__(567); +const AggregateError = __webpack_require__(569); module.exports = async ( iterable, @@ -64053,12 +64235,12 @@ module.exports = async ( /***/ }), -/* 567 */ +/* 569 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const indentString = __webpack_require__(568); +const indentString = __webpack_require__(570); const cleanStack = __webpack_require__(244); const cleanInternalStack = stack => stack.replace(/\s+at .*aggregate-error\/index.js:\d+:\d+\)?/g, ''); @@ -64107,7 +64289,7 @@ module.exports = AggregateError; /***/ }), -/* 568 */ +/* 570 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -64149,17 +64331,17 @@ module.exports = (string, count = 1, options) => { /***/ }), -/* 569 */ +/* 571 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(134); -const arrayUnion = __webpack_require__(570); +const arrayUnion = __webpack_require__(572); const glob = __webpack_require__(147); -const fastGlob = __webpack_require__(572); -const dirGlob = __webpack_require__(751); -const gitignore = __webpack_require__(754); +const fastGlob = __webpack_require__(574); +const dirGlob = __webpack_require__(753); +const gitignore = __webpack_require__(756); const DEFAULT_FILTER = () => false; @@ -64304,12 +64486,12 @@ module.exports.gitignore = gitignore; /***/ }), -/* 570 */ +/* 572 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var arrayUniq = __webpack_require__(571); +var arrayUniq = __webpack_require__(573); module.exports = function () { return arrayUniq([].concat.apply([], arguments)); @@ -64317,7 +64499,7 @@ module.exports = function () { /***/ }), -/* 571 */ +/* 573 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -64386,10 +64568,10 @@ if ('Set' in global) { /***/ }), -/* 572 */ +/* 574 */ /***/ (function(module, exports, __webpack_require__) { -const pkg = __webpack_require__(573); +const pkg = __webpack_require__(575); module.exports = pkg.async; module.exports.default = pkg.async; @@ -64402,19 +64584,19 @@ module.exports.generateTasks = pkg.generateTasks; /***/ }), -/* 573 */ +/* 575 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var optionsManager = __webpack_require__(574); -var taskManager = __webpack_require__(575); -var reader_async_1 = __webpack_require__(722); -var reader_stream_1 = __webpack_require__(746); -var reader_sync_1 = __webpack_require__(747); -var arrayUtils = __webpack_require__(749); -var streamUtils = __webpack_require__(750); +var optionsManager = __webpack_require__(576); +var taskManager = __webpack_require__(577); +var reader_async_1 = __webpack_require__(724); +var reader_stream_1 = __webpack_require__(748); +var reader_sync_1 = __webpack_require__(749); +var arrayUtils = __webpack_require__(751); +var streamUtils = __webpack_require__(752); /** * Synchronous API. */ @@ -64480,7 +64662,7 @@ function isString(source) { /***/ }), -/* 574 */ +/* 576 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -64518,13 +64700,13 @@ exports.prepare = prepare; /***/ }), -/* 575 */ +/* 577 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var patternUtils = __webpack_require__(576); +var patternUtils = __webpack_require__(578); /** * Generate tasks based on parent directory of each pattern. */ @@ -64615,16 +64797,16 @@ exports.convertPatternGroupToTask = convertPatternGroupToTask; /***/ }), -/* 576 */ +/* 578 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var path = __webpack_require__(4); -var globParent = __webpack_require__(577); +var globParent = __webpack_require__(579); var isGlob = __webpack_require__(172); -var micromatch = __webpack_require__(580); +var micromatch = __webpack_require__(582); var GLOBSTAR = '**'; /** * Return true for static pattern. @@ -64770,15 +64952,15 @@ exports.matchAny = matchAny; /***/ }), -/* 577 */ +/* 579 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var path = __webpack_require__(4); -var isglob = __webpack_require__(578); -var pathDirname = __webpack_require__(579); +var isglob = __webpack_require__(580); +var pathDirname = __webpack_require__(581); var isWin32 = __webpack_require__(121).platform() === 'win32'; module.exports = function globParent(str) { @@ -64801,7 +64983,7 @@ module.exports = function globParent(str) { /***/ }), -/* 578 */ +/* 580 */ /***/ (function(module, exports, __webpack_require__) { /*! @@ -64832,7 +65014,7 @@ module.exports = function isGlob(str) { /***/ }), -/* 579 */ +/* 581 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -64982,7 +65164,7 @@ module.exports.win32 = win32; /***/ }), -/* 580 */ +/* 582 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -64993,18 +65175,18 @@ module.exports.win32 = win32; */ var util = __webpack_require__(112); -var braces = __webpack_require__(581); -var toRegex = __webpack_require__(582); -var extend = __webpack_require__(690); +var braces = __webpack_require__(583); +var toRegex = __webpack_require__(584); +var extend = __webpack_require__(692); /** * Local dependencies */ -var compilers = __webpack_require__(692); -var parsers = __webpack_require__(718); -var cache = __webpack_require__(719); -var utils = __webpack_require__(720); +var compilers = __webpack_require__(694); +var parsers = __webpack_require__(720); +var cache = __webpack_require__(721); +var utils = __webpack_require__(722); var MAX_LENGTH = 1024 * 64; /** @@ -65866,7 +66048,7 @@ module.exports = micromatch; /***/ }), -/* 581 */ +/* 583 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -65876,18 +66058,18 @@ module.exports = micromatch; * Module dependencies */ -var toRegex = __webpack_require__(582); -var unique = __webpack_require__(602); -var extend = __webpack_require__(603); +var toRegex = __webpack_require__(584); +var unique = __webpack_require__(604); +var extend = __webpack_require__(605); /** * Local dependencies */ -var compilers = __webpack_require__(605); -var parsers = __webpack_require__(618); -var Braces = __webpack_require__(623); -var utils = __webpack_require__(606); +var compilers = __webpack_require__(607); +var parsers = __webpack_require__(620); +var Braces = __webpack_require__(625); +var utils = __webpack_require__(608); var MAX_LENGTH = 1024 * 64; var cache = {}; @@ -66191,16 +66373,16 @@ module.exports = braces; /***/ }), -/* 582 */ +/* 584 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var safe = __webpack_require__(583); -var define = __webpack_require__(589); -var extend = __webpack_require__(595); -var not = __webpack_require__(599); +var safe = __webpack_require__(585); +var define = __webpack_require__(591); +var extend = __webpack_require__(597); +var not = __webpack_require__(601); var MAX_LENGTH = 1024 * 64; /** @@ -66353,10 +66535,10 @@ module.exports.makeRe = makeRe; /***/ }), -/* 583 */ +/* 585 */ /***/ (function(module, exports, __webpack_require__) { -var parse = __webpack_require__(584); +var parse = __webpack_require__(586); var types = parse.types; module.exports = function (re, opts) { @@ -66402,13 +66584,13 @@ function isRegExp (x) { /***/ }), -/* 584 */ +/* 586 */ /***/ (function(module, exports, __webpack_require__) { -var util = __webpack_require__(585); -var types = __webpack_require__(586); -var sets = __webpack_require__(587); -var positions = __webpack_require__(588); +var util = __webpack_require__(587); +var types = __webpack_require__(588); +var sets = __webpack_require__(589); +var positions = __webpack_require__(590); module.exports = function(regexpStr) { @@ -66690,11 +66872,11 @@ module.exports.types = types; /***/ }), -/* 585 */ +/* 587 */ /***/ (function(module, exports, __webpack_require__) { -var types = __webpack_require__(586); -var sets = __webpack_require__(587); +var types = __webpack_require__(588); +var sets = __webpack_require__(589); // All of these are private and only used by randexp. @@ -66807,7 +66989,7 @@ exports.error = function(regexp, msg) { /***/ }), -/* 586 */ +/* 588 */ /***/ (function(module, exports) { module.exports = { @@ -66823,10 +67005,10 @@ module.exports = { /***/ }), -/* 587 */ +/* 589 */ /***/ (function(module, exports, __webpack_require__) { -var types = __webpack_require__(586); +var types = __webpack_require__(588); var INTS = function() { return [{ type: types.RANGE , from: 48, to: 57 }]; @@ -66911,10 +67093,10 @@ exports.anyChar = function() { /***/ }), -/* 588 */ +/* 590 */ /***/ (function(module, exports, __webpack_require__) { -var types = __webpack_require__(586); +var types = __webpack_require__(588); exports.wordBoundary = function() { return { type: types.POSITION, value: 'b' }; @@ -66934,7 +67116,7 @@ exports.end = function() { /***/ }), -/* 589 */ +/* 591 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -66947,8 +67129,8 @@ exports.end = function() { -var isobject = __webpack_require__(590); -var isDescriptor = __webpack_require__(591); +var isobject = __webpack_require__(592); +var isDescriptor = __webpack_require__(593); var define = (typeof Reflect !== 'undefined' && Reflect.defineProperty) ? Reflect.defineProperty : Object.defineProperty; @@ -66979,7 +67161,7 @@ module.exports = function defineProperty(obj, key, val) { /***/ }), -/* 590 */ +/* 592 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -66998,7 +67180,7 @@ module.exports = function isObject(val) { /***/ }), -/* 591 */ +/* 593 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67011,9 +67193,9 @@ module.exports = function isObject(val) { -var typeOf = __webpack_require__(592); -var isAccessor = __webpack_require__(593); -var isData = __webpack_require__(594); +var typeOf = __webpack_require__(594); +var isAccessor = __webpack_require__(595); +var isData = __webpack_require__(596); module.exports = function isDescriptor(obj, key) { if (typeOf(obj) !== 'object') { @@ -67027,7 +67209,7 @@ module.exports = function isDescriptor(obj, key) { /***/ }), -/* 592 */ +/* 594 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -67162,7 +67344,7 @@ function isBuffer(val) { /***/ }), -/* 593 */ +/* 595 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67175,7 +67357,7 @@ function isBuffer(val) { -var typeOf = __webpack_require__(592); +var typeOf = __webpack_require__(594); // accessor descriptor properties var accessor = { @@ -67238,7 +67420,7 @@ module.exports = isAccessorDescriptor; /***/ }), -/* 594 */ +/* 596 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67251,7 +67433,7 @@ module.exports = isAccessorDescriptor; -var typeOf = __webpack_require__(592); +var typeOf = __webpack_require__(594); module.exports = function isDataDescriptor(obj, prop) { // data descriptor properties @@ -67294,14 +67476,14 @@ module.exports = function isDataDescriptor(obj, prop) { /***/ }), -/* 595 */ +/* 597 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(596); -var assignSymbols = __webpack_require__(598); +var isExtendable = __webpack_require__(598); +var assignSymbols = __webpack_require__(600); module.exports = Object.assign || function(obj/*, objects*/) { if (obj === null || typeof obj === 'undefined') { @@ -67361,7 +67543,7 @@ function isEnum(obj, key) { /***/ }), -/* 596 */ +/* 598 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67374,7 +67556,7 @@ function isEnum(obj, key) { -var isPlainObject = __webpack_require__(597); +var isPlainObject = __webpack_require__(599); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -67382,7 +67564,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 597 */ +/* 599 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67395,7 +67577,7 @@ module.exports = function isExtendable(val) { -var isObject = __webpack_require__(590); +var isObject = __webpack_require__(592); function isObjectObject(o) { return isObject(o) === true @@ -67426,7 +67608,7 @@ module.exports = function isPlainObject(o) { /***/ }), -/* 598 */ +/* 600 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67473,14 +67655,14 @@ module.exports = function(receiver, objects) { /***/ }), -/* 599 */ +/* 601 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var extend = __webpack_require__(600); -var safe = __webpack_require__(583); +var extend = __webpack_require__(602); +var safe = __webpack_require__(585); /** * The main export is a function that takes a `pattern` string and an `options` object. @@ -67552,14 +67734,14 @@ module.exports = toRegex; /***/ }), -/* 600 */ +/* 602 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(601); -var assignSymbols = __webpack_require__(598); +var isExtendable = __webpack_require__(603); +var assignSymbols = __webpack_require__(600); module.exports = Object.assign || function(obj/*, objects*/) { if (obj === null || typeof obj === 'undefined') { @@ -67619,7 +67801,7 @@ function isEnum(obj, key) { /***/ }), -/* 601 */ +/* 603 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67632,7 +67814,7 @@ function isEnum(obj, key) { -var isPlainObject = __webpack_require__(597); +var isPlainObject = __webpack_require__(599); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -67640,7 +67822,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 602 */ +/* 604 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67690,13 +67872,13 @@ module.exports.immutable = function uniqueImmutable(arr) { /***/ }), -/* 603 */ +/* 605 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isObject = __webpack_require__(604); +var isObject = __webpack_require__(606); module.exports = function extend(o/*, objects*/) { if (!isObject(o)) { o = {}; } @@ -67730,7 +67912,7 @@ function hasOwn(obj, key) { /***/ }), -/* 604 */ +/* 606 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67750,13 +67932,13 @@ module.exports = function isExtendable(val) { /***/ }), -/* 605 */ +/* 607 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var utils = __webpack_require__(606); +var utils = __webpack_require__(608); module.exports = function(braces, options) { braces.compiler @@ -68039,25 +68221,25 @@ function hasQueue(node) { /***/ }), -/* 606 */ +/* 608 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var splitString = __webpack_require__(607); +var splitString = __webpack_require__(609); var utils = module.exports; /** * Module dependencies */ -utils.extend = __webpack_require__(603); -utils.flatten = __webpack_require__(610); -utils.isObject = __webpack_require__(590); -utils.fillRange = __webpack_require__(611); -utils.repeat = __webpack_require__(617); -utils.unique = __webpack_require__(602); +utils.extend = __webpack_require__(605); +utils.flatten = __webpack_require__(612); +utils.isObject = __webpack_require__(592); +utils.fillRange = __webpack_require__(613); +utils.repeat = __webpack_require__(619); +utils.unique = __webpack_require__(604); utils.define = function(obj, key, val) { Object.defineProperty(obj, key, { @@ -68389,7 +68571,7 @@ utils.escapeRegex = function(str) { /***/ }), -/* 607 */ +/* 609 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68402,7 +68584,7 @@ utils.escapeRegex = function(str) { -var extend = __webpack_require__(608); +var extend = __webpack_require__(610); module.exports = function(str, options, fn) { if (typeof str !== 'string') { @@ -68567,14 +68749,14 @@ function keepEscaping(opts, str, idx) { /***/ }), -/* 608 */ +/* 610 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(609); -var assignSymbols = __webpack_require__(598); +var isExtendable = __webpack_require__(611); +var assignSymbols = __webpack_require__(600); module.exports = Object.assign || function(obj/*, objects*/) { if (obj === null || typeof obj === 'undefined') { @@ -68634,7 +68816,7 @@ function isEnum(obj, key) { /***/ }), -/* 609 */ +/* 611 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68647,7 +68829,7 @@ function isEnum(obj, key) { -var isPlainObject = __webpack_require__(597); +var isPlainObject = __webpack_require__(599); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -68655,7 +68837,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 610 */ +/* 612 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68684,7 +68866,7 @@ function flat(arr, res) { /***/ }), -/* 611 */ +/* 613 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68698,10 +68880,10 @@ function flat(arr, res) { var util = __webpack_require__(112); -var isNumber = __webpack_require__(612); -var extend = __webpack_require__(603); -var repeat = __webpack_require__(615); -var toRegex = __webpack_require__(616); +var isNumber = __webpack_require__(614); +var extend = __webpack_require__(605); +var repeat = __webpack_require__(617); +var toRegex = __webpack_require__(618); /** * Return a range of numbers or letters. @@ -68899,7 +69081,7 @@ module.exports = fillRange; /***/ }), -/* 612 */ +/* 614 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68912,7 +69094,7 @@ module.exports = fillRange; -var typeOf = __webpack_require__(613); +var typeOf = __webpack_require__(615); module.exports = function isNumber(num) { var type = typeOf(num); @@ -68928,10 +69110,10 @@ module.exports = function isNumber(num) { /***/ }), -/* 613 */ +/* 615 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(614); +var isBuffer = __webpack_require__(616); var toString = Object.prototype.toString; /** @@ -69050,7 +69232,7 @@ module.exports = function kindOf(val) { /***/ }), -/* 614 */ +/* 616 */ /***/ (function(module, exports) { /*! @@ -69077,7 +69259,7 @@ function isSlowBuffer (obj) { /***/ }), -/* 615 */ +/* 617 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69154,7 +69336,7 @@ function repeat(str, num) { /***/ }), -/* 616 */ +/* 618 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69167,8 +69349,8 @@ function repeat(str, num) { -var repeat = __webpack_require__(615); -var isNumber = __webpack_require__(612); +var repeat = __webpack_require__(617); +var isNumber = __webpack_require__(614); var cache = {}; function toRegexRange(min, max, options) { @@ -69455,7 +69637,7 @@ module.exports = toRegexRange; /***/ }), -/* 617 */ +/* 619 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69480,14 +69662,14 @@ module.exports = function repeat(ele, num) { /***/ }), -/* 618 */ +/* 620 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var Node = __webpack_require__(619); -var utils = __webpack_require__(606); +var Node = __webpack_require__(621); +var utils = __webpack_require__(608); /** * Braces parsers @@ -69847,15 +70029,15 @@ function concatNodes(pos, node, parent, options) { /***/ }), -/* 619 */ +/* 621 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isObject = __webpack_require__(590); -var define = __webpack_require__(620); -var utils = __webpack_require__(621); +var isObject = __webpack_require__(592); +var define = __webpack_require__(622); +var utils = __webpack_require__(623); var ownNames; /** @@ -70346,7 +70528,7 @@ exports = module.exports = Node; /***/ }), -/* 620 */ +/* 622 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -70359,7 +70541,7 @@ exports = module.exports = Node; -var isDescriptor = __webpack_require__(591); +var isDescriptor = __webpack_require__(593); module.exports = function defineProperty(obj, prop, val) { if (typeof obj !== 'object' && typeof obj !== 'function') { @@ -70384,13 +70566,13 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 621 */ +/* 623 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var typeOf = __webpack_require__(622); +var typeOf = __webpack_require__(624); var utils = module.exports; /** @@ -71410,10 +71592,10 @@ function assert(val, message) { /***/ }), -/* 622 */ +/* 624 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(614); +var isBuffer = __webpack_require__(616); var toString = Object.prototype.toString; /** @@ -71532,17 +71714,17 @@ module.exports = function kindOf(val) { /***/ }), -/* 623 */ +/* 625 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var extend = __webpack_require__(603); -var Snapdragon = __webpack_require__(624); -var compilers = __webpack_require__(605); -var parsers = __webpack_require__(618); -var utils = __webpack_require__(606); +var extend = __webpack_require__(605); +var Snapdragon = __webpack_require__(626); +var compilers = __webpack_require__(607); +var parsers = __webpack_require__(620); +var utils = __webpack_require__(608); /** * Customize Snapdragon parser and renderer @@ -71643,17 +71825,17 @@ module.exports = Braces; /***/ }), -/* 624 */ +/* 626 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var Base = __webpack_require__(625); -var define = __webpack_require__(653); -var Compiler = __webpack_require__(664); -var Parser = __webpack_require__(687); -var utils = __webpack_require__(667); +var Base = __webpack_require__(627); +var define = __webpack_require__(655); +var Compiler = __webpack_require__(666); +var Parser = __webpack_require__(689); +var utils = __webpack_require__(669); var regexCache = {}; var cache = {}; @@ -71824,20 +72006,20 @@ module.exports.Parser = Parser; /***/ }), -/* 625 */ +/* 627 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var util = __webpack_require__(112); -var define = __webpack_require__(626); -var CacheBase = __webpack_require__(627); -var Emitter = __webpack_require__(628); -var isObject = __webpack_require__(590); -var merge = __webpack_require__(647); -var pascal = __webpack_require__(650); -var cu = __webpack_require__(651); +var define = __webpack_require__(628); +var CacheBase = __webpack_require__(629); +var Emitter = __webpack_require__(630); +var isObject = __webpack_require__(592); +var merge = __webpack_require__(649); +var pascal = __webpack_require__(652); +var cu = __webpack_require__(653); /** * Optionally define a custom `cache` namespace to use. @@ -72266,7 +72448,7 @@ module.exports.namespace = namespace; /***/ }), -/* 626 */ +/* 628 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -72279,7 +72461,7 @@ module.exports.namespace = namespace; -var isDescriptor = __webpack_require__(591); +var isDescriptor = __webpack_require__(593); module.exports = function defineProperty(obj, prop, val) { if (typeof obj !== 'object' && typeof obj !== 'function') { @@ -72304,21 +72486,21 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 627 */ +/* 629 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isObject = __webpack_require__(590); -var Emitter = __webpack_require__(628); -var visit = __webpack_require__(629); -var toPath = __webpack_require__(632); -var union = __webpack_require__(634); -var del = __webpack_require__(638); -var get = __webpack_require__(636); -var has = __webpack_require__(643); -var set = __webpack_require__(646); +var isObject = __webpack_require__(592); +var Emitter = __webpack_require__(630); +var visit = __webpack_require__(631); +var toPath = __webpack_require__(634); +var union = __webpack_require__(636); +var del = __webpack_require__(640); +var get = __webpack_require__(638); +var has = __webpack_require__(645); +var set = __webpack_require__(648); /** * Create a `Cache` constructor that when instantiated will @@ -72572,7 +72754,7 @@ module.exports.namespace = namespace; /***/ }), -/* 628 */ +/* 630 */ /***/ (function(module, exports, __webpack_require__) { @@ -72741,7 +72923,7 @@ Emitter.prototype.hasListeners = function(event){ /***/ }), -/* 629 */ +/* 631 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -72754,8 +72936,8 @@ Emitter.prototype.hasListeners = function(event){ -var visit = __webpack_require__(630); -var mapVisit = __webpack_require__(631); +var visit = __webpack_require__(632); +var mapVisit = __webpack_require__(633); module.exports = function(collection, method, val) { var result; @@ -72778,7 +72960,7 @@ module.exports = function(collection, method, val) { /***/ }), -/* 630 */ +/* 632 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -72791,7 +72973,7 @@ module.exports = function(collection, method, val) { -var isObject = __webpack_require__(590); +var isObject = __webpack_require__(592); module.exports = function visit(thisArg, method, target, val) { if (!isObject(thisArg) && typeof thisArg !== 'function') { @@ -72818,14 +73000,14 @@ module.exports = function visit(thisArg, method, target, val) { /***/ }), -/* 631 */ +/* 633 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var util = __webpack_require__(112); -var visit = __webpack_require__(630); +var visit = __webpack_require__(632); /** * Map `visit` over an array of objects. @@ -72862,7 +73044,7 @@ function isObject(val) { /***/ }), -/* 632 */ +/* 634 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -72875,7 +73057,7 @@ function isObject(val) { -var typeOf = __webpack_require__(633); +var typeOf = __webpack_require__(635); module.exports = function toPath(args) { if (typeOf(args) !== 'arguments') { @@ -72902,10 +73084,10 @@ function filter(arr) { /***/ }), -/* 633 */ +/* 635 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(614); +var isBuffer = __webpack_require__(616); var toString = Object.prototype.toString; /** @@ -73024,16 +73206,16 @@ module.exports = function kindOf(val) { /***/ }), -/* 634 */ +/* 636 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isObject = __webpack_require__(604); -var union = __webpack_require__(635); -var get = __webpack_require__(636); -var set = __webpack_require__(637); +var isObject = __webpack_require__(606); +var union = __webpack_require__(637); +var get = __webpack_require__(638); +var set = __webpack_require__(639); module.exports = function unionValue(obj, prop, value) { if (!isObject(obj)) { @@ -73061,7 +73243,7 @@ function arrayify(val) { /***/ }), -/* 635 */ +/* 637 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -73097,7 +73279,7 @@ module.exports = function union(init) { /***/ }), -/* 636 */ +/* 638 */ /***/ (function(module, exports) { /*! @@ -73153,7 +73335,7 @@ function toString(val) { /***/ }), -/* 637 */ +/* 639 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -73166,10 +73348,10 @@ function toString(val) { -var split = __webpack_require__(607); -var extend = __webpack_require__(603); -var isPlainObject = __webpack_require__(597); -var isObject = __webpack_require__(604); +var split = __webpack_require__(609); +var extend = __webpack_require__(605); +var isPlainObject = __webpack_require__(599); +var isObject = __webpack_require__(606); module.exports = function(obj, prop, val) { if (!isObject(obj)) { @@ -73215,7 +73397,7 @@ function isValidKey(key) { /***/ }), -/* 638 */ +/* 640 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -73228,8 +73410,8 @@ function isValidKey(key) { -var isObject = __webpack_require__(590); -var has = __webpack_require__(639); +var isObject = __webpack_require__(592); +var has = __webpack_require__(641); module.exports = function unset(obj, prop) { if (!isObject(obj)) { @@ -73254,7 +73436,7 @@ module.exports = function unset(obj, prop) { /***/ }), -/* 639 */ +/* 641 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -73267,9 +73449,9 @@ module.exports = function unset(obj, prop) { -var isObject = __webpack_require__(640); -var hasValues = __webpack_require__(642); -var get = __webpack_require__(636); +var isObject = __webpack_require__(642); +var hasValues = __webpack_require__(644); +var get = __webpack_require__(638); module.exports = function(obj, prop, noZero) { if (isObject(obj)) { @@ -73280,7 +73462,7 @@ module.exports = function(obj, prop, noZero) { /***/ }), -/* 640 */ +/* 642 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -73293,7 +73475,7 @@ module.exports = function(obj, prop, noZero) { -var isArray = __webpack_require__(641); +var isArray = __webpack_require__(643); module.exports = function isObject(val) { return val != null && typeof val === 'object' && isArray(val) === false; @@ -73301,7 +73483,7 @@ module.exports = function isObject(val) { /***/ }), -/* 641 */ +/* 643 */ /***/ (function(module, exports) { var toString = {}.toString; @@ -73312,7 +73494,7 @@ module.exports = Array.isArray || function (arr) { /***/ }), -/* 642 */ +/* 644 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -73355,7 +73537,7 @@ module.exports = function hasValue(o, noZero) { /***/ }), -/* 643 */ +/* 645 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -73368,9 +73550,9 @@ module.exports = function hasValue(o, noZero) { -var isObject = __webpack_require__(590); -var hasValues = __webpack_require__(644); -var get = __webpack_require__(636); +var isObject = __webpack_require__(592); +var hasValues = __webpack_require__(646); +var get = __webpack_require__(638); module.exports = function(val, prop) { return hasValues(isObject(val) && prop ? get(val, prop) : val); @@ -73378,7 +73560,7 @@ module.exports = function(val, prop) { /***/ }), -/* 644 */ +/* 646 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -73391,8 +73573,8 @@ module.exports = function(val, prop) { -var typeOf = __webpack_require__(645); -var isNumber = __webpack_require__(612); +var typeOf = __webpack_require__(647); +var isNumber = __webpack_require__(614); module.exports = function hasValue(val) { // is-number checks for NaN and other edge cases @@ -73445,10 +73627,10 @@ module.exports = function hasValue(val) { /***/ }), -/* 645 */ +/* 647 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(614); +var isBuffer = __webpack_require__(616); var toString = Object.prototype.toString; /** @@ -73570,7 +73752,7 @@ module.exports = function kindOf(val) { /***/ }), -/* 646 */ +/* 648 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -73583,10 +73765,10 @@ module.exports = function kindOf(val) { -var split = __webpack_require__(607); -var extend = __webpack_require__(603); -var isPlainObject = __webpack_require__(597); -var isObject = __webpack_require__(604); +var split = __webpack_require__(609); +var extend = __webpack_require__(605); +var isPlainObject = __webpack_require__(599); +var isObject = __webpack_require__(606); module.exports = function(obj, prop, val) { if (!isObject(obj)) { @@ -73632,14 +73814,14 @@ function isValidKey(key) { /***/ }), -/* 647 */ +/* 649 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(648); -var forIn = __webpack_require__(649); +var isExtendable = __webpack_require__(650); +var forIn = __webpack_require__(651); function mixinDeep(target, objects) { var len = arguments.length, i = 0; @@ -73703,7 +73885,7 @@ module.exports = mixinDeep; /***/ }), -/* 648 */ +/* 650 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -73716,7 +73898,7 @@ module.exports = mixinDeep; -var isPlainObject = __webpack_require__(597); +var isPlainObject = __webpack_require__(599); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -73724,7 +73906,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 649 */ +/* 651 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -73747,7 +73929,7 @@ module.exports = function forIn(obj, fn, thisArg) { /***/ }), -/* 650 */ +/* 652 */ /***/ (function(module, exports) { /*! @@ -73774,14 +73956,14 @@ module.exports = pascalcase; /***/ }), -/* 651 */ +/* 653 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var util = __webpack_require__(112); -var utils = __webpack_require__(652); +var utils = __webpack_require__(654); /** * Expose class utils @@ -74146,7 +74328,7 @@ cu.bubble = function(Parent, events) { /***/ }), -/* 652 */ +/* 654 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -74160,10 +74342,10 @@ var utils = {}; * Lazily required module dependencies */ -utils.union = __webpack_require__(635); -utils.define = __webpack_require__(653); -utils.isObj = __webpack_require__(590); -utils.staticExtend = __webpack_require__(660); +utils.union = __webpack_require__(637); +utils.define = __webpack_require__(655); +utils.isObj = __webpack_require__(592); +utils.staticExtend = __webpack_require__(662); /** @@ -74174,7 +74356,7 @@ module.exports = utils; /***/ }), -/* 653 */ +/* 655 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -74187,7 +74369,7 @@ module.exports = utils; -var isDescriptor = __webpack_require__(654); +var isDescriptor = __webpack_require__(656); module.exports = function defineProperty(obj, prop, val) { if (typeof obj !== 'object' && typeof obj !== 'function') { @@ -74212,7 +74394,7 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 654 */ +/* 656 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -74225,9 +74407,9 @@ module.exports = function defineProperty(obj, prop, val) { -var typeOf = __webpack_require__(655); -var isAccessor = __webpack_require__(656); -var isData = __webpack_require__(658); +var typeOf = __webpack_require__(657); +var isAccessor = __webpack_require__(658); +var isData = __webpack_require__(660); module.exports = function isDescriptor(obj, key) { if (typeOf(obj) !== 'object') { @@ -74241,7 +74423,7 @@ module.exports = function isDescriptor(obj, key) { /***/ }), -/* 655 */ +/* 657 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -74394,7 +74576,7 @@ function isBuffer(val) { /***/ }), -/* 656 */ +/* 658 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -74407,7 +74589,7 @@ function isBuffer(val) { -var typeOf = __webpack_require__(657); +var typeOf = __webpack_require__(659); // accessor descriptor properties var accessor = { @@ -74470,10 +74652,10 @@ module.exports = isAccessorDescriptor; /***/ }), -/* 657 */ +/* 659 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(614); +var isBuffer = __webpack_require__(616); var toString = Object.prototype.toString; /** @@ -74592,7 +74774,7 @@ module.exports = function kindOf(val) { /***/ }), -/* 658 */ +/* 660 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -74605,7 +74787,7 @@ module.exports = function kindOf(val) { -var typeOf = __webpack_require__(659); +var typeOf = __webpack_require__(661); // data descriptor properties var data = { @@ -74654,10 +74836,10 @@ module.exports = isDataDescriptor; /***/ }), -/* 659 */ +/* 661 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(614); +var isBuffer = __webpack_require__(616); var toString = Object.prototype.toString; /** @@ -74776,7 +74958,7 @@ module.exports = function kindOf(val) { /***/ }), -/* 660 */ +/* 662 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -74789,8 +74971,8 @@ module.exports = function kindOf(val) { -var copy = __webpack_require__(661); -var define = __webpack_require__(653); +var copy = __webpack_require__(663); +var define = __webpack_require__(655); var util = __webpack_require__(112); /** @@ -74873,15 +75055,15 @@ module.exports = extend; /***/ }), -/* 661 */ +/* 663 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var typeOf = __webpack_require__(662); -var copyDescriptor = __webpack_require__(663); -var define = __webpack_require__(653); +var typeOf = __webpack_require__(664); +var copyDescriptor = __webpack_require__(665); +var define = __webpack_require__(655); /** * Copy static properties, prototype properties, and descriptors from one object to another. @@ -75054,10 +75236,10 @@ module.exports.has = has; /***/ }), -/* 662 */ +/* 664 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(614); +var isBuffer = __webpack_require__(616); var toString = Object.prototype.toString; /** @@ -75176,7 +75358,7 @@ module.exports = function kindOf(val) { /***/ }), -/* 663 */ +/* 665 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -75264,16 +75446,16 @@ function isObject(val) { /***/ }), -/* 664 */ +/* 666 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var use = __webpack_require__(665); -var define = __webpack_require__(653); -var debug = __webpack_require__(543)('snapdragon:compiler'); -var utils = __webpack_require__(667); +var use = __webpack_require__(667); +var define = __webpack_require__(655); +var debug = __webpack_require__(545)('snapdragon:compiler'); +var utils = __webpack_require__(669); /** * Create a new `Compiler` with the given `options`. @@ -75427,7 +75609,7 @@ Compiler.prototype = { // source map support if (opts.sourcemap) { - var sourcemaps = __webpack_require__(686); + var sourcemaps = __webpack_require__(688); sourcemaps(this); this.mapVisit(this.ast.nodes); this.applySourceMaps(); @@ -75448,7 +75630,7 @@ module.exports = Compiler; /***/ }), -/* 665 */ +/* 667 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -75461,7 +75643,7 @@ module.exports = Compiler; -var utils = __webpack_require__(666); +var utils = __webpack_require__(668); module.exports = function base(app, opts) { if (!utils.isObject(app) && typeof app !== 'function') { @@ -75576,7 +75758,7 @@ module.exports = function base(app, opts) { /***/ }), -/* 666 */ +/* 668 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -75590,8 +75772,8 @@ var utils = {}; * Lazily required module dependencies */ -utils.define = __webpack_require__(653); -utils.isObject = __webpack_require__(590); +utils.define = __webpack_require__(655); +utils.isObject = __webpack_require__(592); utils.isString = function(val) { @@ -75606,7 +75788,7 @@ module.exports = utils; /***/ }), -/* 667 */ +/* 669 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -75616,9 +75798,9 @@ module.exports = utils; * Module dependencies */ -exports.extend = __webpack_require__(603); -exports.SourceMap = __webpack_require__(668); -exports.sourceMapResolve = __webpack_require__(679); +exports.extend = __webpack_require__(605); +exports.SourceMap = __webpack_require__(670); +exports.sourceMapResolve = __webpack_require__(681); /** * Convert backslash in the given string to forward slashes @@ -75661,7 +75843,7 @@ exports.last = function(arr, n) { /***/ }), -/* 668 */ +/* 670 */ /***/ (function(module, exports, __webpack_require__) { /* @@ -75669,13 +75851,13 @@ exports.last = function(arr, n) { * Licensed under the New BSD license. See LICENSE.txt or: * http://opensource.org/licenses/BSD-3-Clause */ -exports.SourceMapGenerator = __webpack_require__(669).SourceMapGenerator; -exports.SourceMapConsumer = __webpack_require__(675).SourceMapConsumer; -exports.SourceNode = __webpack_require__(678).SourceNode; +exports.SourceMapGenerator = __webpack_require__(671).SourceMapGenerator; +exports.SourceMapConsumer = __webpack_require__(677).SourceMapConsumer; +exports.SourceNode = __webpack_require__(680).SourceNode; /***/ }), -/* 669 */ +/* 671 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -75685,10 +75867,10 @@ exports.SourceNode = __webpack_require__(678).SourceNode; * http://opensource.org/licenses/BSD-3-Clause */ -var base64VLQ = __webpack_require__(670); -var util = __webpack_require__(672); -var ArraySet = __webpack_require__(673).ArraySet; -var MappingList = __webpack_require__(674).MappingList; +var base64VLQ = __webpack_require__(672); +var util = __webpack_require__(674); +var ArraySet = __webpack_require__(675).ArraySet; +var MappingList = __webpack_require__(676).MappingList; /** * An instance of the SourceMapGenerator represents a source map which is @@ -76097,7 +76279,7 @@ exports.SourceMapGenerator = SourceMapGenerator; /***/ }), -/* 670 */ +/* 672 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -76137,7 +76319,7 @@ exports.SourceMapGenerator = SourceMapGenerator; * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -var base64 = __webpack_require__(671); +var base64 = __webpack_require__(673); // A single base 64 digit can contain 6 bits of data. For the base 64 variable // length quantities we use in the source map spec, the first bit is the sign, @@ -76243,7 +76425,7 @@ exports.decode = function base64VLQ_decode(aStr, aIndex, aOutParam) { /***/ }), -/* 671 */ +/* 673 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -76316,7 +76498,7 @@ exports.decode = function (charCode) { /***/ }), -/* 672 */ +/* 674 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -76739,7 +76921,7 @@ exports.compareByGeneratedPositionsInflated = compareByGeneratedPositionsInflate /***/ }), -/* 673 */ +/* 675 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -76749,7 +76931,7 @@ exports.compareByGeneratedPositionsInflated = compareByGeneratedPositionsInflate * http://opensource.org/licenses/BSD-3-Clause */ -var util = __webpack_require__(672); +var util = __webpack_require__(674); var has = Object.prototype.hasOwnProperty; var hasNativeMap = typeof Map !== "undefined"; @@ -76866,7 +77048,7 @@ exports.ArraySet = ArraySet; /***/ }), -/* 674 */ +/* 676 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -76876,7 +77058,7 @@ exports.ArraySet = ArraySet; * http://opensource.org/licenses/BSD-3-Clause */ -var util = __webpack_require__(672); +var util = __webpack_require__(674); /** * Determine whether mappingB is after mappingA with respect to generated @@ -76951,7 +77133,7 @@ exports.MappingList = MappingList; /***/ }), -/* 675 */ +/* 677 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -76961,11 +77143,11 @@ exports.MappingList = MappingList; * http://opensource.org/licenses/BSD-3-Clause */ -var util = __webpack_require__(672); -var binarySearch = __webpack_require__(676); -var ArraySet = __webpack_require__(673).ArraySet; -var base64VLQ = __webpack_require__(670); -var quickSort = __webpack_require__(677).quickSort; +var util = __webpack_require__(674); +var binarySearch = __webpack_require__(678); +var ArraySet = __webpack_require__(675).ArraySet; +var base64VLQ = __webpack_require__(672); +var quickSort = __webpack_require__(679).quickSort; function SourceMapConsumer(aSourceMap) { var sourceMap = aSourceMap; @@ -78039,7 +78221,7 @@ exports.IndexedSourceMapConsumer = IndexedSourceMapConsumer; /***/ }), -/* 676 */ +/* 678 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -78156,7 +78338,7 @@ exports.search = function search(aNeedle, aHaystack, aCompare, aBias) { /***/ }), -/* 677 */ +/* 679 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -78276,7 +78458,7 @@ exports.quickSort = function (ary, comparator) { /***/ }), -/* 678 */ +/* 680 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -78286,8 +78468,8 @@ exports.quickSort = function (ary, comparator) { * http://opensource.org/licenses/BSD-3-Clause */ -var SourceMapGenerator = __webpack_require__(669).SourceMapGenerator; -var util = __webpack_require__(672); +var SourceMapGenerator = __webpack_require__(671).SourceMapGenerator; +var util = __webpack_require__(674); // Matches a Windows-style `\r\n` newline or a `\n` newline used by all other // operating systems these days (capturing the result). @@ -78695,17 +78877,17 @@ exports.SourceNode = SourceNode; /***/ }), -/* 679 */ +/* 681 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2014, 2015, 2016, 2017 Simon Lydell // X11 (“MIT”) Licensed. (See LICENSE.) -var sourceMappingURL = __webpack_require__(680) -var resolveUrl = __webpack_require__(681) -var decodeUriComponent = __webpack_require__(682) -var urix = __webpack_require__(684) -var atob = __webpack_require__(685) +var sourceMappingURL = __webpack_require__(682) +var resolveUrl = __webpack_require__(683) +var decodeUriComponent = __webpack_require__(684) +var urix = __webpack_require__(686) +var atob = __webpack_require__(687) @@ -79003,7 +79185,7 @@ module.exports = { /***/ }), -/* 680 */ +/* 682 */ /***/ (function(module, exports, __webpack_require__) { var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_RESULT__;// Copyright 2014 Simon Lydell @@ -79066,7 +79248,7 @@ void (function(root, factory) { /***/ }), -/* 681 */ +/* 683 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2014 Simon Lydell @@ -79084,13 +79266,13 @@ module.exports = resolveUrl /***/ }), -/* 682 */ +/* 684 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2017 Simon Lydell // X11 (“MIT”) Licensed. (See LICENSE.) -var decodeUriComponent = __webpack_require__(683) +var decodeUriComponent = __webpack_require__(685) function customDecodeUriComponent(string) { // `decodeUriComponent` turns `+` into ` `, but that's not wanted. @@ -79101,7 +79283,7 @@ module.exports = customDecodeUriComponent /***/ }), -/* 683 */ +/* 685 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -79202,7 +79384,7 @@ module.exports = function (encodedURI) { /***/ }), -/* 684 */ +/* 686 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2014 Simon Lydell @@ -79225,7 +79407,7 @@ module.exports = urix /***/ }), -/* 685 */ +/* 687 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -79239,7 +79421,7 @@ module.exports = atob.atob = atob; /***/ }), -/* 686 */ +/* 688 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -79247,8 +79429,8 @@ module.exports = atob.atob = atob; var fs = __webpack_require__(134); var path = __webpack_require__(4); -var define = __webpack_require__(653); -var utils = __webpack_require__(667); +var define = __webpack_require__(655); +var utils = __webpack_require__(669); /** * Expose `mixin()`. @@ -79391,19 +79573,19 @@ exports.comment = function(node) { /***/ }), -/* 687 */ +/* 689 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var use = __webpack_require__(665); +var use = __webpack_require__(667); var util = __webpack_require__(112); -var Cache = __webpack_require__(688); -var define = __webpack_require__(653); -var debug = __webpack_require__(543)('snapdragon:parser'); -var Position = __webpack_require__(689); -var utils = __webpack_require__(667); +var Cache = __webpack_require__(690); +var define = __webpack_require__(655); +var debug = __webpack_require__(545)('snapdragon:parser'); +var Position = __webpack_require__(691); +var utils = __webpack_require__(669); /** * Create a new `Parser` with the given `input` and `options`. @@ -79931,7 +80113,7 @@ module.exports = Parser; /***/ }), -/* 688 */ +/* 690 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -80038,13 +80220,13 @@ MapCache.prototype.del = function mapDelete(key) { /***/ }), -/* 689 */ +/* 691 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var define = __webpack_require__(653); +var define = __webpack_require__(655); /** * Store position for a node @@ -80059,14 +80241,14 @@ module.exports = function Position(start, parser) { /***/ }), -/* 690 */ +/* 692 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(691); -var assignSymbols = __webpack_require__(598); +var isExtendable = __webpack_require__(693); +var assignSymbols = __webpack_require__(600); module.exports = Object.assign || function(obj/*, objects*/) { if (obj === null || typeof obj === 'undefined') { @@ -80126,7 +80308,7 @@ function isEnum(obj, key) { /***/ }), -/* 691 */ +/* 693 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -80139,7 +80321,7 @@ function isEnum(obj, key) { -var isPlainObject = __webpack_require__(597); +var isPlainObject = __webpack_require__(599); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -80147,14 +80329,14 @@ module.exports = function isExtendable(val) { /***/ }), -/* 692 */ +/* 694 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var nanomatch = __webpack_require__(693); -var extglob = __webpack_require__(707); +var nanomatch = __webpack_require__(695); +var extglob = __webpack_require__(709); module.exports = function(snapdragon) { var compilers = snapdragon.compiler.compilers; @@ -80231,7 +80413,7 @@ function escapeExtglobs(compiler) { /***/ }), -/* 693 */ +/* 695 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -80242,17 +80424,17 @@ function escapeExtglobs(compiler) { */ var util = __webpack_require__(112); -var toRegex = __webpack_require__(582); -var extend = __webpack_require__(694); +var toRegex = __webpack_require__(584); +var extend = __webpack_require__(696); /** * Local dependencies */ -var compilers = __webpack_require__(696); -var parsers = __webpack_require__(697); -var cache = __webpack_require__(700); -var utils = __webpack_require__(702); +var compilers = __webpack_require__(698); +var parsers = __webpack_require__(699); +var cache = __webpack_require__(702); +var utils = __webpack_require__(704); var MAX_LENGTH = 1024 * 64; /** @@ -81076,14 +81258,14 @@ module.exports = nanomatch; /***/ }), -/* 694 */ +/* 696 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(695); -var assignSymbols = __webpack_require__(598); +var isExtendable = __webpack_require__(697); +var assignSymbols = __webpack_require__(600); module.exports = Object.assign || function(obj/*, objects*/) { if (obj === null || typeof obj === 'undefined') { @@ -81143,7 +81325,7 @@ function isEnum(obj, key) { /***/ }), -/* 695 */ +/* 697 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81156,7 +81338,7 @@ function isEnum(obj, key) { -var isPlainObject = __webpack_require__(597); +var isPlainObject = __webpack_require__(599); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -81164,7 +81346,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 696 */ +/* 698 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81510,15 +81692,15 @@ module.exports = function(nanomatch, options) { /***/ }), -/* 697 */ +/* 699 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var regexNot = __webpack_require__(599); -var toRegex = __webpack_require__(582); -var isOdd = __webpack_require__(698); +var regexNot = __webpack_require__(601); +var toRegex = __webpack_require__(584); +var isOdd = __webpack_require__(700); /** * Characters to use in negation regex (we want to "not" match @@ -81904,7 +82086,7 @@ module.exports.not = NOT_REGEX; /***/ }), -/* 698 */ +/* 700 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81917,7 +82099,7 @@ module.exports.not = NOT_REGEX; -var isNumber = __webpack_require__(699); +var isNumber = __webpack_require__(701); module.exports = function isOdd(i) { if (!isNumber(i)) { @@ -81931,7 +82113,7 @@ module.exports = function isOdd(i) { /***/ }), -/* 699 */ +/* 701 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81959,14 +82141,14 @@ module.exports = function isNumber(num) { /***/ }), -/* 700 */ +/* 702 */ /***/ (function(module, exports, __webpack_require__) { -module.exports = new (__webpack_require__(701))(); +module.exports = new (__webpack_require__(703))(); /***/ }), -/* 701 */ +/* 703 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81979,7 +82161,7 @@ module.exports = new (__webpack_require__(701))(); -var MapCache = __webpack_require__(688); +var MapCache = __webpack_require__(690); /** * Create a new `FragmentCache` with an optional object to use for `caches`. @@ -82101,7 +82283,7 @@ exports = module.exports = FragmentCache; /***/ }), -/* 702 */ +/* 704 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82114,14 +82296,14 @@ var path = __webpack_require__(4); * Module dependencies */ -var isWindows = __webpack_require__(703)(); -var Snapdragon = __webpack_require__(624); -utils.define = __webpack_require__(704); -utils.diff = __webpack_require__(705); -utils.extend = __webpack_require__(694); -utils.pick = __webpack_require__(706); -utils.typeOf = __webpack_require__(592); -utils.unique = __webpack_require__(602); +var isWindows = __webpack_require__(705)(); +var Snapdragon = __webpack_require__(626); +utils.define = __webpack_require__(706); +utils.diff = __webpack_require__(707); +utils.extend = __webpack_require__(696); +utils.pick = __webpack_require__(708); +utils.typeOf = __webpack_require__(594); +utils.unique = __webpack_require__(604); /** * Returns true if the given value is effectively an empty string @@ -82487,7 +82669,7 @@ utils.unixify = function(options) { /***/ }), -/* 703 */ +/* 705 */ /***/ (function(module, exports, __webpack_require__) { var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/*! @@ -82515,7 +82697,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ /***/ }), -/* 704 */ +/* 706 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82528,8 +82710,8 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ -var isobject = __webpack_require__(590); -var isDescriptor = __webpack_require__(591); +var isobject = __webpack_require__(592); +var isDescriptor = __webpack_require__(593); var define = (typeof Reflect !== 'undefined' && Reflect.defineProperty) ? Reflect.defineProperty : Object.defineProperty; @@ -82560,7 +82742,7 @@ module.exports = function defineProperty(obj, key, val) { /***/ }), -/* 705 */ +/* 707 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82614,7 +82796,7 @@ function diffArray(one, two) { /***/ }), -/* 706 */ +/* 708 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82627,7 +82809,7 @@ function diffArray(one, two) { -var isObject = __webpack_require__(590); +var isObject = __webpack_require__(592); module.exports = function pick(obj, keys) { if (!isObject(obj) && typeof obj !== 'function') { @@ -82656,7 +82838,7 @@ module.exports = function pick(obj, keys) { /***/ }), -/* 707 */ +/* 709 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82666,18 +82848,18 @@ module.exports = function pick(obj, keys) { * Module dependencies */ -var extend = __webpack_require__(603); -var unique = __webpack_require__(602); -var toRegex = __webpack_require__(582); +var extend = __webpack_require__(605); +var unique = __webpack_require__(604); +var toRegex = __webpack_require__(584); /** * Local dependencies */ -var compilers = __webpack_require__(708); -var parsers = __webpack_require__(714); -var Extglob = __webpack_require__(717); -var utils = __webpack_require__(716); +var compilers = __webpack_require__(710); +var parsers = __webpack_require__(716); +var Extglob = __webpack_require__(719); +var utils = __webpack_require__(718); var MAX_LENGTH = 1024 * 64; /** @@ -82994,13 +83176,13 @@ module.exports = extglob; /***/ }), -/* 708 */ +/* 710 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var brackets = __webpack_require__(709); +var brackets = __webpack_require__(711); /** * Extglob compilers @@ -83170,7 +83352,7 @@ module.exports = function(extglob) { /***/ }), -/* 709 */ +/* 711 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -83180,17 +83362,17 @@ module.exports = function(extglob) { * Local dependencies */ -var compilers = __webpack_require__(710); -var parsers = __webpack_require__(712); +var compilers = __webpack_require__(712); +var parsers = __webpack_require__(714); /** * Module dependencies */ -var debug = __webpack_require__(543)('expand-brackets'); -var extend = __webpack_require__(603); -var Snapdragon = __webpack_require__(624); -var toRegex = __webpack_require__(582); +var debug = __webpack_require__(545)('expand-brackets'); +var extend = __webpack_require__(605); +var Snapdragon = __webpack_require__(626); +var toRegex = __webpack_require__(584); /** * Parses the given POSIX character class `pattern` and returns a @@ -83388,13 +83570,13 @@ module.exports = brackets; /***/ }), -/* 710 */ +/* 712 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var posix = __webpack_require__(711); +var posix = __webpack_require__(713); module.exports = function(brackets) { brackets.compiler @@ -83482,7 +83664,7 @@ module.exports = function(brackets) { /***/ }), -/* 711 */ +/* 713 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -83511,14 +83693,14 @@ module.exports = { /***/ }), -/* 712 */ +/* 714 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var utils = __webpack_require__(713); -var define = __webpack_require__(653); +var utils = __webpack_require__(715); +var define = __webpack_require__(655); /** * Text regex @@ -83737,14 +83919,14 @@ module.exports.TEXT_REGEX = TEXT_REGEX; /***/ }), -/* 713 */ +/* 715 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var toRegex = __webpack_require__(582); -var regexNot = __webpack_require__(599); +var toRegex = __webpack_require__(584); +var regexNot = __webpack_require__(601); var cached; /** @@ -83778,15 +83960,15 @@ exports.createRegex = function(pattern, include) { /***/ }), -/* 714 */ +/* 716 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var brackets = __webpack_require__(709); -var define = __webpack_require__(715); -var utils = __webpack_require__(716); +var brackets = __webpack_require__(711); +var define = __webpack_require__(717); +var utils = __webpack_require__(718); /** * Characters to use in text regex (we want to "not" match @@ -83941,7 +84123,7 @@ module.exports = parsers; /***/ }), -/* 715 */ +/* 717 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -83954,7 +84136,7 @@ module.exports = parsers; -var isDescriptor = __webpack_require__(591); +var isDescriptor = __webpack_require__(593); module.exports = function defineProperty(obj, prop, val) { if (typeof obj !== 'object' && typeof obj !== 'function') { @@ -83979,14 +84161,14 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 716 */ +/* 718 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var regex = __webpack_require__(599); -var Cache = __webpack_require__(701); +var regex = __webpack_require__(601); +var Cache = __webpack_require__(703); /** * Utils @@ -84055,7 +84237,7 @@ utils.createRegex = function(str) { /***/ }), -/* 717 */ +/* 719 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -84065,16 +84247,16 @@ utils.createRegex = function(str) { * Module dependencies */ -var Snapdragon = __webpack_require__(624); -var define = __webpack_require__(715); -var extend = __webpack_require__(603); +var Snapdragon = __webpack_require__(626); +var define = __webpack_require__(717); +var extend = __webpack_require__(605); /** * Local dependencies */ -var compilers = __webpack_require__(708); -var parsers = __webpack_require__(714); +var compilers = __webpack_require__(710); +var parsers = __webpack_require__(716); /** * Customize Snapdragon parser and renderer @@ -84140,16 +84322,16 @@ module.exports = Extglob; /***/ }), -/* 718 */ +/* 720 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var extglob = __webpack_require__(707); -var nanomatch = __webpack_require__(693); -var regexNot = __webpack_require__(599); -var toRegex = __webpack_require__(582); +var extglob = __webpack_require__(709); +var nanomatch = __webpack_require__(695); +var regexNot = __webpack_require__(601); +var toRegex = __webpack_require__(584); var not; /** @@ -84230,14 +84412,14 @@ function textRegex(pattern) { /***/ }), -/* 719 */ +/* 721 */ /***/ (function(module, exports, __webpack_require__) { -module.exports = new (__webpack_require__(701))(); +module.exports = new (__webpack_require__(703))(); /***/ }), -/* 720 */ +/* 722 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -84250,13 +84432,13 @@ var path = __webpack_require__(4); * Module dependencies */ -var Snapdragon = __webpack_require__(624); -utils.define = __webpack_require__(721); -utils.diff = __webpack_require__(705); -utils.extend = __webpack_require__(690); -utils.pick = __webpack_require__(706); -utils.typeOf = __webpack_require__(592); -utils.unique = __webpack_require__(602); +var Snapdragon = __webpack_require__(626); +utils.define = __webpack_require__(723); +utils.diff = __webpack_require__(707); +utils.extend = __webpack_require__(692); +utils.pick = __webpack_require__(708); +utils.typeOf = __webpack_require__(594); +utils.unique = __webpack_require__(604); /** * Returns true if the platform is windows, or `path.sep` is `\\`. @@ -84553,7 +84735,7 @@ utils.unixify = function(options) { /***/ }), -/* 721 */ +/* 723 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -84566,8 +84748,8 @@ utils.unixify = function(options) { -var isobject = __webpack_require__(590); -var isDescriptor = __webpack_require__(591); +var isobject = __webpack_require__(592); +var isDescriptor = __webpack_require__(593); var define = (typeof Reflect !== 'undefined' && Reflect.defineProperty) ? Reflect.defineProperty : Object.defineProperty; @@ -84598,7 +84780,7 @@ module.exports = function defineProperty(obj, key, val) { /***/ }), -/* 722 */ +/* 724 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -84617,9 +84799,9 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var readdir = __webpack_require__(723); -var reader_1 = __webpack_require__(736); -var fs_stream_1 = __webpack_require__(740); +var readdir = __webpack_require__(725); +var reader_1 = __webpack_require__(738); +var fs_stream_1 = __webpack_require__(742); var ReaderAsync = /** @class */ (function (_super) { __extends(ReaderAsync, _super); function ReaderAsync() { @@ -84680,15 +84862,15 @@ exports.default = ReaderAsync; /***/ }), -/* 723 */ +/* 725 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const readdirSync = __webpack_require__(724); -const readdirAsync = __webpack_require__(732); -const readdirStream = __webpack_require__(735); +const readdirSync = __webpack_require__(726); +const readdirAsync = __webpack_require__(734); +const readdirStream = __webpack_require__(737); module.exports = exports = readdirAsyncPath; exports.readdir = exports.readdirAsync = exports.async = readdirAsyncPath; @@ -84772,7 +84954,7 @@ function readdirStreamStat (dir, options) { /***/ }), -/* 724 */ +/* 726 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -84780,11 +84962,11 @@ function readdirStreamStat (dir, options) { module.exports = readdirSync; -const DirectoryReader = __webpack_require__(725); +const DirectoryReader = __webpack_require__(727); let syncFacade = { - fs: __webpack_require__(730), - forEach: __webpack_require__(731), + fs: __webpack_require__(732), + forEach: __webpack_require__(733), sync: true }; @@ -84813,7 +84995,7 @@ function readdirSync (dir, options, internalOptions) { /***/ }), -/* 725 */ +/* 727 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -84822,9 +85004,9 @@ function readdirSync (dir, options, internalOptions) { const Readable = __webpack_require__(138).Readable; const EventEmitter = __webpack_require__(156).EventEmitter; const path = __webpack_require__(4); -const normalizeOptions = __webpack_require__(726); -const stat = __webpack_require__(728); -const call = __webpack_require__(729); +const normalizeOptions = __webpack_require__(728); +const stat = __webpack_require__(730); +const call = __webpack_require__(731); /** * Asynchronously reads the contents of a directory and streams the results @@ -85200,14 +85382,14 @@ module.exports = DirectoryReader; /***/ }), -/* 726 */ +/* 728 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(4); -const globToRegExp = __webpack_require__(727); +const globToRegExp = __webpack_require__(729); module.exports = normalizeOptions; @@ -85384,7 +85566,7 @@ function normalizeOptions (options, internalOptions) { /***/ }), -/* 727 */ +/* 729 */ /***/ (function(module, exports) { module.exports = function (glob, opts) { @@ -85521,13 +85703,13 @@ module.exports = function (glob, opts) { /***/ }), -/* 728 */ +/* 730 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const call = __webpack_require__(729); +const call = __webpack_require__(731); module.exports = stat; @@ -85602,7 +85784,7 @@ function symlinkStat (fs, path, lstats, callback) { /***/ }), -/* 729 */ +/* 731 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -85663,14 +85845,14 @@ function callOnce (fn) { /***/ }), -/* 730 */ +/* 732 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(134); -const call = __webpack_require__(729); +const call = __webpack_require__(731); /** * A facade around {@link fs.readdirSync} that allows it to be called @@ -85734,7 +85916,7 @@ exports.lstat = function (path, callback) { /***/ }), -/* 731 */ +/* 733 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -85763,7 +85945,7 @@ function syncForEach (array, iterator, done) { /***/ }), -/* 732 */ +/* 734 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -85771,12 +85953,12 @@ function syncForEach (array, iterator, done) { module.exports = readdirAsync; -const maybe = __webpack_require__(733); -const DirectoryReader = __webpack_require__(725); +const maybe = __webpack_require__(735); +const DirectoryReader = __webpack_require__(727); let asyncFacade = { fs: __webpack_require__(134), - forEach: __webpack_require__(734), + forEach: __webpack_require__(736), async: true }; @@ -85818,7 +86000,7 @@ function readdirAsync (dir, options, callback, internalOptions) { /***/ }), -/* 733 */ +/* 735 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -85845,7 +86027,7 @@ module.exports = function maybe (cb, promise) { /***/ }), -/* 734 */ +/* 736 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -85881,7 +86063,7 @@ function asyncForEach (array, iterator, done) { /***/ }), -/* 735 */ +/* 737 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -85889,11 +86071,11 @@ function asyncForEach (array, iterator, done) { module.exports = readdirStream; -const DirectoryReader = __webpack_require__(725); +const DirectoryReader = __webpack_require__(727); let streamFacade = { fs: __webpack_require__(134), - forEach: __webpack_require__(734), + forEach: __webpack_require__(736), async: true }; @@ -85913,16 +86095,16 @@ function readdirStream (dir, options, internalOptions) { /***/ }), -/* 736 */ +/* 738 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var path = __webpack_require__(4); -var deep_1 = __webpack_require__(737); -var entry_1 = __webpack_require__(739); -var pathUtil = __webpack_require__(738); +var deep_1 = __webpack_require__(739); +var entry_1 = __webpack_require__(741); +var pathUtil = __webpack_require__(740); var Reader = /** @class */ (function () { function Reader(options) { this.options = options; @@ -85988,14 +86170,14 @@ exports.default = Reader; /***/ }), -/* 737 */ +/* 739 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var pathUtils = __webpack_require__(738); -var patternUtils = __webpack_require__(576); +var pathUtils = __webpack_require__(740); +var patternUtils = __webpack_require__(578); var DeepFilter = /** @class */ (function () { function DeepFilter(options, micromatchOptions) { this.options = options; @@ -86078,7 +86260,7 @@ exports.default = DeepFilter; /***/ }), -/* 738 */ +/* 740 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -86109,14 +86291,14 @@ exports.makeAbsolute = makeAbsolute; /***/ }), -/* 739 */ +/* 741 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var pathUtils = __webpack_require__(738); -var patternUtils = __webpack_require__(576); +var pathUtils = __webpack_require__(740); +var patternUtils = __webpack_require__(578); var EntryFilter = /** @class */ (function () { function EntryFilter(options, micromatchOptions) { this.options = options; @@ -86201,7 +86383,7 @@ exports.default = EntryFilter; /***/ }), -/* 740 */ +/* 742 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -86221,8 +86403,8 @@ var __extends = (this && this.__extends) || (function () { })(); Object.defineProperty(exports, "__esModule", { value: true }); var stream = __webpack_require__(138); -var fsStat = __webpack_require__(741); -var fs_1 = __webpack_require__(745); +var fsStat = __webpack_require__(743); +var fs_1 = __webpack_require__(747); var FileSystemStream = /** @class */ (function (_super) { __extends(FileSystemStream, _super); function FileSystemStream() { @@ -86272,14 +86454,14 @@ exports.default = FileSystemStream; /***/ }), -/* 741 */ +/* 743 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const optionsManager = __webpack_require__(742); -const statProvider = __webpack_require__(744); +const optionsManager = __webpack_require__(744); +const statProvider = __webpack_require__(746); /** * Asynchronous API. */ @@ -86310,13 +86492,13 @@ exports.statSync = statSync; /***/ }), -/* 742 */ +/* 744 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const fsAdapter = __webpack_require__(743); +const fsAdapter = __webpack_require__(745); function prepare(opts) { const options = Object.assign({ fs: fsAdapter.getFileSystemAdapter(opts ? opts.fs : undefined), @@ -86329,7 +86511,7 @@ exports.prepare = prepare; /***/ }), -/* 743 */ +/* 745 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -86352,7 +86534,7 @@ exports.getFileSystemAdapter = getFileSystemAdapter; /***/ }), -/* 744 */ +/* 746 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -86404,7 +86586,7 @@ exports.isFollowedSymlink = isFollowedSymlink; /***/ }), -/* 745 */ +/* 747 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -86435,7 +86617,7 @@ exports.default = FileSystem; /***/ }), -/* 746 */ +/* 748 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -86455,9 +86637,9 @@ var __extends = (this && this.__extends) || (function () { })(); Object.defineProperty(exports, "__esModule", { value: true }); var stream = __webpack_require__(138); -var readdir = __webpack_require__(723); -var reader_1 = __webpack_require__(736); -var fs_stream_1 = __webpack_require__(740); +var readdir = __webpack_require__(725); +var reader_1 = __webpack_require__(738); +var fs_stream_1 = __webpack_require__(742); var TransformStream = /** @class */ (function (_super) { __extends(TransformStream, _super); function TransformStream(reader) { @@ -86525,7 +86707,7 @@ exports.default = ReaderStream; /***/ }), -/* 747 */ +/* 749 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -86544,9 +86726,9 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var readdir = __webpack_require__(723); -var reader_1 = __webpack_require__(736); -var fs_sync_1 = __webpack_require__(748); +var readdir = __webpack_require__(725); +var reader_1 = __webpack_require__(738); +var fs_sync_1 = __webpack_require__(750); var ReaderSync = /** @class */ (function (_super) { __extends(ReaderSync, _super); function ReaderSync() { @@ -86606,7 +86788,7 @@ exports.default = ReaderSync; /***/ }), -/* 748 */ +/* 750 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -86625,8 +86807,8 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var fsStat = __webpack_require__(741); -var fs_1 = __webpack_require__(745); +var fsStat = __webpack_require__(743); +var fs_1 = __webpack_require__(747); var FileSystemSync = /** @class */ (function (_super) { __extends(FileSystemSync, _super); function FileSystemSync() { @@ -86672,7 +86854,7 @@ exports.default = FileSystemSync; /***/ }), -/* 749 */ +/* 751 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -86688,7 +86870,7 @@ exports.flatten = flatten; /***/ }), -/* 750 */ +/* 752 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -86709,13 +86891,13 @@ exports.merge = merge; /***/ }), -/* 751 */ +/* 753 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(4); -const pathType = __webpack_require__(752); +const pathType = __webpack_require__(754); const getExtensions = extensions => extensions.length > 1 ? `{${extensions.join(',')}}` : extensions[0]; @@ -86781,13 +86963,13 @@ module.exports.sync = (input, opts) => { /***/ }), -/* 752 */ +/* 754 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(134); -const pify = __webpack_require__(753); +const pify = __webpack_require__(755); function type(fn, fn2, fp) { if (typeof fp !== 'string') { @@ -86830,7 +87012,7 @@ exports.symlinkSync = typeSync.bind(null, 'lstatSync', 'isSymbolicLink'); /***/ }), -/* 753 */ +/* 755 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -86921,17 +87103,17 @@ module.exports = (obj, opts) => { /***/ }), -/* 754 */ +/* 756 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(134); const path = __webpack_require__(4); -const fastGlob = __webpack_require__(572); -const gitIgnore = __webpack_require__(755); -const pify = __webpack_require__(756); -const slash = __webpack_require__(757); +const fastGlob = __webpack_require__(574); +const gitIgnore = __webpack_require__(757); +const pify = __webpack_require__(758); +const slash = __webpack_require__(759); const DEFAULT_IGNORE = [ '**/node_modules/**', @@ -87029,7 +87211,7 @@ module.exports.sync = options => { /***/ }), -/* 755 */ +/* 757 */ /***/ (function(module, exports) { // A simple implementation of make-array @@ -87498,7 +87680,7 @@ module.exports = options => new IgnoreBase(options) /***/ }), -/* 756 */ +/* 758 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -87573,7 +87755,7 @@ module.exports = (input, options) => { /***/ }), -/* 757 */ +/* 759 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -87591,7 +87773,7 @@ module.exports = input => { /***/ }), -/* 758 */ +/* 760 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -87604,7 +87786,7 @@ module.exports = input => { -var isGlob = __webpack_require__(759); +var isGlob = __webpack_require__(761); module.exports = function hasGlob(val) { if (val == null) return false; @@ -87624,7 +87806,7 @@ module.exports = function hasGlob(val) { /***/ }), -/* 759 */ +/* 761 */ /***/ (function(module, exports, __webpack_require__) { /*! @@ -87655,17 +87837,17 @@ module.exports = function isGlob(str) { /***/ }), -/* 760 */ +/* 762 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(4); const {constants: fsConstants} = __webpack_require__(134); -const pEvent = __webpack_require__(761); -const CpFileError = __webpack_require__(764); -const fs = __webpack_require__(766); -const ProgressEmitter = __webpack_require__(769); +const pEvent = __webpack_require__(763); +const CpFileError = __webpack_require__(766); +const fs = __webpack_require__(768); +const ProgressEmitter = __webpack_require__(771); const cpFileAsync = async (source, destination, options, progressEmitter) => { let readError; @@ -87779,12 +87961,12 @@ module.exports.sync = (source, destination, options) => { /***/ }), -/* 761 */ +/* 763 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const pTimeout = __webpack_require__(762); +const pTimeout = __webpack_require__(764); const symbolAsyncIterator = Symbol.asyncIterator || '@@asyncIterator'; @@ -88075,12 +88257,12 @@ module.exports.iterator = (emitter, event, options) => { /***/ }), -/* 762 */ +/* 764 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const pFinally = __webpack_require__(763); +const pFinally = __webpack_require__(765); class TimeoutError extends Error { constructor(message) { @@ -88126,7 +88308,7 @@ module.exports.TimeoutError = TimeoutError; /***/ }), -/* 763 */ +/* 765 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -88148,12 +88330,12 @@ module.exports = (promise, onFinally) => { /***/ }), -/* 764 */ +/* 766 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const NestedError = __webpack_require__(765); +const NestedError = __webpack_require__(767); class CpFileError extends NestedError { constructor(message, nested) { @@ -88167,7 +88349,7 @@ module.exports = CpFileError; /***/ }), -/* 765 */ +/* 767 */ /***/ (function(module, exports, __webpack_require__) { var inherits = __webpack_require__(112).inherits; @@ -88223,16 +88405,16 @@ module.exports = NestedError; /***/ }), -/* 766 */ +/* 768 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const {promisify} = __webpack_require__(112); const fs = __webpack_require__(133); -const makeDir = __webpack_require__(767); -const pEvent = __webpack_require__(761); -const CpFileError = __webpack_require__(764); +const makeDir = __webpack_require__(769); +const pEvent = __webpack_require__(763); +const CpFileError = __webpack_require__(766); const stat = promisify(fs.stat); const lstat = promisify(fs.lstat); @@ -88329,7 +88511,7 @@ exports.copyFileSync = (source, destination, flags) => { /***/ }), -/* 767 */ +/* 769 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -88337,7 +88519,7 @@ exports.copyFileSync = (source, destination, flags) => { const fs = __webpack_require__(134); const path = __webpack_require__(4); const {promisify} = __webpack_require__(112); -const semver = __webpack_require__(768); +const semver = __webpack_require__(770); const useNativeRecursiveOption = semver.satisfies(process.version, '>=10.12.0'); @@ -88492,7 +88674,7 @@ module.exports.sync = (input, options) => { /***/ }), -/* 768 */ +/* 770 */ /***/ (function(module, exports) { exports = module.exports = SemVer @@ -90094,7 +90276,7 @@ function coerce (version, options) { /***/ }), -/* 769 */ +/* 771 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -90135,7 +90317,7 @@ module.exports = ProgressEmitter; /***/ }), -/* 770 */ +/* 772 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -90181,12 +90363,12 @@ exports.default = module.exports; /***/ }), -/* 771 */ +/* 773 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const pMap = __webpack_require__(772); +const pMap = __webpack_require__(774); const pFilter = async (iterable, filterer, options) => { const values = await pMap( @@ -90203,7 +90385,7 @@ module.exports.default = pFilter; /***/ }), -/* 772 */ +/* 774 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -90282,12 +90464,12 @@ module.exports.default = pMap; /***/ }), -/* 773 */ +/* 775 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const NestedError = __webpack_require__(765); +const NestedError = __webpack_require__(767); class CpyError extends NestedError { constructor(message, nested) { @@ -90301,7 +90483,7 @@ module.exports = CpyError; /***/ }), -/* 774 */ +/* 776 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -90309,10 +90491,10 @@ module.exports = CpyError; const fs = __webpack_require__(134); const arrayUnion = __webpack_require__(145); const merge2 = __webpack_require__(146); -const fastGlob = __webpack_require__(775); +const fastGlob = __webpack_require__(777); const dirGlob = __webpack_require__(232); -const gitignore = __webpack_require__(810); -const {FilterStream, UniqueStream} = __webpack_require__(811); +const gitignore = __webpack_require__(812); +const {FilterStream, UniqueStream} = __webpack_require__(813); const DEFAULT_FILTER = () => false; @@ -90489,17 +90671,17 @@ module.exports.gitignore = gitignore; /***/ }), -/* 775 */ +/* 777 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const taskManager = __webpack_require__(776); -const async_1 = __webpack_require__(796); -const stream_1 = __webpack_require__(806); -const sync_1 = __webpack_require__(807); -const settings_1 = __webpack_require__(809); -const utils = __webpack_require__(777); +const taskManager = __webpack_require__(778); +const async_1 = __webpack_require__(798); +const stream_1 = __webpack_require__(808); +const sync_1 = __webpack_require__(809); +const settings_1 = __webpack_require__(811); +const utils = __webpack_require__(779); async function FastGlob(source, options) { assertPatternsInput(source); const works = getWorks(source, async_1.default, options); @@ -90563,14 +90745,14 @@ module.exports = FastGlob; /***/ }), -/* 776 */ +/* 778 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.convertPatternGroupToTask = exports.convertPatternGroupsToTasks = exports.groupPatternsByBaseDirectory = exports.getNegativePatternsAsPositive = exports.getPositivePatterns = exports.convertPatternsToTasks = exports.generate = void 0; -const utils = __webpack_require__(777); +const utils = __webpack_require__(779); function generate(patterns, settings) { const positivePatterns = getPositivePatterns(patterns); const negativePatterns = getNegativePatternsAsPositive(patterns, settings.ignore); @@ -90635,31 +90817,31 @@ exports.convertPatternGroupToTask = convertPatternGroupToTask; /***/ }), -/* 777 */ +/* 779 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.string = exports.stream = exports.pattern = exports.path = exports.fs = exports.errno = exports.array = void 0; -const array = __webpack_require__(778); +const array = __webpack_require__(780); exports.array = array; -const errno = __webpack_require__(779); +const errno = __webpack_require__(781); exports.errno = errno; -const fs = __webpack_require__(780); +const fs = __webpack_require__(782); exports.fs = fs; -const path = __webpack_require__(781); +const path = __webpack_require__(783); exports.path = path; -const pattern = __webpack_require__(782); +const pattern = __webpack_require__(784); exports.pattern = pattern; -const stream = __webpack_require__(794); +const stream = __webpack_require__(796); exports.stream = stream; -const string = __webpack_require__(795); +const string = __webpack_require__(797); exports.string = string; /***/ }), -/* 778 */ +/* 780 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -90688,7 +90870,7 @@ exports.splitWhen = splitWhen; /***/ }), -/* 779 */ +/* 781 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -90702,7 +90884,7 @@ exports.isEnoentCodeError = isEnoentCodeError; /***/ }), -/* 780 */ +/* 782 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -90728,7 +90910,7 @@ exports.createDirentFromStats = createDirentFromStats; /***/ }), -/* 781 */ +/* 783 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -90768,7 +90950,7 @@ exports.removeLeadingDotSegment = removeLeadingDotSegment; /***/ }), -/* 782 */ +/* 784 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -90777,7 +90959,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.matchAny = exports.convertPatternsToRe = exports.makeRe = exports.getPatternParts = exports.expandBraceExpansion = exports.expandPatternsWithBraceExpansion = exports.isAffectDepthOfReadingPattern = exports.endsWithSlashGlobStar = exports.hasGlobStar = exports.getBaseDirectory = exports.getPositivePatterns = exports.getNegativePatterns = exports.isPositivePattern = exports.isNegativePattern = exports.convertToNegativePattern = exports.convertToPositivePattern = exports.isDynamicPattern = exports.isStaticPattern = void 0; const path = __webpack_require__(4); const globParent = __webpack_require__(171); -const micromatch = __webpack_require__(783); +const micromatch = __webpack_require__(785); const picomatch = __webpack_require__(185); const GLOBSTAR = '**'; const ESCAPE_SYMBOL = '\\'; @@ -90907,14 +91089,14 @@ exports.matchAny = matchAny; /***/ }), -/* 783 */ +/* 785 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const util = __webpack_require__(112); -const braces = __webpack_require__(784); +const braces = __webpack_require__(786); const picomatch = __webpack_require__(185); const utils = __webpack_require__(188); const isEmptyString = val => typeof val === 'string' && (val === '' || val === './'); @@ -91381,16 +91563,16 @@ module.exports = micromatch; /***/ }), -/* 784 */ +/* 786 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const stringify = __webpack_require__(785); -const compile = __webpack_require__(787); -const expand = __webpack_require__(791); -const parse = __webpack_require__(792); +const stringify = __webpack_require__(787); +const compile = __webpack_require__(789); +const expand = __webpack_require__(793); +const parse = __webpack_require__(794); /** * Expand the given pattern or create a regex-compatible string. @@ -91558,13 +91740,13 @@ module.exports = braces; /***/ }), -/* 785 */ +/* 787 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const utils = __webpack_require__(786); +const utils = __webpack_require__(788); module.exports = (ast, options = {}) => { let stringify = (node, parent = {}) => { @@ -91597,7 +91779,7 @@ module.exports = (ast, options = {}) => { /***/ }), -/* 786 */ +/* 788 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -91716,14 +91898,14 @@ exports.flatten = (...args) => { /***/ }), -/* 787 */ +/* 789 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const fill = __webpack_require__(788); -const utils = __webpack_require__(786); +const fill = __webpack_require__(790); +const utils = __webpack_require__(788); const compile = (ast, options = {}) => { let walk = (node, parent = {}) => { @@ -91780,7 +91962,7 @@ module.exports = compile; /***/ }), -/* 788 */ +/* 790 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -91794,7 +91976,7 @@ module.exports = compile; const util = __webpack_require__(112); -const toRegexRange = __webpack_require__(789); +const toRegexRange = __webpack_require__(791); const isObject = val => val !== null && typeof val === 'object' && !Array.isArray(val); @@ -92036,7 +92218,7 @@ module.exports = fill; /***/ }), -/* 789 */ +/* 791 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -92049,7 +92231,7 @@ module.exports = fill; -const isNumber = __webpack_require__(790); +const isNumber = __webpack_require__(792); const toRegexRange = (min, max, options) => { if (isNumber(min) === false) { @@ -92331,7 +92513,7 @@ module.exports = toRegexRange; /***/ }), -/* 790 */ +/* 792 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -92356,15 +92538,15 @@ module.exports = function(num) { /***/ }), -/* 791 */ +/* 793 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const fill = __webpack_require__(788); -const stringify = __webpack_require__(785); -const utils = __webpack_require__(786); +const fill = __webpack_require__(790); +const stringify = __webpack_require__(787); +const utils = __webpack_require__(788); const append = (queue = '', stash = '', enclose = false) => { let result = []; @@ -92476,13 +92658,13 @@ module.exports = expand; /***/ }), -/* 792 */ +/* 794 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const stringify = __webpack_require__(785); +const stringify = __webpack_require__(787); /** * Constants @@ -92504,7 +92686,7 @@ const { CHAR_SINGLE_QUOTE, /* ' */ CHAR_NO_BREAK_SPACE, CHAR_ZERO_WIDTH_NOBREAK_SPACE -} = __webpack_require__(793); +} = __webpack_require__(795); /** * parse @@ -92816,7 +92998,7 @@ module.exports = parse; /***/ }), -/* 793 */ +/* 795 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -92880,7 +93062,7 @@ module.exports = { /***/ }), -/* 794 */ +/* 796 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -92904,7 +93086,7 @@ function propagateCloseEventToSources(streams) { /***/ }), -/* 795 */ +/* 797 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -92922,14 +93104,14 @@ exports.isEmpty = isEmpty; /***/ }), -/* 796 */ +/* 798 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const stream_1 = __webpack_require__(797); -const provider_1 = __webpack_require__(799); +const stream_1 = __webpack_require__(799); +const provider_1 = __webpack_require__(801); class ProviderAsync extends provider_1.default { constructor() { super(...arguments); @@ -92957,7 +93139,7 @@ exports.default = ProviderAsync; /***/ }), -/* 797 */ +/* 799 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -92966,7 +93148,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); const stream_1 = __webpack_require__(138); const fsStat = __webpack_require__(195); const fsWalk = __webpack_require__(200); -const reader_1 = __webpack_require__(798); +const reader_1 = __webpack_require__(800); class ReaderStream extends reader_1.default { constructor() { super(...arguments); @@ -93019,7 +93201,7 @@ exports.default = ReaderStream; /***/ }), -/* 798 */ +/* 800 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -93027,7 +93209,7 @@ exports.default = ReaderStream; Object.defineProperty(exports, "__esModule", { value: true }); const path = __webpack_require__(4); const fsStat = __webpack_require__(195); -const utils = __webpack_require__(777); +const utils = __webpack_require__(779); class Reader { constructor(_settings) { this._settings = _settings; @@ -93059,17 +93241,17 @@ exports.default = Reader; /***/ }), -/* 799 */ +/* 801 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const path = __webpack_require__(4); -const deep_1 = __webpack_require__(800); -const entry_1 = __webpack_require__(803); -const error_1 = __webpack_require__(804); -const entry_2 = __webpack_require__(805); +const deep_1 = __webpack_require__(802); +const entry_1 = __webpack_require__(805); +const error_1 = __webpack_require__(806); +const entry_2 = __webpack_require__(807); class Provider { constructor(_settings) { this._settings = _settings; @@ -93114,14 +93296,14 @@ exports.default = Provider; /***/ }), -/* 800 */ +/* 802 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const utils = __webpack_require__(777); -const partial_1 = __webpack_require__(801); +const utils = __webpack_require__(779); +const partial_1 = __webpack_require__(803); class DeepFilter { constructor(_settings, _micromatchOptions) { this._settings = _settings; @@ -93183,13 +93365,13 @@ exports.default = DeepFilter; /***/ }), -/* 801 */ +/* 803 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const matcher_1 = __webpack_require__(802); +const matcher_1 = __webpack_require__(804); class PartialMatcher extends matcher_1.default { match(filepath) { const parts = filepath.split('/'); @@ -93228,13 +93410,13 @@ exports.default = PartialMatcher; /***/ }), -/* 802 */ +/* 804 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const utils = __webpack_require__(777); +const utils = __webpack_require__(779); class Matcher { constructor(_patterns, _settings, _micromatchOptions) { this._patterns = _patterns; @@ -93285,13 +93467,13 @@ exports.default = Matcher; /***/ }), -/* 803 */ +/* 805 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const utils = __webpack_require__(777); +const utils = __webpack_require__(779); class EntryFilter { constructor(_settings, _micromatchOptions) { this._settings = _settings; @@ -93348,13 +93530,13 @@ exports.default = EntryFilter; /***/ }), -/* 804 */ +/* 806 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const utils = __webpack_require__(777); +const utils = __webpack_require__(779); class ErrorFilter { constructor(_settings) { this._settings = _settings; @@ -93370,13 +93552,13 @@ exports.default = ErrorFilter; /***/ }), -/* 805 */ +/* 807 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const utils = __webpack_require__(777); +const utils = __webpack_require__(779); class EntryTransformer { constructor(_settings) { this._settings = _settings; @@ -93403,15 +93585,15 @@ exports.default = EntryTransformer; /***/ }), -/* 806 */ +/* 808 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const stream_1 = __webpack_require__(138); -const stream_2 = __webpack_require__(797); -const provider_1 = __webpack_require__(799); +const stream_2 = __webpack_require__(799); +const provider_1 = __webpack_require__(801); class ProviderStream extends provider_1.default { constructor() { super(...arguments); @@ -93441,14 +93623,14 @@ exports.default = ProviderStream; /***/ }), -/* 807 */ +/* 809 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const sync_1 = __webpack_require__(808); -const provider_1 = __webpack_require__(799); +const sync_1 = __webpack_require__(810); +const provider_1 = __webpack_require__(801); class ProviderSync extends provider_1.default { constructor() { super(...arguments); @@ -93471,7 +93653,7 @@ exports.default = ProviderSync; /***/ }), -/* 808 */ +/* 810 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -93479,7 +93661,7 @@ exports.default = ProviderSync; Object.defineProperty(exports, "__esModule", { value: true }); const fsStat = __webpack_require__(195); const fsWalk = __webpack_require__(200); -const reader_1 = __webpack_require__(798); +const reader_1 = __webpack_require__(800); class ReaderSync extends reader_1.default { constructor() { super(...arguments); @@ -93521,7 +93703,7 @@ exports.default = ReaderSync; /***/ }), -/* 809 */ +/* 811 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -93585,7 +93767,7 @@ exports.default = Settings; /***/ }), -/* 810 */ +/* 812 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -93593,7 +93775,7 @@ exports.default = Settings; const {promisify} = __webpack_require__(112); const fs = __webpack_require__(134); const path = __webpack_require__(4); -const fastGlob = __webpack_require__(775); +const fastGlob = __webpack_require__(777); const gitIgnore = __webpack_require__(235); const slash = __webpack_require__(236); @@ -93712,7 +93894,7 @@ module.exports.sync = options => { /***/ }), -/* 811 */ +/* 813 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -93765,7 +93947,7 @@ module.exports = { /***/ }), -/* 812 */ +/* 814 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -93773,13 +93955,13 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "buildNonBazelProductionProjects", function() { return buildNonBazelProductionProjects; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "getProductionProjects", function() { return getProductionProjects; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "buildProject", function() { return buildProject; }); -/* harmony import */ var cpy__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(565); +/* harmony import */ var cpy__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(567); /* harmony import */ var cpy__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(cpy__WEBPACK_IMPORTED_MODULE_0__); /* harmony import */ var del__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(143); /* harmony import */ var del__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(del__WEBPACK_IMPORTED_MODULE_1__); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(4); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_2__); -/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(562); +/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(564); /* harmony import */ var _utils_fs__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(131); /* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(246); /* harmony import */ var _utils_package_json__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(251); diff --git a/packages/kbn-pm/src/commands/bootstrap.ts b/packages/kbn-pm/src/commands/bootstrap.ts index b383a52be63f50..bad6eef3266f89 100644 --- a/packages/kbn-pm/src/commands/bootstrap.ts +++ b/packages/kbn-pm/src/commands/bootstrap.ts @@ -66,7 +66,7 @@ export const BootstrapCommand: ICommand = { await runBazel(['run', '@nodejs//:yarn'], runOffline); } - await runBazel(['build', '//packages:build'], runOffline); + await runBazel(['build', '//packages:build', '--show_result=1'], runOffline); // Install monorepo npm dependencies outside of the Bazel managed ones for (const batch of batchedNonBazelProjects) { diff --git a/packages/kbn-pm/src/commands/build_bazel.ts b/packages/kbn-pm/src/commands/build_bazel.ts new file mode 100644 index 00000000000000..f71e2e96e31b0e --- /dev/null +++ b/packages/kbn-pm/src/commands/build_bazel.ts @@ -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 + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { runBazel } from '../utils/bazel'; +import { ICommand } from './'; + +export const BuildBazelCommand: ICommand = { + description: 'Runs a build in the Bazel built packages', + name: 'build-bazel', + + async run(projects, projectGraph, { options }) { + const runOffline = options?.offline === true; + + // Call bazel with the target to build all available packages + await runBazel(['build', '//packages:build', '--show_result=1'], runOffline); + }, +}; diff --git a/packages/kbn-pm/src/commands/index.ts b/packages/kbn-pm/src/commands/index.ts index 0ab6bc9c7808a3..2f5c04c2f434f1 100644 --- a/packages/kbn-pm/src/commands/index.ts +++ b/packages/kbn-pm/src/commands/index.ts @@ -27,16 +27,20 @@ export interface ICommand { } import { BootstrapCommand } from './bootstrap'; +import { BuildBazelCommand } from './build_bazel'; import { CleanCommand } from './clean'; import { ResetCommand } from './reset'; import { RunCommand } from './run'; import { WatchCommand } from './watch'; +import { WatchBazelCommand } from './watch_bazel'; import { Kibana } from '../utils/kibana'; export const commands: { [key: string]: ICommand } = { bootstrap: BootstrapCommand, + 'build-bazel': BuildBazelCommand, clean: CleanCommand, reset: ResetCommand, run: RunCommand, watch: WatchCommand, + 'watch-bazel': WatchBazelCommand, }; diff --git a/packages/kbn-pm/src/commands/run.ts b/packages/kbn-pm/src/commands/run.ts index 5535fe0d8358f8..9a3a19d9e625ed 100644 --- a/packages/kbn-pm/src/commands/run.ts +++ b/packages/kbn-pm/src/commands/run.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import dedent from 'dedent'; import { CliError } from '../utils/errors'; import { log } from '../utils/log'; import { parallelizeBatches } from '../utils/parallelize'; @@ -13,10 +14,17 @@ import { topologicallyBatchProjects } from '../utils/projects'; import { ICommand } from './'; export const RunCommand: ICommand = { - description: 'Run script defined in package.json in each package that contains that script.', + description: + 'Run script defined in package.json in each package that contains that script (only works on packages not using Bazel yet)', name: 'run', async run(projects, projectGraph, { extraArgs, options }) { + log.warning(dedent` + We are migrating packages into the Bazel build system and we will no longer support running npm scripts on + packages using 'yarn kbn run' on Bazel built packages. If the package you are trying to act on contains a + BUILD.bazel file please just use 'yarn kbn build-bazel' to build it or 'yarn kbn watch-bazel' to watch it + `); + const batchedProjects = topologicallyBatchProjects(projects, projectGraph); if (extraArgs.length === 0) { diff --git a/packages/kbn-pm/src/commands/watch.ts b/packages/kbn-pm/src/commands/watch.ts index fb398d68521369..5d0f6d086d3e80 100644 --- a/packages/kbn-pm/src/commands/watch.ts +++ b/packages/kbn-pm/src/commands/watch.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import dedent from 'dedent'; import { CliError } from '../utils/errors'; import { log } from '../utils/log'; import { parallelizeBatches } from '../utils/parallelize'; @@ -34,10 +35,16 @@ const kibanaProjectName = 'kibana'; * `webpack` and `tsc` only, for the rest we rely on predefined timeouts. */ export const WatchCommand: ICommand = { - description: 'Runs `kbn:watch` script for every project.', + description: + 'Runs `kbn:watch` script for every project (only works on packages not using Bazel yet)', name: 'watch', async run(projects, projectGraph) { + log.warning(dedent` + We are migrating packages into the Bazel build system. If the package you are trying to watch + contains a BUILD.bazel file please just use 'yarn kbn watch-bazel' + `); + const projectsToWatch: ProjectMap = new Map(); for (const project of projects.values()) { // We can't watch project that doesn't have `kbn:watch` script. diff --git a/packages/kbn-pm/src/commands/watch_bazel.ts b/packages/kbn-pm/src/commands/watch_bazel.ts new file mode 100644 index 00000000000000..1273562dd25116 --- /dev/null +++ b/packages/kbn-pm/src/commands/watch_bazel.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 + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { runIBazel } from '../utils/bazel'; +import { ICommand } from './'; + +export const WatchBazelCommand: ICommand = { + description: 'Runs a build in the Bazel built packages and keeps watching them for changes', + name: 'watch-bazel', + + async run(projects, projectGraph, { options }) { + const runOffline = options?.offline === true; + + // Call bazel with the target to build all available packages and run it through iBazel to watch it for changes + // + // Note: --run_output=false arg will disable the iBazel notifications about gazelle and buildozer when running it + // Can also be solved by adding a root `.bazel_fix_commands.json` but its not needed at the moment + await runIBazel(['--run_output=false', 'build', '//packages:build'], runOffline); + }, +}; diff --git a/packages/kbn-pm/src/utils/bazel/run.ts b/packages/kbn-pm/src/utils/bazel/run.ts index ab20150768b780..34718606db98e4 100644 --- a/packages/kbn-pm/src/utils/bazel/run.ts +++ b/packages/kbn-pm/src/utils/bazel/run.ts @@ -13,8 +13,12 @@ import { tap } from 'rxjs/operators'; import { observeLines } from '@kbn/dev-utils/stdio'; import { spawn } from '../child_process'; import { log } from '../log'; +import { CliError } from '../errors'; -export async function runBazel( +type BazelCommandRunner = 'bazel' | 'ibazel'; + +async function runBazelCommandWithRunner( + bazelCommandRunner: BazelCommandRunner, bazelArgs: string[], offline: boolean = false, runOpts: execa.Options = {} @@ -29,7 +33,7 @@ export async function runBazel( bazelArgs.push('--config=offline'); } - const bazelProc = spawn('bazel', bazelArgs, bazelOpts); + const bazelProc = spawn(bazelCommandRunner, bazelArgs, bazelOpts); const bazelLogs$ = new Rx.Subject(); @@ -37,15 +41,35 @@ export async function runBazel( // Therefore we need to get both. In order to get errors we need to parse the actual text line const bazelLogSubscription = Rx.merge( observeLines(bazelProc.stdout!).pipe( - tap((line) => log.info(`${chalk.cyan('[bazel]')} ${line}`)) + tap((line) => log.info(`${chalk.cyan(`[${bazelCommandRunner}]`)} ${line}`)) ), observeLines(bazelProc.stderr!).pipe( - tap((line) => log.info(`${chalk.cyan('[bazel]')} ${line}`)) + tap((line) => log.info(`${chalk.cyan(`[${bazelCommandRunner}]`)} ${line}`)) ) ).subscribe(bazelLogs$); // Wait for process and logs to finish, unsubscribing in the end - await bazelProc; + try { + await bazelProc; + } catch { + throw new CliError(`The bazel command that was running failed to complete.`); + } await bazelLogs$.toPromise(); await bazelLogSubscription.unsubscribe(); } + +export async function runBazel( + bazelArgs: string[], + offline: boolean = false, + runOpts: execa.Options = {} +) { + await runBazelCommandWithRunner('bazel', bazelArgs, offline, runOpts); +} + +export async function runIBazel( + bazelArgs: string[], + offline: boolean = false, + runOpts: execa.Options = {} +) { + await runBazelCommandWithRunner('ibazel', bazelArgs, offline, runOpts); +} diff --git a/packages/kbn-server-http-tools/package.json b/packages/kbn-server-http-tools/package.json index 6c65a0dd6e475e..24f8f8d67dfd70 100644 --- a/packages/kbn-server-http-tools/package.json +++ b/packages/kbn-server-http-tools/package.json @@ -11,7 +11,6 @@ "kbn:watch": "yarn build --watch" }, "dependencies": { - "@kbn/config-schema": "link:../kbn-config-schema", "@kbn/crypto": "link:../kbn-crypto", "@kbn/std": "link:../kbn-std" }, diff --git a/packages/kbn-test/src/jest/utils/testbed/testbed.ts b/packages/kbn-test/src/jest/utils/testbed/testbed.ts index edb040db8186c2..472b9f2df939cf 100644 --- a/packages/kbn-test/src/jest/utils/testbed/testbed.ts +++ b/packages/kbn-test/src/jest/utils/testbed/testbed.ts @@ -6,7 +6,8 @@ * Side Public License, v 1. */ -import { ComponentType, ReactWrapper } from 'enzyme'; +import { Component as ReactComponent } from 'react'; +import { ComponentType, HTMLAttributes, ReactWrapper } from 'enzyme'; import { findTestSubject } from '../find_test_subject'; import { reactRouterMock } from '../router_helpers'; @@ -250,8 +251,17 @@ export const registerTestBed = ( component.update(); }; - const getErrorsMessages: TestBed['form']['getErrorsMessages'] = () => { - const errorMessagesWrappers = component.find('.euiFormErrorText'); + const getErrorsMessages: TestBed['form']['getErrorsMessages'] = ( + wrapper?: T | ReactWrapper + ) => { + let errorMessagesWrappers: ReactWrapper; + if (typeof wrapper === 'string') { + errorMessagesWrappers = find(wrapper).find('.euiFormErrorText'); + } else { + errorMessagesWrappers = wrapper + ? wrapper.find('.euiFormErrorText') + : component.find('.euiFormErrorText'); + } return errorMessagesWrappers.map((err) => err.text()); }; diff --git a/packages/kbn-test/src/jest/utils/testbed/types.ts b/packages/kbn-test/src/jest/utils/testbed/types.ts index 338794869d9b18..520a78d03d7013 100644 --- a/packages/kbn-test/src/jest/utils/testbed/types.ts +++ b/packages/kbn-test/src/jest/utils/testbed/types.ts @@ -133,7 +133,7 @@ export interface TestBed { /** * Get a list of the form error messages that are visible in the DOM. */ - getErrorsMessages: () => string[]; + getErrorsMessages: (wrapper?: T | ReactWrapper) => string[]; }; table: { getMetaData: (tableTestSubject: T) => EuiTableMetaData; diff --git a/packages/kbn-tinymath/BUILD.bazel b/packages/kbn-tinymath/BUILD.bazel new file mode 100644 index 00000000000000..9d521776fb4919 --- /dev/null +++ b/packages/kbn-tinymath/BUILD.bazel @@ -0,0 +1,71 @@ +load("@build_bazel_rules_nodejs//:index.bzl", "js_library", "pkg_npm") +load("@npm//pegjs:index.bzl", "pegjs") + +PKG_BASE_NAME = "kbn-tinymath" +PKG_REQUIRE_NAME = "@kbn/tinymath" + +SOURCE_FILES = glob( + [ + "src/**/*", + ] +) + +TYPE_FILES = [ + "index.d.ts", +] + +SRCS = SOURCE_FILES + TYPE_FILES + +filegroup( + name = "srcs", + srcs = SRCS, +) + +NPM_MODULE_EXTRA_FILES = [ + "package.json", + "README.md", +] + +DEPS = [ + "@npm//lodash", +] + +pegjs( + name = "grammar", + data = [ + ":grammar/grammar.pegjs" + ], + output_dir = True, + args = [ + "-o", + "$(@D)/index.js", + "./%s/grammar/grammar.pegjs" % package_name() + ], +) + +js_library( + name = PKG_BASE_NAME, + srcs = [ + ":srcs", + ":grammar" + ], + deps = DEPS, + package_name = PKG_REQUIRE_NAME, + visibility = ["//visibility:public"], +) + +pkg_npm( + name = "npm_module", + srcs = NPM_MODULE_EXTRA_FILES, + deps = [ + ":%s" % PKG_BASE_NAME, + ] +) + +filegroup( + name = "build", + srcs = [ + ":npm_module", + ], + visibility = ["//visibility:public"], +) diff --git a/packages/kbn-tinymath/src/grammar.pegjs b/packages/kbn-tinymath/grammar/grammar.pegjs similarity index 99% rename from packages/kbn-tinymath/src/grammar.pegjs rename to packages/kbn-tinymath/grammar/grammar.pegjs index 9cb92fa9374a2b..70f275776e45dc 100644 --- a/packages/kbn-tinymath/src/grammar.pegjs +++ b/packages/kbn-tinymath/grammar/grammar.pegjs @@ -107,7 +107,7 @@ String / [\'] value:(ValidChar)+ [\'] { return value.join(''); } / value:(ValidChar)+ { return value.join(''); } - + Argument = name:[a-zA-Z_]+ _ '=' _ value:(Number / String) _ { return { diff --git a/packages/kbn-tinymath/tinymath.d.ts b/packages/kbn-tinymath/index.d.ts similarity index 100% rename from packages/kbn-tinymath/tinymath.d.ts rename to packages/kbn-tinymath/index.d.ts diff --git a/packages/kbn-tinymath/package.json b/packages/kbn-tinymath/package.json index cc4fa0a64d9c32..915afda7ba2d2b 100644 --- a/packages/kbn-tinymath/package.json +++ b/packages/kbn-tinymath/package.json @@ -4,10 +4,5 @@ "license": "SSPL-1.0 OR Elastic License 2.0", "private": true, "main": "src/index.js", - "types": "tinymath.d.ts", - "scripts": { - "kbn:bootstrap": "yarn build", - "build": "../../node_modules/.bin/pegjs -o src/grammar.js src/grammar.pegjs" - }, - "dependencies": {} + "types": "index.d.ts" } \ No newline at end of file diff --git a/packages/kbn-tinymath/src/grammar.js b/packages/kbn-tinymath/src/grammar.js deleted file mode 100644 index 5454143530c398..00000000000000 --- a/packages/kbn-tinymath/src/grammar.js +++ /dev/null @@ -1,1555 +0,0 @@ -/* - * Generated by PEG.js 0.10.0. - * - * http://pegjs.org/ - */ - -"use strict"; - -function peg$subclass(child, parent) { - function ctor() { this.constructor = child; } - ctor.prototype = parent.prototype; - child.prototype = new ctor(); -} - -function peg$SyntaxError(message, expected, found, location) { - this.message = message; - this.expected = expected; - this.found = found; - this.location = location; - this.name = "SyntaxError"; - - if (typeof Error.captureStackTrace === "function") { - Error.captureStackTrace(this, peg$SyntaxError); - } -} - -peg$subclass(peg$SyntaxError, Error); - -peg$SyntaxError.buildMessage = function(expected, found) { - var DESCRIBE_EXPECTATION_FNS = { - literal: function(expectation) { - return "\"" + literalEscape(expectation.text) + "\""; - }, - - "class": function(expectation) { - var escapedParts = "", - i; - - for (i = 0; i < expectation.parts.length; i++) { - escapedParts += expectation.parts[i] instanceof Array - ? classEscape(expectation.parts[i][0]) + "-" + classEscape(expectation.parts[i][1]) - : classEscape(expectation.parts[i]); - } - - return "[" + (expectation.inverted ? "^" : "") + escapedParts + "]"; - }, - - any: function(expectation) { - return "any character"; - }, - - end: function(expectation) { - return "end of input"; - }, - - other: function(expectation) { - return expectation.description; - } - }; - - function hex(ch) { - return ch.charCodeAt(0).toString(16).toUpperCase(); - } - - function literalEscape(s) { - return s - .replace(/\\/g, '\\\\') - .replace(/"/g, '\\"') - .replace(/\0/g, '\\0') - .replace(/\t/g, '\\t') - .replace(/\n/g, '\\n') - .replace(/\r/g, '\\r') - .replace(/[\x00-\x0F]/g, function(ch) { return '\\x0' + hex(ch); }) - .replace(/[\x10-\x1F\x7F-\x9F]/g, function(ch) { return '\\x' + hex(ch); }); - } - - function classEscape(s) { - return s - .replace(/\\/g, '\\\\') - .replace(/\]/g, '\\]') - .replace(/\^/g, '\\^') - .replace(/-/g, '\\-') - .replace(/\0/g, '\\0') - .replace(/\t/g, '\\t') - .replace(/\n/g, '\\n') - .replace(/\r/g, '\\r') - .replace(/[\x00-\x0F]/g, function(ch) { return '\\x0' + hex(ch); }) - .replace(/[\x10-\x1F\x7F-\x9F]/g, function(ch) { return '\\x' + hex(ch); }); - } - - function describeExpectation(expectation) { - return DESCRIBE_EXPECTATION_FNS[expectation.type](expectation); - } - - function describeExpected(expected) { - var descriptions = new Array(expected.length), - i, j; - - for (i = 0; i < expected.length; i++) { - descriptions[i] = describeExpectation(expected[i]); - } - - descriptions.sort(); - - if (descriptions.length > 0) { - for (i = 1, j = 1; i < descriptions.length; i++) { - if (descriptions[i - 1] !== descriptions[i]) { - descriptions[j] = descriptions[i]; - j++; - } - } - descriptions.length = j; - } - - switch (descriptions.length) { - case 1: - return descriptions[0]; - - case 2: - return descriptions[0] + " or " + descriptions[1]; - - default: - return descriptions.slice(0, -1).join(", ") - + ", or " - + descriptions[descriptions.length - 1]; - } - } - - function describeFound(found) { - return found ? "\"" + literalEscape(found) + "\"" : "end of input"; - } - - return "Expected " + describeExpected(expected) + " but " + describeFound(found) + " found."; -}; - -function peg$parse(input, options) { - options = options !== void 0 ? options : {}; - - var peg$FAILED = {}, - - peg$startRuleFunctions = { start: peg$parsestart }, - peg$startRuleFunction = peg$parsestart, - - peg$c0 = peg$otherExpectation("whitespace"), - peg$c1 = /^[ \t\n\r]/, - peg$c2 = peg$classExpectation([" ", "\t", "\n", "\r"], false, false), - peg$c3 = /^[ ]/, - peg$c4 = peg$classExpectation([" "], false, false), - peg$c5 = /^["']/, - peg$c6 = peg$classExpectation(["\"", "'"], false, false), - peg$c7 = /^[A-Za-z_@.[\]\-]/, - peg$c8 = peg$classExpectation([["A", "Z"], ["a", "z"], "_", "@", ".", "[", "]", "-"], false, false), - peg$c9 = /^[0-9A-Za-z._@[\]\-]/, - peg$c10 = peg$classExpectation([["0", "9"], ["A", "Z"], ["a", "z"], ".", "_", "@", "[", "]", "-"], false, false), - peg$c11 = peg$otherExpectation("literal"), - peg$c12 = function(literal) { - return literal; - }, - peg$c13 = function(chars) { - return { - type: 'variable', - value: chars.join(''), - location: simpleLocation(location()), - text: text() - }; - }, - peg$c14 = function(rest) { - return { - type: 'variable', - value: rest.join(''), - location: simpleLocation(location()), - text: text() - }; - }, - peg$c15 = "+", - peg$c16 = peg$literalExpectation("+", false), - peg$c17 = "-", - peg$c18 = peg$literalExpectation("-", false), - peg$c19 = function(left, rest) { - return rest.reduce((acc, curr) => ({ - type: 'function', - name: curr[0] === '+' ? 'add' : 'subtract', - args: [acc, curr[1]], - location: simpleLocation(location()), - text: text() - }), left) - }, - peg$c20 = "*", - peg$c21 = peg$literalExpectation("*", false), - peg$c22 = "/", - peg$c23 = peg$literalExpectation("/", false), - peg$c24 = function(left, rest) { - return rest.reduce((acc, curr) => ({ - type: 'function', - name: curr[0] === '*' ? 'multiply' : 'divide', - args: [acc, curr[1]], - location: simpleLocation(location()), - text: text() - }), left) - }, - peg$c25 = "(", - peg$c26 = peg$literalExpectation("(", false), - peg$c27 = ")", - peg$c28 = peg$literalExpectation(")", false), - peg$c29 = function(expr) { - return expr - }, - peg$c30 = peg$otherExpectation("arguments"), - peg$c31 = ",", - peg$c32 = peg$literalExpectation(",", false), - peg$c33 = function(first, arg) {return arg}, - peg$c34 = function(first, rest) { - return [first].concat(rest); - }, - peg$c35 = /^["]/, - peg$c36 = peg$classExpectation(["\""], false, false), - peg$c37 = function(value) { return value.join(''); }, - peg$c38 = /^[']/, - peg$c39 = peg$classExpectation(["'"], false, false), - peg$c40 = /^[a-zA-Z_]/, - peg$c41 = peg$classExpectation([["a", "z"], ["A", "Z"], "_"], false, false), - peg$c42 = "=", - peg$c43 = peg$literalExpectation("=", false), - peg$c44 = function(name, value) { - return { - type: 'namedArgument', - name: name.join(''), - value: value, - location: simpleLocation(location()), - text: text() - }; - }, - peg$c45 = peg$otherExpectation("function"), - peg$c46 = /^[a-zA-Z_\-]/, - peg$c47 = peg$classExpectation([["a", "z"], ["A", "Z"], "_", "-"], false, false), - peg$c48 = function(name, args) { - return { - type: 'function', - name: name.join(''), - args: args || [], - location: simpleLocation(location()), - text: text() - }; - }, - peg$c49 = peg$otherExpectation("number"), - peg$c50 = function() { - return parseFloat(text()); - }, - peg$c51 = /^[eE]/, - peg$c52 = peg$classExpectation(["e", "E"], false, false), - peg$c53 = peg$otherExpectation("exponent"), - peg$c54 = ".", - peg$c55 = peg$literalExpectation(".", false), - peg$c56 = "0", - peg$c57 = peg$literalExpectation("0", false), - peg$c58 = /^[1-9]/, - peg$c59 = peg$classExpectation([["1", "9"]], false, false), - peg$c60 = /^[0-9]/, - peg$c61 = peg$classExpectation([["0", "9"]], false, false), - - peg$currPos = 0, - peg$savedPos = 0, - peg$posDetailsCache = [{ line: 1, column: 1 }], - peg$maxFailPos = 0, - peg$maxFailExpected = [], - peg$silentFails = 0, - - peg$result; - - if ("startRule" in options) { - if (!(options.startRule in peg$startRuleFunctions)) { - throw new Error("Can't start parsing from rule \"" + options.startRule + "\"."); - } - - peg$startRuleFunction = peg$startRuleFunctions[options.startRule]; - } - - function text() { - return input.substring(peg$savedPos, peg$currPos); - } - - function location() { - return peg$computeLocation(peg$savedPos, peg$currPos); - } - - function expected(description, location) { - location = location !== void 0 ? location : peg$computeLocation(peg$savedPos, peg$currPos) - - throw peg$buildStructuredError( - [peg$otherExpectation(description)], - input.substring(peg$savedPos, peg$currPos), - location - ); - } - - function error(message, location) { - location = location !== void 0 ? location : peg$computeLocation(peg$savedPos, peg$currPos) - - throw peg$buildSimpleError(message, location); - } - - function peg$literalExpectation(text, ignoreCase) { - return { type: "literal", text: text, ignoreCase: ignoreCase }; - } - - function peg$classExpectation(parts, inverted, ignoreCase) { - return { type: "class", parts: parts, inverted: inverted, ignoreCase: ignoreCase }; - } - - function peg$anyExpectation() { - return { type: "any" }; - } - - function peg$endExpectation() { - return { type: "end" }; - } - - function peg$otherExpectation(description) { - return { type: "other", description: description }; - } - - function peg$computePosDetails(pos) { - var details = peg$posDetailsCache[pos], p; - - if (details) { - return details; - } else { - p = pos - 1; - while (!peg$posDetailsCache[p]) { - p--; - } - - details = peg$posDetailsCache[p]; - details = { - line: details.line, - column: details.column - }; - - while (p < pos) { - if (input.charCodeAt(p) === 10) { - details.line++; - details.column = 1; - } else { - details.column++; - } - - p++; - } - - peg$posDetailsCache[pos] = details; - return details; - } - } - - function peg$computeLocation(startPos, endPos) { - var startPosDetails = peg$computePosDetails(startPos), - endPosDetails = peg$computePosDetails(endPos); - - return { - start: { - offset: startPos, - line: startPosDetails.line, - column: startPosDetails.column - }, - end: { - offset: endPos, - line: endPosDetails.line, - column: endPosDetails.column - } - }; - } - - function peg$fail(expected) { - if (peg$currPos < peg$maxFailPos) { return; } - - if (peg$currPos > peg$maxFailPos) { - peg$maxFailPos = peg$currPos; - peg$maxFailExpected = []; - } - - peg$maxFailExpected.push(expected); - } - - function peg$buildSimpleError(message, location) { - return new peg$SyntaxError(message, null, null, location); - } - - function peg$buildStructuredError(expected, found, location) { - return new peg$SyntaxError( - peg$SyntaxError.buildMessage(expected, found), - expected, - found, - location - ); - } - - function peg$parsestart() { - var s0; - - s0 = peg$parseAddSubtract(); - - return s0; - } - - function peg$parse_() { - var s0, s1; - - peg$silentFails++; - s0 = []; - if (peg$c1.test(input.charAt(peg$currPos))) { - s1 = input.charAt(peg$currPos); - peg$currPos++; - } else { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c2); } - } - while (s1 !== peg$FAILED) { - s0.push(s1); - if (peg$c1.test(input.charAt(peg$currPos))) { - s1 = input.charAt(peg$currPos); - peg$currPos++; - } else { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c2); } - } - } - peg$silentFails--; - if (s0 === peg$FAILED) { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c0); } - } - - return s0; - } - - function peg$parseSpace() { - var s0; - - if (peg$c3.test(input.charAt(peg$currPos))) { - s0 = input.charAt(peg$currPos); - peg$currPos++; - } else { - s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c4); } - } - - return s0; - } - - function peg$parseQuote() { - var s0; - - if (peg$c5.test(input.charAt(peg$currPos))) { - s0 = input.charAt(peg$currPos); - peg$currPos++; - } else { - s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c6); } - } - - return s0; - } - - function peg$parseStartChar() { - var s0; - - if (peg$c7.test(input.charAt(peg$currPos))) { - s0 = input.charAt(peg$currPos); - peg$currPos++; - } else { - s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c8); } - } - - return s0; - } - - function peg$parseValidChar() { - var s0; - - if (peg$c9.test(input.charAt(peg$currPos))) { - s0 = input.charAt(peg$currPos); - peg$currPos++; - } else { - s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c10); } - } - - return s0; - } - - function peg$parseLiteral() { - var s0, s1, s2, s3; - - peg$silentFails++; - s0 = peg$currPos; - s1 = peg$parse_(); - if (s1 !== peg$FAILED) { - s2 = peg$parseNumber(); - if (s2 === peg$FAILED) { - s2 = peg$parseVariable(); - } - if (s2 !== peg$FAILED) { - s3 = peg$parse_(); - if (s3 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$c12(s2); - s0 = s1; - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - peg$silentFails--; - if (s0 === peg$FAILED) { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c11); } - } - - return s0; - } - - function peg$parseVariable() { - var s0, s1, s2, s3, s4, s5; - - s0 = peg$currPos; - s1 = peg$parse_(); - if (s1 !== peg$FAILED) { - s2 = peg$parseQuote(); - if (s2 !== peg$FAILED) { - s3 = []; - s4 = peg$parseValidChar(); - if (s4 === peg$FAILED) { - s4 = peg$parseSpace(); - } - while (s4 !== peg$FAILED) { - s3.push(s4); - s4 = peg$parseValidChar(); - if (s4 === peg$FAILED) { - s4 = peg$parseSpace(); - } - } - if (s3 !== peg$FAILED) { - s4 = peg$parseQuote(); - if (s4 !== peg$FAILED) { - s5 = peg$parse_(); - if (s5 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$c13(s3); - s0 = s1; - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - if (s0 === peg$FAILED) { - s0 = peg$currPos; - s1 = peg$parse_(); - if (s1 !== peg$FAILED) { - s2 = []; - s3 = peg$parseValidChar(); - if (s3 !== peg$FAILED) { - while (s3 !== peg$FAILED) { - s2.push(s3); - s3 = peg$parseValidChar(); - } - } else { - s2 = peg$FAILED; - } - if (s2 !== peg$FAILED) { - s3 = peg$parse_(); - if (s3 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$c14(s2); - s0 = s1; - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } - - return s0; - } - - function peg$parseAddSubtract() { - var s0, s1, s2, s3, s4, s5, s6; - - s0 = peg$currPos; - s1 = peg$parse_(); - if (s1 !== peg$FAILED) { - s2 = peg$parseMultiplyDivide(); - if (s2 !== peg$FAILED) { - s3 = []; - s4 = peg$currPos; - if (input.charCodeAt(peg$currPos) === 43) { - s5 = peg$c15; - peg$currPos++; - } else { - s5 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c16); } - } - if (s5 === peg$FAILED) { - if (input.charCodeAt(peg$currPos) === 45) { - s5 = peg$c17; - peg$currPos++; - } else { - s5 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c18); } - } - } - if (s5 !== peg$FAILED) { - s6 = peg$parseMultiplyDivide(); - if (s6 !== peg$FAILED) { - s5 = [s5, s6]; - s4 = s5; - } else { - peg$currPos = s4; - s4 = peg$FAILED; - } - } else { - peg$currPos = s4; - s4 = peg$FAILED; - } - while (s4 !== peg$FAILED) { - s3.push(s4); - s4 = peg$currPos; - if (input.charCodeAt(peg$currPos) === 43) { - s5 = peg$c15; - peg$currPos++; - } else { - s5 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c16); } - } - if (s5 === peg$FAILED) { - if (input.charCodeAt(peg$currPos) === 45) { - s5 = peg$c17; - peg$currPos++; - } else { - s5 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c18); } - } - } - if (s5 !== peg$FAILED) { - s6 = peg$parseMultiplyDivide(); - if (s6 !== peg$FAILED) { - s5 = [s5, s6]; - s4 = s5; - } else { - peg$currPos = s4; - s4 = peg$FAILED; - } - } else { - peg$currPos = s4; - s4 = peg$FAILED; - } - } - if (s3 !== peg$FAILED) { - s4 = peg$parse_(); - if (s4 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$c19(s2, s3); - s0 = s1; - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - - return s0; - } - - function peg$parseMultiplyDivide() { - var s0, s1, s2, s3, s4, s5, s6; - - s0 = peg$currPos; - s1 = peg$parse_(); - if (s1 !== peg$FAILED) { - s2 = peg$parseFactor(); - if (s2 !== peg$FAILED) { - s3 = []; - s4 = peg$currPos; - if (input.charCodeAt(peg$currPos) === 42) { - s5 = peg$c20; - peg$currPos++; - } else { - s5 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c21); } - } - if (s5 === peg$FAILED) { - if (input.charCodeAt(peg$currPos) === 47) { - s5 = peg$c22; - peg$currPos++; - } else { - s5 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c23); } - } - } - if (s5 !== peg$FAILED) { - s6 = peg$parseFactor(); - if (s6 !== peg$FAILED) { - s5 = [s5, s6]; - s4 = s5; - } else { - peg$currPos = s4; - s4 = peg$FAILED; - } - } else { - peg$currPos = s4; - s4 = peg$FAILED; - } - while (s4 !== peg$FAILED) { - s3.push(s4); - s4 = peg$currPos; - if (input.charCodeAt(peg$currPos) === 42) { - s5 = peg$c20; - peg$currPos++; - } else { - s5 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c21); } - } - if (s5 === peg$FAILED) { - if (input.charCodeAt(peg$currPos) === 47) { - s5 = peg$c22; - peg$currPos++; - } else { - s5 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c23); } - } - } - if (s5 !== peg$FAILED) { - s6 = peg$parseFactor(); - if (s6 !== peg$FAILED) { - s5 = [s5, s6]; - s4 = s5; - } else { - peg$currPos = s4; - s4 = peg$FAILED; - } - } else { - peg$currPos = s4; - s4 = peg$FAILED; - } - } - if (s3 !== peg$FAILED) { - s4 = peg$parse_(); - if (s4 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$c24(s2, s3); - s0 = s1; - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - - return s0; - } - - function peg$parseFactor() { - var s0; - - s0 = peg$parseGroup(); - if (s0 === peg$FAILED) { - s0 = peg$parseFunction(); - if (s0 === peg$FAILED) { - s0 = peg$parseLiteral(); - } - } - - return s0; - } - - function peg$parseGroup() { - var s0, s1, s2, s3, s4, s5, s6, s7; - - s0 = peg$currPos; - s1 = peg$parse_(); - if (s1 !== peg$FAILED) { - if (input.charCodeAt(peg$currPos) === 40) { - s2 = peg$c25; - peg$currPos++; - } else { - s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c26); } - } - if (s2 !== peg$FAILED) { - s3 = peg$parse_(); - if (s3 !== peg$FAILED) { - s4 = peg$parseAddSubtract(); - if (s4 !== peg$FAILED) { - s5 = peg$parse_(); - if (s5 !== peg$FAILED) { - if (input.charCodeAt(peg$currPos) === 41) { - s6 = peg$c27; - peg$currPos++; - } else { - s6 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c28); } - } - if (s6 !== peg$FAILED) { - s7 = peg$parse_(); - if (s7 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$c29(s4); - s0 = s1; - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - - return s0; - } - - function peg$parseArgument_List() { - var s0, s1, s2, s3, s4, s5, s6, s7; - - peg$silentFails++; - s0 = peg$currPos; - s1 = peg$parseArgument(); - if (s1 !== peg$FAILED) { - s2 = []; - s3 = peg$currPos; - s4 = peg$parse_(); - if (s4 !== peg$FAILED) { - if (input.charCodeAt(peg$currPos) === 44) { - s5 = peg$c31; - peg$currPos++; - } else { - s5 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c32); } - } - if (s5 !== peg$FAILED) { - s6 = peg$parse_(); - if (s6 !== peg$FAILED) { - s7 = peg$parseArgument(); - if (s7 !== peg$FAILED) { - peg$savedPos = s3; - s4 = peg$c33(s1, s7); - s3 = s4; - } else { - peg$currPos = s3; - s3 = peg$FAILED; - } - } else { - peg$currPos = s3; - s3 = peg$FAILED; - } - } else { - peg$currPos = s3; - s3 = peg$FAILED; - } - } else { - peg$currPos = s3; - s3 = peg$FAILED; - } - while (s3 !== peg$FAILED) { - s2.push(s3); - s3 = peg$currPos; - s4 = peg$parse_(); - if (s4 !== peg$FAILED) { - if (input.charCodeAt(peg$currPos) === 44) { - s5 = peg$c31; - peg$currPos++; - } else { - s5 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c32); } - } - if (s5 !== peg$FAILED) { - s6 = peg$parse_(); - if (s6 !== peg$FAILED) { - s7 = peg$parseArgument(); - if (s7 !== peg$FAILED) { - peg$savedPos = s3; - s4 = peg$c33(s1, s7); - s3 = s4; - } else { - peg$currPos = s3; - s3 = peg$FAILED; - } - } else { - peg$currPos = s3; - s3 = peg$FAILED; - } - } else { - peg$currPos = s3; - s3 = peg$FAILED; - } - } else { - peg$currPos = s3; - s3 = peg$FAILED; - } - } - if (s2 !== peg$FAILED) { - s3 = peg$parse_(); - if (s3 !== peg$FAILED) { - if (input.charCodeAt(peg$currPos) === 44) { - s4 = peg$c31; - peg$currPos++; - } else { - s4 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c32); } - } - if (s4 === peg$FAILED) { - s4 = null; - } - if (s4 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$c34(s1, s2); - s0 = s1; - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - peg$silentFails--; - if (s0 === peg$FAILED) { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c30); } - } - - return s0; - } - - function peg$parseString() { - var s0, s1, s2, s3; - - s0 = peg$currPos; - if (peg$c35.test(input.charAt(peg$currPos))) { - s1 = input.charAt(peg$currPos); - peg$currPos++; - } else { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c36); } - } - if (s1 !== peg$FAILED) { - s2 = []; - s3 = peg$parseValidChar(); - if (s3 !== peg$FAILED) { - while (s3 !== peg$FAILED) { - s2.push(s3); - s3 = peg$parseValidChar(); - } - } else { - s2 = peg$FAILED; - } - if (s2 !== peg$FAILED) { - if (peg$c35.test(input.charAt(peg$currPos))) { - s3 = input.charAt(peg$currPos); - peg$currPos++; - } else { - s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c36); } - } - if (s3 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$c37(s2); - s0 = s1; - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - if (s0 === peg$FAILED) { - s0 = peg$currPos; - if (peg$c38.test(input.charAt(peg$currPos))) { - s1 = input.charAt(peg$currPos); - peg$currPos++; - } else { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c39); } - } - if (s1 !== peg$FAILED) { - s2 = []; - s3 = peg$parseValidChar(); - if (s3 !== peg$FAILED) { - while (s3 !== peg$FAILED) { - s2.push(s3); - s3 = peg$parseValidChar(); - } - } else { - s2 = peg$FAILED; - } - if (s2 !== peg$FAILED) { - if (peg$c38.test(input.charAt(peg$currPos))) { - s3 = input.charAt(peg$currPos); - peg$currPos++; - } else { - s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c39); } - } - if (s3 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$c37(s2); - s0 = s1; - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - if (s0 === peg$FAILED) { - s0 = peg$currPos; - s1 = []; - s2 = peg$parseValidChar(); - if (s2 !== peg$FAILED) { - while (s2 !== peg$FAILED) { - s1.push(s2); - s2 = peg$parseValidChar(); - } - } else { - s1 = peg$FAILED; - } - if (s1 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$c37(s1); - } - s0 = s1; - } - } - - return s0; - } - - function peg$parseArgument() { - var s0, s1, s2, s3, s4, s5, s6; - - s0 = peg$currPos; - s1 = []; - if (peg$c40.test(input.charAt(peg$currPos))) { - s2 = input.charAt(peg$currPos); - peg$currPos++; - } else { - s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c41); } - } - if (s2 !== peg$FAILED) { - while (s2 !== peg$FAILED) { - s1.push(s2); - if (peg$c40.test(input.charAt(peg$currPos))) { - s2 = input.charAt(peg$currPos); - peg$currPos++; - } else { - s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c41); } - } - } - } else { - s1 = peg$FAILED; - } - if (s1 !== peg$FAILED) { - s2 = peg$parse_(); - if (s2 !== peg$FAILED) { - if (input.charCodeAt(peg$currPos) === 61) { - s3 = peg$c42; - peg$currPos++; - } else { - s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c43); } - } - if (s3 !== peg$FAILED) { - s4 = peg$parse_(); - if (s4 !== peg$FAILED) { - s5 = peg$parseNumber(); - if (s5 === peg$FAILED) { - s5 = peg$parseString(); - } - if (s5 !== peg$FAILED) { - s6 = peg$parse_(); - if (s6 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$c44(s1, s5); - s0 = s1; - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - if (s0 === peg$FAILED) { - s0 = peg$parseAddSubtract(); - } - - return s0; - } - - function peg$parseFunction() { - var s0, s1, s2, s3, s4, s5, s6, s7, s8; - - peg$silentFails++; - s0 = peg$currPos; - s1 = peg$parse_(); - if (s1 !== peg$FAILED) { - s2 = []; - if (peg$c46.test(input.charAt(peg$currPos))) { - s3 = input.charAt(peg$currPos); - peg$currPos++; - } else { - s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c47); } - } - if (s3 !== peg$FAILED) { - while (s3 !== peg$FAILED) { - s2.push(s3); - if (peg$c46.test(input.charAt(peg$currPos))) { - s3 = input.charAt(peg$currPos); - peg$currPos++; - } else { - s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c47); } - } - } - } else { - s2 = peg$FAILED; - } - if (s2 !== peg$FAILED) { - if (input.charCodeAt(peg$currPos) === 40) { - s3 = peg$c25; - peg$currPos++; - } else { - s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c26); } - } - if (s3 !== peg$FAILED) { - s4 = peg$parse_(); - if (s4 !== peg$FAILED) { - s5 = peg$parseArgument_List(); - if (s5 === peg$FAILED) { - s5 = null; - } - if (s5 !== peg$FAILED) { - s6 = peg$parse_(); - if (s6 !== peg$FAILED) { - if (input.charCodeAt(peg$currPos) === 41) { - s7 = peg$c27; - peg$currPos++; - } else { - s7 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c28); } - } - if (s7 !== peg$FAILED) { - s8 = peg$parse_(); - if (s8 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$c48(s2, s5); - s0 = s1; - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - peg$silentFails--; - if (s0 === peg$FAILED) { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c45); } - } - - return s0; - } - - function peg$parseNumber() { - var s0, s1, s2, s3, s4; - - peg$silentFails++; - s0 = peg$currPos; - if (input.charCodeAt(peg$currPos) === 45) { - s1 = peg$c17; - peg$currPos++; - } else { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c18); } - } - if (s1 === peg$FAILED) { - s1 = null; - } - if (s1 !== peg$FAILED) { - s2 = peg$parseInteger(); - if (s2 !== peg$FAILED) { - s3 = peg$parseFraction(); - if (s3 === peg$FAILED) { - s3 = null; - } - if (s3 !== peg$FAILED) { - s4 = peg$parseExp(); - if (s4 === peg$FAILED) { - s4 = null; - } - if (s4 !== peg$FAILED) { - peg$savedPos = s0; - s1 = peg$c50(); - s0 = s1; - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - peg$silentFails--; - if (s0 === peg$FAILED) { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c49); } - } - - return s0; - } - - function peg$parseE() { - var s0; - - if (peg$c51.test(input.charAt(peg$currPos))) { - s0 = input.charAt(peg$currPos); - peg$currPos++; - } else { - s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c52); } - } - - return s0; - } - - function peg$parseExp() { - var s0, s1, s2, s3, s4; - - peg$silentFails++; - s0 = peg$currPos; - s1 = peg$parseE(); - if (s1 !== peg$FAILED) { - if (input.charCodeAt(peg$currPos) === 45) { - s2 = peg$c17; - peg$currPos++; - } else { - s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c18); } - } - if (s2 === peg$FAILED) { - s2 = null; - } - if (s2 !== peg$FAILED) { - s3 = []; - s4 = peg$parseDigit(); - if (s4 !== peg$FAILED) { - while (s4 !== peg$FAILED) { - s3.push(s4); - s4 = peg$parseDigit(); - } - } else { - s3 = peg$FAILED; - } - if (s3 !== peg$FAILED) { - s1 = [s1, s2, s3]; - s0 = s1; - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - peg$silentFails--; - if (s0 === peg$FAILED) { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c53); } - } - - return s0; - } - - function peg$parseFraction() { - var s0, s1, s2, s3; - - s0 = peg$currPos; - if (input.charCodeAt(peg$currPos) === 46) { - s1 = peg$c54; - peg$currPos++; - } else { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c55); } - } - if (s1 !== peg$FAILED) { - s2 = []; - s3 = peg$parseDigit(); - if (s3 !== peg$FAILED) { - while (s3 !== peg$FAILED) { - s2.push(s3); - s3 = peg$parseDigit(); - } - } else { - s2 = peg$FAILED; - } - if (s2 !== peg$FAILED) { - s1 = [s1, s2]; - s0 = s1; - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - - return s0; - } - - function peg$parseInteger() { - var s0, s1, s2, s3; - - if (input.charCodeAt(peg$currPos) === 48) { - s0 = peg$c56; - peg$currPos++; - } else { - s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c57); } - } - if (s0 === peg$FAILED) { - s0 = peg$currPos; - if (peg$c58.test(input.charAt(peg$currPos))) { - s1 = input.charAt(peg$currPos); - peg$currPos++; - } else { - s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c59); } - } - if (s1 !== peg$FAILED) { - s2 = []; - s3 = peg$parseDigit(); - while (s3 !== peg$FAILED) { - s2.push(s3); - s3 = peg$parseDigit(); - } - if (s2 !== peg$FAILED) { - s1 = [s1, s2]; - s0 = s1; - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } - } - - return s0; - } - - function peg$parseDigit() { - var s0; - - if (peg$c60.test(input.charAt(peg$currPos))) { - s0 = input.charAt(peg$currPos); - peg$currPos++; - } else { - s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c61); } - } - - return s0; - } - - - function simpleLocation (location) { - // Returns an object representing the position of the function within the expression, - // demarcated by the position of its first character and last character. We calculate these values - // using the offset because the expression could span multiple lines, and we don't want to deal - // with column and line values. - return { - min: location.start.offset, - max: location.end.offset - } - } - - - peg$result = peg$startRuleFunction(); - - if (peg$result !== peg$FAILED && peg$currPos === input.length) { - return peg$result; - } else { - if (peg$result !== peg$FAILED && peg$currPos < input.length) { - peg$fail(peg$endExpectation()); - } - - throw peg$buildStructuredError( - peg$maxFailExpected, - peg$maxFailPos < input.length ? input.charAt(peg$maxFailPos) : null, - peg$maxFailPos < input.length - ? peg$computeLocation(peg$maxFailPos, peg$maxFailPos + 1) - : peg$computeLocation(peg$maxFailPos, peg$maxFailPos) - ); - } -} - -module.exports = { - SyntaxError: peg$SyntaxError, - parse: peg$parse -}; diff --git a/packages/kbn-tinymath/src/index.js b/packages/kbn-tinymath/src/index.js index 4db7df9c573156..9f1bb7b8514634 100644 --- a/packages/kbn-tinymath/src/index.js +++ b/packages/kbn-tinymath/src/index.js @@ -7,7 +7,8 @@ */ const { get } = require('lodash'); -const { parse: parseFn } = require('./grammar'); +// eslint-disable-next-line import/no-unresolved +const { parse: parseFn } = require('../grammar'); const { functions: includedFunctions } = require('./functions'); module.exports = { parse, evaluate, interpret }; diff --git a/packages/kbn-tinymath/test/library.test.js b/packages/kbn-tinymath/test/library.test.js index d11822625b98f5..5ddf1b049b8d4f 100644 --- a/packages/kbn-tinymath/test/library.test.js +++ b/packages/kbn-tinymath/test/library.test.js @@ -11,7 +11,7 @@ Need tests for spacing, etc */ -import { evaluate, parse } from '..'; +import { evaluate, parse } from '@kbn/tinymath'; function variableEqual(value) { return expect.objectContaining({ type: 'variable', value }); diff --git a/packages/kbn-tinymath/tsconfig.json b/packages/kbn-tinymath/tsconfig.json index 62a7376efdfa6d..73133b7318a0d6 100644 --- a/packages/kbn-tinymath/tsconfig.json +++ b/packages/kbn-tinymath/tsconfig.json @@ -1,7 +1,4 @@ { "extends": "../../tsconfig.base.json", - "compilerOptions": { - "tsBuildInfoFile": "../../build/tsbuildinfo/packages/kbn-tinymath" - }, - "include": ["tinymath.d.ts"] + "include": ["index.d.ts"] } diff --git a/packages/kbn-utils/package.json b/packages/kbn-utils/package.json index b6bb7759c40efe..2c3c0c11b65ab8 100644 --- a/packages/kbn-utils/package.json +++ b/packages/kbn-utils/package.json @@ -9,8 +9,5 @@ "build": "rm -rf target && ../../node_modules/.bin/tsc", "kbn:bootstrap": "yarn build", "kbn:watch": "yarn build --watch" - }, - "dependencies": { - "@kbn/config-schema": "link:../kbn-config-schema" } } \ No newline at end of file diff --git a/src/cli_keystore/utils/prompt.js b/src/cli_keystore/utils/prompt.js index d681f7de2e32cb..195f794db3e6e5 100644 --- a/src/cli_keystore/utils/prompt.js +++ b/src/cli_keystore/utils/prompt.js @@ -75,6 +75,7 @@ export function question(question, options = {}) { }); rl.question(questionPrompt, (value) => { + rl.close(); resolve(value); }); }); diff --git a/src/cli_keystore/utils/prompt.test.js b/src/cli_keystore/utils/prompt.test.js index 306d4b2bd66df5..e7ccac4e83e113 100644 --- a/src/cli_keystore/utils/prompt.test.js +++ b/src/cli_keystore/utils/prompt.test.js @@ -6,14 +6,11 @@ * Side Public License, v 1. */ -import sinon from 'sinon'; import { PassThrough } from 'stream'; import { confirm, question } from './prompt'; describe('prompt', () => { - const sandbox = sinon.createSandbox(); - let input; let output; @@ -23,30 +20,27 @@ describe('prompt', () => { }); afterEach(() => { - sandbox.restore(); + input.end(); + output.end(); }); describe('confirm', () => { it('prompts for question', async () => { - const onData = sandbox.stub(output, 'write'); - - confirm('my question', { output }); + const write = jest.spyOn(output, 'write'); - sinon.assert.calledOnce(onData); + process.nextTick(() => input.write('Y\n')); + await confirm('my question', { input, output }); - const { args } = onData.getCall(0); - expect(args[0]).toEqual('my question [y/N] '); + expect(write).toHaveBeenCalledWith('my question [y/N] '); }); it('prompts for question with default true', async () => { - const onData = sandbox.stub(output, 'write'); - - confirm('my question', { output, default: true }); + const write = jest.spyOn(output, 'write'); - sinon.assert.calledOnce(onData); + process.nextTick(() => input.write('Y\n')); + await confirm('my question', { input, output, default: true }); - const { args } = onData.getCall(0); - expect(args[0]).toEqual('my question [Y/n] '); + expect(write).toHaveBeenCalledWith('my question [Y/n] '); }); it('defaults to false', async () => { @@ -87,14 +81,12 @@ describe('prompt', () => { describe('question', () => { it('prompts for question', async () => { - const onData = sandbox.stub(output, 'write'); - - question('my question', { output }); + const write = jest.spyOn(output, 'write'); - sinon.assert.calledOnce(onData); + process.nextTick(() => input.write('my answer\n')); + await question('my question', { input, output }); - const { args } = onData.getCall(0); - expect(args[0]).toEqual('my question: '); + expect(write).toHaveBeenCalledWith('my question: '); }); it('can be answered', async () => { diff --git a/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap b/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap index 00cc827a1e83f1..29407c54e28345 100644 --- a/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap +++ b/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap @@ -4072,8 +4072,34 @@ exports[`Header renders 1`] = ` aria-expanded={false} aria-haspopup="true" aria-label="Help menu" - buttonRef={null} - className="euiHeaderSectionItem__button" + buttonRef={ + Object { + "current": , + } + } + className="euiHeaderSectionItemButton" color="text" onClick={[Function]} > @@ -4081,7 +4107,7 @@ exports[`Header renders 1`] = ` aria-expanded={false} aria-haspopup="true" aria-label="Help menu" - className="euiButtonEmpty euiButtonEmpty--text euiHeaderSectionItem__button" + className="euiButtonEmpty euiButtonEmpty--text euiHeaderSectionItemButton" disabled={false} onClick={[Function]} type="button" @@ -4101,15 +4127,19 @@ exports[`Header renders 1`] = ` - - - + type="help" + > + + + @@ -4226,7 +4256,7 @@ exports[`Header renders 1`] = ` aria-expanded="false" aria-label="Toggle primary navigation" aria-pressed="false" - class="euiButtonEmpty euiButtonEmpty--text euiHeaderSectionItem__button" + class="euiButtonEmpty euiButtonEmpty--text euiHeaderSectionItemButton" data-test-subj="toggleNavButton" type="button" > @@ -4237,14 +4267,18 @@ exports[`Header renders 1`] = ` class="euiButtonEmpty__text" > + class="euiHeaderSectionItemButton__content" + > + + , } } - className="euiHeaderSectionItem__button" + className="euiHeaderSectionItemButton" color="text" data-test-subj="toggleNavButton" onClick={[Function]} @@ -4254,7 +4288,7 @@ exports[`Header renders 1`] = ` aria-expanded={false} aria-label="Toggle primary navigation" aria-pressed={false} - className="euiButtonEmpty euiButtonEmpty--text euiHeaderSectionItem__button" + className="euiButtonEmpty euiButtonEmpty--text euiHeaderSectionItemButton" data-test-subj="toggleNavButton" disabled={false} onClick={[Function]} @@ -4275,15 +4309,19 @@ exports[`Header renders 1`] = ` - - - + type="menu" + > + + + diff --git a/src/core/public/chrome/ui/header/header.tsx b/src/core/public/chrome/ui/header/header.tsx index 16c89fdca380ab..67cdd24aae8487 100644 --- a/src/core/public/chrome/ui/header/header.tsx +++ b/src/core/public/chrome/ui/header/header.tsx @@ -98,7 +98,7 @@ export function Header({ ); } - const toggleCollapsibleNavRef = createRef(); + const toggleCollapsibleNavRef = createRef void }>(); const navId = htmlIdGenerator()(); const className = classnames('hide-for-sharing', 'headerGlobalNav'); diff --git a/src/core/public/doc_links/doc_links_service.ts b/src/core/public/doc_links/doc_links_service.ts index baf8ed2a61645b..4220d3e490f63a 100644 --- a/src/core/public/doc_links/doc_links_service.ts +++ b/src/core/public/doc_links/doc_links_service.ts @@ -109,6 +109,7 @@ export class DocLinksService { top_hits: `${ELASTICSEARCH_DOCS}search-aggregations-metrics-top-hits-aggregation.html`, }, runtimeFields: { + overview: `${ELASTICSEARCH_DOCS}runtime.html`, mapping: `${ELASTICSEARCH_DOCS}runtime-mapping-fields.html`, }, scriptedFields: { @@ -129,9 +130,51 @@ export class DocLinksService { }, addData: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/connect-to-elasticsearch.html`, kibana: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/index.html`, + upgradeAssistant: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/upgrade-assistant.html`, elasticsearch: { + docsBase: `${ELASTICSEARCH_DOCS}`, + asyncSearch: `${ELASTICSEARCH_DOCS}async-search-intro.html`, + dataStreams: `${ELASTICSEARCH_DOCS}data-streams.html`, indexModules: `${ELASTICSEARCH_DOCS}index-modules.html`, + indexSettings: `${ELASTICSEARCH_DOCS}index-modules.html#index-modules-settings`, + indexTemplates: `${ELASTICSEARCH_DOCS}indices-templates.html`, mapping: `${ELASTICSEARCH_DOCS}mapping.html`, + mappingAnalyzer: `${ELASTICSEARCH_DOCS}analyzer.html`, + mappingCoerce: `${ELASTICSEARCH_DOCS}coerce.html`, + mappingCopyTo: `${ELASTICSEARCH_DOCS}copy-to.html`, + mappingDocValues: `${ELASTICSEARCH_DOCS}doc-values.html`, + mappingDynamic: `${ELASTICSEARCH_DOCS}dynamic.html`, + mappingDynamicFields: `${ELASTICSEARCH_DOCS}dynamic-field-mapping.html`, + mappingDynamicTemplates: `${ELASTICSEARCH_DOCS}dynamic-templates.html`, + mappingEagerGlobalOrdinals: `${ELASTICSEARCH_DOCS}eager-global-ordinals.html`, + mappingEnabled: `${ELASTICSEARCH_DOCS}enabled.html`, + mappingFieldData: `${ELASTICSEARCH_DOCS}text.html#fielddata-mapping-param`, + mappingFieldDataEnable: `${ELASTICSEARCH_DOCS}text.html#before-enabling-fielddata`, + mappingFieldDataFilter: `${ELASTICSEARCH_DOCS}text.html#field-data-filtering`, + mappingFieldDataTypes: `${ELASTICSEARCH_DOCS}mapping-types.html`, + mappingFormat: `${ELASTICSEARCH_DOCS}mapping-date-format.html`, + mappingIgnoreAbove: `${ELASTICSEARCH_DOCS}ignore-above.html`, + mappingIgnoreMalformed: `${ELASTICSEARCH_DOCS}ignore-malformed.html`, + mappingIndex: `${ELASTICSEARCH_DOCS}mapping-index.html`, + mappingIndexOptions: `${ELASTICSEARCH_DOCS}index-options.html`, + mappingIndexPhrases: `${ELASTICSEARCH_DOCS}index-phrases.html`, + mappingIndexPrefixes: `${ELASTICSEARCH_DOCS}index-prefixes.html`, + mappingJoinFieldsPerformance: `${ELASTICSEARCH_DOCS}parent-join.html#_parent_join_and_performance`, + mappingMeta: `${ELASTICSEARCH_DOCS}mapping-field-meta.html`, + mappingMetaFields: `${ELASTICSEARCH_DOCS}mapping-meta-field.html`, + mappingNormalizer: `${ELASTICSEARCH_DOCS}normalizer.html`, + mappingNorms: `${ELASTICSEARCH_DOCS}norms.html`, + mappingNullValue: `${ELASTICSEARCH_DOCS}null-value.html`, + mappingParameters: `${ELASTICSEARCH_DOCS}mapping-params.html`, + mappingPositionIncrementGap: `${ELASTICSEARCH_DOCS}position-increment-gap.html`, + mappingRankFeatureFields: `${ELASTICSEARCH_DOCS}rank-feature.html`, + mappingRouting: `${ELASTICSEARCH_DOCS}mapping-routing-field.html`, + mappingSimilarity: `${ELASTICSEARCH_DOCS}similarity.html`, + mappingSourceFields: `${ELASTICSEARCH_DOCS}mapping-source-field.html`, + mappingSourceFieldsDisable: `${ELASTICSEARCH_DOCS}mapping-source-field.html#disable-source-field`, + mappingStore: `${ELASTICSEARCH_DOCS}mapping-store.html`, + mappingTermVector: `${ELASTICSEARCH_DOCS}term-vector.html`, + mappingTypesRemoval: `${ELASTICSEARCH_DOCS}removal-of-types.html`, nodeRoles: `${ELASTICSEARCH_DOCS}modules-node.html#node-roles`, remoteClusters: `${ELASTICSEARCH_DOCS}modules-remote-clusters.html`, remoteClustersProxy: `${ELASTICSEARCH_DOCS}modules-remote-clusters.html#proxy-mode`, @@ -139,6 +182,7 @@ export class DocLinksService { scriptParameters: `${ELASTICSEARCH_DOCS}modules-scripting-using.html#prefer-params`, transportSettings: `${ELASTICSEARCH_DOCS}modules-transport.html`, typesRemoval: `${ELASTICSEARCH_DOCS}removal-of-types.html`, + deprecationLogging: `${ELASTICSEARCH_DOCS}logging.html#deprecation-logging`, }, siem: { guide: `${ELASTIC_WEBSITE_URL}guide/en/security/${DOC_LINK_VERSION}/index.html`, @@ -146,17 +190,19 @@ export class DocLinksService { }, query: { eql: `${ELASTICSEARCH_DOCS}eql.html`, + kueryQuerySyntax: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/kuery-query.html`, luceneQuerySyntax: `${ELASTICSEARCH_DOCS}query-dsl-query-string-query.html#query-string-syntax`, + percolate: `${ELASTICSEARCH_DOCS}query-dsl-percolate-query.html`, queryDsl: `${ELASTICSEARCH_DOCS}query-dsl.html`, - kueryQuerySyntax: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/kuery-query.html`, }, date: { dateMath: `${ELASTICSEARCH_DOCS}common-options.html#date-math`, dateMathIndexNames: `${ELASTICSEARCH_DOCS}date-math-index-names.html`, }, management: { - kibanaSearchSettings: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/advanced-options.html#kibana-search-settings`, dashboardSettings: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/advanced-options.html#kibana-dashboard-settings`, + indexManagement: `${ELASTICSEARCH_DOCS}index-mgmt.html`, + kibanaSearchSettings: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/advanced-options.html#kibana-search-settings`, visualizationSettings: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/advanced-options.html#kibana-visualization-settings`, }, ml: { @@ -258,6 +304,7 @@ export class DocLinksService { skippingDisconnectedClusters: `${ELASTICSEARCH_DOCS}modules-cross-cluster-search.html#skip-unavailable-clusters`, }, apis: { + bulkIndexAlias: `${ELASTICSEARCH_DOCS}indices-aliases.html`, createIndex: `${ELASTICSEARCH_DOCS}indices-create-index.html`, createSnapshotLifecyclePolicy: `${ELASTICSEARCH_DOCS}slm-api-put-policy.html`, createRoleMapping: `${ELASTICSEARCH_DOCS}security-api-put-role-mapping.html`, @@ -274,6 +321,7 @@ export class DocLinksService { painlessExecuteAPIContexts: `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/painless/${DOC_LINK_VERSION}/painless-execute-api.html#_contexts`, putComponentTemplateMetadata: `${ELASTICSEARCH_DOCS}indices-component-template.html#component-templates-metadata`, putEnrichPolicy: `${ELASTICSEARCH_DOCS}put-enrich-policy-api.html`, + putIndexTemplateV1: `${ELASTICSEARCH_DOCS}indices-templates-v1.html`, putSnapshotLifecyclePolicy: `${ELASTICSEARCH_DOCS}slm-api-put-policy.html`, putWatch: `${ELASTICSEARCH_DOCS}watcher-api-put-watch.html`, simulatePipeline: `${ELASTICSEARCH_DOCS}simulate-pipeline-api.html`, @@ -429,6 +477,7 @@ export interface DocLinksStart { readonly top_hits: string; }; readonly runtimeFields: { + readonly overview: string; readonly mapping: string; }; readonly scriptedFields: { @@ -448,6 +497,7 @@ export interface DocLinksStart { }; readonly addData: string; readonly kibana: string; + readonly upgradeAssistant: string; readonly elasticsearch: Record; readonly siem: { readonly guide: string; @@ -455,9 +505,10 @@ export interface DocLinksStart { }; readonly query: { readonly eql: string; + readonly kueryQuerySyntax: string; readonly luceneQuerySyntax: string; + readonly percolate: string; readonly queryDsl: string; - readonly kueryQuerySyntax: string; }; readonly date: { readonly dateMath: string; @@ -468,6 +519,7 @@ export interface DocLinksStart { readonly transforms: Record; readonly visualize: Record; readonly apis: Readonly<{ + bulkIndexAlias: string; createIndex: string; createSnapshotLifecyclePolicy: string; createRoleMapping: string; @@ -484,6 +536,7 @@ export interface DocLinksStart { painlessExecuteAPIContexts: string; putComponentTemplateMetadata: string; putSnapshotLifecyclePolicy: string; + putIndexTemplateV1: string; putWatch: string; simulatePipeline: string; updateTransform: string; diff --git a/src/core/public/index.ts b/src/core/public/index.ts index 750f2e27dc9504..ca432d6b8269fa 100644 --- a/src/core/public/index.ts +++ b/src/core/public/index.ts @@ -67,7 +67,12 @@ import { DocLinksStart } from './doc_links'; import { SavedObjectsStart } from './saved_objects'; import { DeprecationsServiceStart } from './deprecations'; -export type { PackageInfo, EnvironmentMode, IExternalUrlPolicy } from '../server/types'; +export type { + PackageInfo, + EnvironmentMode, + IExternalUrlPolicy, + DomainDeprecationDetails, +} from '../server/types'; export type { CoreContext, CoreSystem } from './core_system'; export { DEFAULT_APP_CATEGORIES } from '../utils'; export type { diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index 8327428991e13b..661ac51c4983c6 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -476,7 +476,6 @@ export const DEFAULT_APP_CATEGORIES: Record; // @public export interface DeprecationsServiceStart { - // Warning: (ae-forgotten-export) The symbol "DomainDeprecationDetails" needs to be exported by the entry point index.d.ts getAllDeprecations: () => Promise; getDeprecations: (domainId: string) => Promise; isDeprecationResolvable: (details: DomainDeprecationDetails) => boolean; @@ -571,6 +570,7 @@ export interface DocLinksStart { readonly top_hits: string; }; readonly runtimeFields: { + readonly overview: string; readonly mapping: string; }; readonly scriptedFields: { @@ -590,6 +590,7 @@ export interface DocLinksStart { }; readonly addData: string; readonly kibana: string; + readonly upgradeAssistant: string; readonly elasticsearch: Record; readonly siem: { readonly guide: string; @@ -597,9 +598,10 @@ export interface DocLinksStart { }; readonly query: { readonly eql: string; + readonly kueryQuerySyntax: string; readonly luceneQuerySyntax: string; + readonly percolate: string; readonly queryDsl: string; - readonly kueryQuerySyntax: string; }; readonly date: { readonly dateMath: string; @@ -610,6 +612,7 @@ export interface DocLinksStart { readonly transforms: Record; readonly visualize: Record; readonly apis: Readonly<{ + bulkIndexAlias: string; createIndex: string; createSnapshotLifecyclePolicy: string; createRoleMapping: string; @@ -626,6 +629,7 @@ export interface DocLinksStart { painlessExecuteAPIContexts: string; putComponentTemplateMetadata: string; putSnapshotLifecyclePolicy: string; + putIndexTemplateV1: string; putWatch: string; simulatePipeline: string; updateTransform: string; @@ -654,6 +658,15 @@ export interface DocLinksStart { }; } +// Warning: (ae-forgotten-export) The symbol "DeprecationsDetails" needs to be exported by the entry point index.d.ts +// Warning: (ae-missing-release-tag) "DomainDeprecationDetails" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface DomainDeprecationDetails extends DeprecationsDetails { + // (undocumented) + domainId: string; +} + export { EnvironmentMode } // @public diff --git a/src/core/server/saved_objects/migrations/core/elastic_index.test.ts b/src/core/server/saved_objects/migrations/core/elastic_index.test.ts index 2fc78fc619cab5..1d2ec6abc0dd14 100644 --- a/src/core/server/saved_objects/migrations/core/elastic_index.test.ts +++ b/src/core/server/saved_objects/migrations/core/elastic_index.test.ts @@ -425,6 +425,22 @@ describe('ElasticIndex', () => { type: 'tsvb-validation-telemetry', }, }, + { + bool: { + must: [ + { + match: { + type: 'search-session', + }, + }, + { + match: { + 'search-session.persisted': false, + }, + }, + ], + }, + }, ], }, }, diff --git a/src/core/server/saved_objects/migrations/core/elastic_index.ts b/src/core/server/saved_objects/migrations/core/elastic_index.ts index 462425ff6e3e0e..460aabbc77415c 100644 --- a/src/core/server/saved_objects/migrations/core/elastic_index.ts +++ b/src/core/server/saved_objects/migrations/core/elastic_index.ts @@ -29,6 +29,46 @@ export interface FullIndexInfo { mappings: IndexMapping; } +// When migrating from the outdated index we use a read query which excludes +// saved objects which are no longer used. These saved objects will still be +// kept in the outdated index for backup purposes, but won't be availble in +// the upgraded index. +export const excludeUnusedTypesQuery: estypes.QueryContainer = { + bool: { + must_not: [ + // https://github.com/elastic/kibana/issues/91869 + { + term: { + type: 'fleet-agent-events', + }, + }, + // https://github.com/elastic/kibana/issues/95617 + { + term: { + type: 'tsvb-validation-telemetry', + }, + }, + // https://github.com/elastic/kibana/issues/96131 + { + bool: { + must: [ + { + match: { + type: 'search-session', + }, + }, + { + match: { + 'search-session.persisted': false, + }, + }, + ], + }, + }, + ], + }, +}; + /** * A slight enhancement to indices.get, that adds indexName, and validates that the * index mappings are somewhat what we expect. @@ -69,23 +109,6 @@ export function reader( const scroll = scrollDuration; let scrollId: string | undefined; - // When migrating from the outdated index we use a read query which excludes - // saved object types which are no longer used. These saved objects will - // still be kept in the outdated index for backup purposes, but won't be - // availble in the upgraded index. - const EXCLUDE_UNUSED_TYPES = [ - 'fleet-agent-events', // https://github.com/elastic/kibana/issues/91869 - 'tsvb-validation-telemetry', // https://github.com/elastic/kibana/issues/95617 - ]; - - const excludeUnusedTypesQuery = { - bool: { - must_not: EXCLUDE_UNUSED_TYPES.map((type) => ({ - term: { type }, - })), - }, - }; - const nextBatch = () => scrollId !== undefined ? client.scroll>({ diff --git a/src/core/server/saved_objects/migrations/core/index.ts b/src/core/server/saved_objects/migrations/core/index.ts index 322150e2b850eb..1e51983a0ffbdb 100644 --- a/src/core/server/saved_objects/migrations/core/index.ts +++ b/src/core/server/saved_objects/migrations/core/index.ts @@ -14,3 +14,4 @@ export type { LogFn, SavedObjectsMigrationLogger } from './migration_logger'; export type { MigrationResult, MigrationStatus } from './migration_coordinator'; export { createMigrationEsClient } from './migration_es_client'; export type { MigrationEsClient } from './migration_es_client'; +export { excludeUnusedTypesQuery } from './elastic_index'; diff --git a/src/core/server/saved_objects/migrationsv2/actions/index.ts b/src/core/server/saved_objects/migrationsv2/actions/index.ts index 9d6afbd3b0d87c..02d3f8e21a5106 100644 --- a/src/core/server/saved_objects/migrationsv2/actions/index.ts +++ b/src/core/server/saved_objects/migrationsv2/actions/index.ts @@ -14,7 +14,6 @@ import { errors as EsErrors } from '@elastic/elasticsearch'; import type { ElasticsearchClientError, ResponseError } from '@elastic/elasticsearch/lib/errors'; import { pipe } from 'fp-ts/lib/pipeable'; import { flow } from 'fp-ts/lib/function'; -import { QueryContainer } from '@elastic/eui/src/components/search_bar/query/ast_to_es_query_dsl'; import { ElasticsearchClient } from '../../../elasticsearch'; import { IndexMapping } from '../../mappings'; import { SavedObjectsRawDoc, SavedObjectsRawDocSource } from '../../serialization'; @@ -440,9 +439,9 @@ export const reindex = ( requireAlias: boolean, /* When reindexing we use a source query to exclude saved objects types which * are no longer used. These saved objects will still be kept in the outdated - * index for backup purposes, but won't be availble in the upgraded index. + * index for backup purposes, but won't be available in the upgraded index. */ - unusedTypesToExclude: Option.Option + unusedTypesQuery: Option.Option ): TaskEither.TaskEither => () => { return client .reindex({ @@ -457,14 +456,10 @@ export const reindex = ( // Set reindex batch size size: BATCH_SIZE, // Exclude saved object types - query: Option.fold( + query: Option.fold( () => undefined, - (types) => ({ - bool: { - must_not: types.map((type) => ({ term: { type } })), - }, - }) - )(unusedTypesToExclude), + (query) => query + )(unusedTypesQuery), }, dest: { index: targetIndex, diff --git a/src/core/server/saved_objects/migrationsv2/integration_tests/actions.test.ts b/src/core/server/saved_objects/migrationsv2/integration_tests/actions.test.ts index 21c05d22b05819..3905044f04e2fc 100644 --- a/src/core/server/saved_objects/migrationsv2/integration_tests/actions.test.ts +++ b/src/core/server/saved_objects/migrationsv2/integration_tests/actions.test.ts @@ -416,14 +416,20 @@ describe('migration actions', () => { ] `); }); - it('resolves right and excludes all unusedTypesToExclude documents', async () => { + it('resolves right and excludes all documents not matching the unusedTypesQuery', async () => { const res = (await reindex( client, 'existing_index_with_docs', 'reindex_target_excluded_docs', Option.none, false, - Option.some(['f_agent_event', 'another_unused_type']) + Option.of({ + bool: { + must_not: ['f_agent_event', 'another_unused_type'].map((type) => ({ + term: { type }, + })), + }, + }) )()) as Either.Right; const task = waitForReindexTask(client, res.right.taskId, '10s'); await expect(task()).resolves.toMatchInlineSnapshot(` diff --git a/src/core/server/saved_objects/migrationsv2/migrations_state_action_machine.test.ts b/src/core/server/saved_objects/migrationsv2/migrations_state_action_machine.test.ts index bec09c4b243280..a6617fc2fb7f48 100644 --- a/src/core/server/saved_objects/migrationsv2/migrations_state_action_machine.test.ts +++ b/src/core/server/saved_objects/migrationsv2/migrations_state_action_machine.test.ts @@ -256,12 +256,40 @@ describe('migrationsStateActionMachine', () => { }, }, }, - "unusedTypesToExclude": Object { + "unusedTypesQuery": Object { "_tag": "Some", - "value": Array [ - "fleet-agent-events", - "tsvb-validation-telemetry", - ], + "value": Object { + "bool": Object { + "must_not": Array [ + Object { + "term": Object { + "type": "fleet-agent-events", + }, + }, + Object { + "term": Object { + "type": "tsvb-validation-telemetry", + }, + }, + Object { + "bool": Object { + "must": Array [ + Object { + "match": Object { + "type": "search-session", + }, + }, + Object { + "match": Object { + "search-session.persisted": false, + }, + }, + ], + }, + }, + ], + }, + }, }, "versionAlias": ".my-so-index_7.11.0", "versionIndex": ".my-so-index_7.11.0_001", @@ -328,12 +356,40 @@ describe('migrationsStateActionMachine', () => { }, }, }, - "unusedTypesToExclude": Object { + "unusedTypesQuery": Object { "_tag": "Some", - "value": Array [ - "fleet-agent-events", - "tsvb-validation-telemetry", - ], + "value": Object { + "bool": Object { + "must_not": Array [ + Object { + "term": Object { + "type": "fleet-agent-events", + }, + }, + Object { + "term": Object { + "type": "tsvb-validation-telemetry", + }, + }, + Object { + "bool": Object { + "must": Array [ + Object { + "match": Object { + "type": "search-session", + }, + }, + Object { + "match": Object { + "search-session.persisted": false, + }, + }, + ], + }, + }, + ], + }, + }, }, "versionAlias": ".my-so-index_7.11.0", "versionIndex": ".my-so-index_7.11.0_001", @@ -485,12 +541,40 @@ describe('migrationsStateActionMachine', () => { }, }, }, - "unusedTypesToExclude": Object { + "unusedTypesQuery": Object { "_tag": "Some", - "value": Array [ - "fleet-agent-events", - "tsvb-validation-telemetry", - ], + "value": Object { + "bool": Object { + "must_not": Array [ + Object { + "term": Object { + "type": "fleet-agent-events", + }, + }, + Object { + "term": Object { + "type": "tsvb-validation-telemetry", + }, + }, + Object { + "bool": Object { + "must": Array [ + Object { + "match": Object { + "type": "search-session", + }, + }, + Object { + "match": Object { + "search-session.persisted": false, + }, + }, + ], + }, + }, + ], + }, + }, }, "versionAlias": ".my-so-index_7.11.0", "versionIndex": ".my-so-index_7.11.0_001", @@ -552,12 +636,40 @@ describe('migrationsStateActionMachine', () => { }, }, }, - "unusedTypesToExclude": Object { + "unusedTypesQuery": Object { "_tag": "Some", - "value": Array [ - "fleet-agent-events", - "tsvb-validation-telemetry", - ], + "value": Object { + "bool": Object { + "must_not": Array [ + Object { + "term": Object { + "type": "fleet-agent-events", + }, + }, + Object { + "term": Object { + "type": "tsvb-validation-telemetry", + }, + }, + Object { + "bool": Object { + "must": Array [ + Object { + "match": Object { + "type": "search-session", + }, + }, + Object { + "match": Object { + "search-session.persisted": false, + }, + }, + ], + }, + }, + ], + }, + }, }, "versionAlias": ".my-so-index_7.11.0", "versionIndex": ".my-so-index_7.11.0_001", diff --git a/src/core/server/saved_objects/migrationsv2/model.test.ts b/src/core/server/saved_objects/migrationsv2/model.test.ts index 8aad62f13b8fea..0267ae33dd157c 100644 --- a/src/core/server/saved_objects/migrationsv2/model.test.ts +++ b/src/core/server/saved_objects/migrationsv2/model.test.ts @@ -70,7 +70,17 @@ describe('migrations v2 model', () => { versionAlias: '.kibana_7.11.0', versionIndex: '.kibana_7.11.0_001', tempIndex: '.kibana_7.11.0_reindex_temp', - unusedTypesToExclude: Option.some(['unused-fleet-agent-events']), + unusedTypesQuery: Option.of({ + bool: { + must_not: [ + { + term: { + type: 'unused-fleet-agent-events', + }, + }, + ], + }, + }), }; describe('exponential retry delays for retryable_es_client_error', () => { @@ -1177,12 +1187,40 @@ describe('migrations v2 model', () => { }, }, }, - "unusedTypesToExclude": Object { + "unusedTypesQuery": Object { "_tag": "Some", - "value": Array [ - "fleet-agent-events", - "tsvb-validation-telemetry", - ], + "value": Object { + "bool": Object { + "must_not": Array [ + Object { + "term": Object { + "type": "fleet-agent-events", + }, + }, + Object { + "term": Object { + "type": "tsvb-validation-telemetry", + }, + }, + Object { + "bool": Object { + "must": Array [ + Object { + "match": Object { + "type": "search-session", + }, + }, + Object { + "match": Object { + "search-session.persisted": false, + }, + }, + ], + }, + }, + ], + }, + }, }, "versionAlias": ".kibana_task_manager_8.1.0", "versionIndex": ".kibana_task_manager_8.1.0_001", diff --git a/src/core/server/saved_objects/migrationsv2/model.ts b/src/core/server/saved_objects/migrationsv2/model.ts index ee78692a7044f0..acf0f620136a2c 100644 --- a/src/core/server/saved_objects/migrationsv2/model.ts +++ b/src/core/server/saved_objects/migrationsv2/model.ts @@ -16,6 +16,7 @@ import { IndexMapping } from '../mappings'; import { ResponseType } from './next'; import { SavedObjectsMigrationVersion } from '../types'; import { disableUnknownTypeMappingFields } from '../migrations/core/migration_context'; +import { excludeUnusedTypesQuery } from '../migrations/core'; import { SavedObjectsMigrationConfigType } from '../saved_objects_config'; /** @@ -74,6 +75,7 @@ function indexBelongsToLaterVersion(indexName: string, kibanaVersion: string): b const version = valid(indexVersion(indexName)); return version != null ? gt(version, kibanaVersion) : false; } + /** * Extracts the version number from a >= 7.11 index * @param indexName A >= v7.11 index name @@ -781,11 +783,6 @@ export const createInitialState = ({ }, }; - const unusedTypesToExclude = Option.some([ - 'fleet-agent-events', // https://github.com/elastic/kibana/issues/91869 - 'tsvb-validation-telemetry', // https://github.com/elastic/kibana/issues/95617 - ]); - const initialState: InitState = { controlState: 'INIT', indexPrefix, @@ -804,7 +801,7 @@ export const createInitialState = ({ retryAttempts: migrationsConfig.retryAttempts, batchSize: migrationsConfig.batchSize, logs: [], - unusedTypesToExclude, + unusedTypesQuery: Option.of(excludeUnusedTypesQuery), }; return initialState; }; diff --git a/src/core/server/saved_objects/migrationsv2/next.ts b/src/core/server/saved_objects/migrationsv2/next.ts index 5cbda741a0ce5a..bb506cbca66fb1 100644 --- a/src/core/server/saved_objects/migrationsv2/next.ts +++ b/src/core/server/saved_objects/migrationsv2/next.ts @@ -70,7 +70,7 @@ export const nextActionMap = (client: ElasticsearchClient, transformRawDocs: Tra state.tempIndex, Option.none, false, - state.unusedTypesToExclude + state.unusedTypesQuery ), SET_TEMP_WRITE_BLOCK: (state: SetTempWriteBlock) => Actions.setWriteBlock(client, state.tempIndex), @@ -115,7 +115,7 @@ export const nextActionMap = (client: ElasticsearchClient, transformRawDocs: Tra state.sourceIndex.value, state.preMigrationScript, false, - state.unusedTypesToExclude + state.unusedTypesQuery ), LEGACY_REINDEX_WAIT_FOR_TASK: (state: LegacyReindexWaitForTaskState) => Actions.waitForReindexTask(client, state.legacyReindexTaskId, '60s'), diff --git a/src/core/server/saved_objects/migrationsv2/types.ts b/src/core/server/saved_objects/migrationsv2/types.ts index e9b351c0152fc0..5e84bc23b1d161 100644 --- a/src/core/server/saved_objects/migrationsv2/types.ts +++ b/src/core/server/saved_objects/migrationsv2/types.ts @@ -7,6 +7,7 @@ */ import * as Option from 'fp-ts/lib/Option'; +import { estypes } from '@elastic/elasticsearch'; import { ControlState } from './state_action_machine'; import { AliasAction } from './actions'; import { IndexMapping } from '../mappings'; @@ -91,9 +92,9 @@ export interface BaseState extends ControlState { readonly tempIndex: string; /* When reindexing we use a source query to exclude saved objects types which * are no longer used. These saved objects will still be kept in the outdated - * index for backup purposes, but won't be availble in the upgraded index. + * index for backup purposes, but won't be available in the upgraded index. */ - readonly unusedTypesToExclude: Option.Option; + readonly unusedTypesQuery: Option.Option; } export type InitState = BaseState & { diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 53c1a82db6abdc..3c43947f9fd857 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -368,7 +368,7 @@ export const config: { healthCheck: import("@kbn/config-schema").ObjectType<{ delay: Type; }>; - ignoreVersionMismatch: import("@kbn/config-schema/target/types/types").ConditionalType; + ignoreVersionMismatch: import("@kbn/config-schema/target/types").ConditionalType; }>; }; logging: { diff --git a/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker b/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker index 1ad15592889922..c65a3569448a38 100755 --- a/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker +++ b/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker @@ -200,6 +200,8 @@ kibana_vars=( xpack.fleet.agents.elasticsearch.host xpack.fleet.agents.kibana.host xpack.fleet.agents.tlsCheckDisabled + xpack.fleet.agentPolicies + xpack.fleet.packages xpack.fleet.registryUrl xpack.graph.canEditDrillDownUrls xpack.graph.enabled diff --git a/src/plugins/dashboard/.storybook/storyshots.test.tsx b/src/plugins/dashboard/.storybook/storyshots.test.tsx deleted file mode 100644 index 80e8aa795ed400..00000000000000 --- a/src/plugins/dashboard/.storybook/storyshots.test.tsx +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import fs from 'fs'; -import { ReactChildren } from 'react'; -import path from 'path'; -import moment from 'moment'; -import 'moment-timezone'; -import ReactDOM from 'react-dom'; - -import initStoryshots, { multiSnapshotWithOptions } from '@storybook/addon-storyshots'; -// @ts-ignore -import styleSheetSerializer from 'jest-styled-components/src/styleSheetSerializer'; -import { addSerializer } from 'jest-specific-snapshot'; - -// Set our default timezone to UTC for tests so we can generate predictable snapshots -moment.tz.setDefault('UTC'); - -// Freeze time for the tests for predictable snapshots -const testTime = new Date(Date.UTC(2019, 5, 1)); // June 1 2019 -Date.now = jest.fn(() => testTime.getTime()); - -// Mock React Portal for components that use modals, tooltips, etc -// @ts-expect-error Portal mocks are notoriously difficult to type -ReactDOM.createPortal = jest.fn((element) => element); - -// Mock EUI generated ids to be consistently predictable for snapshots. -jest.mock(`@elastic/eui/lib/components/form/form_row/make_id`, () => () => `generated-id`); - -// Mock react-datepicker dep used by eui to avoid rendering the entire large component -jest.mock('@elastic/eui/packages/react-datepicker', () => { - return { - __esModule: true, - default: 'ReactDatePicker', - }; -}); - -// Mock the EUI HTML ID Generator so elements have a predictable ID in snapshots -jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => { - return { - htmlIdGenerator: () => () => `generated-id`, - }; -}); - -// To be resolved by EUI team. -// https://github.com/elastic/eui/issues/3712 -jest.mock('@elastic/eui/lib/components/overlay_mask/overlay_mask', () => { - return { - EuiOverlayMask: ({ children }: { children: ReactChildren }) => children, - }; -}); - -// @ts-ignore -import { EuiObserver } from '@elastic/eui/test-env/components/observer/observer'; -jest.mock('@elastic/eui/test-env/components/observer/observer'); -EuiObserver.mockImplementation(() => 'EuiObserver'); - -// 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 -initStoryshots({ - configPath: path.resolve(__dirname, './../.storybook'), - framework: 'react', - test: multiSnapshotWithOptions({}), -}); diff --git a/src/plugins/dashboard/kibana.json b/src/plugins/dashboard/kibana.json index 32507cbc5e5f47..41335069461fae 100644 --- a/src/plugins/dashboard/kibana.json +++ b/src/plugins/dashboard/kibana.json @@ -23,6 +23,7 @@ "requiredBundles": [ "home", "kibanaReact", - "kibanaUtils" + "kibanaUtils", + "presentationUtil" ] } diff --git a/src/plugins/dashboard/public/application/embeddable/empty_screen/__snapshots__/dashboard_empty_screen.test.tsx.snap b/src/plugins/dashboard/public/application/embeddable/empty_screen/__snapshots__/dashboard_empty_screen.test.tsx.snap index 9e3018fb512c37..4cd3eb13f36095 100644 --- a/src/plugins/dashboard/public/application/embeddable/empty_screen/__snapshots__/dashboard_empty_screen.test.tsx.snap +++ b/src/plugins/dashboard/public/application/embeddable/empty_screen/__snapshots__/dashboard_empty_screen.test.tsx.snap @@ -617,9 +617,11 @@ exports[`DashboardEmptyScreen renders correctly with readonly mode 1`] = `
({ chromeIsVisible: false }); const [isSaveInProgress, setIsSaveInProgress] = useState(false); + const stateTransferService = embeddable.getStateTransfer(); + useEffect(() => { const visibleSubscription = chrome.getIsVisible$().subscribe((chromeIsVisible) => { setState((s) => ({ ...s, chromeIsVisible })); @@ -147,12 +155,26 @@ export function DashboardTopNav({ const createNew = useCallback(async () => { const type = 'visualization'; const factory = embeddable.getEmbeddableFactory(type); + if (!factory) { throw new EmbeddableFactoryNotFoundError(type); } + await factory.create({} as EmbeddableInput, dashboardContainer); }, [dashboardContainer, embeddable]); + const createNewVisType = useCallback( + (newVisType: string) => async () => { + stateTransferService.navigateToEditor('visualize', { + path: `#/create?type=${encodeURIComponent(newVisType)}`, + state: { + originatingApp: DashboardConstants.DASHBOARDS_ID, + }, + }); + }, + [stateTransferService] + ); + const clearAddPanel = useCallback(() => { if (state.addPanelOverlay) { state.addPanelOverlay.close(); @@ -540,11 +562,51 @@ export function DashboardTopNav({ }; const { TopNavMenu } = navigation.ui; + + const quickButtons = [ + { + iconType: 'visText', + createType: i18n.translate('dashboard.solutionToolbar.markdownQuickButtonLabel', { + defaultMessage: 'Markdown', + }), + onClick: createNewVisType('markdown'), + 'data-test-subj': 'dashboardMarkdownQuickButton', + }, + { + iconType: 'controlsHorizontal', + createType: i18n.translate('dashboard.solutionToolbar.inputControlsQuickButtonLabel', { + defaultMessage: 'Input control', + }), + onClick: createNewVisType('input_control_vis'), + 'data-test-subj': 'dashboardInputControlsQuickButton', + }, + ]; + return ( <> {viewMode !== ViewMode.VIEW ? ( - + + {{ + primaryActionButton: ( + + ), + quickButtonGroup: , + addFromLibraryButton: ( + + ), + }} + ) : null} ); diff --git a/src/plugins/dashboard/public/application/top_nav/panel_toolbar/__snapshots__/panel_toolbar.stories.storyshot b/src/plugins/dashboard/public/application/top_nav/panel_toolbar/__snapshots__/panel_toolbar.stories.storyshot deleted file mode 100644 index afbbecb3935e0e..00000000000000 --- a/src/plugins/dashboard/public/application/top_nav/panel_toolbar/__snapshots__/panel_toolbar.stories.storyshot +++ /dev/null @@ -1,71 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Storyshots components/PanelToolbar default 1`] = ` -
-
- -
-
- -
-
-`; diff --git a/src/plugins/dashboard/public/application/top_nav/panel_toolbar/panel_toolbar.tsx b/src/plugins/dashboard/public/application/top_nav/panel_toolbar/panel_toolbar.tsx deleted file mode 100644 index 0449fae80186d0..00000000000000 --- a/src/plugins/dashboard/public/application/top_nav/panel_toolbar/panel_toolbar.tsx +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import './panel_toolbar.scss'; -import React, { FC } from 'react'; -import { i18n } from '@kbn/i18n'; -import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; - -interface Props { - /** The click handler for the Add Panel button for creating new panels */ - onAddPanelClick: () => void; - /** The click handler for the Library button for adding existing visualizations/embeddables */ - onLibraryClick: () => void; -} - -export const PanelToolbar: FC = ({ onAddPanelClick, onLibraryClick }) => ( - - - - {i18n.translate('dashboard.panelToolbar.addPanelButtonLabel', { - defaultMessage: 'Create panel', - })} - - - - - {i18n.translate('dashboard.panelToolbar.libraryButtonLabel', { - defaultMessage: 'Add from library', - })} - - - -); diff --git a/src/plugins/dashboard/tsconfig.json b/src/plugins/dashboard/tsconfig.json index dd99119cfb4570..12820fc08310d6 100644 --- a/src/plugins/dashboard/tsconfig.json +++ b/src/plugins/dashboard/tsconfig.json @@ -32,5 +32,8 @@ { "path": "../saved_objects/tsconfig.json" }, { "path": "../ui_actions/tsconfig.json" }, { "path": "../spaces_oss/tsconfig.json" }, + { "path": "../charts/tsconfig.json" }, + { "path": "../discover/tsconfig.json" }, + { "path": "../visualizations/tsconfig.json" }, ] } diff --git a/src/plugins/data/public/autocomplete/providers/value_suggestion_provider.ts b/src/plugins/data/public/autocomplete/providers/value_suggestion_provider.ts index b8af6ad3a99e58..3dda97566da5ad 100644 --- a/src/plugins/data/public/autocomplete/providers/value_suggestion_provider.ts +++ b/src/plugins/data/public/autocomplete/providers/value_suggestion_provider.ts @@ -59,7 +59,7 @@ export const setupValueSuggestionProvider = ( return core.http .fetch(`/api/kibana/suggestions/values/${index}`, { method: 'POST', - body: JSON.stringify({ query, field: field.name, filters }), + body: JSON.stringify({ query, field: field.name, fieldMeta: field?.toSpec?.(), filters }), signal, }) .then((r) => { diff --git a/src/plugins/data/public/utils/table_inspector_view/components/__snapshots__/data_view.test.tsx.snap b/src/plugins/data/public/utils/table_inspector_view/components/__snapshots__/data_view.test.tsx.snap index 837cff41ccd6bc..4436efb1f3508f 100644 --- a/src/plugins/data/public/utils/table_inspector_view/components/__snapshots__/data_view.test.tsx.snap +++ b/src/plugins/data/public/utils/table_inspector_view/components/__snapshots__/data_view.test.tsx.snap @@ -1472,13 +1472,13 @@ exports[`Inspector Data View component should render single table without select href="#__table_generated-id" isDisabled={true} onClick={[Function]} - size="xs" + size="s" > + +); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/shared_columns.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/shared_columns.tsx new file mode 100644 index 00000000000000..3375b25cdcd6ca --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/shared_columns.tsx @@ -0,0 +1,127 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { + EuiTableFieldDataColumnType, + EuiTableComputedColumnType, + EuiTableActionsColumnType, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedNumber } from '@kbn/i18n/react'; + +import { MANAGE_BUTTON_LABEL, DELETE_BUTTON_LABEL } from '../../../../../shared/constants'; +import { FormattedDateTime } from '../../../../utils/formatted_date_time'; +import { EngineDetails } from '../../../engine/types'; +import { EnginesLogic } from '../../../engines'; + +import { navigateToEngine } from './engine_link_helpers'; + +export const BLANK_COLUMN: EuiTableComputedColumnType = { + render: () => <>, + 'aria-hidden': true, +}; + +export const NAME_COLUMN: EuiTableFieldDataColumnType = { + field: 'name', + name: i18n.translate('xpack.enterpriseSearch.appSearch.enginesOverview.table.column.name', { + defaultMessage: 'Name', + }), + width: '30%', + truncateText: true, + mobileOptions: { + header: true, + // Note: the below props are valid props per https://elastic.github.io/eui/#/tabular-content/tables (Responsive tables), but EUI's types have a bug reporting it as an error + // @ts-ignore + enlarge: true, + width: '100%', + truncateText: false, + }, +}; + +export const CREATED_AT_COLUMN: EuiTableFieldDataColumnType = { + field: 'created_at', + name: i18n.translate('xpack.enterpriseSearch.appSearch.enginesOverview.table.column.createdAt', { + defaultMessage: 'Created at', + }), + dataType: 'string', + render: (dateString: string) => , +}; + +export const DOCUMENT_COUNT_COLUMN: EuiTableFieldDataColumnType = { + field: 'document_count', + name: i18n.translate( + 'xpack.enterpriseSearch.appSearch.enginesOverview.table.column.documentCount', + { + defaultMessage: 'Document count', + } + ), + dataType: 'number', + render: (number: number) => , + truncateText: true, +}; + +export const FIELD_COUNT_COLUMN: EuiTableFieldDataColumnType = { + field: 'field_count', + name: i18n.translate('xpack.enterpriseSearch.appSearch.enginesOverview.table.column.fieldCount', { + defaultMessage: 'Field count', + }), + dataType: 'number', + render: (number: number) => , + truncateText: true, +}; + +export const ACTIONS_COLUMN: EuiTableActionsColumnType = { + name: i18n.translate('xpack.enterpriseSearch.appSearch.enginesOverview.table.column.actions', { + defaultMessage: 'Actions', + }), + actions: [ + { + name: MANAGE_BUTTON_LABEL, + description: i18n.translate( + 'xpack.enterpriseSearch.appSearch.enginesOverview.table.action.manage.buttonDescription', + { + defaultMessage: 'Manage this engine', + } + ), + type: 'icon', + icon: 'eye', + onClick: (engineDetails) => navigateToEngine(engineDetails.name), + }, + { + name: DELETE_BUTTON_LABEL, + description: i18n.translate( + 'xpack.enterpriseSearch.appSearch.enginesOverview.table.action.delete.buttonDescription', + { + defaultMessage: 'Delete this engine', + } + ), + type: 'icon', + icon: 'trash', + color: 'danger', + onClick: (engine) => { + if ( + window.confirm( + i18n.translate( + 'xpack.enterpriseSearch.appSearch.enginesOverview.table.action.delete.confirmationPopupMessage', + { + defaultMessage: + 'Are you sure you want to permanently delete "{engineName}" and all of its content?', + values: { + engineName: engine.name, + }, + } + ) + ) + ) { + EnginesLogic.actions.deleteEngine(engine); + } + }, + }, + ], +}; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/find_rules_schema.mocks.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/test_helpers/index.ts similarity index 50% rename from x-pack/plugins/security_solution/common/detection_engine/schemas/response/find_rules_schema.mocks.ts rename to x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/test_helpers/index.ts index 67964a7ab26c39..c2989c5d1f9725 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/find_rules_schema.mocks.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/test_helpers/index.ts @@ -5,12 +5,5 @@ * 2.0. */ -import { FindRulesSchema } from './find_rules_schema'; -import { getRulesSchemaMock } from './rules_schema.mocks'; - -export const getFindRulesSchemaMock = (): FindRulesSchema => ({ - page: 1, - perPage: 1, - total: 1, - data: [getRulesSchemaMock()], -}); +export { runSharedColumnsTests } from './shared_columns'; +export { runSharedPropsTests } from './shared_props'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/test_helpers/shared_columns.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/test_helpers/shared_columns.tsx new file mode 100644 index 00000000000000..97e2057cea2d96 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/test_helpers/shared_columns.tsx @@ -0,0 +1,111 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { setMockValues, rerender } from '../../../../../../__mocks__'; +import '../__mocks__/engines_logic.mock'; + +import { ShallowWrapper } from 'enzyme'; + +import { EuiBasicTable, EuiButtonIcon } from '@elastic/eui'; + +import { EnginesLogic } from '../../../../engines'; + +import * as engineLinkHelpers from '../engine_link_helpers'; + +export const runSharedColumnsTests = ( + wrapper: ShallowWrapper, + tableContent: string, + values: object = {} +) => { + const getTable = () => wrapper.find(EuiBasicTable).dive(); + + describe('name column', () => { + it('renders', () => { + expect(tableContent).toContain('test-engine'); + }); + + // Link behavior is tested in engine_link_helpers.test.tsx + }); + + describe('created at column', () => { + it('renders', () => { + expect(tableContent).toContain('Created at'); + expect(tableContent).toContain('Jan 1, 1970'); + }); + }); + + describe('document count column', () => { + it('renders', () => { + expect(tableContent).toContain('Document count'); + expect(tableContent).toContain('99,999'); + }); + }); + + describe('field count column', () => { + it('renders', () => { + expect(tableContent).toContain('Field count'); + expect(tableContent).toContain('10'); + }); + }); + + describe('actions column', () => { + const getActions = () => getTable().find('ExpandedItemActions'); + const getActionItems = () => getActions().dive().find('DefaultItemAction'); + + it('will hide the action buttons if the user cannot manage/delete engines', () => { + setMockValues({ + ...values, + myRole: { canManageEngines: false, canManageMetaEngines: false }, + }); + rerender(wrapper); + expect(getActions()).toHaveLength(0); + }); + + describe('when the user can manage/delete engines', () => { + const getManageAction = () => getActionItems().at(0).dive().find(EuiButtonIcon); + const getDeleteAction = () => getActionItems().at(1).dive().find(EuiButtonIcon); + + beforeAll(() => { + setMockValues({ + ...values, + myRole: { canManageEngines: true, canManageMetaEngines: true }, + }); + rerender(wrapper); + }); + + describe('manage action', () => { + it('sends the user to the engine overview on click', () => { + jest.spyOn(engineLinkHelpers, 'navigateToEngine'); + const { navigateToEngine } = engineLinkHelpers; + getManageAction().simulate('click'); + + expect(navigateToEngine).toHaveBeenCalledWith('test-engine'); + }); + }); + + describe('delete action', () => { + const { deleteEngine } = EnginesLogic.actions; + + it('clicking the action and confirming deletes the engine', () => { + jest.spyOn(global, 'confirm').mockReturnValueOnce(true); + getDeleteAction().simulate('click'); + + expect(deleteEngine).toHaveBeenCalledWith( + expect.objectContaining({ name: 'test-engine' }) + ); + }); + + it('clicking the action and not confirming does not delete the engine', () => { + jest.spyOn(global, 'confirm').mockReturnValueOnce(false); + getDeleteAction().simulate('click'); + + expect(deleteEngine).not.toHaveBeenCalled(); + }); + }); + }); + }); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/test_helpers/shared_props.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/test_helpers/shared_props.tsx new file mode 100644 index 00000000000000..0b0a8a0a995930 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/test_helpers/shared_props.tsx @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ShallowWrapper } from 'enzyme'; + +import { EuiBasicTable } from '@elastic/eui'; + +export const runSharedPropsTests = (wrapper: ShallowWrapper) => { + it('passes the loading prop', () => { + wrapper.setProps({ loading: true }); + expect(wrapper.find(EuiBasicTable).prop('loading')).toEqual(true); + }); + + it('passes the noItemsMessage prop', () => { + wrapper.setProps({ noItemsMessage: 'No items.' }); + expect(wrapper.find(EuiBasicTable).prop('noItemsMessage')).toEqual('No items.'); + }); + + describe('pagination', () => { + it('passes the pagination prop', () => { + const pagination = { + pageIndex: 0, + pageSize: 10, + totalItemCount: 50, + }; + wrapper.setProps({ pagination }); + expect(wrapper.find(EuiBasicTable).prop('pagination')).toEqual(pagination); + }); + + it('triggers onChange', () => { + const onChange = jest.fn(); + wrapper.setProps({ onChange }); + + wrapper.find(EuiBasicTable).simulate('change', { page: { index: 4 } }); + expect(onChange).toHaveBeenCalledWith({ page: { index: 4 } }); + }); + }); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/types.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/types.ts new file mode 100644 index 00000000000000..707c086e01827f --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/types.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ReactNode } from 'react'; + +import { CriteriaWithPagination } from '@elastic/eui'; + +import { EngineDetails } from '../../../engine/types'; + +export interface EnginesTableProps { + items: EngineDetails[]; + loading: boolean; + noItemsMessage?: ReactNode; + pagination: { + pageIndex: number; + pageSize: number; + totalItemCount: number; + hidePerPageOptions: boolean; + }; + onChange(criteria: CriteriaWithPagination): void; +} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/utils.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/utils.test.ts new file mode 100644 index 00000000000000..f65a2e52bae064 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/utils.test.ts @@ -0,0 +1,101 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { SchemaConflictFieldTypes, SchemaConflicts } from '../../../../../shared/types'; +import { EngineDetails } from '../../../engine/types'; + +import { + getConflictingEnginesFromConflictingField, + getConflictingEnginesFromSchemaConflicts, + getConflictingEnginesSet, +} from './utils'; + +describe('getConflictingEnginesFromConflictingField', () => { + const CONFLICTING_FIELD: SchemaConflictFieldTypes = { + text: ['source-engine-1'], + number: ['source-engine-2', 'source-engine-3'], + geolocation: ['source-engine-4'], + date: ['source-engine-5', 'source-engine-6'], + }; + + it('returns a flat array of all engines with conflicts across different schema types, including duplicates', () => { + const result = getConflictingEnginesFromConflictingField(CONFLICTING_FIELD); + + // we can't guarantee ordering + expect(result).toHaveLength(6); + expect(result).toContain('source-engine-1'); + expect(result).toContain('source-engine-2'); + expect(result).toContain('source-engine-3'); + expect(result).toContain('source-engine-4'); + expect(result).toContain('source-engine-5'); + expect(result).toContain('source-engine-6'); + }); +}); + +describe('getConflictingEnginesFromSchemaConflicts', () => { + it('returns a flat array of all engines with conflicts across all fields, including duplicates', () => { + const SCHEMA_CONFLICTS: SchemaConflicts = { + 'conflicting-field-1': { + text: ['source-engine-1'], + number: ['source-engine-2'], + geolocation: [], + date: [], + }, + 'conflicting-field-2': { + text: [], + number: [], + geolocation: ['source-engine-2'], + date: ['source-engine-3'], + }, + }; + + const result = getConflictingEnginesFromSchemaConflicts(SCHEMA_CONFLICTS); + + // we can't guarantee ordering + expect(result).toHaveLength(4); + expect(result).toContain('source-engine-1'); + expect(result).toContain('source-engine-2'); + expect(result).toContain('source-engine-3'); + }); +}); + +describe('getConflictingEnginesSet', () => { + const DEFAULT_META_ENGINE_DETAILS = { + name: 'test-engine-1', + includedEngines: [ + { + name: 'source-engine-1', + }, + { + name: 'source-engine-2', + }, + { + name: 'source-engine-3', + }, + ] as EngineDetails[], + schemaConflicts: { + 'conflicting-field-1': { + text: ['source-engine-1'], + number: ['source-engine-2'], + geolocation: [], + date: [], + }, + 'conflicting-field-2': { + text: [], + number: [], + geolocation: ['source-engine-2'], + date: ['source-engine-3'], + }, + } as SchemaConflicts, + } as EngineDetails; + + it('generates a set of engine names with any field conflicts for the meta-engine', () => { + expect(getConflictingEnginesSet(DEFAULT_META_ENGINE_DETAILS)).toEqual( + new Set(['source-engine-1', 'source-engine-2', 'source-engine-3']) + ); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/utils.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/utils.ts new file mode 100644 index 00000000000000..b1172237e3ad30 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/tables/utils.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { SchemaConflictFieldTypes, SchemaConflicts } from '../../../../../shared/types'; +import { EngineDetails } from '../../../engine/types'; + +export const getConflictingEnginesFromConflictingField = ( + conflictingField: SchemaConflictFieldTypes +): string[] => Object.values(conflictingField).flat(); + +export const getConflictingEnginesFromSchemaConflicts = ( + schemaConflicts: SchemaConflicts +): string[] => Object.values(schemaConflicts).flatMap(getConflictingEnginesFromConflictingField); + +// Given a meta-engine (represented by IEngineDetails), generate a Set of all source engines +// who have schema conflicts in the context of that meta-engine +// +// A Set allows us to enforce uniqueness and has O(1) lookup time +export const getConflictingEnginesSet = (metaEngine: EngineDetails): Set => { + const conflictingEngines: string[] = metaEngine.schemaConflicts + ? getConflictingEnginesFromSchemaConflicts(metaEngine.schemaConflicts) + : []; + return new Set(conflictingEngines); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/constants.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/constants.ts index 1955084393e570..c6c077e984efe7 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/constants.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/constants.ts @@ -16,6 +16,11 @@ export const META_ENGINES_TITLE = i18n.translate( { defaultMessage: 'Meta Engines' } ); +export const SOURCE_ENGINES_TITLE = i18n.translate( + 'xpack.enterpriseSearch.appSearch.enginesOverview.metaEnginesTable.sourceEngines.title', + { defaultMessage: 'Source Engines' } +); + export const CREATE_AN_ENGINE_BUTTON_LABEL = i18n.translate( 'xpack.enterpriseSearch.appSearch.engines.createAnEngineButton.ButtonLabel', { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_overview.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_overview.test.tsx index 3ca039907932ee..c47b169ede3644 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_overview.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_overview.test.tsx @@ -15,7 +15,8 @@ import { shallow, ShallowWrapper } from 'enzyme'; import { EuiEmptyPrompt } from '@elastic/eui'; import { LoadingState, EmptyState } from './components'; -import { EnginesTable } from './engines_table'; +import { EnginesTable } from './components/tables/engines_table'; +import { MetaEnginesTable } from './components/tables/meta_engines_table'; import { EnginesOverview } from './'; @@ -41,7 +42,11 @@ describe('EnginesOverview', () => { }, metaEnginesLoading: false, hasPlatinumLicense: false, + // AppLogic myRole: { canManageEngines: false }, + // MetaEnginesTableLogic + expandedSourceEngines: {}, + conflictingEnginesSets: {}, }; const actions = { loadEngines: jest.fn(), @@ -120,7 +125,7 @@ describe('EnginesOverview', () => { }); const wrapper = shallow(); - expect(wrapper.find(EnginesTable)).toHaveLength(2); + expect(wrapper.find(MetaEnginesTable)).toHaveLength(1); expect(actions.loadMetaEngines).toHaveBeenCalled(); }); @@ -147,7 +152,7 @@ describe('EnginesOverview', () => { metaEngines: [], }); const wrapper = shallow(); - const metaEnginesTable = wrapper.find(EnginesTable).last().dive(); + const metaEnginesTable = wrapper.find(MetaEnginesTable).dive(); const emptyPrompt = metaEnginesTable.dive().find(EuiEmptyPrompt).dive(); expect( @@ -199,10 +204,10 @@ describe('EnginesOverview', () => { const wrapper = shallow(); const pageEvent = { page: { index: 0 } }; - wrapper.find(EnginesTable).first().simulate('change', pageEvent); + wrapper.find(EnginesTable).simulate('change', pageEvent); expect(actions.onEnginesPagination).toHaveBeenCalledWith(1); - wrapper.find(EnginesTable).last().simulate('change', pageEvent); + wrapper.find(MetaEnginesTable).simulate('change', pageEvent); expect(actions.onMetaEnginesPagination).toHaveBeenCalledWith(1); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_overview.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_overview.tsx index 4d51012f2aa2ab..4e17278d25d1a3 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_overview.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_overview.tsx @@ -29,6 +29,8 @@ import { EngineIcon, MetaEngineIcon } from '../../icons'; import { ENGINE_CREATION_PATH, META_ENGINE_CREATION_PATH } from '../../routes'; import { EnginesOverviewHeader, LoadingState, EmptyState } from './components'; +import { EnginesTable } from './components/tables/engines_table'; +import { MetaEnginesTable } from './components/tables/meta_engines_table'; import { CREATE_AN_ENGINE_BUTTON_LABEL, CREATE_A_META_ENGINE_BUTTON_LABEL, @@ -38,7 +40,6 @@ import { META_ENGINES_TITLE, } from './constants'; import { EnginesLogic } from './engines_logic'; -import { EnginesTable } from './engines_table'; import './engines_overview.scss'; @@ -58,13 +59,9 @@ export const EnginesOverview: React.FC = () => { metaEnginesLoading, } = useValues(EnginesLogic); - const { - deleteEngine, - loadEngines, - loadMetaEngines, - onEnginesPagination, - onMetaEnginesPagination, - } = useActions(EnginesLogic); + const { loadEngines, loadMetaEngines, onEnginesPagination, onMetaEnginesPagination } = useActions( + EnginesLogic + ); useEffect(() => { loadEngines(); @@ -116,7 +113,6 @@ export const EnginesOverview: React.FC = () => { hidePerPageOptions: true, }} onChange={handlePageChange(onEnginesPagination)} - onDeleteEngine={deleteEngine} /> @@ -134,8 +130,9 @@ export const EnginesOverview: React.FC = () => { {canManageEngines && ( @@ -145,7 +142,7 @@ export const EnginesOverview: React.FC = () => { - { /> } onChange={handlePageChange(onMetaEnginesPagination)} - onDeleteEngine={deleteEngine} /> diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_table.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_table.test.tsx deleted file mode 100644 index fc37c3543af569..00000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_table.test.tsx +++ /dev/null @@ -1,245 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import '../../../__mocks__/enterprise_search_url.mock'; -import { mockTelemetryActions, mountWithIntl, setMockValues } from '../../../__mocks__'; - -import React from 'react'; - -import { ReactWrapper, shallow } from 'enzyme'; - -import { EuiBasicTable, EuiPagination, EuiButtonEmpty, EuiIcon, EuiTableRow } from '@elastic/eui'; - -import { KibanaLogic } from '../../../shared/kibana'; -import { EuiLinkTo } from '../../../shared/react_router_helpers'; - -import { TelemetryLogic } from '../../../shared/telemetry'; -import { EngineDetails } from '../engine/types'; - -import { EnginesLogic } from './engines_logic'; -import { EnginesTable } from './engines_table'; - -describe('EnginesTable', () => { - const onChange = jest.fn(); - const onDeleteEngine = jest.fn(); - - const data = [ - { - name: 'test-engine', - created_at: 'Fri, 1 Jan 1970 12:00:00 +0000', - language: 'English', - isMeta: false, - document_count: 99999, - field_count: 10, - } as EngineDetails, - ]; - const pagination = { - pageIndex: 0, - pageSize: 10, - totalItemCount: 50, - hidePerPageOptions: true, - }; - const props = { - items: data, - loading: false, - pagination, - onChange, - onDeleteEngine, - }; - - const resetMocks = () => { - jest.clearAllMocks(); - setMockValues({ - myRole: { - canManageEngines: false, - }, - }); - }; - - describe('basic table', () => { - let wrapper: ReactWrapper; - let table: ReactWrapper; - - beforeAll(() => { - resetMocks(); - wrapper = mountWithIntl(); - table = wrapper.find(EuiBasicTable); - }); - - it('renders', () => { - expect(table).toHaveLength(1); - expect(table.prop('pagination').totalItemCount).toEqual(50); - - const tableContent = table.text(); - expect(tableContent).toContain('test-engine'); - expect(tableContent).toContain('Jan 1, 1970'); - expect(tableContent).toContain('English'); - expect(tableContent).toContain('99,999'); - expect(tableContent).toContain('10'); - - expect(table.find(EuiPagination).find(EuiButtonEmpty)).toHaveLength(5); // Should display 5 pages at 10 engines per page - }); - - it('contains engine links which send telemetry', () => { - const engineLinks = wrapper.find(EuiLinkTo); - - engineLinks.forEach((link) => { - expect(link.prop('to')).toEqual('/engines/test-engine'); - link.simulate('click'); - - expect(mockTelemetryActions.sendAppSearchTelemetry).toHaveBeenCalledWith({ - action: 'clicked', - metric: 'engine_table_link', - }); - }); - }); - - it('triggers onPaginate', () => { - table.prop('onChange')({ page: { index: 4 } }); - expect(onChange).toHaveBeenCalledWith({ page: { index: 4 } }); - }); - }); - - describe('loading', () => { - it('passes the loading prop', () => { - resetMocks(); - const wrapper = mountWithIntl(); - - expect(wrapper.find(EuiBasicTable).prop('loading')).toEqual(true); - }); - }); - - describe('noItemsMessage', () => { - it('passes the noItemsMessage prop', () => { - resetMocks(); - const wrapper = mountWithIntl(); - expect(wrapper.find(EuiBasicTable).prop('noItemsMessage')).toEqual('No items.'); - }); - }); - - describe('language field', () => { - beforeAll(() => { - resetMocks(); - }); - - it('renders language when available', () => { - const wrapper = mountWithIntl( - - ); - const tableContent = wrapper.find(EuiBasicTable).text(); - expect(tableContent).toContain('German'); - }); - - it('renders the language as Universal if no language is set', () => { - const wrapper = mountWithIntl( - - ); - const tableContent = wrapper.find(EuiBasicTable).text(); - expect(tableContent).toContain('Universal'); - }); - - it('renders no language text if the engine is a Meta Engine', () => { - const wrapper = mountWithIntl( - - ); - const tableContent = wrapper.find(EuiBasicTable).text(); - expect(tableContent).not.toContain('Universal'); - }); - }); - - describe('actions', () => { - it('will hide the action buttons if the user cannot manage/delete engines', () => { - resetMocks(); - const wrapper = shallow(); - const tableRow = wrapper.find(EuiTableRow).first(); - - expect(tableRow.find(EuiIcon)).toHaveLength(0); - }); - - describe('when the user can manage/delete engines', () => { - let wrapper: ReactWrapper; - let tableRow: ReactWrapper; - let actions: ReactWrapper; - - beforeEach(() => { - resetMocks(); - setMockValues({ - myRole: { - canManageEngines: true, - }, - }); - - wrapper = mountWithIntl(); - tableRow = wrapper.find(EuiTableRow).first(); - actions = tableRow.find(EuiIcon); - EnginesLogic.mount(); - }); - - it('renders a manage action', () => { - jest.spyOn(TelemetryLogic.actions, 'sendAppSearchTelemetry'); - jest.spyOn(KibanaLogic.values, 'navigateToUrl'); - actions.at(0).simulate('click'); - - expect(TelemetryLogic.actions.sendAppSearchTelemetry).toHaveBeenCalled(); - expect(KibanaLogic.values.navigateToUrl).toHaveBeenCalledWith('/engines/test-engine'); - }); - - describe('delete action', () => { - it('shows the user a confirm message when the action is clicked', () => { - jest.spyOn(global, 'confirm' as any).mockReturnValueOnce(true); - actions.at(1).simulate('click'); - expect(global.confirm).toHaveBeenCalled(); - }); - - it('clicking the action and confirming deletes the engine', () => { - jest.spyOn(global, 'confirm' as any).mockReturnValueOnce(true); - jest.spyOn(EnginesLogic.actions, 'deleteEngine'); - - actions.at(1).simulate('click'); - - expect(onDeleteEngine).toHaveBeenCalled(); - }); - - it('clicking the action and not confirming does not delete the engine', () => { - jest.spyOn(global, 'confirm' as any).mockReturnValueOnce(false); - jest.spyOn(EnginesLogic.actions, 'deleteEngine'); - - actions.at(1).simulate('click'); - - expect(onDeleteEngine).toHaveBeenCalledTimes(0); - }); - }); - }); - }); -}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_table.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_table.tsx deleted file mode 100644 index 3a65d9c449d6ec..00000000000000 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_table.tsx +++ /dev/null @@ -1,210 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { ReactNode } from 'react'; - -import { useActions, useValues } from 'kea'; - -import { - EuiBasicTable, - EuiBasicTableColumn, - CriteriaWithPagination, - EuiTableActionsColumnType, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { FormattedNumber } from '@kbn/i18n/react'; - -import { MANAGE_BUTTON_LABEL, DELETE_BUTTON_LABEL } from '../../../shared/constants'; -import { KibanaLogic } from '../../../shared/kibana'; -import { EuiLinkTo } from '../../../shared/react_router_helpers'; -import { TelemetryLogic } from '../../../shared/telemetry'; -import { AppLogic } from '../../app_logic'; -import { UNIVERSAL_LANGUAGE } from '../../constants'; -import { ENGINE_PATH } from '../../routes'; -import { generateEncodedPath } from '../../utils/encode_path_params'; -import { FormattedDateTime } from '../../utils/formatted_date_time'; -import { EngineDetails } from '../engine/types'; - -interface EnginesTableProps { - items: EngineDetails[]; - loading: boolean; - noItemsMessage?: ReactNode; - pagination: { - pageIndex: number; - pageSize: number; - totalItemCount: number; - hidePerPageOptions: boolean; - }; - onChange(criteria: CriteriaWithPagination): void; - onDeleteEngine(engine: EngineDetails): void; -} - -export const EnginesTable: React.FC = ({ - items, - loading, - noItemsMessage, - pagination, - onChange, - onDeleteEngine, -}) => { - const { sendAppSearchTelemetry } = useActions(TelemetryLogic); - const { navigateToUrl } = useValues(KibanaLogic); - const { - myRole: { canManageEngines }, - } = useValues(AppLogic); - - const generateEncodedEnginePath = (engineName: string) => - generateEncodedPath(ENGINE_PATH, { engineName }); - const sendEngineTableLinkClickTelemetry = () => - sendAppSearchTelemetry({ - action: 'clicked', - metric: 'engine_table_link', - }); - - const columns: Array> = [ - { - field: 'name', - name: i18n.translate('xpack.enterpriseSearch.appSearch.enginesOverview.table.column.name', { - defaultMessage: 'Name', - }), - render: (name: string) => ( - - {name} - - ), - width: '30%', - truncateText: true, - mobileOptions: { - header: true, - // Note: the below props are valid props per https://elastic.github.io/eui/#/tabular-content/tables (Responsive tables), but EUI's types have a bug reporting it as an error - // @ts-ignore - enlarge: true, - fullWidth: true, - truncateText: false, - }, - }, - { - field: 'created_at', - name: i18n.translate( - 'xpack.enterpriseSearch.appSearch.enginesOverview.table.column.createdAt', - { - defaultMessage: 'Created At', - } - ), - dataType: 'string', - render: (dateString: string) => , - }, - { - field: 'language', - name: i18n.translate( - 'xpack.enterpriseSearch.appSearch.enginesOverview.table.column.language', - { - defaultMessage: 'Language', - } - ), - dataType: 'string', - render: (language: string, engine: EngineDetails) => - engine.isMeta ? '' : language || UNIVERSAL_LANGUAGE, - }, - { - field: 'document_count', - name: i18n.translate( - 'xpack.enterpriseSearch.appSearch.enginesOverview.table.column.documentCount', - { - defaultMessage: 'Document Count', - } - ), - dataType: 'number', - render: (number: number) => , - truncateText: true, - }, - { - field: 'field_count', - name: i18n.translate( - 'xpack.enterpriseSearch.appSearch.enginesOverview.table.column.fieldCount', - { - defaultMessage: 'Field Count', - } - ), - dataType: 'number', - render: (number: number) => , - truncateText: true, - }, - ]; - - const actionsColumn: EuiTableActionsColumnType = { - name: i18n.translate('xpack.enterpriseSearch.appSearch.enginesOverview.table.column.actions', { - defaultMessage: 'Actions', - }), - actions: [ - { - name: MANAGE_BUTTON_LABEL, - description: i18n.translate( - 'xpack.enterpriseSearch.appSearch.enginesOverview.table.action.manage.buttonDescription', - { - defaultMessage: 'Manage this engine', - } - ), - type: 'icon', - icon: 'eye', - onClick: (engineDetails) => { - sendEngineTableLinkClickTelemetry(); - navigateToUrl(generateEncodedEnginePath(engineDetails.name)); - }, - }, - { - name: DELETE_BUTTON_LABEL, - description: i18n.translate( - 'xpack.enterpriseSearch.appSearch.enginesOverview.table.action.delete.buttonDescription', - { - defaultMessage: 'Delete this engine', - } - ), - type: 'icon', - icon: 'trash', - color: 'danger', - onClick: (engine) => { - if ( - window.confirm( - i18n.translate( - 'xpack.enterpriseSearch.appSearch.enginesOverview.table.action.delete.confirmationPopupMessage', - { - defaultMessage: - 'Are you sure you want to permanently delete "{engineName}" and all of its content?', - values: { - engineName: engine.name, - }, - } - ) - ) - ) { - onDeleteEngine(engine); - } - }, - }, - ], - }; - - if (canManageEngines) { - columns.push(actionsColumn); - } - - return ( - - ); -}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning.test.tsx index e2adce7dd76876..c76c50094aeddc 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning.test.tsx @@ -4,8 +4,10 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import '../../../__mocks__/shallow_useeffect.mock'; + import { setMockActions, setMockValues } from '../../../__mocks__/kea.mock'; +import '../../../__mocks__/shallow_useeffect.mock'; +import '../../__mocks__/engine_logic.mock'; import React from 'react'; @@ -37,7 +39,7 @@ describe('RelevanceTuning', () => { resetSearchSettings: jest.fn(), }; - const subject = () => shallow(); + const subject = () => shallow(); beforeEach(() => { jest.clearAllMocks(); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning.tsx index 70adc91dd2b301..ab9bbaa9a1773e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning.tsx @@ -23,10 +23,6 @@ import { RelevanceTuningPreview } from './relevance_tuning_preview'; import { RelevanceTuningLogic } from '.'; -interface Props { - engineBreadcrumb: string[]; -} - const EmptyCallout: React.FC = () => { return ( { ); }; -export const RelevanceTuning: React.FC = ({ engineBreadcrumb }) => { +export const RelevanceTuning: React.FC = () => { const { dataLoading, engineHasSchemaFields, unsavedChanges } = useValues(RelevanceTuningLogic); const { initializeRelevanceTuning } = useActions(RelevanceTuningLogic); @@ -95,7 +91,7 @@ export const RelevanceTuning: React.FC = ({ engineBreadcrumb }) => { }; return ( - + {body()} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_layout.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_layout.test.tsx index 9ed6e17c2bcd94..6f4333d94919b8 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_layout.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_layout.test.tsx @@ -6,6 +6,7 @@ */ import { setMockActions, setMockValues } from '../../../__mocks__/kea.mock'; +import '../../__mocks__/engine_logic.mock'; import React from 'react'; @@ -32,7 +33,7 @@ describe('RelevanceTuningLayout', () => { setMockActions(actions); }); - const subject = () => shallow(); + const subject = () => shallow(); const findButtons = (wrapper: ShallowWrapper) => wrapper.find(EuiPageHeader).prop('rightSideItems') as React.ReactElement[]; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_layout.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_layout.tsx index f29cc12f20a98c..69043d80bd8d00 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_layout.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_layout.tsx @@ -17,16 +17,13 @@ import { SAVE_BUTTON_LABEL } from '../../../shared/constants'; import { FlashMessages } from '../../../shared/flash_messages'; import { SetAppSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome'; import { RESTORE_DEFAULTS_BUTTON_LABEL } from '../../constants'; +import { getEngineBreadcrumbs } from '../engine'; import { RELEVANCE_TUNING_TITLE } from './constants'; import { RelevanceTuningCallouts } from './relevance_tuning_callouts'; import { RelevanceTuningLogic } from './relevance_tuning_logic'; -interface Props { - engineBreadcrumb: string[]; -} - -export const RelevanceTuningLayout: React.FC = ({ engineBreadcrumb, children }) => { +export const RelevanceTuningLayout: React.FC = ({ children }) => { const { resetSearchSettings, updateSearchSettings } = useActions(RelevanceTuningLogic); const { engineHasSchemaFields } = useValues(RelevanceTuningLogic); @@ -66,7 +63,7 @@ export const RelevanceTuningLayout: React.FC = ({ engineBreadcrumb, child return ( <> - + {pageHeader()} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings.test.tsx index 5365cc0f029f83..e5a901f8d07790 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings.test.tsx @@ -5,23 +5,28 @@ * 2.0. */ -import '../../../__mocks__/shallow_useeffect.mock'; - import { setMockValues, setMockActions } from '../../../__mocks__'; +import '../../../__mocks__/shallow_useeffect.mock'; +import '../../__mocks__/engine_logic.mock'; import React from 'react'; import { shallow, ShallowWrapper } from 'enzyme'; -import { EuiPageHeader } from '@elastic/eui'; +import { EuiPageHeader, EuiEmptyPrompt } from '@elastic/eui'; import { ResultSettings } from './result_settings'; import { ResultSettingsTable } from './result_settings_table'; import { SampleResponse } from './sample_response'; -describe('RelevanceTuning', () => { +describe('ResultSettings', () => { const values = { + schema: { + foo: 'text', + }, dataLoading: false, + stagedUpdates: true, + resultFieldsAtDefaultSettings: false, }; const actions = { @@ -32,12 +37,12 @@ describe('RelevanceTuning', () => { }; beforeEach(() => { + jest.clearAllMocks(); setMockValues(values); setMockActions(actions); - jest.clearAllMocks(); }); - const subject = () => shallow(); + const subject = () => shallow(); const findButtons = (wrapper: ShallowWrapper) => wrapper.find(EuiPageHeader).prop('rightSideItems') as React.ReactElement[]; @@ -48,7 +53,7 @@ describe('RelevanceTuning', () => { }); it('initializes result settings data when mounted', () => { - shallow(); + shallow(); expect(actions.initializeResultSettingsData).toHaveBeenCalled(); }); @@ -69,6 +74,16 @@ describe('RelevanceTuning', () => { expect(actions.saveResultSettings).toHaveBeenCalled(); }); + it('renders the "save" button as disabled if the user has made no changes since the page loaded', () => { + setMockValues({ + ...values, + stagedUpdates: false, + }); + const buttons = findButtons(subject()); + const saveButton = shallow(buttons[0]); + expect(saveButton.prop('disabled')).toBe(true); + }); + it('renders a "restore defaults" button that will reset all values to their defaults', () => { const buttons = findButtons(subject()); expect(buttons.length).toBe(3); @@ -77,6 +92,16 @@ describe('RelevanceTuning', () => { expect(actions.confirmResetAllFields).toHaveBeenCalled(); }); + it('renders the "restore defaults" button as disabled if the values are already at their defaults', () => { + setMockValues({ + ...values, + resultFieldsAtDefaultSettings: true, + }); + const buttons = findButtons(subject()); + const resetButton = shallow(buttons[1]); + expect(resetButton.prop('disabled')).toBe(true); + }); + it('renders a "clear" button that will remove all selected options', () => { const buttons = findButtons(subject()); expect(buttons.length).toBe(3); @@ -84,4 +109,29 @@ describe('RelevanceTuning', () => { clearButton.simulate('click'); expect(actions.clearAllFields).toHaveBeenCalled(); }); + + describe('when there is no schema yet', () => { + let wrapper: ShallowWrapper; + beforeAll(() => { + setMockValues({ + ...values, + schema: {}, + }); + wrapper = subject(); + }); + + it('will not render action buttons', () => { + const buttons = findButtons(wrapper); + expect(buttons.length).toBe(0); + }); + + it('will not render the main page content', () => { + expect(wrapper.find(ResultSettingsTable).exists()).toBe(false); + expect(wrapper.find(SampleResponse).exists()).toBe(false); + }); + + it('will render an "empty" message', () => { + expect(wrapper.find(EuiEmptyPrompt).exists()).toBe(true); + }); + }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings.tsx index a513d0c1b9f34c..285d8fef357703 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings.tsx @@ -9,7 +9,15 @@ import React, { useEffect } from 'react'; import { useActions, useValues } from 'kea'; -import { EuiPageHeader, EuiFlexGroup, EuiFlexItem, EuiButton, EuiButtonEmpty } from '@elastic/eui'; +import { + EuiPageHeader, + EuiFlexGroup, + EuiFlexItem, + EuiButton, + EuiButtonEmpty, + EuiEmptyPrompt, + EuiPanel, +} from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -18,10 +26,10 @@ import { FlashMessages } from '../../../shared/flash_messages'; import { SetAppSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome'; import { Loading } from '../../../shared/loading'; import { RESTORE_DEFAULTS_BUTTON_LABEL } from '../../constants'; +import { getEngineBreadcrumbs } from '../engine'; import { RESULT_SETTINGS_TITLE } from './constants'; import { ResultSettingsTable } from './result_settings_table'; - import { SampleResponse } from './sample_response'; import { ResultSettingsLogic } from '.'; @@ -31,12 +39,10 @@ const CLEAR_BUTTON_LABEL = i18n.translate( { defaultMessage: 'Clear all values' } ); -interface Props { - engineBreadcrumb: string[]; -} - -export const ResultSettings: React.FC = ({ engineBreadcrumb }) => { - const { dataLoading } = useValues(ResultSettingsLogic); +export const ResultSettings: React.FC = () => { + const { dataLoading, schema, stagedUpdates, resultFieldsAtDefaultSettings } = useValues( + ResultSettingsLogic + ); const { initializeResultSettingsData, saveResultSettings, @@ -49,46 +55,76 @@ export const ResultSettings: React.FC = ({ engineBreadcrumb }) => { }, []); if (dataLoading) return ; + const hasSchema = Object.keys(schema).length > 0; return ( <> - + - {SAVE_BUTTON_LABEL} - , - - {RESTORE_DEFAULTS_BUTTON_LABEL} - , - - {CLEAR_BUTTON_LABEL} - , - ]} + rightSideItems={ + hasSchema + ? [ + + {SAVE_BUTTON_LABEL} + , + + {RESTORE_DEFAULTS_BUTTON_LABEL} + , + + {CLEAR_BUTTON_LABEL} + , + ] + : [] + } /> - - - - - - - - + {hasSchema ? ( + + + + + + + + + ) : ( + + + {i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.resultSettings.noSchemaTitle', + { defaultMessage: 'Engine does not have a schema' } + )} + + } + body={i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.resultSettings.noSchemaDescription', + { + defaultMessage: + 'You need one! A schema is created for you after you index some documents.', + } + )} + /> + + )} ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.test.ts index 8d9c33e3c9e680..437949982cb5aa 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.test.ts @@ -19,6 +19,18 @@ import { ServerFieldResultSettingObject } from './types'; import { ResultSettingsLogic } from '.'; +// toHaveBeenCalledWith uses toEqual which is a more lenient check. We have a couple of +// methods that need a stricter check, using `toStrictEqual`. +const expectToHaveBeenCalledWithStrict = ( + mock: jest.Mock, + expectedParam1: string, + expectedParam2: object +) => { + const [param1, param2] = mock.mock.calls[0]; + expect(param1).toEqual(expectedParam1); + expect(param2).toStrictEqual(expectedParam2); +}; + describe('ResultSettingsLogic', () => { const { mount } = new LogicMounter(ResultSettingsLogic); @@ -35,7 +47,6 @@ describe('ResultSettingsLogic', () => { serverResultFields: {}, reducedServerResultFields: {}, resultFieldsAtDefaultSettings: true, - resultFieldsEmpty: true, stagedUpdates: false, nonTextResultFields: {}, textResultFields: {}, @@ -322,30 +333,6 @@ describe('ResultSettingsLogic', () => { }); }); - describe('resultFieldsEmpty', () => { - it('should return true if all fields are empty', () => { - mount({ - resultFields: { - foo: {}, - bar: {}, - }, - }); - - expect(ResultSettingsLogic.values.resultFieldsEmpty).toEqual(true); - }); - - it('should return false otherwise', () => { - mount({ - resultFields: { - foo: {}, - bar: { raw: true, snippet: true, snippetFallback: false }, - }, - }); - - expect(ResultSettingsLogic.values.resultFieldsEmpty).toEqual(false); - }); - }); - describe('stagedUpdates', () => { it('should return true if changes have been made since the last save', () => { mount({ @@ -535,17 +522,20 @@ describe('ResultSettingsLogic', () => { mount({ resultFields: { foo: { raw: true, rawSize: 5, snippet: false }, - bar: { raw: true, rawSize: 5, snippet: false }, }, }); jest.spyOn(ResultSettingsLogic.actions, 'updateField'); ResultSettingsLogic.actions.clearRawSizeForField('foo'); - expect(ResultSettingsLogic.actions.updateField).toHaveBeenCalledWith('foo', { - raw: true, - snippet: false, - }); + expectToHaveBeenCalledWithStrict( + ResultSettingsLogic.actions.updateField as jest.Mock, + 'foo', + { + raw: true, + snippet: false, + } + ); }); }); @@ -554,17 +544,20 @@ describe('ResultSettingsLogic', () => { mount({ resultFields: { foo: { raw: false, snippet: true, snippetSize: 5 }, - bar: { raw: true, rawSize: 5, snippet: false }, }, }); jest.spyOn(ResultSettingsLogic.actions, 'updateField'); ResultSettingsLogic.actions.clearSnippetSizeForField('foo'); - expect(ResultSettingsLogic.actions.updateField).toHaveBeenCalledWith('foo', { - raw: false, - snippet: true, - }); + expectToHaveBeenCalledWithStrict( + ResultSettingsLogic.actions.updateField as jest.Mock, + 'foo', + { + raw: false, + snippet: true, + } + ); }); }); @@ -572,7 +565,6 @@ describe('ResultSettingsLogic', () => { it('should toggle the raw value on for a field', () => { mount({ resultFields: { - foo: { raw: false, snippet: true, snippetSize: 5 }, bar: { raw: false, snippet: false }, }, }); @@ -580,16 +572,19 @@ describe('ResultSettingsLogic', () => { ResultSettingsLogic.actions.toggleRawForField('bar'); - expect(ResultSettingsLogic.actions.updateField).toHaveBeenCalledWith('bar', { - raw: true, - snippet: false, - }); + expectToHaveBeenCalledWithStrict( + ResultSettingsLogic.actions.updateField as jest.Mock, + 'bar', + { + raw: true, + snippet: false, + } + ); }); it('should maintain rawSize if it was set prior', () => { mount({ resultFields: { - foo: { raw: false, snippet: true, snippetSize: 5 }, bar: { raw: false, rawSize: 10, snippet: false }, }, }); @@ -597,17 +592,20 @@ describe('ResultSettingsLogic', () => { ResultSettingsLogic.actions.toggleRawForField('bar'); - expect(ResultSettingsLogic.actions.updateField).toHaveBeenCalledWith('bar', { - raw: true, - rawSize: 10, - snippet: false, - }); + expectToHaveBeenCalledWithStrict( + ResultSettingsLogic.actions.updateField as jest.Mock, + 'bar', + { + raw: true, + rawSize: 10, + snippet: false, + } + ); }); it('should remove rawSize value when toggling off', () => { mount({ resultFields: { - foo: { raw: false, snippet: true, snippetSize: 5 }, bar: { raw: true, rawSize: 5, snippet: false }, }, }); @@ -615,16 +613,19 @@ describe('ResultSettingsLogic', () => { ResultSettingsLogic.actions.toggleRawForField('bar'); - expect(ResultSettingsLogic.actions.updateField).toHaveBeenCalledWith('bar', { - raw: false, - snippet: false, - }); + expectToHaveBeenCalledWithStrict( + ResultSettingsLogic.actions.updateField as jest.Mock, + 'bar', + { + raw: false, + snippet: false, + } + ); }); it('should still work if the object is empty', () => { mount({ resultFields: { - foo: { raw: false, snippet: true, snippetSize: 5 }, bar: {}, }, }); @@ -632,9 +633,13 @@ describe('ResultSettingsLogic', () => { ResultSettingsLogic.actions.toggleRawForField('bar'); - expect(ResultSettingsLogic.actions.updateField).toHaveBeenCalledWith('bar', { - raw: true, - }); + expectToHaveBeenCalledWithStrict( + ResultSettingsLogic.actions.updateField as jest.Mock, + 'bar', + { + raw: true, + } + ); }); }); @@ -642,7 +647,6 @@ describe('ResultSettingsLogic', () => { it('should toggle the raw value on for a field, always setting the snippet size to 100', () => { mount({ resultFields: { - foo: { raw: false, snippet: true, snippetSize: 5 }, bar: { raw: false, snippet: false }, }, }); @@ -650,17 +654,20 @@ describe('ResultSettingsLogic', () => { ResultSettingsLogic.actions.toggleSnippetForField('bar'); - expect(ResultSettingsLogic.actions.updateField).toHaveBeenCalledWith('bar', { - raw: false, - snippet: true, - snippetSize: 100, - }); + expectToHaveBeenCalledWithStrict( + ResultSettingsLogic.actions.updateField as jest.Mock, + 'bar', + { + raw: false, + snippet: true, + snippetSize: 100, + } + ); }); it('should remove rawSize value when toggling off', () => { mount({ resultFields: { - foo: { raw: false, snippet: true, snippetSize: 5 }, bar: { raw: false, snippet: true, snippetSize: 5 }, }, }); @@ -668,16 +675,19 @@ describe('ResultSettingsLogic', () => { ResultSettingsLogic.actions.toggleSnippetForField('bar'); - expect(ResultSettingsLogic.actions.updateField).toHaveBeenCalledWith('bar', { - raw: false, - snippet: false, - }); + expectToHaveBeenCalledWithStrict( + ResultSettingsLogic.actions.updateField as jest.Mock, + 'bar', + { + raw: false, + snippet: false, + } + ); }); it('should still work if the object is empty', () => { mount({ resultFields: { - foo: { raw: false, snippet: true, snippetSize: 5 }, bar: {}, }, }); @@ -685,10 +695,14 @@ describe('ResultSettingsLogic', () => { ResultSettingsLogic.actions.toggleSnippetForField('bar'); - expect(ResultSettingsLogic.actions.updateField).toHaveBeenCalledWith('bar', { - snippet: true, - snippetSize: 100, - }); + expectToHaveBeenCalledWithStrict( + ResultSettingsLogic.actions.updateField as jest.Mock, + 'bar', + { + snippet: true, + snippetSize: 100, + } + ); }); }); @@ -697,19 +711,22 @@ describe('ResultSettingsLogic', () => { mount({ resultFields: { foo: { raw: false, snippet: true, snippetSize: 5, snippetFallback: true }, - bar: { raw: false, snippet: false }, }, }); jest.spyOn(ResultSettingsLogic.actions, 'updateField'); ResultSettingsLogic.actions.toggleSnippetFallbackForField('foo'); - expect(ResultSettingsLogic.actions.updateField).toHaveBeenCalledWith('foo', { - raw: false, - snippet: true, - snippetSize: 5, - snippetFallback: false, - }); + expectToHaveBeenCalledWithStrict( + ResultSettingsLogic.actions.updateField as jest.Mock, + 'foo', + { + raw: false, + snippet: true, + snippetSize: 5, + snippetFallback: false, + } + ); }); }); @@ -717,7 +734,6 @@ describe('ResultSettingsLogic', () => { it('should update the rawSize value for a field', () => { mount({ resultFields: { - foo: { raw: false, snippet: true, snippetSize: 5, snippetFallback: true }, bar: { raw: true, rawSize: 5, snippet: false }, }, }); @@ -725,11 +741,15 @@ describe('ResultSettingsLogic', () => { ResultSettingsLogic.actions.updateRawSizeForField('bar', 7); - expect(ResultSettingsLogic.actions.updateField).toHaveBeenCalledWith('bar', { - raw: true, - rawSize: 7, - snippet: false, - }); + expectToHaveBeenCalledWithStrict( + ResultSettingsLogic.actions.updateField as jest.Mock, + 'bar', + { + raw: true, + rawSize: 7, + snippet: false, + } + ); }); }); @@ -738,19 +758,22 @@ describe('ResultSettingsLogic', () => { mount({ resultFields: { foo: { raw: false, snippet: true, snippetSize: 5, snippetFallback: true }, - bar: { raw: true, rawSize: 5, snippet: false }, }, }); jest.spyOn(ResultSettingsLogic.actions, 'updateField'); ResultSettingsLogic.actions.updateSnippetSizeForField('foo', 7); - expect(ResultSettingsLogic.actions.updateField).toHaveBeenCalledWith('foo', { - raw: false, - snippet: true, - snippetSize: 7, - snippetFallback: true, - }); + expectToHaveBeenCalledWithStrict( + ResultSettingsLogic.actions.updateField as jest.Mock, + 'foo', + { + raw: false, + snippet: true, + snippetSize: 7, + snippetFallback: true, + } + ); }); }); @@ -759,17 +782,20 @@ describe('ResultSettingsLogic', () => { mount({ resultFields: { foo: { raw: false, snippet: true, snippetSize: 5 }, - bar: { raw: true, rawSize: 5, snippet: false }, }, }); jest.spyOn(ResultSettingsLogic.actions, 'updateField'); ResultSettingsLogic.actions.clearSnippetSizeForField('foo'); - expect(ResultSettingsLogic.actions.updateField).toHaveBeenCalledWith('foo', { - raw: false, - snippet: true, - }); + expectToHaveBeenCalledWithStrict( + ResultSettingsLogic.actions.updateField as jest.Mock, + 'foo', + { + raw: false, + snippet: true, + } + ); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.ts index f518fc945bfbf0..af78543cda2b23 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.ts @@ -24,7 +24,6 @@ import { import { areFieldsAtDefaultSettings, - areFieldsEmpty, clearAllFields, convertServerResultFieldsToResultFields, convertToServerFieldResultSetting, @@ -198,10 +197,6 @@ export const ResultSettingsLogic = kea [selectors.resultFields], (resultFields) => areFieldsAtDefaultSettings(resultFields), ], - resultFieldsEmpty: [ - () => [selectors.resultFields], - (resultFields) => areFieldsEmpty(resultFields), - ], stagedUpdates: [ () => [selectors.lastSavedResultFields, selectors.resultFields], (lastSavedResultFields, resultFields) => !isEqual(lastSavedResultFields, resultFields), @@ -256,10 +251,11 @@ export const ResultSettingsLogic = kea { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/utils.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/utils.test.ts index 5797e5c633bc7d..6fee0a25003575 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/utils.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/utils.test.ts @@ -9,7 +9,6 @@ import { SchemaTypes } from '../../../shared/types'; import { areFieldsAtDefaultSettings, - areFieldsEmpty, convertServerResultFieldsToResultFields, convertToServerFieldResultSetting, clearAllFields, @@ -145,35 +144,6 @@ describe('splitResultFields', () => { }); }); -describe('areFieldsEmpty', () => { - it('should return true if all fields are empty objects', () => { - expect( - areFieldsEmpty({ - foo: {}, - bar: {}, - }) - ).toBe(true); - }); - it('should return false otherwise', () => { - expect( - areFieldsEmpty({ - foo: { - raw: true, - rawSize: 5, - snippet: false, - snippetFallback: false, - }, - bar: { - raw: true, - rawSize: 5, - snippet: false, - snippetFallback: false, - }, - }) - ).toBe(false); - }); -}); - describe('areFieldsAtDefaultSettings', () => { it('will return true if all settings for all fields are at their defaults', () => { expect( diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/utils.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/utils.ts index bde67c268ac168..ff88aaac193d78 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/utils.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/utils.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { isEqual, isEmpty } from 'lodash'; +import { isEqual } from 'lodash'; import { Schema } from '../../../shared/types'; @@ -112,13 +112,6 @@ export const splitResultFields = (resultFields: FieldResultSettingObject, schema return { textResultFields, nonTextResultFields }; }; -export const areFieldsEmpty = (fields: FieldResultSettingObject) => { - const anyNonEmptyField = Object.values(fields).find((resultSettings) => { - return !isEmpty(resultSettings); - }); - return !anyNonEmptyField; -}; - export const areFieldsAtDefaultSettings = (fields: FieldResultSettingObject) => { const anyNonDefaultSettingsValue = Object.values(fields).find((resultSettings) => { return !isEqual(resultSettings, DEFAULT_FIELD_SETTINGS); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/constants.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/constants.ts index 1fed750a86dc4f..2f9ff707f96317 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/constants.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/constants.ts @@ -18,15 +18,6 @@ export const UPDATE_ROLE_MAPPING = i18n.translate( { defaultMessage: 'Update role mapping' } ); -export const ADD_ROLE_MAPPING_TITLE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.roleMapping.newRoleMappingTitle', - { defaultMessage: 'Add role mapping' } -); -export const MANAGE_ROLE_MAPPING_TITLE = i18n.translate( - 'xpack.enterpriseSearch.appSearch.roleMapping.manageRoleMappingTitle', - { defaultMessage: 'Manage role mapping' } -); - export const EMPTY_ROLE_MAPPINGS_BODY = i18n.translate( 'xpack.enterpriseSearch.appSearch.roleMapping.emptyRoleMappingsBody', { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mapping.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mapping.tsx index ebd034caaedb39..610ceae8856f24 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mapping.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mapping.tsx @@ -33,7 +33,11 @@ import { DeleteMappingCallout, RoleSelector, } from '../../../shared/role_mapping'; -import { ROLE_MAPPINGS_TITLE } from '../../../shared/role_mapping/constants'; +import { + ROLE_MAPPINGS_TITLE, + ADD_ROLE_MAPPING_TITLE, + MANAGE_ROLE_MAPPING_TITLE, +} from '../../../shared/role_mapping/constants'; import { AppLogic } from '../../app_logic'; import { roleHasScopedEngines } from '../../utils/role/has_scoped_engines'; @@ -42,8 +46,6 @@ import { Engine } from '../engine/types'; import { SAVE_ROLE_MAPPING, UPDATE_ROLE_MAPPING, - ADD_ROLE_MAPPING_TITLE, - MANAGE_ROLE_MAPPING_TITLE, ADVANCED_ROLE_TYPES, STANDARD_ROLE_TYPES, ADVANCED_ROLE_SELECTORS_TITLE, @@ -166,7 +168,7 @@ export const RoleMapping: React.FC = ({ isNew }) => { - +

{ROLE_TITLE}

@@ -189,7 +191,7 @@ export const RoleMapping: React.FC = ({ isNew }) => {
{hasAdvancedRoles && ( - +

{ENGINE_ACCESS_TITLE}

diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings.tsx index 2ec2b93d1e24f3..e8d9e06142ef82 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings.tsx @@ -17,6 +17,7 @@ import { EuiPageContent, EuiPageContentBody, EuiPageHeader, + EuiPanel, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -78,12 +79,14 @@ export const RoleMappings: React.FC = () => { const addMappingButton = ; const roleMappingEmptyState = ( - {EMPTY_ROLE_MAPPINGS_TITLE}} - body={

{EMPTY_ROLE_MAPPINGS_BODY}

} - actions={addMappingButton} - /> + + {EMPTY_ROLE_MAPPINGS_TITLE}} + body={

{EMPTY_ROLE_MAPPINGS_BODY}

} + actions={addMappingButton} + /> +
); const roleMappingsTable = ( @@ -127,7 +130,7 @@ export const RoleMappings: React.FC = () => { pageTitle={ROLE_MAPPINGS_TITLE} description={ROLE_MAPPINGS_DESCRIPTION} /> - + 0}> {roleMappings.length === 0 ? roleMappingEmptyState : roleMappingsTable} diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/add_role_mapping_button.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/add_role_mapping_button.tsx index 0ae9f16ea2f9be..097302e0aa5f12 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/add_role_mapping_button.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/add_role_mapping_button.tsx @@ -16,7 +16,7 @@ interface Props { } export const AddRoleMappingButton: React.FC = ({ path }) => ( - + {ADD_ROLE_MAPPING_BUTTON} ); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/attribute_selector.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/attribute_selector.tsx index 0417331be208d6..0ee093ed934c9b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/attribute_selector.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/attribute_selector.tsx @@ -100,7 +100,7 @@ export const AttributeSelector: React.FC = ({ handleAuthProviderChange = () => null, }) => { return ( - +

{ATTRIBUTE_SELECTOR_TITLE}

diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/constants.ts b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/constants.ts index 8abab6d060a96e..a172fbae18d8fe 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/constants.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/constants.ts @@ -108,6 +108,16 @@ export const ROLE_MAPPINGS_TITLE = i18n.translate( } ); +export const ADD_ROLE_MAPPING_TITLE = i18n.translate( + 'xpack.enterpriseSearch.roleMapping.newRoleMappingTitle', + { defaultMessage: 'Add role mapping' } +); + +export const MANAGE_ROLE_MAPPING_TITLE = i18n.translate( + 'xpack.enterpriseSearch.roleMapping.manageRoleMappingTitle', + { defaultMessage: 'Manage role mapping' } +); + export const EMPTY_ROLE_MAPPINGS_TITLE = i18n.translate( 'xpack.enterpriseSearch.roleMapping.emptyRoleMappingsTitle', { diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/empty_prompt/index.ts b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mappings_table.scss similarity index 81% rename from x-pack/plugins/security/public/management/api_keys/api_keys_grid/empty_prompt/index.ts rename to x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mappings_table.scss index c68b2c170df5b1..6eaa3b92579367 100644 --- a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/empty_prompt/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mappings_table.scss @@ -5,4 +5,8 @@ * 2.0. */ -export { EmptyPrompt } from './empty_prompt'; +.roleMappingsTable { + td { + vertical-align: top; + } +} diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mappings_table.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mappings_table.tsx index 6db62e4c10b6bb..a5f6fb368c96f1 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mappings_table.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mappings_table.tsx @@ -29,6 +29,8 @@ import { MANAGE_BUTTON_LABEL } from '../constants'; import { EuiLinkTo } from '../react_router_helpers'; import { RoleRules } from '../types'; +import './role_mappings_table.scss'; + import { ANY_AUTH_PROVIDER, ANY_AUTH_PROVIDER_OPTION_LABEL, @@ -108,7 +110,7 @@ export const RoleMappingsTable: React.FC = ({
{filteredResults.length > 0 ? ( - + {EXTERNAL_ATTRIBUTE_LABEL} {ATTRIBUTE_VALUE_LABEL} @@ -152,7 +154,7 @@ export const RoleMappingsTable: React.FC = ({ {authProvider.map(getAuthProviderDisplayValue).join(', ')} )} - + {id && {MANAGE_BUTTON_LABEL}} {toolTip && } diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/nav.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/nav.tsx index f2edc04a5661c4..51cdcc688e682b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/nav.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/layout/nav.tsx @@ -42,7 +42,9 @@ export const WorkplaceSearchNav: React.FC = ({ {NAV.GROUPS} - {NAV.ROLE_MAPPINGS} + + {NAV.ROLE_MAPPINGS} + {NAV.SECURITY} {NAV.SETTINGS} diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/constants.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/constants.ts index d7716735067617..9f758cacdfce35 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/constants.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/constants.ts @@ -40,7 +40,7 @@ export const NAV = { defaultMessage: 'Content', }), ROLE_MAPPINGS: i18n.translate('xpack.enterpriseSearch.workplaceSearch.nav.roleMappings', { - defaultMessage: 'Role Mappings', + defaultMessage: 'Users & roles', }), SECURITY: i18n.translate('xpack.enterpriseSearch.workplaceSearch.nav.security', { defaultMessage: 'Security', diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/save_custom.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/save_custom.tsx index 1bf8239a6b3994..9689ecfae4a94d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/save_custom.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/save_custom.tsx @@ -62,9 +62,10 @@ export const SaveCustom: React.FC = ({ }) => ( <> {header} + - + diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings.test.tsx index c1f526e24b8e22..54be43596a4314 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings.test.tsx @@ -7,7 +7,6 @@ import '../../../../../__mocks__/shallow_useeffect.mock'; -import { mockKibanaValues } from '../../../../../__mocks__'; import { setMockValues, setMockActions } from '../../../../../__mocks__'; import { exampleResult } from '../../../../__mocks__/content_sources.mock'; @@ -25,11 +24,11 @@ import { DisplaySettings } from './display_settings'; import { FieldEditorModal } from './field_editor_modal'; describe('DisplaySettings', () => { - const { navigateToUrl } = mockKibanaValues; const { exampleDocuments, searchResultConfig } = exampleResult; const initializeDisplaySettings = jest.fn(); const setServerData = jest.fn(); const setColorField = jest.fn(); + const handleSelectedTabChanged = jest.fn(); const values = { isOrganization: true, @@ -46,6 +45,7 @@ describe('DisplaySettings', () => { initializeDisplaySettings, setServerData, setColorField, + handleSelectedTabChanged, }); setMockValues({ ...values }); }); @@ -83,7 +83,7 @@ describe('DisplaySettings', () => { const tabsEl = wrapper.find(EuiTabbedContent); tabsEl.prop('onTabClick')!(tabs[0]); - expect(navigateToUrl).toHaveBeenCalledWith('/sources/123/display_settings/'); + expect(handleSelectedTabChanged).toHaveBeenCalledWith('search_results'); }); it('handles second tab click', () => { @@ -91,7 +91,7 @@ describe('DisplaySettings', () => { const tabsEl = wrapper.find(EuiTabbedContent); tabsEl.prop('onTabClick')!(tabs[1]); - expect(navigateToUrl).toHaveBeenCalledWith('/sources/123/display_settings/result_detail'); + expect(handleSelectedTabChanged).toHaveBeenCalledWith('result_detail'); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings.tsx index e39a8d17e406c9..3441e5fcbaf82c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings.tsx @@ -20,19 +20,11 @@ import { } from '@elastic/eui'; import { clearFlashMessages } from '../../../../../shared/flash_messages'; -import { KibanaLogic } from '../../../../../shared/kibana'; import { Loading } from '../../../../../shared/loading'; import { UnsavedChangesPrompt } from '../../../../../shared/unsaved_changes_prompt'; -import { AppLogic } from '../../../../app_logic'; import { ViewContentHeader } from '../../../../components/shared/view_content_header'; import { SAVE_BUTTON } from '../../../../constants'; -import { - DISPLAY_SETTINGS_RESULT_DETAIL_PATH, - DISPLAY_SETTINGS_SEARCH_RESULT_PATH, - getContentSourcePath, -} from '../../../../routes'; - import { UNSAVED_MESSAGE, DISPLAY_SETTINGS_TITLE, @@ -42,7 +34,7 @@ import { SEARCH_RESULTS_LABEL, RESULT_DETAIL_LABEL, } from './constants'; -import { DisplaySettingsLogic } from './display_settings_logic'; +import { DisplaySettingsLogic, TabId } from './display_settings_logic'; import { FieldEditorModal } from './field_editor_modal'; import { ResultDetail } from './result_detail'; import { SearchResults } from './search_results'; @@ -52,19 +44,20 @@ interface DisplaySettingsProps { } export const DisplaySettings: React.FC = ({ tabId }) => { - const { initializeDisplaySettings, setServerData } = useActions(DisplaySettingsLogic); + const { initializeDisplaySettings, setServerData, handleSelectedTabChanged } = useActions( + DisplaySettingsLogic + ); const { dataLoading, - sourceId, addFieldModalVisible, unsavedChanges, exampleDocuments, + navigatingBetweenTabs, } = useValues(DisplaySettingsLogic); - const { isOrganization } = useValues(AppLogic); - const hasDocuments = exampleDocuments.length > 0; + const hasUnsavedChanges = hasDocuments && unsavedChanges; useEffect(() => { initializeDisplaySettings(); @@ -87,12 +80,7 @@ export const DisplaySettings: React.FC = ({ tabId }) => { ] as EuiTabbedContentTab[]; const onSelectedTabChanged = (tab: EuiTabbedContentTab) => { - const path = - tab.id === tabs[1].id - ? getContentSourcePath(DISPLAY_SETTINGS_RESULT_DETAIL_PATH, sourceId, isOrganization) - : getContentSourcePath(DISPLAY_SETTINGS_SEARCH_RESULT_PATH, sourceId, isOrganization); - - KibanaLogic.values.navigateToUrl(path); + handleSelectedTabChanged(tab.id as TabId); }; const handleFormSubmit = (e: FormEvent) => { @@ -103,7 +91,7 @@ export const DisplaySettings: React.FC = ({ tabId }) => { return ( <> diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings_logic.test.ts index 73df0298ecd196..5a6ef5ba5990f1 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings_logic.test.ts @@ -5,7 +5,12 @@ * 2.0. */ -import { LogicMounter, mockFlashMessageHelpers, mockHttpValues } from '../../../../../__mocks__'; +import { + LogicMounter, + mockFlashMessageHelpers, + mockHttpValues, + mockKibanaValues, +} from '../../../../../__mocks__'; import { exampleResult } from '../../../../__mocks__/content_sources.mock'; import { nextTick } from '@kbn/test/jest'; @@ -25,6 +30,7 @@ import { DisplaySettingsLogic, defaultSearchResultConfig } from './display_setti describe('DisplaySettingsLogic', () => { const { http } = mockHttpValues; + const { navigateToUrl } = mockKibanaValues; const { clearFlashMessages, flashAPIErrors, setSuccessMessage } = mockFlashMessageHelpers; const { mount } = new LogicMounter(DisplaySettingsLogic); @@ -40,6 +46,7 @@ describe('DisplaySettingsLogic', () => { serverRoute: '', editFieldIndex: null, dataLoading: true, + navigatingBetweenTabs: false, addFieldModalVisible: false, titleFieldHover: false, urlFieldHover: false, @@ -203,6 +210,12 @@ describe('DisplaySettingsLogic', () => { }); }); + it('setNavigatingBetweenTabs', () => { + DisplaySettingsLogic.actions.setNavigatingBetweenTabs(true); + + expect(DisplaySettingsLogic.values.navigatingBetweenTabs).toEqual(true); + }); + it('addDetailField', () => { const newField = { label: 'Monkey', fieldName: 'primate' }; DisplaySettingsLogic.actions.setServerResponseData(serverProps); @@ -351,6 +364,31 @@ describe('DisplaySettingsLogic', () => { expect(flashAPIErrors).toHaveBeenCalledWith('this is an error'); }); }); + + describe('handleSelectedTabChanged', () => { + beforeEach(() => { + DisplaySettingsLogic.actions.onInitializeDisplaySettings(serverProps); + }); + + it('calls sets navigatingBetweenTabs', async () => { + const setNavigatingBetweenTabsSpy = jest.spyOn( + DisplaySettingsLogic.actions, + 'setNavigatingBetweenTabs' + ); + DisplaySettingsLogic.actions.handleSelectedTabChanged('search_results'); + await nextTick(); + + expect(setNavigatingBetweenTabsSpy).toHaveBeenCalledWith(true); + expect(navigateToUrl).toHaveBeenCalledWith('/p/sources/123/display_settings/'); + }); + + it('calls calls correct route for "result_detail"', async () => { + DisplaySettingsLogic.actions.handleSelectedTabChanged('result_detail'); + await nextTick(); + + expect(navigateToUrl).toHaveBeenCalledWith('/p/sources/123/display_settings/result_detail'); + }); + }); }); describe('selectors', () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings_logic.ts index 62d959083af594..e8b419a31abb2d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings_logic.ts @@ -16,7 +16,13 @@ import { flashAPIErrors, } from '../../../../../shared/flash_messages'; import { HttpLogic } from '../../../../../shared/http'; +import { KibanaLogic } from '../../../../../shared/kibana'; import { AppLogic } from '../../../../app_logic'; +import { + DISPLAY_SETTINGS_RESULT_DETAIL_PATH, + DISPLAY_SETTINGS_SEARCH_RESULT_PATH, + getContentSourcePath, +} from '../../../../routes'; import { DetailField, SearchResultConfig, OptionValue, Result } from '../../../../types'; import { SourceLogic } from '../../source_logic'; @@ -34,6 +40,8 @@ export interface DisplaySettingsInitialData extends DisplaySettingsResponseProps serverRoute: string; } +export type TabId = 'search_results' | 'result_detail'; + interface DisplaySettingsActions { initializeDisplaySettings(): void; setServerData(): void; @@ -51,6 +59,8 @@ interface DisplaySettingsActions { setDetailFields(result: DropResult): { result: DropResult }; openEditDetailField(editFieldIndex: number | null): number | null; removeDetailField(index: number): number; + setNavigatingBetweenTabs(navigatingBetweenTabs: boolean): boolean; + handleSelectedTabChanged(tabId: TabId): TabId; addDetailField(newField: DetailField): DetailField; updateDetailField( updatedField: DetailField, @@ -73,6 +83,7 @@ interface DisplaySettingsValues { serverRoute: string; editFieldIndex: number | null; dataLoading: boolean; + navigatingBetweenTabs: boolean; addFieldModalVisible: boolean; titleFieldHover: boolean; urlFieldHover: boolean; @@ -109,6 +120,8 @@ export const DisplaySettingsLogic = kea< setDetailFields: (result: DropResult) => ({ result }), openEditDetailField: (editFieldIndex: number | null) => editFieldIndex, removeDetailField: (index: number) => index, + setNavigatingBetweenTabs: (navigatingBetweenTabs: boolean) => navigatingBetweenTabs, + handleSelectedTabChanged: (tabId: TabId) => tabId, addDetailField: (newField: DetailField) => newField, updateDetailField: (updatedField: DetailField, index: number) => ({ updatedField, index }), toggleFieldEditorModal: () => true, @@ -224,6 +237,12 @@ export const DisplaySettingsLogic = kea< onInitializeDisplaySettings: () => false, }, ], + navigatingBetweenTabs: [ + false, + { + setNavigatingBetweenTabs: (_, navigatingBetweenTabs) => navigatingBetweenTabs, + }, + ], addFieldModalVisible: [ false, { @@ -330,6 +349,26 @@ export const DisplaySettingsLogic = kea< toggleFieldEditorModal: () => { clearFlashMessages(); }, + + handleSelectedTabChanged: async (tabId, breakpoint) => { + const { isOrganization } = AppLogic.values; + const { sourceId } = values; + const path = + tabId === 'result_detail' + ? getContentSourcePath(DISPLAY_SETTINGS_RESULT_DETAIL_PATH, sourceId, isOrganization) + : getContentSourcePath(DISPLAY_SETTINGS_SEARCH_RESULT_PATH, sourceId, isOrganization); + + // This method is needed because the shared `UnsavedChangesPrompt` component is triggered + // when navigating between tabs. We set a boolean flag that tells the prompt there are no + // unsaved changes when navigating between the tabs and reset it one the transition is complete + // in order to restore the intended functionality when navigating away with unsaved changes. + actions.setNavigatingBetweenTabs(true); + + await breakpoint(); + + KibanaLogic.values.navigateToUrl(path); + actions.setNavigatingBetweenTabs(false); + }, }), }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/overview.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/overview.tsx index dc925e21460da1..a5a2d8ab73d94d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/overview.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/overview.tsx @@ -230,7 +230,12 @@ export const Overview: React.FC = () => { {groups.map((group, index) => ( - + {group.name} @@ -248,7 +253,7 @@ export const Overview: React.FC = () => {

{CONFIGURATION_TITLE}

- + {details.map((detail, index) => ( {

{DOCUMENT_PERMISSIONS_TITLE}

- + @@ -298,7 +303,7 @@ export const Overview: React.FC = () => {

{DOCUMENT_PERMISSIONS_TITLE}

- + @@ -329,7 +334,7 @@ export const Overview: React.FC = () => { ); const sourceStatus = ( - +
{STATUS_HEADER} @@ -353,7 +358,7 @@ export const Overview: React.FC = () => { ); const permissionsStatus = ( - +
{STATUS_HEADING} @@ -389,7 +394,7 @@ export const Overview: React.FC = () => { ); const credentials = ( - +
{CREDENTIALS_TITLE} @@ -409,7 +414,7 @@ export const Overview: React.FC = () => { title: string; children: React.ReactNode; }) => ( - +
{DOCUMENTATION_LINK_TITLE} @@ -424,7 +429,7 @@ export const Overview: React.FC = () => { ); const documentPermssionsLicenseLocked = ( - + diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_added.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_added.test.tsx index ddf89159b26755..9eecc41aa17780 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_added.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_added.test.tsx @@ -7,7 +7,7 @@ import '../../../../__mocks__/shallow_useeffect.mock'; -import { setMockActions } from '../../../../__mocks__'; +import { setMockActions, setMockValues } from '../../../../__mocks__'; import React from 'react'; import { useLocation } from 'react-router-dom'; @@ -20,9 +20,11 @@ import { SourceAdded } from './source_added'; describe('SourceAdded', () => { const saveSourceParams = jest.fn(); + const setChromeIsVisible = jest.fn(); beforeEach(() => { setMockActions({ saveSourceParams }); + setMockValues({ setChromeIsVisible }); }); it('renders', () => { @@ -32,5 +34,6 @@ describe('SourceAdded', () => { expect(wrapper.find(Loading)).toHaveLength(1); expect(saveSourceParams).toHaveBeenCalled(); + expect(setChromeIsVisible).toHaveBeenCalled(); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_added.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_added.tsx index 7c4e81d8e0755c..5b93b7a426936e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_added.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_added.tsx @@ -9,10 +9,11 @@ import React, { useEffect } from 'react'; import { useLocation } from 'react-router-dom'; import { Location } from 'history'; -import { useActions } from 'kea'; +import { useActions, useValues } from 'kea'; import { EuiPage, EuiPageBody } from '@elastic/eui'; +import { KibanaLogic } from '../../../../shared/kibana'; import { Loading } from '../../../../shared/loading'; import { AddSourceLogic } from './add_source/add_source_logic'; @@ -24,8 +25,12 @@ import { AddSourceLogic } from './add_source/add_source_logic'; */ export const SourceAdded: React.FC = () => { const { search } = useLocation() as Location; + const { setChromeIsVisible } = useValues(KibanaLogic); const { saveSourceParams } = useActions(AddSourceLogic); + // We don't want the personal dashboard to flash the Kibana chrome, so we hide it. + setChromeIsVisible(false); + useEffect(() => { saveSourceParams(search); }, []); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_sub_nav.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_sub_nav.tsx index bf0c5471f7b574..12e1506ec6efda 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_sub_nav.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_sub_nav.tsx @@ -45,7 +45,10 @@ export const SourceSubNav: React.FC = () => { {NAV.SCHEMA} - + {NAV.DISPLAY_SETTINGS} diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/constants.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/constants.ts index 3398427a7111b3..32df63d0faba94 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/constants.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/constants.ts @@ -148,7 +148,7 @@ export const ACCESS_TOKEN_LABEL = i18n.translate( ); export const ID_LABEL = i18n.translate('xpack.enterpriseSearch.workplaceSearch.sources.id.label', { - defaultMessage: 'ID', + defaultMessage: 'Source Identifier', }); export const LEARN_CUSTOM_FEATURES_BUTTON = i18n.translate( diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_overview.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_overview.test.tsx index e39d72a861b6fe..8d5714fd057929 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_overview.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_overview.test.tsx @@ -12,18 +12,14 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { EuiFieldText } from '@elastic/eui'; +import { EuiFieldText, EuiEmptyPrompt } from '@elastic/eui'; import { Loading } from '../../../../shared/loading'; import { ContentSection } from '../../../components/shared/content_section'; import { SourcesTable } from '../../../components/shared/sources_table'; import { ViewContentHeader } from '../../../components/shared/view_content_header'; -import { - GroupOverview, - EMPTY_SOURCES_DESCRIPTION, - EMPTY_USERS_DESCRIPTION, -} from './group_overview'; +import { GroupOverview } from './group_overview'; const deleteGroup = jest.fn(); const showSharedSourcesModal = jest.fn(); @@ -92,7 +88,7 @@ describe('GroupOverview', () => { expect(updateGroupName).toHaveBeenCalled(); }); - it('renders empty state messages', () => { + it('renders empty state', () => { setMockValues({ ...mockValues, group: { @@ -103,10 +99,7 @@ describe('GroupOverview', () => { }); const wrapper = shallow(); - const sourcesSection = wrapper.find('[data-test-subj="GroupContentSourcesSection"]') as any; - const usersSection = wrapper.find('[data-test-subj="GroupUsersSection"]') as any; - expect(sourcesSection.prop('description')).toEqual(EMPTY_SOURCES_DESCRIPTION); - expect(usersSection.prop('description')).toEqual(EMPTY_USERS_DESCRIPTION); + expect(wrapper.find(EuiEmptyPrompt)).toHaveLength(1); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_overview.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_overview.tsx index 375ac7476f9b69..364ca0ba472567 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_overview.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_overview.tsx @@ -12,10 +12,12 @@ import { useActions, useValues } from 'kea'; import { EuiButton, EuiConfirmModal, + EuiEmptyPrompt, EuiFieldText, EuiFlexGroup, EuiFlexItem, EuiFormRow, + EuiPanel, EuiSpacer, EuiHorizontalRule, } from '@elastic/eui'; @@ -24,6 +26,7 @@ import { i18n } from '@kbn/i18n'; import { Loading } from '../../../../shared/loading'; import { TruncatedContent } from '../../../../shared/truncate'; import { AppLogic } from '../../../app_logic'; +import noSharedSourcesIcon from '../../../assets/share_circle.svg'; import { ContentSection } from '../../../components/shared/content_section'; import { SourcesTable } from '../../../components/shared/sources_table'; import { ViewContentHeader } from '../../../components/shared/view_content_header'; @@ -145,6 +148,12 @@ export const GroupOverview: React.FC = () => { values: { name }, } ); + const GROUP_SOURCES_TITLE = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.groups.overview.groupSourcesTitle', + { + defaultMessage: 'Group content sources', + } + ); const GROUP_SOURCES_DESCRIPTION = i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.groups.overview.groupSourcesDescription', { @@ -170,15 +179,29 @@ export const GroupOverview: React.FC = () => { const sourcesSection = ( - {hasContentSources && sourcesTable} + {sourcesTable} ); + const sourcesEmptyState = ( + <> + + {GROUP_SOURCES_TITLE}
} + body={

{EMPTY_SOURCES_DESCRIPTION}

} + actions={manageSourcesButton} + /> +
+ + + ); + const usersSection = !isFederatedAuth && ( { <> - {sourcesSection} + {hasContentSources ? sourcesSection : sourcesEmptyState} {usersSection} {nameSection} {canDeleteGroup && deleteSection} diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups.tsx index b2bf0364b2d1f3..b82e141bc810ef 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups.tsx @@ -60,7 +60,7 @@ export const Groups: React.FC = () => { messages[0].description = ( {i18n.translate('xpack.enterpriseSearch.workplaceSearch.groups.newGroup.action', { diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mapping.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mapping.tsx index 7db1e82d29449c..fb366883601a6f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mapping.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mapping.tsx @@ -24,13 +24,19 @@ import { import { i18n } from '@kbn/i18n'; import { FlashMessages } from '../../../shared/flash_messages'; +import { SetWorkplaceSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome'; import { Loading } from '../../../shared/loading'; import { AttributeSelector, DeleteMappingCallout, RoleSelector, } from '../../../shared/role_mapping'; -import { ROLE_LABEL } from '../../../shared/role_mapping/constants'; +import { + ROLE_LABEL, + ROLE_MAPPINGS_TITLE, + ADD_ROLE_MAPPING_TITLE, + MANAGE_ROLE_MAPPING_TITLE, +} from '../../../shared/role_mapping/constants'; import { ViewContentHeader } from '../../components/shared/view_content_header'; import { Role } from '../../types'; @@ -105,6 +111,7 @@ export const RoleMapping: React.FC = ({ isNew }) => { const hasGroupAssignment = selectedGroups.size > 0 || includeInAllGroups; + const TITLE = isNew ? ADD_ROLE_MAPPING_TITLE : MANAGE_ROLE_MAPPING_TITLE; const SAVE_ROLE_MAPPING_LABEL = i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.roleMapping.saveRoleMappingButtonMessage', { @@ -121,6 +128,7 @@ export const RoleMapping: React.FC = ({ isNew }) => { return ( <> +
@@ -141,7 +149,7 @@ export const RoleMapping: React.FC = ({ isNew }) => { - +

{ROLE_LABEL}

@@ -158,7 +166,7 @@ export const RoleMapping: React.FC = ({ isNew }) => {
- +

{GROUP_ASSIGNMENT_TITLE}

diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings.tsx index 842c59e683f06e..9ec0dfc0acefc4 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings.tsx @@ -9,9 +9,10 @@ import React, { useEffect } from 'react'; import { useActions, useValues } from 'kea'; -import { EuiEmptyPrompt } from '@elastic/eui'; +import { EuiEmptyPrompt, EuiPanel } from '@elastic/eui'; import { FlashMessages } from '../../../shared/flash_messages'; +import { SetWorkplaceSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome'; import { Loading } from '../../../shared/loading'; import { AddRoleMappingButton, RoleMappingsTable } from '../../../shared/role_mapping'; import { @@ -39,12 +40,14 @@ export const RoleMappings: React.FC = () => { const addMappingButton = ; const emptyPrompt = ( - {EMPTY_ROLE_MAPPINGS_TITLE}
} - body={

{EMPTY_ROLE_MAPPINGS_BODY}

} - actions={addMappingButton} - /> + + {EMPTY_ROLE_MAPPINGS_TITLE}
} + body={

{EMPTY_ROLE_MAPPINGS_BODY}

} + actions={addMappingButton} + /> +
); const roleMappingsTable = ( { return ( <> +
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/security/components/private_sources_table.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/security/components/private_sources_table.tsx index 312745ee7496c7..68f2a2289c1f26 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/security/components/private_sources_table.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/security/components/private_sources_table.tsx @@ -152,7 +152,7 @@ export const PrivateSourcesTable: React.FC = ({ {contentSources.map((source, i) => ( {source.name} - + { { messageText={SECURITY_UNSAVED_CHANGES_MESSAGE} /> {header} - {allSourcesToggle} - {!hasPlatinumLicense && platinumLicenseCallout} - {sourceTables} + + {allSourcesToggle} + {!hasPlatinumLicense && platinumLicenseCallout} + {sourceTables} + {confirmModalVisible && confirmModal} ); diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/engines.test.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/engines.test.ts index c653cad5c1c0d7..bc4259fa37889e 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/engines.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/engines.test.ts @@ -259,4 +259,47 @@ describe('engine routes', () => { }); }); }); + + describe('GET /api/app_search/engines/{name}/source_engines', () => { + let mockRouter: MockRouter; + + beforeEach(() => { + jest.clearAllMocks(); + mockRouter = new MockRouter({ + method: 'get', + path: '/api/app_search/engines/{name}/source_engines', + }); + + registerEnginesRoutes({ + ...mockDependencies, + router: mockRouter.router, + }); + }); + + it('validates correctly with name', () => { + const request = { params: { name: 'test-engine' } }; + mockRouter.shouldValidate(request); + }); + + it('fails validation without name', () => { + const request = { params: {} }; + mockRouter.shouldThrow(request); + }); + + it('fails validation with a non-string name', () => { + const request = { params: { name: 1 } }; + mockRouter.shouldThrow(request); + }); + + it('fails validation with missing query params', () => { + const request = { query: {} }; + mockRouter.shouldThrow(request); + }); + + it('creates a request to enterprise search', () => { + expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ + path: '/as/engines/:name/source_engines', + }); + }); + }); }); diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/engines.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/engines.ts index 77b055add7d793..f6e9d30dd0adeb 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/engines.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/engines.ts @@ -95,4 +95,21 @@ export function registerEnginesRoutes({ path: '/as/engines/:name/overview_metrics', }) ); + router.get( + { + path: '/api/app_search/engines/{name}/source_engines', + validate: { + params: schema.object({ + name: schema.string(), + }), + query: schema.object({ + 'page[current]': schema.number(), + 'page[size]': schema.number(), + }), + }, + }, + enterpriseSearchRequestHandler.createRequest({ + path: '/as/engines/:name/source_engines', + }) + ); } diff --git a/x-pack/plugins/fleet/common/constants/index.ts b/x-pack/plugins/fleet/common/constants/index.ts index 5598e63219776f..3704533e79b4ae 100644 --- a/x-pack/plugins/fleet/common/constants/index.ts +++ b/x-pack/plugins/fleet/common/constants/index.ts @@ -15,6 +15,7 @@ export * from './epm'; export * from './output'; export * from './enrollment_api_key'; export * from './settings'; +export * from './preconfiguration'; // TODO: This is the default `index.max_result_window` ES setting, which dictates // the maximum amount of results allowed to be returned from a search. It's possible diff --git a/x-pack/plugins/fleet/common/constants/preconfiguration.ts b/x-pack/plugins/fleet/common/constants/preconfiguration.ts new file mode 100644 index 00000000000000..376ba551b13593 --- /dev/null +++ b/x-pack/plugins/fleet/common/constants/preconfiguration.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const PRECONFIGURATION_DELETION_RECORD_SAVED_OBJECT_TYPE = + 'fleet-preconfiguration-deletion-record'; diff --git a/x-pack/plugins/fleet/common/types/index.ts b/x-pack/plugins/fleet/common/types/index.ts index 1984de79a6357e..cdea56448f3a2e 100644 --- a/x-pack/plugins/fleet/common/types/index.ts +++ b/x-pack/plugins/fleet/common/types/index.ts @@ -7,6 +7,7 @@ export * from './models'; export * from './rest_spec'; +import type { PreconfiguredAgentPolicy, PreconfiguredPackage } from './models/preconfiguration'; export interface FleetConfigType { enabled: boolean; @@ -32,6 +33,8 @@ export interface FleetConfigType { agentPolicyRolloutRateLimitIntervalMs: number; agentPolicyRolloutRateLimitRequestPerInterval: number; }; + agentPolicies?: PreconfiguredAgentPolicy[]; + packages?: PreconfiguredPackage[]; } // Calling Object.entries(PackagesGroupedByStatus) gave `status: string` diff --git a/x-pack/plugins/fleet/common/types/rest_spec/ingest_setup.ts b/x-pack/plugins/fleet/common/types/rest_spec/ingest_setup.ts index 12054aff124f7c..2180b669084982 100644 --- a/x-pack/plugins/fleet/common/types/rest_spec/ingest_setup.ts +++ b/x-pack/plugins/fleet/common/types/rest_spec/ingest_setup.ts @@ -7,4 +7,5 @@ export interface PostIngestSetupResponse { isInitialized: boolean; + preconfigurationError?: { name: string; message: string }; } diff --git a/x-pack/plugins/fleet/public/applications/fleet/app.tsx b/x-pack/plugins/fleet/public/applications/fleet/app.tsx index 2c24468b147826..5663bd4768d5cf 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/app.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/app.tsx @@ -28,6 +28,7 @@ import { sendSetup, useBreadcrumbs, useConfig, + useStartServices, } from './hooks'; import { Error, Loading } from './components'; import { IntraAppStateProvider } from './hooks/use_intra_app_state'; @@ -59,6 +60,7 @@ const Panel = styled(EuiPanel)` export const WithPermissionsAndSetup: React.FC = memo(({ children }) => { useBreadcrumbs('base'); + const { notifications } = useStartServices(); const [isPermissionsLoading, setIsPermissionsLoading] = useState(false); const [permissionsError, setPermissionsError] = useState(); @@ -81,6 +83,13 @@ export const WithPermissionsAndSetup: React.FC = memo(({ children }) => { if (setupResponse.error) { setInitializationError(setupResponse.error); } + if (setupResponse.data.preconfigurationError) { + notifications.toasts.addError(setupResponse.data.preconfigurationError, { + title: i18n.translate('xpack.fleet.setup.uiPreconfigurationErrorTitle', { + defaultMessage: 'Configuration error', + }), + }); + } } catch (err) { setInitializationError(err); } @@ -92,7 +101,7 @@ export const WithPermissionsAndSetup: React.FC = memo(({ children }) => { setPermissionsError('REQUEST_ERROR'); } })(); - }, []); + }, [notifications.toasts]); if (isPermissionsLoading || permissionsError) { return ( diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/policies/persona.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/policies/persona.tsx index 100945327e8247..c2a7e1530cbf56 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/policies/persona.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/epm/screens/detail/policies/persona.tsx @@ -7,7 +7,7 @@ import type { CSSProperties } from 'react'; import React, { memo, useCallback } from 'react'; import { EuiAvatar, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; -import type { EuiAvatarProps } from '@elastic/eui/src/components/avatar/avatar'; +import type { EuiAvatarProps } from '@elastic/eui'; const MIN_WIDTH: CSSProperties = { minWidth: 0 }; diff --git a/x-pack/plugins/fleet/server/constants/index.ts b/x-pack/plugins/fleet/server/constants/index.ts index 7f5586fb0f0348..27af46d0a757df 100644 --- a/x-pack/plugins/fleet/server/constants/index.ts +++ b/x-pack/plugins/fleet/server/constants/index.ts @@ -52,4 +52,5 @@ export { // Fleet Server index ENROLLMENT_API_KEYS_INDEX, AGENTS_INDEX, + PRECONFIGURATION_DELETION_RECORD_SAVED_OBJECT_TYPE, } from '../../common'; diff --git a/x-pack/plugins/fleet/server/index.ts b/x-pack/plugins/fleet/server/index.ts index 0178b801f4d2fc..c66dd471690eb6 100644 --- a/x-pack/plugins/fleet/server/index.ts +++ b/x-pack/plugins/fleet/server/index.ts @@ -15,6 +15,8 @@ import { AGENT_POLLING_REQUEST_TIMEOUT_MS, } from '../common'; +import { PreconfiguredPackagesSchema, PreconfiguredAgentPoliciesSchema } from './types'; + import { FleetPlugin } from './plugin'; export { default as apm } from 'elastic-apm-node'; @@ -77,6 +79,8 @@ export const config: PluginConfigDescriptor = { defaultValue: AGENT_POLICY_ROLLOUT_RATE_LIMIT_REQUEST_PER_INTERVAL, }), }), + packages: schema.maybe(PreconfiguredPackagesSchema), + agentPolicies: schema.maybe(PreconfiguredAgentPoliciesSchema), }), }; diff --git a/x-pack/plugins/fleet/server/plugin.ts b/x-pack/plugins/fleet/server/plugin.ts index 20cfae6bc1cf24..d25b1e13904db5 100644 --- a/x-pack/plugins/fleet/server/plugin.ts +++ b/x-pack/plugins/fleet/server/plugin.ts @@ -48,6 +48,7 @@ import { AGENT_SAVED_OBJECT_TYPE, AGENT_EVENT_SAVED_OBJECT_TYPE, ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE, + PRECONFIGURATION_DELETION_RECORD_SAVED_OBJECT_TYPE, } from './constants'; import { registerSavedObjects, registerEncryptedSavedObjects } from './saved_objects'; import { @@ -133,6 +134,7 @@ const allSavedObjectTypes = [ AGENT_SAVED_OBJECT_TYPE, AGENT_EVENT_SAVED_OBJECT_TYPE, ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE, + PRECONFIGURATION_DELETION_RECORD_SAVED_OBJECT_TYPE, ]; /** diff --git a/x-pack/plugins/fleet/server/routes/data_streams/handlers.ts b/x-pack/plugins/fleet/server/routes/data_streams/handlers.ts index c684c050036124..6d4d107adb796d 100644 --- a/x-pack/plugins/fleet/server/routes/data_streams/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/data_streams/handlers.ts @@ -6,12 +6,12 @@ */ import { keyBy, keys, merge } from 'lodash'; -import type { RequestHandler, SavedObjectsClientContract } from 'src/core/server'; +import type { RequestHandler, SavedObjectsBulkGetObject } from 'src/core/server'; import type { DataStream } from '../../types'; -import { KibanaAssetType, KibanaSavedObjectType } from '../../../common'; +import { KibanaSavedObjectType } from '../../../common'; import type { GetDataStreamsResponse } from '../../../common'; -import { getPackageSavedObjects, getKibanaSavedObject } from '../../services/epm/packages/get'; +import { getPackageSavedObjects } from '../../services/epm/packages/get'; import { defaultIngestErrorHandler } from '../../errors'; const DATA_STREAM_INDEX_PATTERN = 'logs-*-*,metrics-*-*,traces-*-*'; @@ -78,6 +78,40 @@ export const getListHandler: RequestHandler = async (context, request, response) const packageSavedObjectsByName = keyBy(packageSavedObjects.saved_objects, 'id'); const packageMetadata: any = {}; + // Get dashboard information for all packages + const dashboardIdsByPackageName = packageSavedObjects.saved_objects.reduce< + Record + >((allDashboards, pkgSavedObject) => { + const dashboards: string[] = []; + (pkgSavedObject.attributes?.installed_kibana || []).forEach((o) => { + if (o.type === KibanaSavedObjectType.dashboard) { + dashboards.push(o.id); + } + }); + allDashboards[pkgSavedObject.id] = dashboards; + return allDashboards; + }, {}); + const allDashboardSavedObjects = await context.core.savedObjects.client.bulkGet<{ + title?: string; + }>( + Object.values(dashboardIdsByPackageName).reduce( + (allDashboards, dashboardIds) => { + return allDashboards.concat( + dashboardIds.map((id) => ({ + id, + type: KibanaSavedObjectType.dashboard, + fields: ['title'], + })) + ); + }, + [] + ) + ); + const allDashboardSavedObjectsById = keyBy( + allDashboardSavedObjects.saved_objects, + (dashboardSavedObject) => dashboardSavedObject.id + ); + // Query additional information for each data stream const dataStreamPromises = dataStreamNames.map(async (dataStreamName) => { const dataStream = dataStreams[dataStreamName]; @@ -158,19 +192,23 @@ export const getListHandler: RequestHandler = async (context, request, response) // - and we didn't pick the metadata in an earlier iteration of this map() if (!packageMetadata[pkgName]) { // then pick the dashboards from the package saved object - const dashboards = - pkgSavedObject.attributes?.installed_kibana?.filter( - (o) => o.type === KibanaSavedObjectType.dashboard - ) || []; - // and then pick the human-readable titles from the dashboard saved objects - const enhancedDashboards = await getEnhancedDashboards( - context.core.savedObjects.client, - dashboards - ); + const packageDashboardIds = dashboardIdsByPackageName[pkgName] || []; + const packageDashboards = packageDashboardIds.reduce< + Array<{ id: string; title: string }> + >((dashboards, dashboardId) => { + const dashboard = allDashboardSavedObjectsById[dashboardId]; + if (dashboard) { + dashboards.push({ + id: dashboard.id, + title: dashboard.attributes.title || dashboard.id, + }); + } + return dashboards; + }, []); packageMetadata[pkgName] = { version: pkgSavedObject.attributes?.version || '', - dashboards: enhancedDashboards, + dashboards: packageDashboards, }; } @@ -195,21 +233,3 @@ export const getListHandler: RequestHandler = async (context, request, response) return defaultIngestErrorHandler({ error, response }); } }; - -const getEnhancedDashboards = async ( - savedObjectsClient: SavedObjectsClientContract, - dashboards: any[] -) => { - const dashboardsPromises = dashboards.map(async (db) => { - const dbSavedObject: any = await getKibanaSavedObject( - savedObjectsClient, - KibanaAssetType.dashboard, - db.id - ); - return { - id: db.id, - title: dbSavedObject.attributes?.title || db.id, - }; - }); - return await Promise.all(dashboardsPromises); -}; diff --git a/x-pack/plugins/fleet/server/routes/setup/handlers.test.ts b/x-pack/plugins/fleet/server/routes/setup/handlers.test.ts index 469b2409f140ac..2618f3de0d5342 100644 --- a/x-pack/plugins/fleet/server/routes/setup/handlers.test.ts +++ b/x-pack/plugins/fleet/server/routes/setup/handlers.test.ts @@ -45,7 +45,9 @@ describe('FleetSetupHandler', () => { }); it('POST /setup succeeds w/200 and body of resolved value', async () => { - mockSetupIngestManager.mockImplementation(() => Promise.resolve({ isIntialized: true })); + mockSetupIngestManager.mockImplementation(() => + Promise.resolve({ isInitialized: true, preconfigurationError: undefined }) + ); await FleetSetupHandler(context, request, response); const expectedBody: PostIngestSetupResponse = { isInitialized: true }; diff --git a/x-pack/plugins/fleet/server/routes/setup/handlers.ts b/x-pack/plugins/fleet/server/routes/setup/handlers.ts index a7fdcf78f4be99..e94c9470dd350f 100644 --- a/x-pack/plugins/fleet/server/routes/setup/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/setup/handlers.ts @@ -63,13 +63,13 @@ export const createFleetSetupHandler: RequestHandler< try { const soClient = context.core.savedObjects.client; const esClient = context.core.elasticsearch.client.asCurrentUser; - await setupIngestManager(soClient, esClient); + const body = await setupIngestManager(soClient, esClient); await setupFleet(soClient, esClient, { forceRecreate: request.body?.forceRecreate ?? false, }); return response.ok({ - body: { isInitialized: true }, + body, }); } catch (error) { return defaultIngestErrorHandler({ error, response }); @@ -81,8 +81,7 @@ export const FleetSetupHandler: RequestHandler = async (context, request, respon const esClient = context.core.elasticsearch.client.asCurrentUser; try { - const body: PostIngestSetupResponse = { isInitialized: true }; - await setupIngestManager(soClient, esClient); + const body: PostIngestSetupResponse = await setupIngestManager(soClient, esClient); return response.ok({ body, }); diff --git a/x-pack/plugins/fleet/server/saved_objects/index.ts b/x-pack/plugins/fleet/server/saved_objects/index.ts index 8554c0702f733d..58ec3972ca5179 100644 --- a/x-pack/plugins/fleet/server/saved_objects/index.ts +++ b/x-pack/plugins/fleet/server/saved_objects/index.ts @@ -19,6 +19,7 @@ import { AGENT_ACTION_SAVED_OBJECT_TYPE, ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE, GLOBAL_SETTINGS_SAVED_OBJECT_TYPE, + PRECONFIGURATION_DELETION_RECORD_SAVED_OBJECT_TYPE, } from '../constants'; import { @@ -358,6 +359,19 @@ const getSavedObjectTypes = ( }, }, }, + [PRECONFIGURATION_DELETION_RECORD_SAVED_OBJECT_TYPE]: { + name: PRECONFIGURATION_DELETION_RECORD_SAVED_OBJECT_TYPE, + hidden: false, + namespaceType: 'agnostic', + management: { + importableAndExportable: false, + }, + mappings: { + properties: { + preconfiguration_id: { type: 'keyword' }, + }, + }, + }, }); export function registerSavedObjects( diff --git a/x-pack/plugins/fleet/server/services/agent_policy.ts b/x-pack/plugins/fleet/server/services/agent_policy.ts index 7f793a41ab9855..59214e287c873d 100644 --- a/x-pack/plugins/fleet/server/services/agent_policy.ts +++ b/x-pack/plugins/fleet/server/services/agent_policy.ts @@ -19,6 +19,7 @@ import { DEFAULT_AGENT_POLICY, AGENT_POLICY_SAVED_OBJECT_TYPE, AGENT_SAVED_OBJECT_TYPE, + PRECONFIGURATION_DELETION_RECORD_SAVED_OBJECT_TYPE, } from '../constants'; import type { PackagePolicy, @@ -150,7 +151,7 @@ class AgentPolicyService { config: PreconfiguredAgentPolicy ): Promise<{ created: boolean; - policy: AgentPolicy; + policy?: AgentPolicy; }> { const { id, ...preconfiguredAgentPolicy } = omit(config, 'package_policies'); const preconfigurationId = String(id); @@ -582,6 +583,13 @@ class AgentPolicyService { } ); } + + if (agentPolicy.preconfiguration_id) { + await soClient.create(PRECONFIGURATION_DELETION_RECORD_SAVED_OBJECT_TYPE, { + preconfiguration_id: String(agentPolicy.preconfiguration_id), + }); + } + await soClient.delete(SAVED_OBJECT_TYPE, id); await this.triggerAgentPolicyUpdatedEvent(soClient, esClient, 'deleted', id); return { @@ -819,5 +827,6 @@ export async function addPackageToAgentPolicy( await packagePolicyService.create(soClient, esClient, newPackagePolicy, { bumpRevision: false, + skipEnsureInstalled: true, }); } diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.test.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.test.ts index df82aa90b5a131..dcc685bb270b46 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.test.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.test.ts @@ -301,6 +301,64 @@ describe('EPM template', () => { expect(mappings).toEqual(keywordWithNormalizedMultiFieldsMapping); }); + it('tests processing keyword field with multi fields with long field', () => { + const keywordWithMultiFieldsLiteralYml = ` + - name: keywordWithMultiFields + type: keyword + multi_fields: + - name: number_memory_devices + type: long + normalizer: lowercase + `; + + const keywordWithMultiFieldsMapping = { + properties: { + keywordWithMultiFields: { + ignore_above: 1024, + type: 'keyword', + fields: { + number_memory_devices: { + type: 'long', + }, + }, + }, + }, + }; + const fields: Field[] = safeLoad(keywordWithMultiFieldsLiteralYml); + const processedFields = processFields(fields); + const mappings = generateMappings(processedFields); + expect(mappings).toEqual(keywordWithMultiFieldsMapping); + }); + + it('tests processing keyword field with multi fields with double field', () => { + const keywordWithMultiFieldsLiteralYml = ` + - name: keywordWithMultiFields + type: keyword + multi_fields: + - name: number + type: double + normalizer: lowercase + `; + + const keywordWithMultiFieldsMapping = { + properties: { + keywordWithMultiFields: { + ignore_above: 1024, + type: 'keyword', + fields: { + number: { + type: 'double', + }, + }, + }, + }, + }; + const fields: Field[] = safeLoad(keywordWithMultiFieldsLiteralYml); + const processedFields = processFields(fields); + const mappings = generateMappings(processedFields); + expect(mappings).toEqual(keywordWithMultiFieldsMapping); + }); + it('tests processing object field with no other attributes', () => { const objectFieldLiteralYml = ` - name: objectField diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts index 0b95f8d76627a9..f6ca1dfc99f4e0 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts @@ -204,6 +204,12 @@ function generateMultiFields(fields: Fields): MultiFields { case 'keyword': multiFields[f.name] = { ...generateKeywordMapping(f), type: f.type }; break; + case 'long': + multiFields[f.name] = { type: f.type }; + break; + case 'double': + multiFields[f.name] = { type: f.type }; + break; } }); } diff --git a/x-pack/plugins/fleet/server/services/epm/packages/get.ts b/x-pack/plugins/fleet/server/services/epm/packages/get.ts index 98dbd3bd571621..706b2679ed2eb9 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/get.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/get.ts @@ -19,7 +19,6 @@ import type { RegistryPackage, EpmPackageAdditions, } from '../../../../common/types'; -import type { KibanaAssetType } from '../../../types'; import type { Installation, PackageInfo } from '../../../types'; import { IngestManagerError } from '../../../errors'; import { appContextService } from '../../'; @@ -260,11 +259,3 @@ function sortByName(a: { name: string }, b: { name: string }) { return 0; } } - -export async function getKibanaSavedObject( - savedObjectsClient: SavedObjectsClientContract, - type: KibanaAssetType, - id: string -) { - return savedObjectsClient.get(type, id); -} diff --git a/x-pack/plugins/fleet/server/services/package_policy.ts b/x-pack/plugins/fleet/server/services/package_policy.ts index 7d12aad6f32b50..1d2295a5534629 100644 --- a/x-pack/plugins/fleet/server/services/package_policy.ts +++ b/x-pack/plugins/fleet/server/services/package_policy.ts @@ -60,7 +60,13 @@ class PackagePolicyService { soClient: SavedObjectsClientContract, esClient: ElasticsearchClient, packagePolicy: NewPackagePolicy, - options?: { id?: string; user?: AuthenticatedUser; bumpRevision?: boolean; force?: boolean } + options?: { + id?: string; + user?: AuthenticatedUser; + bumpRevision?: boolean; + force?: boolean; + skipEnsureInstalled?: boolean; + } ): Promise { // Check that its agent policy does not have a package policy with the same name const parentAgentPolicy = await agentPolicyService.get(soClient, packagePolicy.policy_id); @@ -90,18 +96,25 @@ class PackagePolicyService { // Make sure the associated package is installed if (packagePolicy.package?.name) { - const [, pkgInfo] = await Promise.all([ - ensureInstalledPackage({ - savedObjectsClient: soClient, - pkgName: packagePolicy.package.name, - esClient, - }), - getPackageInfo({ - savedObjectsClient: soClient, - pkgName: packagePolicy.package.name, - pkgVersion: packagePolicy.package.version, - }), - ]); + const pkgInfoPromise = getPackageInfo({ + savedObjectsClient: soClient, + pkgName: packagePolicy.package.name, + pkgVersion: packagePolicy.package.version, + }); + + let pkgInfo; + if (options?.skipEnsureInstalled) pkgInfo = await pkgInfoPromise; + else { + const [, packageInfo] = await Promise.all([ + ensureInstalledPackage({ + savedObjectsClient: soClient, + pkgName: packagePolicy.package.name, + esClient, + }), + pkgInfoPromise, + ]); + pkgInfo = packageInfo; + } // Check if it is a limited package, and if so, check that the corresponding agent policy does not // already contain a package policy for this package diff --git a/x-pack/plugins/fleet/server/services/preconfiguration.test.ts b/x-pack/plugins/fleet/server/services/preconfiguration.test.ts index 8a885f9c5c821e..94865f5d3d9171 100644 --- a/x-pack/plugins/fleet/server/services/preconfiguration.test.ts +++ b/x-pack/plugins/fleet/server/services/preconfiguration.test.ts @@ -10,6 +10,8 @@ import { elasticsearchServiceMock, savedObjectsClientMock } from 'src/core/serve import type { PreconfiguredAgentPolicy } from '../../common/types'; import type { AgentPolicy, NewPackagePolicy, Output } from '../types'; +import { AGENT_POLICY_SAVED_OBJECT_TYPE } from '../constants'; + import { ensurePreconfiguredPackagesAndPolicies } from './preconfiguration'; const mockInstalledPackages = new Map(); @@ -27,30 +29,31 @@ const mockDefaultOutput: Output = { function getPutPreconfiguredPackagesMock() { const soClient = savedObjectsClientMock.create(); soClient.find.mockImplementation(async ({ type, search }) => { - const attributes = mockConfiguredPolicies.get(search!.replace(/"/g, '')); - if (attributes) { - return { - saved_objects: [ - { - id: `mocked-${attributes.preconfiguration_id}`, - attributes, - type: type as string, - score: 1, - references: [], - }, - ], - total: 1, - page: 1, - per_page: 1, - }; - } else { - return { - saved_objects: [], - total: 0, - page: 1, - per_page: 0, - }; + if (type === AGENT_POLICY_SAVED_OBJECT_TYPE) { + const attributes = mockConfiguredPolicies.get(search!.replace(/"/g, '')); + if (attributes) { + return { + saved_objects: [ + { + id: `mocked-${attributes.preconfiguration_id}`, + attributes, + type: type as string, + score: 1, + references: [], + }, + ], + total: 1, + page: 1, + per_page: 1, + }; + } } + return { + saved_objects: [], + total: 0, + page: 1, + per_page: 0, + }; }); soClient.create.mockImplementation(async (type, policy) => { const attributes = policy as AgentPolicy; diff --git a/x-pack/plugins/fleet/server/services/preconfiguration.ts b/x-pack/plugins/fleet/server/services/preconfiguration.ts index 97480fcf6b2a86..3bd3169673b318 100644 --- a/x-pack/plugins/fleet/server/services/preconfiguration.ts +++ b/x-pack/plugins/fleet/server/services/preconfiguration.ts @@ -19,6 +19,9 @@ import type { PreconfiguredAgentPolicy, PreconfiguredPackage, } from '../../common'; +import { PRECONFIGURATION_DELETION_RECORD_SAVED_OBJECT_TYPE } from '../constants'; + +import { escapeSearchQueryPhrase } from './saved_object'; import { pkgToPkgKey } from './epm/registry'; import { getInstallation } from './epm/packages'; @@ -69,6 +72,21 @@ export async function ensurePreconfiguredPackagesAndPolicies( // Create policies specified in Kibana config const preconfiguredPolicies = await Promise.all( policies.map(async (preconfiguredAgentPolicy) => { + // Check to see if a preconfigured policy with the same preconfigurationId was already deleted by the user + const preconfigurationId = String(preconfiguredAgentPolicy.id); + const searchParams = { + searchFields: ['preconfiguration_id'], + search: escapeSearchQueryPhrase(preconfigurationId), + }; + const deletionRecords = await soClient.find({ + type: PRECONFIGURATION_DELETION_RECORD_SAVED_OBJECT_TYPE, + ...searchParams, + }); + const wasDeleted = deletionRecords.total > 0; + if (wasDeleted) { + return { created: false, deleted: preconfigurationId }; + } + const { created, policy } = await agentPolicyService.ensurePreconfiguredAgentPolicy( soClient, esClient, @@ -122,22 +140,32 @@ export async function ensurePreconfiguredPackagesAndPolicies( await addPreconfiguredPolicyPackages( soClient, esClient, - policy, + policy!, installedPackagePolicies!, defaultOutput ); // Add the is_managed flag after configuring package policies to avoid errors if (shouldAddIsManagedFlag) { - agentPolicyService.update(soClient, esClient, policy.id, { is_managed: true }); + agentPolicyService.update(soClient, esClient, policy!.id, { is_managed: true }); } } } return { - policies: preconfiguredPolicies.map((p) => ({ - id: p.policy.id, - updated_at: p.policy.updated_at, - })), + policies: preconfiguredPolicies.map((p) => + p.policy + ? { + id: p.policy.id, + updated_at: p.policy.updated_at, + } + : { + id: p.deleted, + updated_at: i18n.translate('xpack.fleet.preconfiguration.policyDeleted', { + defaultMessage: 'Preconfigured policy {id} was deleted; skipping creation', + values: { id: p.deleted }, + }), + } + ), packages: preconfiguredPackages.map((pkg) => pkgToPkgKey(pkg)), }; } @@ -155,20 +183,19 @@ async function addPreconfiguredPolicyPackages( >, defaultOutput: Output ) { - return await Promise.all( - installedPackagePolicies.map(async ({ installedPackage, name, description, inputs }) => - addPackageToAgentPolicy( - soClient, - esClient, - installedPackage, - agentPolicy, - defaultOutput, - name, - description, - (policy) => overridePackageInputs(policy, inputs) - ) - ) - ); + // Add packages synchronously to avoid overwriting + for (const { installedPackage, name, description, inputs } of installedPackagePolicies) { + await addPackageToAgentPolicy( + soClient, + esClient, + installedPackage, + agentPolicy, + defaultOutput, + name, + description, + (policy) => overridePackageInputs(policy, inputs) + ); + } } async function ensureInstalledPreconfiguredPackage( diff --git a/x-pack/plugins/fleet/server/services/setup.ts b/x-pack/plugins/fleet/server/services/setup.ts index b5e2326386e02d..6d98bc4263a16b 100644 --- a/x-pack/plugins/fleet/server/services/setup.ts +++ b/x-pack/plugins/fleet/server/services/setup.ts @@ -15,7 +15,9 @@ import type { PackagePolicy } from '../../common'; import { SO_SEARCH_LIMIT } from '../constants'; +import { appContextService } from './app_context'; import { agentPolicyService, addPackageToAgentPolicy } from './agent_policy'; +import { ensurePreconfiguredPackagesAndPolicies } from './preconfiguration'; import { outputService } from './output'; import { ensureInstalledDefaultPackages, @@ -34,7 +36,8 @@ const FLEET_ENROLL_USERNAME = 'fleet_enroll'; const FLEET_ENROLL_ROLE = 'fleet_enroll'; export interface SetupStatus { - isIntialized: true | undefined; + isInitialized: boolean; + preconfigurationError: { name: string; message: string } | undefined; } export async function setupIngestManager( @@ -48,17 +51,10 @@ async function createSetupSideEffects( soClient: SavedObjectsClientContract, esClient: ElasticsearchClient ): Promise { - const [ - installedPackages, - defaultOutput, - { created: defaultAgentPolicyCreated, policy: defaultAgentPolicy }, - { created: defaultFleetServerPolicyCreated, policy: defaultFleetServerPolicy }, - ] = await Promise.all([ + const [installedPackages, defaultOutput] = await Promise.all([ // packages installed by default ensureInstalledDefaultPackages(soClient, esClient), outputService.ensureDefaultOutput(soClient), - agentPolicyService.ensureDefaultAgentPolicy(soClient, esClient), - agentPolicyService.ensureDefaultFleetServerAgentPolicy(soClient, esClient), updateFleetRoleIfExists(esClient), settingsService.getSettings(soClient).catch((e: any) => { if (e.isBoom && e.output.statusCode === 404) { @@ -86,6 +82,37 @@ async function createSetupSideEffects( esClient, }); + const { agentPolicies: policiesOrUndefined, packages: packagesOrUndefined } = + appContextService.getConfig() ?? {}; + + const policies = policiesOrUndefined ?? []; + const packages = packagesOrUndefined ?? []; + let preconfigurationError; + + try { + await ensurePreconfiguredPackagesAndPolicies( + soClient, + esClient, + policies, + packages, + defaultOutput + ); + } catch (e) { + preconfigurationError = { name: e.name, message: e.message }; + } + + // Ensure the predefined default policies AFTER loading preconfigured policies. This allows the kibana config + // to override the default agent policies. + + const [ + { created: defaultAgentPolicyCreated, policy: defaultAgentPolicy }, + { created: defaultFleetServerPolicyCreated, policy: defaultFleetServerPolicy }, + ] = await Promise.all([ + agentPolicyService.ensureDefaultAgentPolicy(soClient, esClient), + agentPolicyService.ensureDefaultFleetServerAgentPolicy(soClient, esClient), + ]); + + // If we just created the default fleet server policy add the fleet server package if (defaultFleetServerPolicyCreated) { await addPackageToAgentPolicy( soClient, @@ -96,8 +123,6 @@ async function createSetupSideEffects( ); } - // If we just created the default fleet server policy add the fleet server package - // If we just created the default policy, ensure default packages are added to it if (defaultAgentPolicyCreated) { const agentPolicyWithPackagePolicies = await agentPolicyService.get( @@ -151,7 +176,7 @@ async function createSetupSideEffects( await ensureAgentActionPolicyChangeExists(soClient); - return { isIntialized: true }; + return { isInitialized: true, preconfigurationError }; } async function updateFleetRoleIfExists(esClient: ElasticsearchClient) { diff --git a/x-pack/plugins/graph/public/components/settings/url_template_form.tsx b/x-pack/plugins/graph/public/components/settings/url_template_form.tsx index 51dc310ababa2c..e89640ef2dbe21 100644 --- a/x-pack/plugins/graph/public/components/settings/url_template_form.tsx +++ b/x-pack/plugins/graph/public/components/settings/url_template_form.tsx @@ -216,15 +216,18 @@ export function UrlTemplateForm(props: UrlTemplateFormProps) { value={currentTemplate.url} onChange={(e) => { setValue('url', e.target.value); - setAutoformatUrl(false); + if ( + (e.nativeEvent as InputEvent)?.inputType !== 'insertFromPaste' || + !isKibanaUrl(e.target.value) + ) { + setAutoformatUrl(false); + } }} onPaste={(e) => { - e.preventDefault(); const pastedUrl = e.clipboardData.getData('text/plain'); if (isKibanaUrl(pastedUrl)) { setAutoformatUrl(true); } - setValue('url', pastedUrl); }} isInvalid={urlPlaceholderMissing || (touched.url && !currentTemplate.url)} /> diff --git a/x-pack/plugins/graph/server/routes/explore.ts b/x-pack/plugins/graph/server/routes/explore.ts index 9a9a267c40f32a..7109eee3b91114 100644 --- a/x-pack/plugins/graph/server/routes/explore.ts +++ b/x-pack/plugins/graph/server/routes/explore.ts @@ -67,6 +67,7 @@ export function registerExploreRoute({ cause.reason.includes('No support for examining floating point') || cause.reason.includes('Sample diversifying key must be a single valued-field') || cause.reason.includes('Failed to parse query') || + cause.reason.includes('Text fields are not optimised for operations') || cause.type === 'parsing_exception' ); }); diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/__snapshots__/policy_table.test.tsx.snap b/x-pack/plugins/index_lifecycle_management/__jest__/__snapshots__/policy_table.test.tsx.snap index 3db0aa43e0e87d..98e0e7e41b2360 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/__snapshots__/policy_table.test.tsx.snap +++ b/x-pack/plugins/index_lifecycle_management/__jest__/__snapshots__/policy_table.test.tsx.snap @@ -51,6 +51,7 @@ exports[`policy table should show empty state when there are not any policies 1` >
{ act(() => { diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/searchable_snapshots.test.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/searchable_snapshots.test.ts index e21793e650683a..ede40521deb97d 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/searchable_snapshots.test.ts +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/searchable_snapshots.test.ts @@ -77,8 +77,10 @@ describe(' searchable snapshots', () => { const repository = 'myRepo'; await actions.hot.setSearchableSnapshot(repository); await actions.cold.enable(true); + await actions.cold.setMinAgeValue('10'); await actions.cold.toggleSearchableSnapshot(true); await actions.frozen.enable(true); + await actions.frozen.setMinAgeValue('15'); await actions.savePolicy(); const latestRequest = server.requests[server.requests.length - 1]; @@ -96,8 +98,10 @@ describe(' searchable snapshots', () => { await actions.hot.setSearchableSnapshot('myRepo'); await actions.cold.enable(true); + await actions.cold.setMinAgeValue('10'); await actions.cold.toggleSearchableSnapshot(true); await actions.frozen.enable(true); + await actions.frozen.setMinAgeValue('15'); // We update the repository in one phase await actions.frozen.setSearchableSnapshot('changed'); @@ -161,6 +165,7 @@ describe(' searchable snapshots', () => { test('correctly sets snapshot repository default to "found-snapshots"', async () => { const { actions } = testBed; await actions.cold.enable(true); + await actions.cold.setMinAgeValue('10'); await actions.cold.toggleSearchableSnapshot(true); await actions.savePolicy(); const latestRequest = server.requests[server.requests.length - 1]; diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/form_validation/error_indicators.test.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/form_validation/error_indicators.test.ts index e2d937cf9c8dbb..86cf4ab5a48580 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/form_validation/error_indicators.test.ts +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/form_validation/error_indicators.test.ts @@ -56,7 +56,6 @@ describe(' error indicators', () => { const { actions } = testBed; // 0. No validation issues - expect(actions.hasGlobalErrorCallout()).toBe(false); expect(actions.hot.hasErrorIndicator()).toBe(false); expect(actions.warm.hasErrorIndicator()).toBe(false); expect(actions.cold.hasErrorIndicator()).toBe(false); @@ -65,7 +64,6 @@ describe(' error indicators', () => { await actions.hot.toggleForceMerge(true); await actions.hot.setForcemergeSegmentsCount('-22'); runTimers(); - expect(actions.hasGlobalErrorCallout()).toBe(true); expect(actions.hot.hasErrorIndicator()).toBe(true); expect(actions.warm.hasErrorIndicator()).toBe(false); expect(actions.cold.hasErrorIndicator()).toBe(false); @@ -75,7 +73,6 @@ describe(' error indicators', () => { await actions.warm.toggleForceMerge(true); await actions.warm.setForcemergeSegmentsCount('-22'); runTimers(); - expect(actions.hasGlobalErrorCallout()).toBe(true); expect(actions.hot.hasErrorIndicator()).toBe(true); expect(actions.warm.hasErrorIndicator()).toBe(true); expect(actions.cold.hasErrorIndicator()).toBe(false); @@ -84,7 +81,6 @@ describe(' error indicators', () => { await actions.cold.enable(true); await actions.cold.setReplicas('-33'); runTimers(); - expect(actions.hasGlobalErrorCallout()).toBe(true); expect(actions.hot.hasErrorIndicator()).toBe(true); expect(actions.warm.hasErrorIndicator()).toBe(true); expect(actions.cold.hasErrorIndicator()).toBe(true); @@ -92,7 +88,6 @@ describe(' error indicators', () => { // 4. Fix validation issue in hot await actions.hot.setForcemergeSegmentsCount('1'); runTimers(); - expect(actions.hasGlobalErrorCallout()).toBe(true); expect(actions.hot.hasErrorIndicator()).toBe(false); expect(actions.warm.hasErrorIndicator()).toBe(true); expect(actions.cold.hasErrorIndicator()).toBe(true); @@ -100,7 +95,6 @@ describe(' error indicators', () => { // 5. Fix validation issue in warm await actions.warm.setForcemergeSegmentsCount('1'); runTimers(); - expect(actions.hasGlobalErrorCallout()).toBe(true); expect(actions.hot.hasErrorIndicator()).toBe(false); expect(actions.warm.hasErrorIndicator()).toBe(false); expect(actions.cold.hasErrorIndicator()).toBe(true); @@ -108,13 +102,12 @@ describe(' error indicators', () => { // 6. Fix validation issue in cold await actions.cold.setReplicas('1'); runTimers(); - expect(actions.hasGlobalErrorCallout()).toBe(false); expect(actions.hot.hasErrorIndicator()).toBe(false); expect(actions.warm.hasErrorIndicator()).toBe(false); expect(actions.cold.hasErrorIndicator()).toBe(false); }); - test('global error callout should show if there are any form errors', async () => { + test('global error callout should show, after clicking the "Save" button, if there are any form errors', async () => { const { actions } = testBed; expect(actions.hasGlobalErrorCallout()).toBe(false); @@ -125,6 +118,7 @@ describe(' error indicators', () => { await actions.saveAsNewPolicy(true); await actions.setPolicyName(''); runTimers(); + await actions.savePolicy(); expect(actions.hasGlobalErrorCallout()).toBe(true); expect(actions.hot.hasErrorIndicator()).toBe(false); @@ -136,6 +130,7 @@ describe(' error indicators', () => { const { actions } = testBed; await actions.cold.enable(true); + await actions.cold.setMinAgeValue('7'); // introduce validation error await actions.cold.setSearchableSnapshot(''); runTimers(); diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/form_validation/timing.test.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/form_validation/timing.test.ts index 52009902ab8021..c0b30efe150c4f 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/form_validation/timing.test.ts +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/form_validation/timing.test.ts @@ -81,6 +81,10 @@ describe(' timing validation', () => { test(`${phase}: ${name}`, async () => { const { actions } = testBed; await actions[phase as 'warm' | 'cold' | 'delete' | 'frozen'].enable(true); + // 1. We first set as dummy value to have a starting min_age value + await actions[phase as 'warm' | 'cold' | 'delete' | 'frozen'].setMinAgeValue('111'); + // 2. At this point we are sure there will be a change of value and that any validation + // will be displayed under the field. await actions[phase as 'warm' | 'cold' | 'delete' | 'frozen'].setMinAgeValue(value); runTimers(); @@ -89,4 +93,52 @@ describe(' timing validation', () => { }); }); }); + + test('should validate that min_age is equal or greater than previous phase min_age', async () => { + const { actions, form } = testBed; + await actions.warm.enable(true); + await actions.cold.enable(true); + await actions.frozen.enable(true); + await actions.delete.enable(true); + + await actions.warm.setMinAgeValue('10'); + + await actions.cold.setMinAgeValue('9'); + runTimers(); + expect(form.getErrorsMessages('cold-phase')).toEqual([ + 'Must be greater or equal than the warm phase value (10d)', + ]); + + await actions.frozen.setMinAgeValue('8'); + runTimers(); + expect(form.getErrorsMessages('frozen-phase')).toEqual([ + 'Must be greater or equal than the cold phase value (9d)', + ]); + + await actions.delete.setMinAgeValue('7'); + runTimers(); + expect(form.getErrorsMessages('delete-phaseContent')).toEqual([ + 'Must be greater or equal than the frozen phase value (8d)', + ]); + + // Disable the warm phase + await actions.warm.enable(false); + + // No more error for the cold phase + expect(form.getErrorsMessages('cold-phase')).toEqual([]); + + // Change to smaller unit for cold phase + await actions.cold.setMinAgeUnits('h'); + + // No more error for the frozen phase... + expect(form.getErrorsMessages('frozen-phase')).toEqual([]); + // ...but the delete phase has still the error + expect(form.getErrorsMessages('delete-phaseContent')).toEqual([ + 'Must be greater or equal than the frozen phase value (8d)', + ]); + + await actions.delete.setMinAgeValue('9'); + // No more error for the delete phase + expect(form.getErrorsMessages('delete-phaseContent')).toEqual([]); + }); }); diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/serialization/policy_serialization.test.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/serialization/policy_serialization.test.ts index aa176fe3b188fb..7a0571e4a7cb2b 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/serialization/policy_serialization.test.ts +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/serialization/policy_serialization.test.ts @@ -87,7 +87,7 @@ describe(' serialization', () => { unknown_setting: true, }, }, - min_age: '0d', + min_age: '10d', }, }, }); @@ -264,6 +264,7 @@ describe(' serialization', () => { test('default values', async () => { const { actions } = testBed; await actions.warm.enable(true); + await actions.warm.setMinAgeValue('11'); await actions.savePolicy(); const latestRequest = server.requests[server.requests.length - 1]; const warmPhase = JSON.parse(JSON.parse(latestRequest.requestBody).body).phases.warm; @@ -274,7 +275,7 @@ describe(' serialization', () => { "priority": 50, }, }, - "min_age": "0d", + "min_age": "11d", } `); }); @@ -282,6 +283,7 @@ describe(' serialization', () => { test('setting all values', async () => { const { actions } = testBed; await actions.warm.enable(true); + await actions.warm.setMinAgeValue('11'); await actions.warm.setDataAllocation('node_attrs'); await actions.warm.setSelectedNodeAttribute('test:123'); await actions.warm.setReplicas('123'); @@ -329,7 +331,7 @@ describe(' serialization', () => { "number_of_shards": 123, }, }, - "min_age": "0d", + "min_age": "11d", }, }, } @@ -401,6 +403,7 @@ describe(' serialization', () => { const { actions } = testBed; await actions.cold.enable(true); + await actions.cold.setMinAgeValue('11'); await actions.savePolicy(); const latestRequest = server.requests[server.requests.length - 1]; const entirePolicy = JSON.parse(JSON.parse(latestRequest.requestBody).body); @@ -411,7 +414,7 @@ describe(' serialization', () => { "priority": 0, }, }, - "min_age": "0d", + "min_age": "11d", } `); }); @@ -471,6 +474,7 @@ describe(' serialization', () => { test('setting searchable snapshot', async () => { const { actions } = testBed; await actions.cold.enable(true); + await actions.cold.setMinAgeValue('10'); await actions.cold.setSearchableSnapshot('my-repo'); await actions.savePolicy(); const latestRequest2 = server.requests[server.requests.length - 1]; @@ -485,6 +489,7 @@ describe(' serialization', () => { test('default value', async () => { const { actions } = testBed; await actions.frozen.enable(true); + await actions.frozen.setMinAgeValue('13'); await actions.frozen.setSearchableSnapshot('myRepo'); await actions.savePolicy(); @@ -492,7 +497,7 @@ describe(' serialization', () => { const latestRequest = server.requests[server.requests.length - 1]; const entirePolicy = JSON.parse(JSON.parse(latestRequest.requestBody).body); expect(entirePolicy.phases.frozen).toEqual({ - min_age: '0d', + min_age: '13d', actions: { searchable_snapshot: { snapshot_repository: 'myRepo' }, }, diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/form_errors_callout.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/form_errors_callout.tsx index b72ec1df2f26b3..478d1af69f81ce 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/form_errors_callout.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/form_errors_callout.tsx @@ -25,9 +25,10 @@ const i18nTexts = { export const FormErrorsCallout: FunctionComponent = () => { const { errors: { hasErrors }, + isFormSubmitted, } = useFormErrorsContext(); - if (!hasErrors) { + if (!isFormSubmitted || !hasErrors) { return null; } diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/min_age_field/min_age_field.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/min_age_field/min_age_field.tsx index 3fe2f08cb4066e..136a37140cca7c 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/min_age_field/min_age_field.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/min_age_field/min_age_field.tsx @@ -6,8 +6,9 @@ */ import { i18n } from '@kbn/i18n'; -import React, { FunctionComponent } from 'react'; +import React, { FunctionComponent, useEffect } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; +import { get } from 'lodash'; import { EuiFieldNumber, @@ -20,10 +21,9 @@ import { EuiIconTip, } from '@elastic/eui'; -import { getFieldValidityAndErrorMessage } from '../../../../../../../shared_imports'; - -import { UseField, useConfiguration } from '../../../../form'; - +import { getFieldValidityAndErrorMessage, useFormData } from '../../../../../../../shared_imports'; +import { UseField, useConfiguration, useGlobalFields } from '../../../../form'; +import { getPhaseMinAgeInMilliseconds } from '../../../../lib'; import { getUnitsAriaLabelForPhase, getTimingLabelForPhase } from './util'; type PhaseWithMinAgeAction = 'warm' | 'cold' | 'delete'; @@ -81,9 +81,43 @@ interface Props { } export const MinAgeField: FunctionComponent = ({ phase }): React.ReactElement => { + const minAgeValuePath = `phases.${phase}.min_age`; + const minAgeUnitPath = `_meta.${phase}.minAgeUnit`; + const { isUsingRollover } = useConfiguration(); + const globalFields = useGlobalFields(); + + const { setValue: setMillisecondValue } = globalFields[ + `${phase}MinAgeMilliSeconds` as 'coldMinAgeMilliSeconds' + ]; + const [formData] = useFormData({ watch: [minAgeValuePath, minAgeUnitPath] }); + const minAgeValue = get(formData, minAgeValuePath); + const minAgeUnit = get(formData, minAgeUnitPath); + + useEffect(() => { + // Whenever the min_age value of the field OR the min_age unit + // changes, we update the corresponding millisecond global field for the phase + if (minAgeValue === undefined) { + return; + } + + const milliseconds = + minAgeValue.trim() === '' ? -1 : getPhaseMinAgeInMilliseconds(minAgeValue, minAgeUnit); + + setMillisecondValue(milliseconds); + }, [minAgeValue, minAgeUnit, setMillisecondValue]); + + useEffect(() => { + return () => { + // When unmounting (meaning we have disabled the phase), we remove + // the millisecond value so the next time we enable the phase it will + // be updated and trigger the validation + setMillisecondValue(-1); + }; + }, [setMillisecondValue]); + return ( - + {(field) => { const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field); return ( @@ -118,7 +152,7 @@ export const MinAgeField: FunctionComponent = ({ phase }): React.ReactEle /> - + {(unitField) => { const { isInvalid: isUnitFieldInvalid } = getFieldValidityAndErrorMessage( unitField diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/deserializer.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/deserializer.ts index af571d16ca8c5e..356a5b4561d0a6 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/deserializer.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/deserializer.ts @@ -46,20 +46,24 @@ export const createDeserializer = (isCloudEnabled: boolean) => ( bestCompression: warm?.actions?.forcemerge?.index_codec === 'best_compression', dataTierAllocationType: determineDataTierAllocationType(warm?.actions), readonlyEnabled: Boolean(warm?.actions?.readonly), + minAgeToMilliSeconds: -1, }, cold: { enabled: Boolean(cold), dataTierAllocationType: determineDataTierAllocationType(cold?.actions), freezeEnabled: Boolean(cold?.actions?.freeze), readonlyEnabled: Boolean(cold?.actions?.readonly), + minAgeToMilliSeconds: -1, }, frozen: { enabled: Boolean(frozen), dataTierAllocationType: determineDataTierAllocationType(frozen?.actions), freezeEnabled: Boolean(frozen?.actions?.freeze), + minAgeToMilliSeconds: -1, }, delete: { enabled: Boolean(deletePhase), + minAgeToMilliSeconds: -1, }, searchableSnapshot: { repository: defaultRepository, diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/form_errors_context.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/form_errors_context.tsx index b4aab0ffdea600..70199e08aa3084 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/form_errors_context.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/form_errors_context.tsx @@ -38,6 +38,7 @@ interface ContextValue { errors: Errors; addError(phase: PhasesAndOther, fieldPath: string, errorMessages: string[]): void; clearError(phase: PhasesAndOther, fieldPath: string): void; + isFormSubmitted: boolean; } const FormErrorsContext = createContext(null as any); @@ -56,7 +57,7 @@ export const FormErrorsProvider: FunctionComponent = ({ children }) => { const [errors, setErrors] = useState(createEmptyErrors); const form = useFormContext(); - const { getErrors: getFormErrors } = form; + const { getErrors: getFormErrors, isSubmitted } = form; const addError: ContextValue['addError'] = useCallback( (phase, fieldPath, errorMessages) => { @@ -83,9 +84,9 @@ export const FormErrorsProvider: FunctionComponent = ({ children }) => { } = previousErrors; const nextHasErrors = - Object.keys(restOfPhaseErrors).length === 0 && + Object.keys(restOfPhaseErrors).length > 0 || Object.values(otherPhases).some((phaseErrors) => { - return !!Object.keys(phaseErrors).length; + return Object.keys(phaseErrors).length > 0; }); return { @@ -107,6 +108,7 @@ export const FormErrorsProvider: FunctionComponent = ({ children }) => { errors, addError, clearError, + isFormSubmitted: isSubmitted, }} > {children} diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/global_fields_context.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/global_fields_context.tsx index 30a00390a18cca..94b804c1ce5324 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/global_fields_context.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/global_fields_context.tsx @@ -14,6 +14,10 @@ import { UseMultiFields, FieldHook, FieldConfig } from '../../../../shared_impor interface GlobalFieldsTypes { deleteEnabled: boolean; searchableSnapshotRepo: string; + warmMinAgeMilliSeconds: number; + coldMinAgeMilliSeconds: number; + frozenMinAgeMilliSeconds: number; + deleteMinAgeMilliSeconds: number; } type GlobalFields = { @@ -32,6 +36,18 @@ export const globalFields: Record< searchableSnapshotRepo: { path: '_meta.searchableSnapshot.repository', }, + warmMinAgeMilliSeconds: { + path: '_meta.warm.minAgeToMilliSeconds', + }, + coldMinAgeMilliSeconds: { + path: '_meta.cold.minAgeToMilliSeconds', + }, + frozenMinAgeMilliSeconds: { + path: '_meta.frozen.minAgeToMilliSeconds', + }, + deleteMinAgeMilliSeconds: { + path: '_meta.delete.minAgeToMilliSeconds', + }, }; export const GlobalFieldsProvider: FunctionComponent = ({ children }) => { diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/schema.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/schema.ts index ce7b36d69a32e7..93af58644cc060 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/schema.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/schema.ts @@ -10,12 +10,14 @@ import { i18n } from '@kbn/i18n'; import { FormSchema, fieldValidators } from '../../../../shared_imports'; import { defaultIndexPriority } from '../../../constants'; import { ROLLOVER_FORM_PATHS, CLOUD_DEFAULT_REPO } from '../constants'; +import { MinAgePhase } from '../types'; import { i18nTexts } from '../i18n_texts'; import { ifExistsNumberGreaterThanZero, ifExistsNumberNonNegative, rolloverThresholdsValidator, integerValidator, + minAgeGreaterThanPreviousPhase, } from './validations'; const rolloverFormPaths = Object.values(ROLLOVER_FORM_PATHS); @@ -117,8 +119,11 @@ const getPriorityField = (phase: 'hot' | 'warm' | 'cold' | 'frozen') => ({ serializer: serializers.stringToNumber, }); -const getMinAgeField = (defaultValue: string = '0') => ({ +const getMinAgeField = (phase: MinAgePhase, defaultValue?: string) => ({ defaultValue, + // By passing an empty array we make sure to *not* trigger the validation when the field value changes. + // The validation will be triggered when the millisecond variant (in the _meta) is updated (in sync) + fieldsToValidateOnChange: [], validations: [ { validator: emptyField(i18nTexts.editPolicy.errors.numberRequired), @@ -129,8 +134,12 @@ const getMinAgeField = (defaultValue: string = '0') => ({ { validator: integerValidator, }, + { + validator: minAgeGreaterThanPreviousPhase(phase), + }, ], }); + export const getSchema = (isCloudEnabled: boolean): FormSchema => ({ _meta: { hot: { @@ -173,6 +182,15 @@ export const getSchema = (isCloudEnabled: boolean): FormSchema => ({ minAgeUnit: { defaultValue: 'd', }, + minAgeToMilliSeconds: { + defaultValue: -1, + fieldsToValidateOnChange: [ + 'phases.warm.min_age', + 'phases.cold.min_age', + 'phases.frozen.min_age', + 'phases.delete.min_age', + ], + }, bestCompression: { label: i18nTexts.editPolicy.bestCompressionFieldLabel, }, @@ -208,6 +226,14 @@ export const getSchema = (isCloudEnabled: boolean): FormSchema => ({ minAgeUnit: { defaultValue: 'd', }, + minAgeToMilliSeconds: { + defaultValue: -1, + fieldsToValidateOnChange: [ + 'phases.cold.min_age', + 'phases.frozen.min_age', + 'phases.delete.min_age', + ], + }, dataTierAllocationType: { label: i18nTexts.editPolicy.allocationTypeOptionsFieldLabel, }, @@ -232,6 +258,10 @@ export const getSchema = (isCloudEnabled: boolean): FormSchema => ({ minAgeUnit: { defaultValue: 'd', }, + minAgeToMilliSeconds: { + defaultValue: -1, + fieldsToValidateOnChange: ['phases.frozen.min_age', 'phases.delete.min_age'], + }, dataTierAllocationType: { label: i18nTexts.editPolicy.allocationTypeOptionsFieldLabel, }, @@ -250,6 +280,10 @@ export const getSchema = (isCloudEnabled: boolean): FormSchema => ({ minAgeUnit: { defaultValue: 'd', }, + minAgeToMilliSeconds: { + defaultValue: -1, + fieldsToValidateOnChange: ['phases.delete.min_age'], + }, }, searchableSnapshot: { repository: { @@ -324,7 +358,7 @@ export const getSchema = (isCloudEnabled: boolean): FormSchema => ({ }, }, warm: { - min_age: getMinAgeField(), + min_age: getMinAgeField('warm'), actions: { allocate: { number_of_replicas: numberOfReplicasField, @@ -341,7 +375,7 @@ export const getSchema = (isCloudEnabled: boolean): FormSchema => ({ }, }, cold: { - min_age: getMinAgeField(), + min_age: getMinAgeField('cold'), actions: { allocate: { number_of_replicas: numberOfReplicasField, @@ -353,7 +387,7 @@ export const getSchema = (isCloudEnabled: boolean): FormSchema => ({ }, }, frozen: { - min_age: getMinAgeField(), + min_age: getMinAgeField('frozen'), actions: { allocate: { number_of_replicas: numberOfReplicasField, @@ -365,7 +399,7 @@ export const getSchema = (isCloudEnabled: boolean): FormSchema => ({ }, }, delete: { - min_age: getMinAgeField('365'), + min_age: getMinAgeField('delete', '365'), actions: { wait_for_snapshot: { policy: { diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/validations.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/validations.ts index ce85913d5db749..70a58ad1441926 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/validations.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/validations.ts @@ -4,6 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { i18n } from '@kbn/i18n'; import { fieldValidators, ValidationFunc, ValidationConfig } from '../../../../shared_imports'; @@ -11,7 +12,7 @@ import { ROLLOVER_FORM_PATHS } from '../constants'; import { i18nTexts } from '../i18n_texts'; import { PolicyFromES } from '../../../../../common/types'; -import { FormInternal } from '../types'; +import { FormInternal, MinAgePhase } from '../types'; const { numberGreaterThanField, containsCharsField, emptyField, startsWithField } = fieldValidators; @@ -149,3 +150,117 @@ export const createPolicyNameValidations = ({ }, ]; }; + +/** + * This validator guarantees that the user does not specify a min_age + * value smaller that the min_age of a previous phase. + * For example, the user can't define '5 days' for cold phase if the + * warm phase is set to '10 days'. + */ +export const minAgeGreaterThanPreviousPhase = (phase: MinAgePhase) => ({ + formData, +}: { + formData: Record; +}) => { + if (phase === 'warm') { + return; + } + + const getValueFor = (_phase: MinAgePhase) => { + const milli = formData[`_meta.${_phase}.minAgeToMilliSeconds`]; + + const esFormat = + milli >= 0 + ? formData[`phases.${_phase}.min_age`] + formData[`_meta.${_phase}.minAgeUnit`] + : undefined; + + return { + milli, + esFormat, + }; + }; + + const minAgeValues = { + warm: getValueFor('warm'), + cold: getValueFor('cold'), + frozen: getValueFor('frozen'), + delete: getValueFor('delete'), + }; + + const i18nErrors = { + greaterThanWarmPhase: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.minAgeSmallerThanWarmPhaseError', + { + defaultMessage: 'Must be greater or equal than the warm phase value ({value})', + values: { + value: minAgeValues.warm.esFormat, + }, + } + ), + greaterThanColdPhase: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.minAgeSmallerThanColdPhaseError', + { + defaultMessage: 'Must be greater or equal than the cold phase value ({value})', + values: { + value: minAgeValues.cold.esFormat, + }, + } + ), + greaterThanFrozenPhase: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.minAgeSmallerThanFrozenPhaseError', + { + defaultMessage: 'Must be greater or equal than the frozen phase value ({value})', + values: { + value: minAgeValues.frozen.esFormat, + }, + } + ), + }; + + if (phase === 'cold') { + if (minAgeValues.warm.milli >= 0 && minAgeValues.cold.milli < minAgeValues.warm.milli) { + return { + message: i18nErrors.greaterThanWarmPhase, + }; + } + return; + } + + if (phase === 'frozen') { + if (minAgeValues.cold.milli >= 0 && minAgeValues.frozen.milli < minAgeValues.cold.milli) { + return { + message: i18nErrors.greaterThanColdPhase, + }; + } else if ( + minAgeValues.warm.milli >= 0 && + minAgeValues.frozen.milli < minAgeValues.warm.milli + ) { + return { + message: i18nErrors.greaterThanWarmPhase, + }; + } + return; + } + + if (phase === 'delete') { + if (minAgeValues.frozen.milli >= 0 && minAgeValues.delete.milli < minAgeValues.frozen.milli) { + return { + message: i18nErrors.greaterThanFrozenPhase, + }; + } else if ( + minAgeValues.cold.milli >= 0 && + minAgeValues.delete.milli < minAgeValues.cold.milli + ) { + return { + message: i18nErrors.greaterThanColdPhase, + }; + } else if ( + minAgeValues.warm.milli >= 0 && + minAgeValues.delete.milli < minAgeValues.warm.milli + ) { + return { + message: i18nErrors.greaterThanWarmPhase, + }; + } + } +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/lib/absolute_timing_to_relative_timing.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/lib/absolute_timing_to_relative_timing.ts index 5d71bc057966e2..9d55f542db4c47 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/lib/absolute_timing_to_relative_timing.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/lib/absolute_timing_to_relative_timing.ts @@ -24,12 +24,10 @@ import moment from 'moment'; import { splitSizeAndUnits } from '../../../lib/policies'; -import { FormInternal } from '../types'; +import { FormInternal, MinAgePhase } from '../types'; /* -===- Private functions and types -===- */ -type MinAgePhase = 'warm' | 'cold' | 'frozen' | 'delete'; - type Phase = 'hot' | MinAgePhase; const phaseOrder: Phase[] = ['hot', 'warm', 'cold', 'frozen', 'delete']; @@ -44,9 +42,9 @@ const getMinAge = (phase: MinAgePhase, formData: FormInternal) => ({ * See https://www.elastic.co/guide/en/elasticsearch/reference/current/common-options.html#date-math * for all date math values. ILM policies also support "micros" and "nanos". */ -const getPhaseMinAgeInMilliseconds = (phase: { min_age: string }): number => { +export const getPhaseMinAgeInMilliseconds = (size: string, units: string): number => { let milliseconds: number; - const { units, size } = splitSizeAndUnits(phase.min_age); + if (units === 'micros') { milliseconds = parseInt(size, 10) / 1e3; } else if (units === 'nanos') { @@ -126,7 +124,10 @@ export const calculateRelativeFromAbsoluteMilliseconds = ( // If we have a next phase, calculate the timing between this phase and the next if (nextPhase && inputs[nextPhase]?.min_age) { - nextPhaseMinAge = getPhaseMinAgeInMilliseconds(inputs[nextPhase] as { min_age: string }); + const { units, size } = splitSizeAndUnits( + (inputs[nextPhase] as { min_age: string }).min_age + ); + nextPhaseMinAge = getPhaseMinAgeInMilliseconds(size, units); } return { diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/lib/index.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/lib/index.ts index 19d87532f2bfe9..607c62cd3ce8be 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/lib/index.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/lib/index.ts @@ -8,6 +8,7 @@ export { calculateRelativeFromAbsoluteMilliseconds, formDataToAbsoluteTimings, + getPhaseMinAgeInMilliseconds, AbsoluteTimings, PhaseAgeInMilliseconds, RelativePhaseTimingInMs, diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/types.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/types.ts index 5cc631c5d95c0f..688d2ecfaa4a2c 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/types.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/types.ts @@ -15,8 +15,11 @@ export interface DataAllocationMetaFields { export interface MinAgeField { minAgeUnit?: string; + minAgeToMilliSeconds: number; } +export type MinAgePhase = 'warm' | 'cold' | 'frozen' | 'delete'; + export interface ForcemergeFields { bestCompression: boolean; } diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/lib/documentation.ts b/x-pack/plugins/index_management/public/application/components/component_templates/lib/documentation.ts index 5c839262b62ed2..185e521e4a5b84 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/lib/documentation.ts +++ b/x-pack/plugins/index_management/public/application/components/component_templates/lib/documentation.ts @@ -7,14 +7,11 @@ import { DocLinksStart } from 'src/core/public'; -// eslint-disable-next-line @typescript-eslint/naming-convention -export const getDocumentation = ({ ELASTIC_WEBSITE_URL, DOC_LINK_VERSION }: DocLinksStart) => { - const docsBase = `${ELASTIC_WEBSITE_URL}guide/en`; - const esDocsBase = `${docsBase}/elasticsearch/reference/${DOC_LINK_VERSION}`; - +export const getDocumentation = ({ links }: DocLinksStart) => { + const esDocsBase = links.elasticsearch.docsBase; return { esDocsBase, - componentTemplates: `${esDocsBase}/indices-component-template.html`, - componentTemplatesMetadata: `${esDocsBase}/indices-component-template.html#component-templates-metadata`, + componentTemplates: links.apis.putComponentTemplate, + componentTemplatesMetadata: links.apis.putComponentTemplateMetadata, }; }; diff --git a/x-pack/plugins/index_management/public/application/services/documentation.ts b/x-pack/plugins/index_management/public/application/services/documentation.ts index c81c71a32e7e23..3d6c6edf986e8e 100644 --- a/x-pack/plugins/index_management/public/application/services/documentation.ts +++ b/x-pack/plugins/index_management/public/application/services/documentation.ts @@ -10,15 +10,98 @@ import { DataType } from '../components/mappings_editor/types'; import { TYPE_DEFINITION } from '../components/mappings_editor/constants'; class DocumentationService { + private dataStreams: string = ''; private esDocsBase: string = ''; - private kibanaDocsBase: string = ''; - + private indexManagement: string = ''; + private indexSettings: string = ''; + private indexTemplates: string = ''; + private indexV1: string = ''; + private mapping: string = ''; + private mappingAnalyzer: string = ''; + private mappingCoerce: string = ''; + private mappingCopyTo: string = ''; + private mappingDocValues: string = ''; + private mappingDynamic: string = ''; + private mappingDynamicFields: string = ''; + private mappingDynamicTemplates: string = ''; + private mappingEagerGlobalOrdinals: string = ''; + private mappingEnabled: string = ''; + private mappingFieldData: string = ''; + private mappingFieldDataFilter: string = ''; + private mappingFieldDataTypes: string = ''; + private mappingFieldDataEnable: string = ''; + private mappingFormat: string = ''; + private mappingIgnoreAbove: string = ''; + private mappingIgnoreMalformed: string = ''; + private mappingIndex: string = ''; + private mappingIndexOptions: string = ''; + private mappingIndexPhrases: string = ''; + private mappingIndexPrefixes: string = ''; + private mappingJoinFieldsPerformance: string = ''; + private mappingMeta: string = ''; + private mappingMetaFields: string = ''; + private mappingNormalizer: string = ''; + private mappingNorms: string = ''; + private mappingNullValue: string = ''; + private mappingParameters: string = ''; + private mappingPositionIncrementGap: string = ''; + private mappingRankFeatureFields: string = ''; + private mappingRouting: string = ''; + private mappingSimilarity: string = ''; + private mappingSourceFields: string = ''; + private mappingSourceFieldsDisable: string = ''; + private mappingStore: string = ''; + private mappingTermVector: string = ''; + private mappingTypesRemoval: string = ''; + private percolate: string = ''; + private runtimeFields: string = ''; public setup(docLinks: DocLinksStart): void { - const { DOC_LINK_VERSION, ELASTIC_WEBSITE_URL } = docLinks; - const docsBase = `${ELASTIC_WEBSITE_URL}guide/en`; - - this.esDocsBase = `${docsBase}/elasticsearch/reference/${DOC_LINK_VERSION}`; - this.kibanaDocsBase = `${docsBase}/kibana/${DOC_LINK_VERSION}`; + const { links } = docLinks; + this.dataStreams = links.elasticsearch.dataStreams; + this.esDocsBase = links.elasticsearch.docsBase; + this.indexManagement = links.management.indexManagement; + this.indexSettings = links.elasticsearch.indexSettings; + this.indexTemplates = links.elasticsearch.indexTemplates; + this.indexV1 = links.apis.putIndexTemplateV1; + this.mapping = links.elasticsearch.mapping; + this.mappingAnalyzer = links.elasticsearch.mappingAnalyzer; + this.mappingCoerce = links.elasticsearch.mappingCoerce; + this.mappingCopyTo = links.elasticsearch.mappingCopyTo; + this.mappingDocValues = links.elasticsearch.mappingDocValues; + this.mappingDynamic = links.elasticsearch.mappingDynamic; + this.mappingDynamicFields = links.elasticsearch.mappingDynamicFields; + this.mappingDynamicTemplates = links.elasticsearch.mappingDynamicTemplates; + this.mappingEagerGlobalOrdinals = links.elasticsearch.mappingEagerGlobalOrdinals; + this.mappingEnabled = links.elasticsearch.mappingEnabled; + this.mappingFieldData = links.elasticsearch.mappingFieldData; + this.mappingFieldDataTypes = links.elasticsearch.mappingFieldDataTypes; + this.mappingFieldDataEnable = links.elasticsearch.mappingFieldDataEnable; + this.mappingFieldDataFilter = links.elasticsearch.mappingFieldDataFilter; + this.mappingFormat = links.elasticsearch.mappingFormat; + this.mappingIgnoreAbove = links.elasticsearch.mappingIgnoreAbove; + this.mappingIgnoreMalformed = links.elasticsearch.mappingIgnoreMalformed; + this.mappingIndex = links.elasticsearch.mappingIndex; + this.mappingIndexOptions = links.elasticsearch.mappingIndexOptions; + this.mappingIndexPhrases = links.elasticsearch.mappingIndexPhrases; + this.mappingIndexPrefixes = links.elasticsearch.mappingIndexPrefixes; + this.mappingJoinFieldsPerformance = links.elasticsearch.mappingJoinFieldsPerformance; + this.mappingMeta = links.elasticsearch.mappingMeta; + this.mappingMetaFields = links.elasticsearch.mappingMetaFields; + this.mappingNormalizer = links.elasticsearch.mappingNormalizer; + this.mappingNorms = links.elasticsearch.mappingNorms; + this.mappingNullValue = links.elasticsearch.mappingNullValue; + this.mappingParameters = links.elasticsearch.mappingParameters; + this.mappingPositionIncrementGap = links.elasticsearch.mappingPositionIncrementGap; + this.mappingRankFeatureFields = links.elasticsearch.mappingRankFeatureFields; + this.mappingRouting = links.elasticsearch.mappingRouting; + this.mappingSimilarity = links.elasticsearch.mappingSimilarity; + this.mappingSourceFields = links.elasticsearch.mappingSourceFields; + this.mappingSourceFieldsDisable = links.elasticsearch.mappingSourceFieldsDisable; + this.mappingStore = links.elasticsearch.mappingStore; + this.mappingTermVector = links.elasticsearch.mappingTermVector; + this.mappingTypesRemoval = links.elasticsearch.mappingTypesRemoval; + this.percolate = links.query.percolate; + this.runtimeFields = links.runtimeFields.overview; } public getEsDocsBase() { @@ -26,29 +109,27 @@ class DocumentationService { } public getSettingsDocumentationLink() { - return `${this.esDocsBase}/index-modules.html#index-modules-settings`; + return this.indexSettings; } public getMappingDocumentationLink() { - return `${this.esDocsBase}/mapping.html`; + return this.mapping; } public getRoutingLink() { - return `${this.esDocsBase}/mapping-routing-field.html`; + return this.mappingRouting; } public getDataStreamsDocumentationLink() { - return `${this.esDocsBase}/data-streams.html`; + return this.dataStreams; } public getTemplatesDocumentationLink(isLegacy = false) { - return isLegacy - ? `${this.esDocsBase}/indices-templates-v1.html` - : `${this.esDocsBase}/indices-templates.html`; + return isLegacy ? this.indexV1 : this.indexTemplates; } public getIdxMgmtDocumentationLink() { - return `${this.kibanaDocsBase}/managing-indices.html`; + return this.indexManagement; } public getTypeDocLink = (type: DataType, docType = 'main'): string | undefined => { @@ -63,157 +144,154 @@ class DocumentationService { } return `${this.esDocsBase}${typeDefinition.documentation[docType]}`; }; - public getMappingTypesLink() { - return `${this.esDocsBase}/mapping-types.html`; + return this.mappingFieldDataTypes; } - public getDynamicMappingLink() { - return `${this.esDocsBase}/dynamic-field-mapping.html`; + return this.mappingDynamicFields; } - public getPercolatorQueryLink() { - return `${this.esDocsBase}/query-dsl-percolate-query.html`; + return this.percolate; } public getRankFeatureQueryLink() { - return `${this.esDocsBase}/rank-feature.html`; + return this.mappingRankFeatureFields; } public getMetaFieldLink() { - return `${this.esDocsBase}/mapping-meta-field.html`; + return this.mappingMetaFields; } public getDynamicTemplatesLink() { - return `${this.esDocsBase}/dynamic-templates.html`; + return this.mappingDynamicTemplates; } public getMappingSourceFieldLink() { - return `${this.esDocsBase}/mapping-source-field.html`; + return this.mappingSourceFields; } public getDisablingMappingSourceFieldLink() { - return `${this.esDocsBase}/mapping-source-field.html#disable-source-field`; + return this.mappingSourceFieldsDisable; } public getNullValueLink() { - return `${this.esDocsBase}/null-value.html`; + return this.mappingNullValue; } public getTermVectorLink() { - return `${this.esDocsBase}/term-vector.html`; + return this.mappingTermVector; } public getStoreLink() { - return `${this.esDocsBase}/mapping-store.html`; + return this.mappingStore; } public getSimilarityLink() { - return `${this.esDocsBase}/similarity.html`; + return this.mappingSimilarity; } public getNormsLink() { - return `${this.esDocsBase}/norms.html`; + return this.mappingNorms; } public getIndexLink() { - return `${this.esDocsBase}/mapping-index.html`; + return this.mappingIndex; } public getIgnoreMalformedLink() { - return `${this.esDocsBase}/ignore-malformed.html`; + return this.mappingIgnoreMalformed; } public getMetaLink() { - return `${this.esDocsBase}/mapping-field-meta.html`; + return this.mappingMeta; } public getFormatLink() { - return `${this.esDocsBase}/mapping-date-format.html`; + return this.mappingFormat; } public getEagerGlobalOrdinalsLink() { - return `${this.esDocsBase}/eager-global-ordinals.html`; + return this.mappingEagerGlobalOrdinals; } public getDocValuesLink() { - return `${this.esDocsBase}/doc-values.html`; + return this.mappingDocValues; } public getCopyToLink() { - return `${this.esDocsBase}/copy-to.html`; + return this.mappingCopyTo; } public getCoerceLink() { - return `${this.esDocsBase}/coerce.html`; + return this.mappingCoerce; } public getBoostLink() { - return `${this.esDocsBase}/mapping-boost.html`; + return this.mappingParameters; } public getNormalizerLink() { - return `${this.esDocsBase}/normalizer.html`; + return this.mappingNormalizer; } public getIgnoreAboveLink() { - return `${this.esDocsBase}/ignore-above.html`; + return this.mappingIgnoreAbove; } public getFielddataLink() { - return `${this.esDocsBase}/fielddata.html`; + return this.mappingFieldData; } public getFielddataFrequencyLink() { - return `${this.esDocsBase}/fielddata.html#field-data-filtering`; + return this.mappingFieldDataFilter; } public getEnablingFielddataLink() { - return `${this.esDocsBase}/fielddata.html#before-enabling-fielddata`; + return this.mappingFieldDataEnable; } public getIndexPhrasesLink() { - return `${this.esDocsBase}/index-phrases.html`; + return this.mappingIndexPhrases; } public getIndexPrefixesLink() { - return `${this.esDocsBase}/index-prefixes.html`; + return this.mappingIndexPrefixes; } public getPositionIncrementGapLink() { - return `${this.esDocsBase}/position-increment-gap.html`; + return this.mappingPositionIncrementGap; } public getAnalyzerLink() { - return `${this.esDocsBase}/analyzer.html`; + return this.mappingAnalyzer; } public getDateFormatLink() { - return `${this.esDocsBase}/mapping-date-format.html`; + return this.mappingFormat; } public getIndexOptionsLink() { - return `${this.esDocsBase}/index-options.html`; + return this.mappingIndexOptions; } public getAlternativeToMappingTypesLink() { - return `${this.esDocsBase}/removal-of-types.html#_alternatives_to_mapping_types`; + return this.mappingTypesRemoval; } public getJoinMultiLevelsPerformanceLink() { - return `${this.esDocsBase}/parent-join.html#_parent_join_and_performance`; + return this.mappingJoinFieldsPerformance; } public getDynamicLink() { - return `${this.esDocsBase}/dynamic.html`; + return this.mappingDynamic; } public getEnabledLink() { - return `${this.esDocsBase}/enabled.html`; + return this.mappingEnabled; } public getRuntimeFields() { - return `${this.esDocsBase}/runtime.html`; + return this.runtimeFields; } public getWellKnownTextLink() { diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/metrics/chart_header.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/metrics/chart_header.tsx index 03ee51477492e8..9c9e91b814fad0 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/metrics/chart_header.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/metrics/chart_header.tsx @@ -12,6 +12,7 @@ import { EuiFlexGroup } from '@elastic/eui'; import { EuiIcon } from '@elastic/eui'; import { colorTransformer } from '../../../../../../../../common/color_palette'; import { MetricsExplorerOptionsMetric } from '../../../../../metrics_explorer/hooks/use_metrics_explorer_options'; +import { euiStyled } from '../../../../../../../../../../../src/plugins/kibana_react/common'; interface Props { title: string; @@ -21,11 +22,11 @@ interface Props { export const ChartHeader = ({ title, metrics }: Props) => { return ( - + -

{title}

+

{title}

-
+ {metrics.map((chartMetric) => ( @@ -50,3 +51,13 @@ export const ChartHeader = ({ title, metrics }: Props) => { ); }; + +const HeaderItem = euiStyled(EuiFlexItem).attrs({ grow: 1 })` + overflow: hidden; +`; + +const H4 = euiStyled('h4')` + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +`; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/metrics/chart_section.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/metrics/chart_section.tsx new file mode 100644 index 00000000000000..c8f924042b1951 --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/metrics/chart_section.tsx @@ -0,0 +1,103 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + Axis, + Settings, + Position, + Chart, + PointerUpdateListener, + TickFormatter, + TooltipValue, + ChartSizeArray, +} from '@elastic/charts'; +import React from 'react'; +import moment from 'moment'; +import { MetricsExplorerSeries } from '../../../../../../../../common/http_api'; +import { MetricExplorerSeriesChart } from '../../../../../metrics_explorer/components/series_chart'; +import { + MetricsExplorerChartType, + MetricsExplorerOptionsMetric, +} from '../../../../../metrics_explorer/hooks/use_metrics_explorer_options'; +import { ChartHeader } from './chart_header'; +import { getTimelineChartTheme } from '../../../../../metrics_explorer/components/helpers/get_chart_theme'; +import { useUiSetting } from '../../../../../../../../../../../src/plugins/kibana_react/public'; + +const CHART_SIZE: ChartSizeArray = ['100%', 160]; + +interface Props { + title: string; + style: MetricsExplorerChartType; + chartRef: React.Ref; + series: ChartSectionSeries[]; + tickFormatterForTime: TickFormatter; + tickFormatter: TickFormatter; + onPointerUpdate: PointerUpdateListener; + domain: { max: number; min: number }; + stack?: boolean; +} + +export interface ChartSectionSeries { + metric: MetricsExplorerOptionsMetric; + series: MetricsExplorerSeries; +} + +export const ChartSection = ({ + title, + style, + chartRef, + series, + tickFormatterForTime, + tickFormatter, + onPointerUpdate, + domain, + stack = false, +}: Props) => { + const isDarkMode = useUiSetting('theme:darkMode'); + const metrics = series.map((chartSeries) => chartSeries.metric); + const tooltipProps = { + headerFormatter: (tooltipValue: TooltipValue) => + moment(tooltipValue.value).format('Y-MM-DD HH:mm:ss.SSS'), + }; + + return ( + <> + + + {series.map((chartSeries, index) => ( + + ))} + + + + + + ); +}; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/metrics/metrics.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/metrics/metrics.tsx index 5ab8eb380a6576..b554cb8024211c 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/metrics/metrics.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/node_details/tabs/metrics/metrics.tsx @@ -8,17 +8,7 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { first, last } from 'lodash'; import { i18n } from '@kbn/i18n'; -import { - Axis, - Chart, - ChartSizeArray, - niceTimeFormatter, - Position, - Settings, - TooltipValue, - PointerEvent, -} from '@elastic/charts'; -import moment from 'moment'; +import { Chart, niceTimeFormatter, PointerEvent } from '@elastic/charts'; import { EuiLoadingChart, EuiSpacer, EuiFlexGrid, EuiFlexItem } from '@elastic/eui'; import { TabContent, TabProps } from '../shared'; import { useSnapshot } from '../../../../hooks/use_snaphot'; @@ -36,12 +26,10 @@ import { MetricsExplorerAggregation, MetricsExplorerSeries, } from '../../../../../../../../common/http_api'; -import { MetricExplorerSeriesChart } from '../../../../../metrics_explorer/components/series_chart'; import { createInventoryMetricFormatter } from '../../../../lib/create_inventory_metric_formatter'; import { calculateDomain } from '../../../../../metrics_explorer/components/helpers/calculate_domain'; -import { getTimelineChartTheme } from '../../../../../metrics_explorer/components/helpers/get_chart_theme'; -import { useUiSetting } from '../../../../../../../../../../../src/plugins/kibana_react/public'; -import { ChartHeader } from './chart_header'; +import { euiStyled } from '../../../../../../../../../../../src/plugins/kibana_react/common'; +import { ChartSection } from './chart_section'; import { SYSTEM_METRIC_NAME, USER_METRIC_NAME, @@ -53,26 +41,36 @@ import { LOAD_CHART_TITLE, MEMORY_CHART_TITLE, NETWORK_CHART_TITLE, + LOG_RATE_METRIC_NAME, + LOG_RATE_CHART_TITLE, } from './translations'; import { TimeDropdown } from './time_dropdown'; +import { getCustomMetricLabel } from '../../../../../../../../common/formatters/get_custom_metric_label'; +import { createFormatterForMetric } from '../../../../../metrics_explorer/components/helpers/create_formatter_for_metric'; const ONE_HOUR = 60 * 60 * 1000; -const CHART_SIZE: ChartSizeArray = ['100%', 160]; const TabComponent = (props: TabProps) => { const cpuChartRef = useRef(null); const networkChartRef = useRef(null); const memoryChartRef = useRef(null); const loadChartRef = useRef(null); + const logRateChartRef = useRef(null); + const customMetricRefs = useRef>({}); const [time, setTime] = useState(ONE_HOUR); - const chartRefs = useMemo(() => [cpuChartRef, networkChartRef, memoryChartRef, loadChartRef], [ + const chartRefs = useMemo(() => { + const refs = [cpuChartRef, networkChartRef, memoryChartRef, loadChartRef, logRateChartRef]; + return [...refs, customMetricRefs]; + }, [ cpuChartRef, networkChartRef, memoryChartRef, loadChartRef, + logRateChartRef, + customMetricRefs, ]); const { sourceId, createDerivedIndexPattern } = useSourceContext(); - const { nodeType, accountId, region } = useWaffleOptionsContext(); + const { nodeType, accountId, region, customMetrics } = useWaffleOptionsContext(); const { currentTime, options, node } = props; const derivedIndexPattern = useMemo(() => createDerivedIndexPattern('metrics'), [ createDerivedIndexPattern, @@ -102,20 +100,29 @@ const TabComponent = (props: TabProps) => { [setTime] ); + const timeRange = { + interval: '1m', + to: currentTime, + from: currentTime - time, + ignoreLookback: true, + }; + + const defaultMetrics: Array<{ type: SnapshotMetricType }> = [ + { type: 'rx' }, + { type: 'tx' }, + buildCustomMetric('system.cpu.user.pct', 'user'), + buildCustomMetric('system.cpu.system.pct', 'system'), + buildCustomMetric('system.load.1', 'load1m'), + buildCustomMetric('system.load.5', 'load5m'), + buildCustomMetric('system.load.15', 'load15m'), + buildCustomMetric('system.memory.actual.used.bytes', 'usedMemory'), + buildCustomMetric('system.memory.actual.free', 'freeMemory'), + buildCustomMetric('system.cpu.cores', 'cores', 'max'), + ]; + const { nodes, reload } = useSnapshot( filter, - [ - { type: 'rx' }, - { type: 'tx' }, - buildCustomMetric('system.cpu.user.pct', 'user'), - buildCustomMetric('system.cpu.system.pct', 'system'), - buildCustomMetric('system.load.1', 'load1m'), - buildCustomMetric('system.load.5', 'load5m'), - buildCustomMetric('system.load.15', 'load15m'), - buildCustomMetric('system.memory.actual.used.bytes', 'usedMemory'), - buildCustomMetric('system.memory.actual.free', 'freeMemory'), - buildCustomMetric('system.cpu.cores', 'cores', 'max'), - ], + [...defaultMetrics, ...customMetrics], [], nodeType, sourceId, @@ -123,12 +130,20 @@ const TabComponent = (props: TabProps) => { accountId, region, false, - { - interval: '1m', - to: currentTime, - from: currentTime - time, - ignoreLookback: true, - } + timeRange + ); + + const { nodes: logRateNodes, reload: reloadLogRate } = useSnapshot( + filter, + [{ type: 'logRate' }], + [], + nodeType, + sourceId, + currentTime, + accountId, + region, + false, + timeRange ); const getDomain = useCallback( @@ -163,6 +178,7 @@ const TabComponent = (props: TabProps) => { [] ); const loadFormatter = useMemo(() => createInventoryMetricFormatter({ type: 'load' }), []); + const logRateFormatter = useMemo(() => createInventoryMetricFormatter({ type: 'logRate' }), []); const mergeTimeseries = useCallback((...series: MetricsExplorerSeries[]) => { const base = series[0]; @@ -196,19 +212,22 @@ const TabComponent = (props: TabProps) => { (event: PointerEvent) => { chartRefs.forEach((ref) => { if (ref.current) { - ref.current.dispatchExternalPointerEvent(event); + if (ref.current instanceof Chart) { + ref.current.dispatchExternalPointerEvent(event); + } else { + const charts = Object.values(ref.current); + charts.forEach((c) => { + if (c) { + c.dispatchExternalPointerEvent(event); + } + }); + } } }); }, [chartRefs] ); - const isDarkMode = useUiSetting('theme:darkMode'); - const tooltipProps = { - headerFormatter: (tooltipValue: TooltipValue) => - moment(tooltipValue.value).format('Y-MM-DD HH:mm:ss.SSS'), - }; - const getTimeseries = useCallback( (metricName: string) => { if (!nodes || !nodes.length) { @@ -219,6 +238,16 @@ const TabComponent = (props: TabProps) => { [nodes] ); + const getLogRateTimeseries = useCallback(() => { + if (!logRateNodes) { + return null; + } + if (logRateNodes.length === 0) { + return { rows: [], columns: [], id: '0' }; + } + return logRateNodes[0].metrics.find((m) => m.name === 'logRate')!.timeseries!; + }, [logRateNodes]); + const systemMetricsTs = useMemo(() => getTimeseries('system'), [getTimeseries]); const userMetricsTs = useMemo(() => getTimeseries('user'), [getTimeseries]); const rxMetricsTs = useMemo(() => getTimeseries('rx'), [getTimeseries]); @@ -229,10 +258,12 @@ const TabComponent = (props: TabProps) => { const usedMemoryMetricsTs = useMemo(() => getTimeseries('usedMemory'), [getTimeseries]); const freeMemoryMetricsTs = useMemo(() => getTimeseries('freeMemory'), [getTimeseries]); const coresMetricsTs = useMemo(() => getTimeseries('cores'), [getTimeseries]); + const logRateMetricsTs = useMemo(() => getLogRateTimeseries(), [getLogRateTimeseries]); useEffect(() => { reload(); - }, [time, reload]); + reloadLogRate(); + }, [time, reload, reloadLogRate]); if ( !systemMetricsTs || @@ -243,12 +274,14 @@ const TabComponent = (props: TabProps) => { !load5mMetricsTs || !load15mMetricsTs || !usedMemoryMetricsTs || - !freeMemoryMetricsTs + !freeMemoryMetricsTs || + !logRateMetricsTs ) { return ; } const cpuChartMetrics = buildChartMetricLabels([SYSTEM_METRIC_NAME, USER_METRIC_NAME], 'avg'); + const logRateChartMetrics = buildChartMetricLabels([LOG_RATE_METRIC_NAME], 'rate'); const networkChartMetrics = buildChartMetricLabels( [INBOUND_METRIC_NAME, OUTBOUND_METRIC_NAME], 'rate' @@ -277,6 +310,7 @@ const TabComponent = (props: TabProps) => { return r; }); const cpuTimeseries = mergeTimeseries(systemMetricsTs, userMetricsTs); + const logRateTimeseries = mergeTimeseries(logRateMetricsTs); const networkTimeseries = mergeTimeseries(rxMetricsTs, txMetricsTs); const loadTimeseries = mergeTimeseries(load1mMetricsTs, load5mMetricsTs, load15mMetricsTs); const memoryTimeseries = mergeTimeseries(usedMemoryMetricsTs, freeMemoryMetricsTs); @@ -290,173 +324,117 @@ const TabComponent = (props: TabProps) => { - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + {customMetrics.map((c) => { + const metricTS = getTimeseries(c.id); + const chartMetrics = buildChartMetricLabels([c.field], c.aggregation); + if (!metricTS) return null; + return ( + + { + customMetricRefs.current[c.id] = r; + }} + series={[{ metric: chartMetrics[0], series: metricTS }]} + tickFormatterForTime={formatter} + tickFormatter={createFormatterForMetric(c)} + onPointerUpdate={pointerUpdate} + domain={getDomain(mergeTimeseries(metricTS), chartMetrics)} + stack={true} + /> + + ); + })} ); }; +const ChartGridItem = euiStyled(EuiFlexItem)` + overflow: hidden +`; + const LoadingPlaceholder = () => { return (
{ + const deserializedPipelines = pipelineNames.map((name: string) => { return { ...pipelinesByName[name], + processors: (pipelinesByName[name]?.processors as Processor[]) ?? [], + on_failure: pipelinesByName[name]?.on_failure as Processor[], name, }; }); diff --git a/x-pack/plugins/ingest_pipelines/common/types.ts b/x-pack/plugins/ingest_pipelines/common/types.ts index 5a8bed206175aa..303db8423d4016 100644 --- a/x-pack/plugins/ingest_pipelines/common/types.ts +++ b/x-pack/plugins/ingest_pipelines/common/types.ts @@ -19,7 +19,7 @@ export interface Processor { export interface Pipeline { name: string; - description: string; + description?: string; version?: number; processors: Processor[]; on_failure?: Processor[]; diff --git a/x-pack/plugins/ingest_pipelines/server/plugin.ts b/x-pack/plugins/ingest_pipelines/server/plugin.ts index 23accb49ba57b9..7e2f7d5e82e337 100644 --- a/x-pack/plugins/ingest_pipelines/server/plugin.ts +++ b/x-pack/plugins/ingest_pipelines/server/plugin.ts @@ -13,7 +13,7 @@ import { PLUGIN_ID, PLUGIN_MIN_LICENSE_TYPE } from '../common/constants'; import { License } from './services'; import { ApiRoutes } from './routes'; -import { isEsError } from './shared_imports'; +import { handleEsError } from './shared_imports'; import { Dependencies } from './types'; export class IngestPipelinesPlugin implements Plugin { @@ -66,7 +66,7 @@ export class IngestPipelinesPlugin implements Plugin { isSecurityEnabled: () => security !== undefined && security.license.isEnabled(), }, lib: { - isEsError, + handleEsError, }, }); } diff --git a/x-pack/plugins/ingest_pipelines/server/routes/api/create.ts b/x-pack/plugins/ingest_pipelines/server/routes/api/create.ts index afa36e5abe31a1..388c82aa34b3d1 100644 --- a/x-pack/plugins/ingest_pipelines/server/routes/api/create.ts +++ b/x-pack/plugins/ingest_pipelines/server/routes/api/create.ts @@ -11,8 +11,7 @@ import { schema } from '@kbn/config-schema'; import { Pipeline } from '../../../common/types'; import { API_BASE_PATH } from '../../../common/constants'; import { RouteDependencies } from '../../types'; -import { pipelineSchema } from './pipeline_schema'; -import { isObjectWithKeys } from './shared'; +import { pipelineSchema } from './shared'; const bodySchema = schema.object({ name: schema.string(), @@ -22,7 +21,7 @@ const bodySchema = schema.object({ export const registerCreateRoute = ({ router, license, - lib: { isEsError }, + lib: { handleEsError }, }: RouteDependencies): void => { router.post( { @@ -32,7 +31,7 @@ export const registerCreateRoute = ({ }, }, license.guardApiRoute(async (ctx, req, res) => { - const { callAsCurrentUser } = ctx.core.elasticsearch.legacy.client; + const { client: clusterClient } = ctx.core.elasticsearch; const pipeline = req.body as Pipeline; // eslint-disable-next-line @typescript-eslint/naming-convention @@ -40,7 +39,9 @@ export const registerCreateRoute = ({ try { // Check that a pipeline with the same name doesn't already exist - const pipelineByName = await callAsCurrentUser('ingest.getPipeline', { id: name }); + const { body: pipelineByName } = await clusterClient.asCurrentUser.ingest.getPipeline({ + id: name, + }); if (pipelineByName[name]) { return res.conflict({ @@ -59,7 +60,7 @@ export const registerCreateRoute = ({ } try { - const response = await callAsCurrentUser('ingest.putPipeline', { + const { body: response } = await clusterClient.asCurrentUser.ingest.putPipeline({ id: name, body: { description, @@ -71,19 +72,7 @@ export const registerCreateRoute = ({ return res.ok({ body: response }); } catch (error) { - if (isEsError(error)) { - return res.customError({ - statusCode: error.statusCode, - body: isObjectWithKeys(error.body) - ? { - message: error.message, - attributes: error.body, - } - : error, - }); - } - - throw error; + return handleEsError({ error, response: res }); } }) ); diff --git a/x-pack/plugins/ingest_pipelines/server/routes/api/delete.ts b/x-pack/plugins/ingest_pipelines/server/routes/api/delete.ts index f30b3a49f5fe1e..8cc7d7044ad08e 100644 --- a/x-pack/plugins/ingest_pipelines/server/routes/api/delete.ts +++ b/x-pack/plugins/ingest_pipelines/server/routes/api/delete.ts @@ -23,7 +23,7 @@ export const registerDeleteRoute = ({ router, license }: RouteDependencies): voi }, }, license.guardApiRoute(async (ctx, req, res) => { - const { callAsCurrentUser } = ctx.core.elasticsearch.legacy.client; + const { client: clusterClient } = ctx.core.elasticsearch; const { names } = req.params; const pipelineNames = names.split(','); @@ -34,14 +34,16 @@ export const registerDeleteRoute = ({ router, license }: RouteDependencies): voi await Promise.all( pipelineNames.map((pipelineName) => { - return callAsCurrentUser('ingest.deletePipeline', { id: pipelineName }) + return clusterClient.asCurrentUser.ingest + .deletePipeline({ id: pipelineName }) .then(() => response.itemsDeleted.push(pipelineName)) - .catch((e) => + .catch((e) => { response.errors.push({ + error: e?.meta?.body?.error ?? e, + status: e?.meta?.body?.status, name: pipelineName, - error: e, - }) - ); + }); + }); }) ); diff --git a/x-pack/plugins/ingest_pipelines/server/routes/api/documents.ts b/x-pack/plugins/ingest_pipelines/server/routes/api/documents.ts index 635ee015be5162..324bcdd3edb462 100644 --- a/x-pack/plugins/ingest_pipelines/server/routes/api/documents.ts +++ b/x-pack/plugins/ingest_pipelines/server/routes/api/documents.ts @@ -18,7 +18,7 @@ const paramsSchema = schema.object({ export const registerDocumentsRoute = ({ router, license, - lib: { isEsError }, + lib: { handleEsError }, }: RouteDependencies): void => { router.get( { @@ -28,11 +28,11 @@ export const registerDocumentsRoute = ({ }, }, license.guardApiRoute(async (ctx, req, res) => { - const { callAsCurrentUser } = ctx.core.elasticsearch.legacy.client; + const { client: clusterClient } = ctx.core.elasticsearch; const { index, id } = req.params; try { - const document = await callAsCurrentUser('get', { index, id }); + const { body: document } = await clusterClient.asCurrentUser.get({ index, id }); const { _id, _index, _source } = document; @@ -44,14 +44,7 @@ export const registerDocumentsRoute = ({ }, }); } catch (error) { - if (isEsError(error)) { - return res.customError({ - statusCode: error.statusCode, - body: error, - }); - } - - throw error; + return handleEsError({ error, response: res }); } }) ); diff --git a/x-pack/plugins/ingest_pipelines/server/routes/api/get.ts b/x-pack/plugins/ingest_pipelines/server/routes/api/get.ts index 3995448d13fbb9..853bd1c7dde238 100644 --- a/x-pack/plugins/ingest_pipelines/server/routes/api/get.ts +++ b/x-pack/plugins/ingest_pipelines/server/routes/api/get.ts @@ -18,33 +18,26 @@ const paramsSchema = schema.object({ export const registerGetRoutes = ({ router, license, - lib: { isEsError }, + lib: { handleEsError }, }: RouteDependencies): void => { // Get all pipelines router.get( { path: API_BASE_PATH, validate: false }, license.guardApiRoute(async (ctx, req, res) => { - const { callAsCurrentUser } = ctx.core.elasticsearch.legacy.client; + const { client: clusterClient } = ctx.core.elasticsearch; try { - const pipelines = await callAsCurrentUser('ingest.getPipeline'); + const { body: pipelines } = await clusterClient.asCurrentUser.ingest.getPipeline(); return res.ok({ body: deserializePipelines(pipelines) }); } catch (error) { - if (isEsError(error)) { + const esErrorResponse = handleEsError({ error, response: res }); + if (esErrorResponse.status === 404) { // ES returns 404 when there are no pipelines // Instead, we return an empty array and 200 status back to the client - if (error.status === 404) { - return res.ok({ body: [] }); - } - - return res.customError({ - statusCode: error.statusCode, - body: error, - }); + return res.ok({ body: [] }); } - - throw error; + return esErrorResponse; } }) ); @@ -58,27 +51,22 @@ export const registerGetRoutes = ({ }, }, license.guardApiRoute(async (ctx, req, res) => { - const { callAsCurrentUser } = ctx.core.elasticsearch.legacy.client; + const { client: clusterClient } = ctx.core.elasticsearch; const { name } = req.params; try { - const pipeline = await callAsCurrentUser('ingest.getPipeline', { id: name }); + const { body: pipelines } = await clusterClient.asCurrentUser.ingest.getPipeline({ + id: name, + }); return res.ok({ body: { - ...pipeline[name], + ...pipelines[name], name, }, }); } catch (error) { - if (isEsError(error)) { - return res.customError({ - statusCode: error.statusCode, - body: error, - }); - } - - throw error; + return handleEsError({ error, response: res }); } }) ); diff --git a/x-pack/plugins/ingest_pipelines/server/routes/api/privileges.ts b/x-pack/plugins/ingest_pipelines/server/routes/api/privileges.ts index 527b4d4277bf5f..e1e4b2d3d28866 100644 --- a/x-pack/plugins/ingest_pipelines/server/routes/api/privileges.ts +++ b/x-pack/plugins/ingest_pipelines/server/routes/api/privileges.ts @@ -36,24 +36,13 @@ export const registerPrivilegesRoute = ({ license, router, config }: RouteDepend return res.ok({ body: privilegesResult }); } - const { - core: { - elasticsearch: { - legacy: { client }, - }, - }, - } = ctx; + const { client: clusterClient } = ctx.core.elasticsearch; - const { has_all_requested: hasAllPrivileges, cluster } = await client.callAsCurrentUser( - 'transport.request', - { - path: '/_security/user/_has_privileges', - method: 'POST', - body: { - cluster: APP_CLUSTER_REQUIRED_PRIVILEGES, - }, - } - ); + const { + body: { has_all_requested: hasAllPrivileges, cluster }, + } = await clusterClient.asCurrentUser.security.hasPrivileges({ + body: { cluster: APP_CLUSTER_REQUIRED_PRIVILEGES }, + }); if (!hasAllPrivileges) { privilegesResult.missingPrivileges.cluster = extractMissingPrivileges(cluster); diff --git a/x-pack/plugins/ingest_pipelines/server/routes/api/shared/index.ts b/x-pack/plugins/ingest_pipelines/server/routes/api/shared/index.ts index be638975672270..40caae32cbb0f7 100644 --- a/x-pack/plugins/ingest_pipelines/server/routes/api/shared/index.ts +++ b/x-pack/plugins/ingest_pipelines/server/routes/api/shared/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { isObjectWithKeys } from './is_object_with_keys'; +export { pipelineSchema } from './pipeline_schema'; diff --git a/x-pack/plugins/ingest_pipelines/server/routes/api/pipeline_schema.ts b/x-pack/plugins/ingest_pipelines/server/routes/api/shared/pipeline_schema.ts similarity index 100% rename from x-pack/plugins/ingest_pipelines/server/routes/api/pipeline_schema.ts rename to x-pack/plugins/ingest_pipelines/server/routes/api/shared/pipeline_schema.ts diff --git a/x-pack/plugins/ingest_pipelines/server/routes/api/simulate.ts b/x-pack/plugins/ingest_pipelines/server/routes/api/simulate.ts index f02aa0a8d5ed6d..a1d0a4ec2e3d32 100644 --- a/x-pack/plugins/ingest_pipelines/server/routes/api/simulate.ts +++ b/x-pack/plugins/ingest_pipelines/server/routes/api/simulate.ts @@ -4,12 +4,12 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - +import { SimulatePipelineDocument } from '@elastic/elasticsearch/api/types'; import { schema } from '@kbn/config-schema'; import { API_BASE_PATH } from '../../../common/constants'; import { RouteDependencies } from '../../types'; -import { pipelineSchema } from './pipeline_schema'; +import { pipelineSchema } from './shared'; const bodySchema = schema.object({ pipeline: schema.object(pipelineSchema), @@ -20,7 +20,7 @@ const bodySchema = schema.object({ export const registerSimulateRoute = ({ router, license, - lib: { isEsError }, + lib: { handleEsError }, }: RouteDependencies): void => { router.post( { @@ -30,29 +30,22 @@ export const registerSimulateRoute = ({ }, }, license.guardApiRoute(async (ctx, req, res) => { - const { callAsCurrentUser } = ctx.core.elasticsearch.legacy.client; + const { client: clusterClient } = ctx.core.elasticsearch; const { pipeline, documents, verbose } = req.body; try { - const response = await callAsCurrentUser('ingest.simulate', { + const { body: response } = await clusterClient.asCurrentUser.ingest.simulate({ verbose, body: { pipeline, - docs: documents, + docs: documents as SimulatePipelineDocument[], }, }); return res.ok({ body: response }); } catch (error) { - if (isEsError(error)) { - return res.customError({ - statusCode: error.statusCode, - body: error, - }); - } - - throw error; + return handleEsError({ error, response: res }); } }) ); diff --git a/x-pack/plugins/ingest_pipelines/server/routes/api/update.ts b/x-pack/plugins/ingest_pipelines/server/routes/api/update.ts index 8776aace5ad789..0d3e2a37795273 100644 --- a/x-pack/plugins/ingest_pipelines/server/routes/api/update.ts +++ b/x-pack/plugins/ingest_pipelines/server/routes/api/update.ts @@ -9,8 +9,7 @@ import { schema } from '@kbn/config-schema'; import { API_BASE_PATH } from '../../../common/constants'; import { RouteDependencies } from '../../types'; -import { pipelineSchema } from './pipeline_schema'; -import { isObjectWithKeys } from './shared'; +import { pipelineSchema } from './shared'; const bodySchema = schema.object(pipelineSchema); @@ -21,7 +20,7 @@ const paramsSchema = schema.object({ export const registerUpdateRoute = ({ router, license, - lib: { isEsError }, + lib: { handleEsError }, }: RouteDependencies): void => { router.put( { @@ -32,16 +31,16 @@ export const registerUpdateRoute = ({ }, }, license.guardApiRoute(async (ctx, req, res) => { - const { callAsCurrentUser } = ctx.core.elasticsearch.legacy.client; + const { client: clusterClient } = ctx.core.elasticsearch; const { name } = req.params; // eslint-disable-next-line @typescript-eslint/naming-convention const { description, processors, version, on_failure } = req.body; try { // Verify pipeline exists; ES will throw 404 if it doesn't - await callAsCurrentUser('ingest.getPipeline', { id: name }); + await clusterClient.asCurrentUser.ingest.getPipeline({ id: name }); - const response = await callAsCurrentUser('ingest.putPipeline', { + const { body: response } = await clusterClient.asCurrentUser.ingest.putPipeline({ id: name, body: { description, @@ -53,19 +52,7 @@ export const registerUpdateRoute = ({ return res.ok({ body: response }); } catch (error) { - if (isEsError(error)) { - return res.customError({ - statusCode: error.statusCode, - body: isObjectWithKeys(error.body) - ? { - message: error.message, - attributes: error.body, - } - : error, - }); - } - - throw error; + return handleEsError({ error, response: res }); } }) ); diff --git a/x-pack/plugins/ingest_pipelines/server/shared_imports.ts b/x-pack/plugins/ingest_pipelines/server/shared_imports.ts index df9b3dd53cc1f7..7f55d189457c70 100644 --- a/x-pack/plugins/ingest_pipelines/server/shared_imports.ts +++ b/x-pack/plugins/ingest_pipelines/server/shared_imports.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { isEsError } from '../../../../src/plugins/es_ui_shared/server'; +export { handleEsError } from '../../../../src/plugins/es_ui_shared/server'; diff --git a/x-pack/plugins/ingest_pipelines/server/types.ts b/x-pack/plugins/ingest_pipelines/server/types.ts index fc702b40d169d1..912a0c88eef62a 100644 --- a/x-pack/plugins/ingest_pipelines/server/types.ts +++ b/x-pack/plugins/ingest_pipelines/server/types.ts @@ -10,7 +10,7 @@ import { LicensingPluginSetup } from '../../licensing/server'; import { SecurityPluginSetup } from '../../security/server'; import { PluginSetupContract as FeaturesPluginSetup } from '../../features/server'; import { License } from './services'; -import { isEsError } from './shared_imports'; +import { handleEsError } from './shared_imports'; export interface Dependencies { security: SecurityPluginSetup; @@ -25,6 +25,6 @@ export interface RouteDependencies { isSecurityEnabled: () => boolean; }; lib: { - isEsError: typeof isEsError; + handleEsError: typeof handleEsError; }; } diff --git a/x-pack/plugins/lens/public/debounced_component/debounced_component.test.tsx b/x-pack/plugins/lens/public/debounced_component/debounced_component.test.tsx index 43dcefae66ad51..6beb565be098c1 100644 --- a/x-pack/plugins/lens/public/debounced_component/debounced_component.test.tsx +++ b/x-pack/plugins/lens/public/debounced_component/debounced_component.test.tsx @@ -26,7 +26,7 @@ describe('debouncedComponent', () => { component.setProps({ title: 'yall' }); expect(component.text()).toEqual('there'); await act(async () => { - await new Promise((r) => setTimeout(r, 1)); + await new Promise((r) => setTimeout(r, 10)); }); expect(component.text()).toEqual('yall'); }); diff --git a/x-pack/plugins/lens/public/drag_drop/drag_drop.tsx b/x-pack/plugins/lens/public/drag_drop/drag_drop.tsx index 88de9154ffc349..51021a3e50b3f2 100644 --- a/x-pack/plugins/lens/public/drag_drop/drag_drop.tsx +++ b/x-pack/plugins/lens/public/drag_drop/drag_drop.tsx @@ -44,6 +44,16 @@ interface BaseProps { * is dropped onto this DragDrop component. */ onDrop?: DropHandler; + /** + * The event handler that fires when this element is dragged. + */ + onDragStart?: ( + target?: DroppableEvent['currentTarget'] | KeyboardEvent['currentTarget'] + ) => void; + /** + * The event handler that fires when the dragging of this element ends. + */ + onDragEnd?: () => void; /** * The value associated with this item. */ @@ -116,10 +126,6 @@ interface DragInnerProps extends BaseProps { activeDropTarget: DragContextState['activeDropTarget']; dropTargetsByOrder: DragContextState['dropTargetsByOrder']; }; - onDragStart?: ( - target?: DroppableEvent['currentTarget'] | KeyboardEvent['currentTarget'] - ) => void; - onDragEnd?: () => void; extraKeyboardHandler?: (e: KeyboardEvent) => void; ariaDescribedBy?: string; } diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx index 8ae62e4d843c28..2da79020383453 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx @@ -193,6 +193,10 @@ export const InnerFieldItem = function InnerFieldItem(props: FieldItemProps) { } } + const onDragStart = useCallback(() => { + setOpen(false); + }, [setOpen]); + const value = useMemo( () => ({ field, @@ -244,6 +248,7 @@ export const InnerFieldItem = function InnerFieldItem(props: FieldItemProps) { order={order} value={value} dataTestSubj={`lnsFieldListPanelField-${field.name}`} + onDragStart={onDragStart} > { operation: { dataType: 'number', isBucketed: false, - label: 'Cumulative sum of Records', + label: 'Cumulative sum of Records label', scale: undefined, }, }, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions.test.ts index 3add39cc5fb8a9..c131b16512823f 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions.test.ts @@ -11,7 +11,10 @@ import { countOperation, counterRateOperation, movingAverageOperation, + cumulativeSumOperation, derivativeOperation, + AvgIndexPatternColumn, + DerivativeIndexPatternColumn, } from './definitions'; import { getFieldByNameFactory } from '../pure_helpers'; import { documentField } from '../document_field'; @@ -35,7 +38,7 @@ const indexPatternFields = [ }, { name: 'bytes', - displayName: 'bytes', + displayName: 'bytesLabel', type: 'number', aggregatable: true, searchable: true, @@ -98,6 +101,73 @@ const baseColumnArgs: { field: indexPattern.fields[2], }; +const layer: IndexPatternLayer = { + indexPatternId: '1', + columnOrder: ['date', 'metric', 'ref'], + columns: { + date: { + label: '', + customLabel: true, + dataType: 'date', + isBucketed: true, + operationType: 'date_histogram', + sourceField: 'timestamp', + params: { interval: 'auto' }, + }, + metric: { + label: 'metricLabel', + customLabel: true, + dataType: 'number', + isBucketed: false, + operationType: 'average', + sourceField: 'bytes', + params: {}, + } as AvgIndexPatternColumn, + ref: { + label: '', + customLabel: true, + dataType: 'number', + isBucketed: false, + operationType: 'differences', + references: ['metric'], + } as DerivativeIndexPatternColumn, + }, +}; + +describe('labels', () => { + const calcColumnArgs = { + ...baseColumnArgs, + referenceIds: ['metric'], + layer, + previousColumn: layer.columns.metric, + }; + it('should use label of referenced operation to create label for derivative and moving average', () => { + expect(derivativeOperation.buildColumn(calcColumnArgs)).toEqual( + expect.objectContaining({ + label: 'Differences of metricLabel', + }) + ); + expect(movingAverageOperation.buildColumn(calcColumnArgs)).toEqual( + expect.objectContaining({ + label: 'Moving average of metricLabel', + }) + ); + }); + + it('should use displayName of a field for a label for counter rate and cumulative sum', () => { + expect(counterRateOperation.buildColumn(calcColumnArgs)).toEqual( + expect.objectContaining({ + label: 'Counter rate of bytesLabel per second', + }) + ); + expect(cumulativeSumOperation.buildColumn(calcColumnArgs)).toEqual( + expect.objectContaining({ + label: 'Cumulative sum of bytesLabel', + }) + ); + }); +}); + describe('time scale transition', () => { it('should carry over time scale and adjust label on operation from count to sum', () => { expect( @@ -107,7 +177,7 @@ describe('time scale transition', () => { ).toEqual( expect.objectContaining({ timeScale: 'h', - label: 'Sum of bytes per hour', + label: 'Sum of bytesLabel per hour', }) ); }); @@ -125,27 +195,6 @@ describe('time scale transition', () => { ); }); - it('should carry over time scale and adjust label on operation from sum to count', () => { - expect( - countOperation.buildColumn({ - ...baseColumnArgs, - previousColumn: { - label: 'Sum of bytes per hour', - timeScale: 'h', - dataType: 'number', - isBucketed: false, - operationType: 'sum', - sourceField: 'bytes', - }, - }) - ).toEqual( - expect.objectContaining({ - timeScale: 'h', - label: 'Count of records per hour', - }) - ); - }); - it('should not set time scale if it was not set previously', () => { expect( countOperation.buildColumn({ @@ -188,7 +237,7 @@ describe('time scale transition', () => { expect( sumOperation.onFieldChange( { - label: 'Sum of bytes per hour', + label: 'Sum of bytesLabel per hour', timeScale: 'h', dataType: 'number', isBucketed: false, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/counter_rate.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/counter_rate.tsx index 331aa528e6d555..c57f70ba1b58b1 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/counter_rate.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/counter_rate.tsx @@ -66,16 +66,26 @@ export const counterRateOperation: OperationDefinition< }, getDefaultLabel: (column, indexPattern, columns) => { const ref = columns[column.references[0]]; - return ofName(ref && 'sourceField' in ref ? ref.sourceField : undefined, column.timeScale); + return ofName( + ref && 'sourceField' in ref + ? indexPattern.getFieldByName(ref.sourceField)?.displayName + : undefined, + column.timeScale + ); }, toExpression: (layer, columnId) => { return dateBasedOperationToExpression(layer, columnId, 'lens_counter_rate'); }, - buildColumn: ({ referenceIds, previousColumn, layer }) => { + buildColumn: ({ referenceIds, previousColumn, layer, indexPattern }) => { const metric = layer.columns[referenceIds[0]]; const timeScale = previousColumn?.timeScale || DEFAULT_TIME_SCALE; return { - label: ofName(metric && 'sourceField' in metric ? metric.sourceField : undefined, timeScale), + label: ofName( + metric && 'sourceField' in metric + ? indexPattern.getFieldByName(metric.sourceField)?.displayName + : undefined, + timeScale + ), dataType: 'number', operationType: 'counter_rate', isBucketed: false, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/cumulative_sum.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/cumulative_sum.tsx index 1664f3639b598a..7cec1fa0d4bbc9 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/cumulative_sum.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/cumulative_sum.tsx @@ -64,15 +64,23 @@ export const cumulativeSumOperation: OperationDefinition< }, getDefaultLabel: (column, indexPattern, columns) => { const ref = columns[column.references[0]]; - return ofName(ref && 'sourceField' in ref ? ref.sourceField : undefined); + return ofName( + ref && 'sourceField' in ref + ? indexPattern.getFieldByName(ref.sourceField)?.displayName + : undefined + ); }, toExpression: (layer, columnId) => { return dateBasedOperationToExpression(layer, columnId, 'cumulative_sum'); }, - buildColumn: ({ referenceIds, previousColumn, layer }) => { + buildColumn: ({ referenceIds, previousColumn, layer, indexPattern }) => { const ref = layer.columns[referenceIds[0]]; return { - label: ofName(ref && 'sourceField' in ref ? ref.sourceField : undefined), + label: ofName( + ref && 'sourceField' in ref + ? indexPattern.getFieldByName(ref.sourceField)?.displayName + : undefined + ), dataType: 'number', operationType: 'cumulative_sum', isBucketed: false, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/differences.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/differences.tsx index c50e9270eaac1e..bef3fbc2e48aed 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/differences.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/differences.tsx @@ -66,8 +66,7 @@ export const derivativeOperation: OperationDefinition< } }, getDefaultLabel: (column, indexPattern, columns) => { - const ref = columns[column.references[0]]; - return ofName(ref && 'sourceField' in ref ? ref.sourceField : undefined, column.timeScale); + return ofName(columns[column.references[0]]?.label, column.timeScale); }, toExpression: (layer, columnId) => { return dateBasedOperationToExpression(layer, columnId, 'derivative'); @@ -75,10 +74,7 @@ export const derivativeOperation: OperationDefinition< buildColumn: ({ referenceIds, previousColumn, layer }) => { const ref = layer.columns[referenceIds[0]]; return { - label: ofName( - ref && 'sourceField' in ref ? ref.sourceField : undefined, - previousColumn?.timeScale - ), + label: ofName(ref?.label, previousColumn?.timeScale), dataType: 'number', operationType: OPERATION_NAME, isBucketed: false, diff --git a/x-pack/plugins/license_management/__jest__/__snapshots__/add_license.test.js.snap b/x-pack/plugins/license_management/__jest__/__snapshots__/add_license.test.js.snap index 268ce4cbb5165f..95921fa61233c8 100644 --- a/x-pack/plugins/license_management/__jest__/__snapshots__/add_license.test.js.snap +++ b/x-pack/plugins/license_management/__jest__/__snapshots__/add_license.test.js.snap @@ -1,5 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`AddLicense component when license is active should display correct verbiage 1`] = `"
Update your license

If you already have a new license, upload it now.

"`; +exports[`AddLicense component when license is active should display correct verbiage 1`] = `"
Update your license

If you already have a new license, upload it now.

"`; -exports[`AddLicense component when license is expired should display with correct verbiage 1`] = `"
Update your license

If you already have a new license, upload it now.

"`; +exports[`AddLicense component when license is expired should display with correct verbiage 1`] = `"
Update your license

If you already have a new license, upload it now.

"`; 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 b75dee3c78306a..4d8b653c4b10d2 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 subscription features(opens in a new tab or window), 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(opens in a new tab or window), 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(opens in a new tab or window), 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(opens in a new tab or window), 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(opens in a new tab or window), 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(opens in a new tab or window), 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(opens in a new tab or window), 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(opens in a new tab or window), 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 00737c708b824c..be634a5b4f7489 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 subscription features(opens in a new tab or window).

"`; +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(opens in a new tab or window).

"`; -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(opens in a new tab or window).

"`; +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(opens in a new tab or window).

"`; -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(opens in a new tab or window).

"`; +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(opens in a new tab or window).

"`; 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 9f08c5f11c2a21..1cacadb8246307 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 subscription features(opens in a new tab or window) 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(opens in a new tab or window) 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(opens in a new tab or window) 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(opens in a new tab or window) 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(opens in a new tab or window) 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(opens in a new tab or window) 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(opens in a new tab or window) 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(opens in a new tab or window) have to offer.

"`; diff --git a/x-pack/plugins/license_management/__jest__/__snapshots__/upload_license.test.tsx.snap b/x-pack/plugins/license_management/__jest__/__snapshots__/upload_license.test.tsx.snap index c89d183282219b..c785ed7c99bda8 100644 --- a/x-pack/plugins/license_management/__jest__/__snapshots__/upload_license.test.tsx.snap +++ b/x-pack/plugins/license_management/__jest__/__snapshots__/upload_license.test.tsx.snap @@ -268,9 +268,11 @@ exports[`UploadLicense should display a modal when license requires acknowledgem
): EuiTheme => - partialTheme as EuiTheme; diff --git a/x-pack/plugins/lists/public/exceptions/components/and_or_badge/index.stories.tsx b/x-pack/plugins/lists/public/exceptions/components/and_or_badge/index.stories.tsx index 8272ca9683a4f0..74ec0759b057ef 100644 --- a/x-pack/plugins/lists/public/exceptions/components/and_or_badge/index.stories.tsx +++ b/x-pack/plugins/lists/public/exceptions/components/and_or_badge/index.stories.tsx @@ -5,26 +5,17 @@ * 2.0. */ -import { Story, addDecorator } from '@storybook/react'; +import { Story } from '@storybook/react'; import React from 'react'; -import { ThemeProvider } from 'styled-components'; -import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { getMockTheme } from '../../../common/test_utils/kibana_react.mock'; +import { EuiThemeProvider } from '../../../../../../../src/plugins/kibana_react/common'; import { AndOrBadge, AndOrBadgeProps } from '.'; const sampleText = 'Doggo ipsum i am bekom fat snoot wow such tempt waggy wags floofs, ruff heckin good boys and girls mlem. Ruff heckin good boys and girls mlem stop it fren borkf borking doggo very hand that feed shibe, you are doing me the shock big ol heck smol borking doggo with a long snoot for pats heckin good boys. You are doing me the shock smol borking doggo with a long snoot for pats wow very biscit, length boy. Doggo ipsum i am bekom fat snoot wow such tempt waggy wags floofs, ruff heckin good boys and girls mlem. Ruff heckin good boys and girls mlem stop it fren borkf borking doggo very hand that feed shibe, you are doing me the shock big ol heck smol borking doggo with a long snoot for pats heckin good boys.'; -const mockTheme = getMockTheme({ - darkMode: false, - eui: euiLightVars, -}); - -addDecorator((storyFn) => {storyFn()}); - export default { argTypes: { includeAntennas: { @@ -58,6 +49,13 @@ export default { }, }, component: AndOrBadge, + decorators: [ + (DecoratorStory: React.ComponentClass): React.ReactNode => ( + + + + ), + ], title: 'AndOrBadge', }; diff --git a/x-pack/plugins/lists/public/exceptions/components/and_or_badge/index.test.tsx b/x-pack/plugins/lists/public/exceptions/components/and_or_badge/index.test.tsx index 47282d061a65de..26aa41549e61b3 100644 --- a/x-pack/plugins/lists/public/exceptions/components/and_or_badge/index.test.tsx +++ b/x-pack/plugins/lists/public/exceptions/components/and_or_badge/index.test.tsx @@ -6,21 +6,18 @@ */ import React from 'react'; -import { ThemeProvider } from 'styled-components'; import { mount } from 'enzyme'; -import { getMockTheme } from '../../../common/test_utils/kibana_react.mock'; +import { EuiThemeProvider } from '../../../../../../../src/plugins/kibana_react/common'; import { AndOrBadge } from './'; -const mockTheme = getMockTheme({ eui: { euiColorLightShade: '#ece' } }); - describe('AndOrBadge', () => { test('it renders top and bottom antenna bars when "includeAntennas" is true', () => { const wrapper = mount( - + - + ); expect(wrapper.find('[data-test-subj="and-or-badge"]').at(0).text()).toEqual('AND'); @@ -30,9 +27,9 @@ describe('AndOrBadge', () => { test('it does not render top and bottom antenna bars when "includeAntennas" is false', () => { const wrapper = mount( - + - + ); expect(wrapper.find('[data-test-subj="and-or-badge"]').at(0).text()).toEqual('OR'); @@ -42,9 +39,9 @@ describe('AndOrBadge', () => { test('it renders "and" when "type" is "and"', () => { const wrapper = mount( - + - + ); expect(wrapper.find('[data-test-subj="and-or-badge"]').at(0).text()).toEqual('AND'); @@ -52,9 +49,9 @@ describe('AndOrBadge', () => { test('it renders "or" when "type" is "or"', () => { const wrapper = mount( - + - + ); expect(wrapper.find('[data-test-subj="and-or-badge"]').at(0).text()).toEqual('OR'); diff --git a/x-pack/plugins/lists/public/exceptions/components/and_or_badge/rounded_badge_antenna.test.tsx b/x-pack/plugins/lists/public/exceptions/components/and_or_badge/rounded_badge_antenna.test.tsx index 472345b9c9f191..dd5ed999dadcd3 100644 --- a/x-pack/plugins/lists/public/exceptions/components/and_or_badge/rounded_badge_antenna.test.tsx +++ b/x-pack/plugins/lists/public/exceptions/components/and_or_badge/rounded_badge_antenna.test.tsx @@ -6,21 +6,18 @@ */ import React from 'react'; -import { ThemeProvider } from 'styled-components'; import { mount } from 'enzyme'; -import { getMockTheme } from '../../../common/test_utils/kibana_react.mock'; +import { EuiThemeProvider } from '../../../../../../../src/plugins/kibana_react/common'; import { RoundedBadgeAntenna } from './rounded_badge_antenna'; -const mockTheme = getMockTheme({ eui: { euiColorLightShade: '#ece' } }); - describe('RoundedBadgeAntenna', () => { test('it renders top and bottom antenna bars', () => { const wrapper = mount( - + - + ); expect(wrapper.find('[data-test-subj="and-or-badge"]').at(0).text()).toEqual('AND'); @@ -30,9 +27,9 @@ describe('RoundedBadgeAntenna', () => { test('it renders "and" when "type" is "and"', () => { const wrapper = mount( - + - + ); expect(wrapper.find('[data-test-subj="and-or-badge"]').at(0).text()).toEqual('AND'); @@ -40,9 +37,9 @@ describe('RoundedBadgeAntenna', () => { test('it renders "or" when "type" is "or"', () => { const wrapper = mount( - + - + ); expect(wrapper.find('[data-test-subj="and-or-badge"]').at(0).text()).toEqual('OR'); diff --git a/x-pack/plugins/lists/public/exceptions/components/builder/and_badge.test.tsx b/x-pack/plugins/lists/public/exceptions/components/builder/and_badge.test.tsx index dc773e222776b9..4a1471d9a3e5d1 100644 --- a/x-pack/plugins/lists/public/exceptions/components/builder/and_badge.test.tsx +++ b/x-pack/plugins/lists/public/exceptions/components/builder/and_badge.test.tsx @@ -6,21 +6,18 @@ */ import React from 'react'; -import { ThemeProvider } from 'styled-components'; import { mount } from 'enzyme'; -import { getMockTheme } from '../../../common/test_utils/kibana_react.mock'; +import { EuiThemeProvider } from '../../../../../../../src/plugins/kibana_react/common'; import { BuilderAndBadgeComponent } from './and_badge'; -const mockTheme = getMockTheme({ eui: { euiColorLightShade: '#ece' } }); - describe('BuilderAndBadgeComponent', () => { test('it renders exceptionItemEntryFirstRowAndBadge for very first exception item in builder', () => { const wrapper = mount( - + - + ); expect( @@ -30,9 +27,9 @@ describe('BuilderAndBadgeComponent', () => { test('it renders exceptionItemEntryInvisibleAndBadge if "entriesLength" is 1 or less', () => { const wrapper = mount( - + - + ); expect( @@ -42,9 +39,9 @@ describe('BuilderAndBadgeComponent', () => { test('it renders regular "and" badge if exception item is not the first one and includes more than one entry', () => { const wrapper = mount( - + - + ); expect(wrapper.find('[data-test-subj="exceptionItemEntryAndBadge"]').exists()).toBeTruthy(); diff --git a/x-pack/plugins/lists/public/exceptions/components/builder/builder.stories.tsx b/x-pack/plugins/lists/public/exceptions/components/builder/builder.stories.tsx index 5199ead78ca0a0..8eaba9e82d7247 100644 --- a/x-pack/plugins/lists/public/exceptions/components/builder/builder.stories.tsx +++ b/x-pack/plugins/lists/public/exceptions/components/builder/builder.stories.tsx @@ -13,16 +13,14 @@ import { Story, addDecorator } from '@storybook/react'; import React from 'react'; -import { ThemeProvider } from 'styled-components'; -import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; import { HttpStart } from 'kibana/public'; import { AutocompleteStart } from '../../../../../../../src/plugins/data/public'; +import { EuiThemeProvider } from '../../../../../../../src/plugins/kibana_react/common'; import { fields, getField, } from '../../../../../../../src/plugins/data/common/index_patterns/fields/fields.mocks'; -import { getMockTheme } from '../../../common/test_utils/kibana_react.mock'; import { getEntryMatchAnyMock } from '../../../../common/schemas/types/entry_match_any.mock'; import { getEntryMatchMock } from '../../../../common/schemas/types/entry_match.mock'; import { getEntryExistsMock } from '../../../../common/schemas/types/entry_exists.mock'; @@ -35,10 +33,6 @@ import { OnChangeProps, } from './exception_items_renderer'; -const mockTheme = getMockTheme({ - darkMode: false, - eui: euiLightVars, -}); const mockHttpService: HttpStart = ({ addLoadingCountSource: (): void => {}, anonymousPaths: { @@ -76,7 +70,7 @@ const mockAutocompleteService = ({ }), } as unknown) as AutocompleteStart; -addDecorator((storyFn) => {storyFn()}); +addDecorator((storyFn) => {storyFn()}); export default { argTypes: { diff --git a/x-pack/plugins/lists/public/exceptions/components/builder/entry_renderer.stories.tsx b/x-pack/plugins/lists/public/exceptions/components/builder/entry_renderer.stories.tsx index 8408fb7a6a4f1b..5b3730a6deb933 100644 --- a/x-pack/plugins/lists/public/exceptions/components/builder/entry_renderer.stories.tsx +++ b/x-pack/plugins/lists/public/exceptions/components/builder/entry_renderer.stories.tsx @@ -5,24 +5,18 @@ * 2.0. */ -import { Story, addDecorator } from '@storybook/react'; +import { Story } from '@storybook/react'; import { action } from '@storybook/addon-actions'; import React from 'react'; -import { ThemeProvider } from 'styled-components'; -import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; import { HttpStart } from 'kibana/public'; import { OperatorEnum, OperatorTypeEnum } from '../../../../common'; import { AutocompleteStart } from '../../../../../../../src/plugins/data/public'; import { fields } from '../../../../../../../src/plugins/data/common/index_patterns/fields/fields.mocks'; -import { getMockTheme } from '../../../common/test_utils/kibana_react.mock'; +import { EuiThemeProvider } from '../../../../../../../src/plugins/kibana_react/common'; import { BuilderEntryItem, EntryItemProps } from './entry_renderer'; -const mockTheme = getMockTheme({ - darkMode: false, - eui: euiLightVars, -}); const mockAutocompleteService = ({ getValueSuggestions: () => new Promise((resolve) => { @@ -59,8 +53,6 @@ const mockAutocompleteService = ({ }), } as unknown) as AutocompleteStart; -addDecorator((storyFn) => {storyFn()}); - export default { argTypes: { allowLargeValueLists: { @@ -163,6 +155,13 @@ export default { }, }, component: BuilderEntryItem, + decorators: [ + (DecoratorStory: React.ComponentClass): React.ReactNode => ( + + + + ), + ], title: 'BuilderEntryItem', }; diff --git a/x-pack/plugins/lists/public/exceptions/components/builder/exception_item_renderer.test.tsx b/x-pack/plugins/lists/public/exceptions/components/builder/exception_item_renderer.test.tsx index 0fd886bdc742a4..b896f2a44f67b3 100644 --- a/x-pack/plugins/lists/public/exceptions/components/builder/exception_item_renderer.test.tsx +++ b/x-pack/plugins/lists/public/exceptions/components/builder/exception_item_renderer.test.tsx @@ -6,24 +6,18 @@ */ import React from 'react'; -import { ThemeProvider } from 'styled-components'; import { mount } from 'enzyme'; import { dataPluginMock } from 'src/plugins/data/public/mocks'; import { fields } from '../../../../../../../src/plugins/data/common/index_patterns/fields/fields.mocks'; +import { EuiThemeProvider } from '../../../../../../../src/plugins/kibana_react/common'; import { getExceptionListItemSchemaMock } from '../../../../common/schemas/response/exception_list_item_schema.mock'; import { getEntryMatchMock } from '../../../../common/schemas/types/entry_match.mock'; import { getEntryMatchAnyMock } from '../../../../common/schemas/types/entry_match_any.mock'; import { coreMock } from '../../../../../../../src/core/public/mocks'; -import { getMockTheme } from '../../../common/test_utils/kibana_react.mock'; import { BuilderExceptionListItemComponent } from './exception_item_renderer'; -const mockTheme = getMockTheme({ - eui: { - euiColorLightShade: '#ece', - }, -}); const mockKibanaHttpService = coreMock.createStart().http; const { autocomplete: autocompleteStartMock } = dataPluginMock.createStartContract(); @@ -41,7 +35,7 @@ describe('BuilderExceptionListItemComponent', () => { entries: [getEntryMatchMock(), getEntryMatchMock()], }; const wrapper = mount( - + { onDeleteExceptionItem={jest.fn()} setErrorsExist={jest.fn()} /> - + ); expect( @@ -72,7 +66,7 @@ describe('BuilderExceptionListItemComponent', () => { const exceptionItem = getExceptionListItemSchemaMock(); exceptionItem.entries = [getEntryMatchMock(), getEntryMatchMock()]; const wrapper = mount( - + { onDeleteExceptionItem={jest.fn()} setErrorsExist={jest.fn()} /> - + ); expect(wrapper.find('[data-test-subj="exceptionItemEntryAndBadge"]').exists()).toBeTruthy(); @@ -101,7 +95,7 @@ describe('BuilderExceptionListItemComponent', () => { const exceptionItem = getExceptionListItemSchemaMock(); exceptionItem.entries = [getEntryMatchMock()]; const wrapper = mount( - + { onDeleteExceptionItem={jest.fn()} setErrorsExist={jest.fn()} /> - + ); expect( @@ -132,7 +126,7 @@ describe('BuilderExceptionListItemComponent', () => { const exceptionItem = getExceptionListItemSchemaMock(); exceptionItem.entries = [getEntryMatchMock()]; const wrapper = mount( - + { onDeleteExceptionItem={jest.fn()} setErrorsExist={jest.fn()} /> - + ); expect( diff --git a/x-pack/plugins/lists/public/exceptions/components/builder/exception_items_renderer.test.tsx b/x-pack/plugins/lists/public/exceptions/components/builder/exception_items_renderer.test.tsx index b8ec8dc354bf84..a236b102eabf7b 100644 --- a/x-pack/plugins/lists/public/exceptions/components/builder/exception_items_renderer.test.tsx +++ b/x-pack/plugins/lists/public/exceptions/components/builder/exception_items_renderer.test.tsx @@ -6,28 +6,22 @@ */ import React from 'react'; -import { ThemeProvider } from 'styled-components'; import { ReactWrapper, mount } from 'enzyme'; import { waitFor } from '@testing-library/react'; import { coreMock } from 'src/core/public/mocks'; import { dataPluginMock } from 'src/plugins/data/public/mocks'; +import { EuiThemeProvider } from '../../../../../../../src/plugins/kibana_react/common'; import { fields, getField, } from '../../../../../../../src/plugins/data/common/index_patterns/fields/fields.mocks'; import { getExceptionListItemSchemaMock } from '../../../../common/schemas/response/exception_list_item_schema.mock'; import { getEntryMatchAnyMock } from '../../../../common/schemas/types/entry_match_any.mock'; -import { getMockTheme } from '../../../common/test_utils/kibana_react.mock'; import { getEmptyValue } from '../../../common/empty_value'; import { ExceptionBuilderComponent } from './exception_items_renderer'; -const mockTheme = getMockTheme({ - eui: { - euiColorLightShade: '#ece', - }, -}); const mockKibanaHttpService = coreMock.createStart().http; const { autocomplete: autocompleteStartMock } = dataPluginMock.createStartContract(); @@ -44,7 +38,7 @@ describe('ExceptionBuilderComponent', () => { test('it displays empty entry if no "exceptionListItems" are passed in', () => { wrapper = mount( - + { ruleName="Test rule" onChange={jest.fn()} /> - + ); expect(wrapper.find('EuiFlexGroup[data-test-subj="exceptionItemEntryContainer"]')).toHaveLength( @@ -83,7 +77,7 @@ describe('ExceptionBuilderComponent', () => { test('it displays "exceptionListItems" that are passed in', async () => { wrapper = mount( - + { ruleName="Test rule" onChange={jest.fn()} /> - + ); expect(wrapper.find('EuiFlexGroup[data-test-subj="exceptionItemEntryContainer"]')).toHaveLength( 1 @@ -128,7 +122,7 @@ describe('ExceptionBuilderComponent', () => { test('it displays "or", "and" and "add nested button" enabled', () => { wrapper = mount( - + { ruleName="Test rule" onChange={jest.fn()} /> - + ); expect( @@ -165,7 +159,7 @@ describe('ExceptionBuilderComponent', () => { test('it adds an entry when "and" clicked', async () => { wrapper = mount( - + { ruleName="Test rule" onChange={jest.fn()} /> - + ); expect(wrapper.find('EuiFlexGroup[data-test-subj="exceptionItemEntryContainer"]')).toHaveLength( @@ -222,7 +216,7 @@ describe('ExceptionBuilderComponent', () => { test('it adds an exception item when "or" clicked', async () => { wrapper = mount( - + { ruleName="Test rule" onChange={jest.fn()} /> - + ); expect(wrapper.find('EuiFlexGroup[data-test-subj="exceptionEntriesContainer"]')).toHaveLength( @@ -283,7 +277,7 @@ describe('ExceptionBuilderComponent', () => { test('it displays empty entry if user deletes last remaining entry', () => { wrapper = mount( - + { ruleName="Test rule" onChange={jest.fn()} /> - + ); expect(wrapper.find('[data-test-subj="exceptionBuilderEntryField"]').at(0).text()).toEqual( @@ -338,7 +332,7 @@ describe('ExceptionBuilderComponent', () => { test('it displays "and" badge if at least one exception item includes more than one entry', () => { wrapper = mount( - + { ruleName="Test rule" onChange={jest.fn()} /> - + ); expect( @@ -374,7 +368,7 @@ describe('ExceptionBuilderComponent', () => { test('it does not display "and" badge if none of the exception items include more than one entry', () => { wrapper = mount( - + { ruleName="Test rule" onChange={jest.fn()} /> - + ); wrapper.find('[data-test-subj="exceptionsOrButton"] button').simulate('click'); @@ -413,7 +407,7 @@ describe('ExceptionBuilderComponent', () => { describe('nested entry', () => { test('it adds a nested entry when "add nested entry" clicked', async () => { wrapper = mount( - + { ruleName="Test rule" onChange={jest.fn()} /> - + ); wrapper.find('[data-test-subj="exceptionsNestedButton"] button').simulate('click'); diff --git a/x-pack/plugins/maps/common/elasticsearch_util/elasticsearch_geo_utils.ts b/x-pack/plugins/maps/common/elasticsearch_util/elasticsearch_geo_utils.ts index f2a8b95f7b643d..197b7f49eda0af 100644 --- a/x-pack/plugins/maps/common/elasticsearch_util/elasticsearch_geo_utils.ts +++ b/x-pack/plugins/maps/common/elasticsearch_util/elasticsearch_geo_utils.ts @@ -369,7 +369,6 @@ export function createSpatialFilterWithGeometry({ geometryLabel, indexPatternId, geoFieldName, - geoFieldType, relation = ES_SPATIAL_RELATIONS.INTERSECTS, }: { preIndexedShape?: PreIndexedShape; @@ -377,32 +376,20 @@ export function createSpatialFilterWithGeometry({ geometryLabel: string; indexPatternId: string; geoFieldName: string; - geoFieldType: ES_GEO_FIELD_TYPE; relation: ES_SPATIAL_RELATIONS; }): GeoFilter { - ensureGeoField(geoFieldType); - - const isGeoPoint = geoFieldType === ES_GEO_FIELD_TYPE.GEO_POINT; - - const relationLabel = isGeoPoint - ? i18n.translate('xpack.maps.es_geo_utils.shapeFilter.geoPointRelationLabel', { - defaultMessage: 'in', - }) - : getEsSpatialRelationLabel(relation); const meta: FilterMeta = { type: SPATIAL_FILTER_TYPE, negate: false, index: indexPatternId, key: geoFieldName, - alias: `${geoFieldName} ${relationLabel} ${geometryLabel}`, + alias: `${geoFieldName} ${getEsSpatialRelationLabel(relation)} ${geometryLabel}`, disabled: false, }; const shapeQuery: GeoShapeQueryBody = { - // geo_shape query with geo_point field only supports intersects relation - relation: isGeoPoint ? ES_SPATIAL_RELATIONS.INTERSECTS : relation, + relation, }; - if (preIndexedShape) { shapeQuery.indexed_shape = preIndexedShape; } else { diff --git a/x-pack/plugins/maps/public/index.scss b/x-pack/plugins/maps/public/_index.scss similarity index 94% rename from x-pack/plugins/maps/public/index.scss rename to x-pack/plugins/maps/public/_index.scss index d2dd07b0f81f91..5332464ade9fba 100644 --- a/x-pack/plugins/maps/public/index.scss +++ b/x-pack/plugins/maps/public/_index.scss @@ -7,6 +7,7 @@ // mapChart__legend--small // mapChart__legend-isLoading +@import 'mixins'; @import 'main'; @import 'mapbox_hacks'; @import 'connected_components/index'; diff --git a/x-pack/plugins/maps/public/_mapbox_hacks.scss b/x-pack/plugins/maps/public/_mapbox_hacks.scss index 9b2d93986e4263..480232007995d0 100644 --- a/x-pack/plugins/maps/public/_mapbox_hacks.scss +++ b/x-pack/plugins/maps/public/_mapbox_hacks.scss @@ -10,8 +10,13 @@ .mapboxgl-ctrl-group:not(:empty) { @include euiBottomShadowLarge; + @include mapToolbarButtonGroupBorderRadius; background-color: $euiColorEmptyShade; - border-radius: $euiBorderRadius; + transition: transform $euiAnimSpeedNormal ease-in-out; + + &:hover { + transform: translateY(-1px); + } > button { @include size($euiSizeXL); @@ -21,6 +26,18 @@ } } } + + .mapboxgl-ctrl button:not(:disabled) { + transition: background $euiAnimSpeedNormal ease-in-out; + + &:hover { + background-color: transparentize($euiColorDarkShade, .9); + } + } + + .mapboxgl-ctrl-group button:focus:focus-visible { + box-shadow: none; + } } // Custom SVG as background for zoom controls based off of EUI glyphs plusInCircleFilled and minusInCircleFilled diff --git a/x-pack/plugins/maps/public/_mixins.scss b/x-pack/plugins/maps/public/_mixins.scss new file mode 100644 index 00000000000000..914bc23c1163cf --- /dev/null +++ b/x-pack/plugins/maps/public/_mixins.scss @@ -0,0 +1,11 @@ +@mixin mapToolbarButtonGroupBorderRadius { + @include kbnThemeStyle($theme: 'v7') { + border-radius: $euiBorderRadius; + } + + @include kbnThemeStyle($theme: 'v8') { + border-radius: $euiBorderRadiusSmall; + } + + overflow: hidden; +} diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/geo_line_form.tsx b/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/geo_line_form.tsx index bc743fe8d79b4f..081272f40b3444 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/geo_line_form.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/geo_line_form.tsx @@ -64,7 +64,12 @@ export function GeoLineForm(props: Props) { onChange={onSortFieldChange} fields={props.indexPattern.fields.filter((field) => { const isSplitField = props.splitField ? field.name === props.splitField : false; - return !isSplitField && field.sortable && !indexPatterns.isNestedField(field); + return ( + !isSplitField && + field.sortable && + !indexPatterns.isNestedField(field) && + ['number', 'date'].includes(field.type) + ); })} isClearable={false} /> diff --git a/x-pack/plugins/maps/public/components/__snapshots__/geometry_filter_form.test.js.snap b/x-pack/plugins/maps/public/components/__snapshots__/geometry_filter_form.test.js.snap index 2d39a52dfe9745..ccbe4667b78ea4 100644 --- a/x-pack/plugins/maps/public/components/__snapshots__/geometry_filter_form.test.js.snap +++ b/x-pack/plugins/maps/public/components/__snapshots__/geometry_filter_form.test.js.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`should not render relation select when geo field is geo_point 1`] = ` +exports[`should not show "within" relation when filter geometry is not closed 1`] = ` + + + `; -exports[`should not show "within" relation when filter geometry is not closed 1`] = ` +exports[`should render error message 1`] = ` + + Simulated error + @@ -147,7 +177,7 @@ exports[`should not show "within" relation when filter geometry is not closed 1` `; -exports[`should render error message 1`] = ` +exports[`should render relation select when geo field is geo_shape 1`] = ` + + + - - Simulated error - @@ -210,7 +268,7 @@ exports[`should render error message 1`] = ` `; -exports[`should render relation select when geo field is geo_shape 1`] = ` +exports[`should render relation select without "within"-relation when geo field is geo_point 1`] = ` { - // can not filter by within relation when filtering geometry is not closed - return relation !== ES_SPATIAL_RELATIONS.WITHIN; - }); + const spatialRelations = + this.props.isFilterGeometryClosed && + this.state.selectedField.geoFieldType !== ES_GEO_FIELD_TYPE.GEO_POINT + ? Object.values(ES_SPATIAL_RELATIONS) + : Object.values(ES_SPATIAL_RELATIONS).filter((relation) => { + // - cannot filter by "within"-relation when filtering geometry is not closed + // - do not distinguish between intersects/within for filtering for points since they are equivalent + return relation !== ES_SPATIAL_RELATIONS.WITHIN; + }); + const options = spatialRelations.map((relation) => { return { value: relation, diff --git a/x-pack/plugins/maps/public/components/geometry_filter_form.test.js b/x-pack/plugins/maps/public/components/geometry_filter_form.test.js index f1876198f8b676..d981caf944ab97 100644 --- a/x-pack/plugins/maps/public/components/geometry_filter_form.test.js +++ b/x-pack/plugins/maps/public/components/geometry_filter_form.test.js @@ -16,7 +16,7 @@ const defaultProps = { onSubmit: () => {}, }; -test('should not render relation select when geo field is geo_point', async () => { +test('should render relation select without "within"-relation when geo field is geo_point', async () => { const component = shallow( { : geometry, indexPatternId: this.props.drawState.indexPatternId, geoFieldName: this.props.drawState.geoFieldName, - geoFieldType: this.props.drawState.geoFieldType - ? this.props.drawState.geoFieldType - : ES_GEO_FIELD_TYPE.GEO_POINT, geometryLabel: this.props.drawState.geometryLabel ? this.props.drawState.geometryLabel : '', relation: this.props.drawState.relation ? this.props.drawState.relation diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/feature_geometry_filter_form.js b/x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/feature_geometry_filter_form.js index 3950c6ef124bee..9d4cf78c98754e 100644 --- a/x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/feature_geometry_filter_form.js +++ b/x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/feature_geometry_filter_form.js @@ -52,13 +52,7 @@ export class FeatureGeometryFilterForm extends Component { return preIndexedShape; }; - _createFilter = async ({ - geometryLabel, - indexPatternId, - geoFieldName, - geoFieldType, - relation, - }) => { + _createFilter = async ({ geometryLabel, indexPatternId, geoFieldName, relation }) => { this.setState({ errorMsg: undefined }); const preIndexedShape = await this._loadPreIndexedShape(); if (!this._isMounted) { @@ -72,7 +66,6 @@ export class FeatureGeometryFilterForm extends Component { geometryLabel, indexPatternId, geoFieldName, - geoFieldType, relation, }); diff --git a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/_index.scss b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/_index.scss index e92e89b1703709..a472f1b640f682 100644 --- a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/_index.scss +++ b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/_index.scss @@ -1,22 +1,2 @@ @import 'tools_control/index'; - -.mapToolbarOverlay { - position: absolute; - top: ($euiSizeM + $euiSizeS) + ($euiSizeXL * 2); // Position and height of mapbox controls plus margin - left: $euiSizeM; - z-index: 2; // Sit on top of mapbox controls shadow -} - -.mapToolbarOverlay__button { - @include size($euiSizeXL); - // sass-lint:disable-block no-important - background-color: $euiColorEmptyShade !important; - pointer-events: all; - position: relative; - - &:enabled, - &:enabled:hover, - &:enabled:focus { - @include euiBottomShadowLarge; - } -} +@import 'toolbar_overlay'; diff --git a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/_toolbar_overlay.scss b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/_toolbar_overlay.scss new file mode 100644 index 00000000000000..d95dd2504babc3 --- /dev/null +++ b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/_toolbar_overlay.scss @@ -0,0 +1,49 @@ +.mapToolbarOverlay { + position: absolute; + top: ($euiSizeM + $euiSizeS) + ($euiSizeXL * 2); // Position and height of mapbox controls plus margin + left: $euiSizeM; + z-index: 2; // Sit on top of mapbox controls shadow +} + +.mapToolbarOverlay__button, +.mapToolbarOverlay__buttonGroup { + position: relative; + transition: transform $euiAnimSpeedNormal ease-in-out, background $euiAnimSpeedNormal ease-in-out; + + @include kbnThemeStyle($theme: 'v7') { + // Overrides the .euiPanel default border + // sass-lint:disable-block no-important + border: none !important; + + // Overrides the .euiPanel--hasShadow + &.euiPanel.euiPanel--hasShadow { + @include euiBottomShadowLarge; + } + } + + &:hover { + transform: translateY(-1px); + } + + // Removes the hover effect from the .euiButtonIcon because it would create a 1px bottom gap + // So we put this hover effect into the panel that wraps the button or buttons + .euiButtonIcon:hover { + transform: translateY(0); + } + + // Removes the focus background state because it can induce users to think these buttons are "enabled". + // The buttons functionality are just applied once, so they shouldn't stay highlighted. + .euiButtonIcon:focus:not(:hover) { + background: none; + } +} + +.mapToolbarOverlay__buttonGroup { + @include mapToolbarButtonGroupBorderRadius; + display: flex; + flex-direction: column; + + .euiButtonIcon { + border-radius: 0; + } +} \ No newline at end of file diff --git a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/fit_to_data/fit_to_data.tsx b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/fit_to_data/fit_to_data.tsx index 9d074ac760612d..64e163cd96a92d 100644 --- a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/fit_to_data/fit_to_data.tsx +++ b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/fit_to_data/fit_to_data.tsx @@ -7,7 +7,7 @@ import React from 'react'; -import { EuiButtonIcon } from '@elastic/eui'; +import { EuiButtonIcon, EuiPanel } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { ILayer } from '../../../classes/layers/layer'; @@ -56,19 +56,21 @@ export class FitToData extends React.Component { } return ( - + + + ); } } diff --git a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/set_view_control/set_view_control.tsx b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/set_view_control/set_view_control.tsx index b657d6369f8aa3..de37ec5e00877d 100644 --- a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/set_view_control/set_view_control.tsx +++ b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/set_view_control/set_view_control.tsx @@ -15,6 +15,7 @@ import { EuiPopover, EuiTextAlign, EuiSpacer, + EuiPanel, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -190,19 +191,21 @@ export class SetViewControl extends Component { anchorPosition="leftUp" panelPaddingSize="s" button={ - + + + } isOpen={this.state.isPopoverOpen} closePopover={this._closePopover} diff --git a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/__snapshots__/tools_control.test.tsx.snap b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/__snapshots__/tools_control.test.tsx.snap index 456138e191810d..b6d217d6907647 100644 --- a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/__snapshots__/tools_control.test.tsx.snap +++ b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/__snapshots__/tools_control.test.tsx.snap @@ -8,14 +8,19 @@ exports[`Should render cancel button when drawing 1`] = ` + paddingSize="none" + > + + } closePopover={[Function]} display="inlineBlock" @@ -134,14 +139,19 @@ exports[`renders 1`] = ` + paddingSize="none" + > + + } closePopover={[Function]} display="inlineBlock" diff --git a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/tools_control.tsx b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/tools_control.tsx index 1d2354ba3154a5..6779fe945137e8 100644 --- a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/tools_control.tsx +++ b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/tools_control.tsx @@ -13,6 +13,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiButton, + EuiPanel, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -205,18 +206,20 @@ export class ToolsControl extends Component { _renderToolsButton() { return ( - + + + ); } diff --git a/x-pack/plugins/maps/public/lazy_load_bundle/lazy/index.ts b/x-pack/plugins/maps/public/lazy_load_bundle/lazy/index.ts index e7f5df49527b71..4ccc19ae988da8 100644 --- a/x-pack/plugins/maps/public/lazy_load_bundle/lazy/index.ts +++ b/x-pack/plugins/maps/public/lazy_load_bundle/lazy/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import '../../index.scss'; +import '../../_index.scss'; export * from '../../embeddable/map_embeddable'; export * from '../../kibana_services'; export { renderApp } from '../../render_app'; diff --git a/x-pack/plugins/maps/server/maps_telemetry/maps_telemetry.ts b/x-pack/plugins/maps/server/maps_telemetry/maps_telemetry.ts index bf180c514c56fa..569f7e17896f2b 100644 --- a/x-pack/plugins/maps/server/maps_telemetry/maps_telemetry.ts +++ b/x-pack/plugins/maps/server/maps_telemetry/maps_telemetry.ts @@ -125,8 +125,7 @@ async function isFieldGeoShape( if (!indexPattern) { return false; } - const fieldsForIndexPattern = await indexPatternsService.getFieldsForIndexPattern(indexPattern); - return fieldsForIndexPattern.some( + return indexPattern.fields.some( (fieldDescriptor: IFieldType) => fieldDescriptor.name && fieldDescriptor.name === geoField! ); } @@ -192,13 +191,9 @@ async function filterIndexPatternsByField(fields: string[]) { await Promise.all( indexPatternIds.map(async (indexPatternId: string) => { const indexPattern = await indexPatternsService.get(indexPatternId); - const fieldsForIndexPattern = await indexPatternsService.getFieldsForIndexPattern( - indexPattern - ); const containsField = fields.some((field: string) => - fieldsForIndexPattern.some( - (fieldDescriptor: IFieldType) => - fieldDescriptor.esTypes && fieldDescriptor.esTypes.includes(field) + indexPattern.fields.some( + (fieldDescriptor) => fieldDescriptor.esTypes && fieldDescriptor.esTypes.includes(field) ) ); if (containsField) { 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 505673f440ef2c..61abf8476c632d 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 @@ -523,6 +523,9 @@ export const loadEvalData = async ({ [jobType]: { actual_field: dependentVariable, predicted_field: predictedField, + ...(jobType === ANALYSIS_CONFIG_TYPE.CLASSIFICATION + ? { top_classes_field: `${resultsField}.top_classes` } + : {}), metrics: metrics[jobType as keyof EvaluateMetrics], }, }, diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/page.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/page.tsx index b9af6750d6ee9d..f32e60dcf3cc1d 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/page.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/page.tsx @@ -8,10 +8,8 @@ import React, { FC, Fragment, useMemo, useState } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; -import { i18n } from '@kbn/i18n'; import { - EuiBetaBadge, EuiFlexGroup, EuiFlexItem, EuiPage, @@ -81,18 +79,6 @@ export const Page: FC = () => { id="xpack.ml.dataframe.analyticsList.title" defaultMessage="Data frame analytics" /> -   -
diff --git a/x-pack/plugins/ml/public/application/explorer/swimlane_container.tsx b/x-pack/plugins/ml/public/application/explorer/swimlane_container.tsx index 4adb79f065cd4a..c108257094b6aa 100644 --- a/x-pack/plugins/ml/public/application/explorer/swimlane_container.tsx +++ b/x-pack/plugins/ml/public/application/explorer/swimlane_container.tsx @@ -361,7 +361,7 @@ export const SwimlaneContainer: FC = ({ diff --git a/x-pack/plugins/ml/public/application/services/anomaly_explorer_charts_service.ts b/x-pack/plugins/ml/public/application/services/anomaly_explorer_charts_service.ts index 70b7632775bf5a..d760ff9455a885 100644 --- a/x-pack/plugins/ml/public/application/services/anomaly_explorer_charts_service.ts +++ b/x-pack/plugins/ml/public/application/services/anomaly_explorer_charts_service.ts @@ -159,12 +159,13 @@ export class AnomalyExplorerChartsService { const halfPoints = Math.ceil(plotPoints / 2); const bounds = timeFilter.getActiveBounds(); const boundsMin = bounds?.min ? bounds.min.valueOf() : undefined; + const boundsMax = bounds?.max ? bounds.max.valueOf() : undefined; let chartRange: ChartRange = { min: boundsMin ? Math.max(midpointMs - halfPoints * minBucketSpanMs, boundsMin) : midpointMs - halfPoints * minBucketSpanMs, - max: bounds?.max - ? Math.min(midpointMs + halfPoints * minBucketSpanMs, bounds.max.valueOf()) + max: boundsMax + ? Math.min(midpointMs + halfPoints * minBucketSpanMs, boundsMax) : midpointMs + halfPoints * minBucketSpanMs, }; @@ -210,15 +211,21 @@ export class AnomalyExplorerChartsService { } // Elasticsearch aggregation returns points at start of bucket, - // so align the min to the length of the longest bucket. + // so align the min to the length of the longest bucket, + // and use the start of the latest selected bucket in the check + // for too many selected buckets, respecting the max bounds set in the view. chartRange.min = Math.floor(chartRange.min / maxBucketSpanMs) * maxBucketSpanMs; if (boundsMin !== undefined && chartRange.min < boundsMin) { chartRange.min = chartRange.min + maxBucketSpanMs; } + const selectedLatestBucketStart = boundsMax + ? Math.floor(Math.min(selectedLatestMs, boundsMax) / maxBucketSpanMs) * maxBucketSpanMs + : Math.floor(selectedLatestMs / maxBucketSpanMs) * maxBucketSpanMs; + if ( - (chartRange.min > selectedEarliestMs || chartRange.max < selectedLatestMs) && - chartRange.max - chartRange.min < selectedLatestMs - selectedEarliestMs + (chartRange.min > selectedEarliestMs || chartRange.max < selectedLatestBucketStart) && + chartRange.max - chartRange.min < selectedLatestBucketStart - selectedEarliestMs ) { tooManyBuckets = true; } diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/logo.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/logo.json new file mode 100755 index 00000000000000..862f970b7405db --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/logo.json @@ -0,0 +1,3 @@ +{ + "icon": "logoSecurity" +} diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/manifest.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/manifest.json new file mode 100755 index 00000000000000..2a2c0c202f66b3 --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/manifest.json @@ -0,0 +1,59 @@ +{ + "id": "security_network", + "title": "Security: Network", + "description": "Detect anomalous network activity in your ECS-compatible network logs.", + "type": "network data", + "logoFile": "logo.json", + "defaultIndexPattern": "logs-*,filebeat-*,packetbeat-*", + "query": { + "bool": { + "filter": [ + { + "term": { + "event.category": "network" + } + } + ] + } + }, + "jobs": [ + { + "id": "high_count_by_destination_country", + "file": "high_count_by_destination_country.json" + }, + { + "id": "high_count_network_denies", + "file": "high_count_network_denies.json" + }, + { + "id": "high_count_network_events", + "file": "high_count_network_events.json" + }, + { + "id": "rare_destination_country", + "file": "rare_destination_country.json" + } + ], + "datafeeds": [ + { + "id": "datafeed_high_count_by_destination_country", + "file": "datafeed_high_count_by_destination_country.json", + "job_id": "high_count_by_destination_country" + }, + { + "id": "datafeed_high_count_network_denies", + "file": "datafeed_high_count_network_denies.json", + "job_id": "high_count_network_denies" + }, + { + "id": "datafeed_high_count_network_events", + "file": "datafeed_high_count_network_events.json", + "job_id": "high_count_network_events" + }, + { + "id": "datafeed_rare_destination_country", + "file": "datafeed_rare_destination_country.json", + "job_id": "rare_destination_country" + } + ] +} diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/datafeed_high_count_by_destination_country.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/datafeed_high_count_by_destination_country.json new file mode 100755 index 00000000000000..48706c6ea6b5db --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/datafeed_high_count_by_destination_country.json @@ -0,0 +1,25 @@ +{ + "job_id": "high_count_by_destination_country", + "indices": [ + "logs-*", + "filebeat-*", + "packetbeat-*" + ], + "max_empty_searches": 10, + "query": { + "bool": { + "filter": [ + { + "term": { + "event.category": "network" + } + }, + { + "exists": { + "field": "destination.geo.country_name" + } + } + ] + } + } +} diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/datafeed_high_count_network_denies.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/datafeed_high_count_network_denies.json new file mode 100755 index 00000000000000..a4412a6d732e99 --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/datafeed_high_count_network_denies.json @@ -0,0 +1,25 @@ +{ + "job_id": "high_count_network_denies", + "indices": [ + "logs-*", + "filebeat-*", + "packetbeat-*" + ], + "max_empty_searches": 10, + "query": { + "bool": { + "filter": [ + { + "term": { + "event.category": "network" + } + }, + { + "term": { + "event.outcome": "deny" + } + } + ] + } + } +} diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/datafeed_high_count_network_events.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/datafeed_high_count_network_events.json new file mode 100755 index 00000000000000..1e3bbf92b8aeda --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/datafeed_high_count_network_events.json @@ -0,0 +1,20 @@ +{ + "job_id": "high_count_network_events", + "indices": [ + "logs-*", + "filebeat-*", + "packetbeat-*" + ], + "max_empty_searches": 10, + "query": { + "bool": { + "filter": [ + { + "term": { + "event.category": "network" + } + } + ] + } + } +} diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/datafeed_rare_destination_country.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/datafeed_rare_destination_country.json new file mode 100755 index 00000000000000..92431a6912faae --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/datafeed_rare_destination_country.json @@ -0,0 +1,25 @@ +{ + "job_id": "rare_destination_country", + "indices": [ + "logs-*", + "filebeat-*", + "packetbeat-*" + ], + "max_empty_searches": 10, + "query": { + "bool": { + "filter": [ + { + "term": { + "event.category": "network" + } + }, + { + "exists": { + "field": "destination.geo.country_name" + } + } + ] + } + } +} diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/high_count_by_destination_country.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/high_count_by_destination_country.json new file mode 100755 index 00000000000000..aaee46d9cf80b6 --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/high_count_by_destination_country.json @@ -0,0 +1,35 @@ +{ + "job_type": "anomaly_detector", + "description": "Security: Network - looks for an unusually large spike in network activity to one destination country in the network logs. This could be due to unusually large amounts of reconnaissance or enumeration traffic. Data exfiltration activity may also produce such a surge in traffic to a destination country which does not normally appear in network traffic or business work-flows. Malware instances and persistence mechanisms may communicate with command-and-control (C2) infrastructure in their country of origin, which may be an unusual destination country for the source network.", + "groups": [ + "security", + "network" + ], + "analysis_config": { + "bucket_span": "15m", + "detectors": [ + { + "detector_description": "high_non_zero_count by \"destination.geo.country_name\"", + "function": "high_non_zero_count", + "by_field_name": "destination.geo.country_name", + "detector_index": 0 + } + ], + "influencers": [ + "destination.geo.country_name", + "destination.as.organization.name", + "source.ip", + "destination.ip" + ] + }, + "allow_lazy_open": true, + "analysis_limits": { + "model_memory_limit": "32mb" + }, + "data_description": { + "time_field": "@timestamp" + }, + "custom_settings": { + "created_by": "ml-module-security-network" + } +} diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/high_count_network_denies.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/high_count_network_denies.json new file mode 100755 index 00000000000000..bc08aa21f32771 --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/high_count_network_denies.json @@ -0,0 +1,34 @@ +{ + "job_type": "anomaly_detector", + "description": "Security: Network - looks for an unusually large spike in network traffic that was denied by network ACLs or firewall rules. Such a burst of denied traffic is usually either 1) a misconfigured application or firewall or 2) suspicious or malicious activity. Unsuccessful attempts at network transit, in order to connect to command-and-control (C2), or engage in data exfiltration, may produce a burst of failed connections. This could also be due to unusually large amounts of reconnaissance or enumeration traffic. Denial-of-service attacks or traffic floods may also produce such a surge in traffic.", + "groups": [ + "security", + "network" + ], + "analysis_config": { + "bucket_span": "15m", + "detectors": [ + { + "detector_description": "high_count", + "function": "high_count", + "detector_index": 0 + } + ], + "influencers": [ + "destination.geo.country_name", + "destination.as.organization.name", + "source.ip", + "destination.port" + ] + }, + "allow_lazy_open": true, + "analysis_limits": { + "model_memory_limit": "32mb" + }, + "data_description": { + "time_field": "@timestamp" + }, + "custom_settings": { + "created_by": "ml-module-security-network" + } +} diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/high_count_network_events.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/high_count_network_events.json new file mode 100755 index 00000000000000..d709eb21d7c6d9 --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/high_count_network_events.json @@ -0,0 +1,34 @@ +{ + "job_type": "anomaly_detector", + "description": "Security: Network - looks for an unusually large spike in network traffic. Such a burst of traffic, if not caused by a surge in business activity, can be due to suspicious or malicious activity. Large-scale data exfiltration may produce a burst of network traffic; this could also be due to unusually large amounts of reconnaissance or enumeration traffic. Denial-of-service attacks or traffic floods may also produce such a surge in traffic.", + "groups": [ + "security", + "network" + ], + "analysis_config": { + "bucket_span": "15m", + "detectors": [ + { + "detector_description": "high_count", + "function": "high_count", + "detector_index": 0 + } + ], + "influencers": [ + "destination.geo.country_name", + "destination.as.organization.name", + "source.ip", + "destination.ip" + ] + }, + "allow_lazy_open": true, + "analysis_limits": { + "model_memory_limit": "32mb" + }, + "data_description": { + "time_field": "@timestamp" + }, + "custom_settings": { + "created_by": "ml-module-security-network" + } +} diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/rare_destination_country.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/rare_destination_country.json new file mode 100755 index 00000000000000..15571f89b81afc --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/rare_destination_country.json @@ -0,0 +1,35 @@ +{ + "job_type": "anomaly_detector", + "description": "Security: Network - looks for an unusual destination country name in the network logs. This can be due to initial access, persistence, command-and-control, or exfiltration activity. For example, when a user clicks on a link in a phishing email or opens a malicious document, a request may be sent to download and run a payload from a server in a country which does not normally appear in network traffic or business work-flows. Malware instances and persistence mechanisms may communicate with command-and-control (C2) infrastructure in their country of origin, which may be an unusual destination country for the source network.", + "groups": [ + "security", + "network" + ], + "analysis_config": { + "bucket_span": "15m", + "detectors": [ + { + "detector_description": "rare by \"destination.geo.country_name\"", + "function": "rare", + "by_field_name": "destination.geo.country_name", + "detector_index": 0 + } + ], + "influencers": [ + "destination.geo.country_name", + "destination.as.organization.name", + "source.ip", + "destination.ip" + ] + }, + "allow_lazy_open": true, + "analysis_limits": { + "model_memory_limit": "32mb" + }, + "data_description": { + "time_field": "@timestamp" + }, + "custom_settings": { + "created_by": "ml-module-security-network" + } +} diff --git a/x-pack/plugins/monitoring/common/constants.ts b/x-pack/plugins/monitoring/common/constants.ts index bf6e32af0dc391..cd3e28debb7d50 100644 --- a/x-pack/plugins/monitoring/common/constants.ts +++ b/x-pack/plugins/monitoring/common/constants.ts @@ -460,7 +460,7 @@ export const ALERT_DETAILS = { paramDetails: { threshold: { label: i18n.translate('xpack.monitoring.alerts.shardSize.paramDetails.threshold.label', { - defaultMessage: `Notify when a shard exceeds this size`, + defaultMessage: `Notify when average shard size exceeds this value`, }), type: AlertParamType.Number, append: 'GB', @@ -477,7 +477,7 @@ export const ALERT_DETAILS = { defaultMessage: 'Shard size', }), description: i18n.translate('xpack.monitoring.alerts.shardSize.description', { - defaultMessage: 'Alert if an index (primary) shard is oversize.', + defaultMessage: 'Alert if the average shard size is larger than the configured threshold.', }), }, }; diff --git a/x-pack/plugins/monitoring/common/types/es.ts b/x-pack/plugins/monitoring/common/types/es.ts index 9dce32211f4b1a..38a7e7859272ca 100644 --- a/x-pack/plugins/monitoring/common/types/es.ts +++ b/x-pack/plugins/monitoring/common/types/es.ts @@ -100,6 +100,9 @@ export interface ElasticsearchNodeStats { export interface ElasticsearchIndexStats { index?: string; + shards: { + primaries: number; + }; primaries?: { docs?: { count?: number; diff --git a/x-pack/plugins/monitoring/public/components/no_data/__snapshots__/no_data.test.js.snap b/x-pack/plugins/monitoring/public/components/no_data/__snapshots__/no_data.test.js.snap index 351b988ea5bc0f..fe277062bc95af 100644 --- a/x-pack/plugins/monitoring/public/components/no_data/__snapshots__/no_data.test.js.snap +++ b/x-pack/plugins/monitoring/public/components/no_data/__snapshots__/no_data.test.js.snap @@ -15,6 +15,7 @@ exports[`NoData should show a default message if reason is unknown 1`] = ` >
- + - + { }); }); + it('should retrieve all ephemeral ids from all hits for the same cluster', () => { + const results = { + hits: { + hits: [ + { + _source: { + type: 'logstash_stats', + cluster_uuid: 'FlV4ckTxQ0a78hmBkzzc9A', + logstash_stats: { + logstash: { + uuid: '0000000-0000-0000-0000-000000000000', + }, + pipelines: [ + { + id: 'main', + ephemeral_id: 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', + queue: { + type: 'memory', + }, + }, + ], + }, + }, + }, + { + _source: { + type: 'logstash_stats', + cluster_uuid: 'FlV4ckTxQ0a78hmBkzzc9A', + logstash_stats: { + logstash: { + uuid: '11111111-1111-1111-1111-111111111111', + }, + pipelines: [ + { + id: 'main', + ephemeral_id: 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', + queue: { + type: 'memory', + }, + }, + ], + }, + }, + }, + { + _source: { + type: 'logstash_stats', + cluster_uuid: '3', + logstash_stats: { + logstash: { + uuid: '22222222-2222-2222-2222-222222222222', + }, + pipelines: [ + { + id: 'main', + ephemeral_id: 'cccccccc-cccc-cccc-cccc-cccccccccccc', + queue: { + type: 'memory', + }, + }, + ], + }, + }, + }, + ], + }, + }; + + const options = getBaseOptions(); + processStatsResults(results as any, options); + + expect(options.allEphemeralIds).toStrictEqual({ + FlV4ckTxQ0a78hmBkzzc9A: [ + 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', + 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', + ], + '3': ['cccccccc-cccc-cccc-cccc-cccccccccccc'], + }); + + expect(options.clusters).toStrictEqual({ + FlV4ckTxQ0a78hmBkzzc9A: { + count: 2, + cluster_stats: { + plugins: [], + collection_types: { + internal_collection: 2, + }, + pipelines: {}, + queues: { + memory: 2, + }, + }, + versions: [], + }, + '3': { + count: 1, + cluster_stats: { + plugins: [], + collection_types: { + internal_collection: 1, + }, + pipelines: {}, + queues: { + memory: 1, + }, + }, + versions: [], + }, + }); + }); + it('should summarize stats from hits across multiple result objects', () => { const options = getBaseOptions(); @@ -208,6 +319,35 @@ describe('Get Logstash Stats', () => { }); }); + expect(options.allEphemeralIds).toStrictEqual({ + '1n1p': ['cf37c6fa-2f1a-41e2-9a89-36b420a8b9a5'], + '1nmp': [ + '47a70feb-3cb5-4618-8670-2c0bada61acd', + '5a65d966-0330-4bd7-82f2-ee81040c13cf', + '8d33fe25-a2c0-4c54-9ecf-d218cb8dbfe4', + 'f4167a94-20a8-43e7-828e-4cf38d906187', + ], + mnmp: [ + '2fcd4161-e08f-4eea-818b-703ea3ec6389', + 'c6785d63-6e5f-42c2-839d-5edf139b7c19', + 'bc6ef6f2-ecce-4328-96a2-002de41a144d', + '72058ad1-68a1-45f6-a8e8-10621ffc7288', + '18593052-c021-4158-860d-d8122981a0ac', + '4207025c-9b00-4bea-a36c-6fbf2d3c215e', + '0ec4702d-b5e5-4c60-91e9-6fa6a836f0d1', + '41258219-b129-4fad-a629-f244826281f8', + 'e73bc63d-561a-4acd-a0c4-d5f70c4603df', + 'ddf882b7-be26-4a93-8144-0aeb35122651', + '602936f5-98a3-4f8c-9471-cf389a519f4b', + '8b300988-62cc-4bc6-9ee0-9194f3f78e27', + '6ab60531-fb6f-478c-9063-82f2b0af2bed', + '802a5994-a03c-44b8-a650-47c0f71c2e48', + '6070b400-5c10-4c5e-b5c5-a5bd9be6d321', + '3193df5f-2a34-4fe3-816e-6b05999aa5ce', + '994e68cd-d607-40e6-a54c-02a51caa17e0', + ], + }); + expect(options.clusters).toStrictEqual({ '1n1p': { count: 1, diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/get_logstash_stats.ts b/x-pack/plugins/monitoring/server/telemetry_collection/get_logstash_stats.ts index 93c69c644c0649..f4f67a5582303d 100644 --- a/x-pack/plugins/monitoring/server/telemetry_collection/get_logstash_stats.ts +++ b/x-pack/plugins/monitoring/server/telemetry_collection/get_logstash_stats.ts @@ -147,8 +147,6 @@ export function processStatsResults( } clusterStats.collection_types![thisCollectionType] = (clusterStats.collection_types![thisCollectionType] || 0) + 1; - - const theseEphemeralIds: string[] = []; const pipelines = logstashStats.pipelines || []; pipelines.forEach((pipeline) => { @@ -162,10 +160,10 @@ export function processStatsResults( const ephemeralId = pipeline.ephemeral_id; if (ephemeralId !== undefined) { - theseEphemeralIds.push(ephemeralId); + allEphemeralIds[clusterUuid] = allEphemeralIds[clusterUuid] || []; + allEphemeralIds[clusterUuid].push(ephemeralId); } }); - allEphemeralIds[clusterUuid] = theseEphemeralIds; } }); } diff --git a/x-pack/plugins/reporting/public/panel_actions/get_csv_panel_action.test.ts b/x-pack/plugins/reporting/public/panel_actions/get_csv_panel_action.test.ts index 4e1b9ccd2642f8..06d626a4c40442 100644 --- a/x-pack/plugins/reporting/public/panel_actions/get_csv_panel_action.test.ts +++ b/x-pack/plugins/reporting/public/panel_actions/get_csv_panel_action.test.ts @@ -16,6 +16,7 @@ describe('GetCsvReportPanelAction', () => { let core: any; let context: any; let mockLicense$: any; + let mockSearchSource: any; beforeAll(() => { if (typeof window.URL.revokeObjectURL === 'undefined') { @@ -49,22 +50,19 @@ describe('GetCsvReportPanelAction', () => { }, } as any; + mockSearchSource = { + createCopy: () => mockSearchSource, + removeField: jest.fn(), + setField: jest.fn(), + getField: jest.fn(), + getSerializedFields: jest.fn().mockImplementation(() => ({})), + }; + context = { embeddable: { type: 'search', getSavedSearch: () => { - const searchSource = { - createCopy: () => searchSource, - removeField: jest.fn(), - setField: jest.fn(), - getField: jest.fn().mockImplementation((key: string) => { - if (key === 'index') { - return 'my-test-index-*'; - } - }), - getSerializedFields: jest.fn().mockImplementation(() => ({})), - }; - return { searchSource }; + return { searchSource: mockSearchSource }; }, getTitle: () => `The Dude`, getInspectorAdapters: () => null, @@ -79,6 +77,49 @@ describe('GetCsvReportPanelAction', () => { } as any; }); + it('translates empty embeddable context into job params', async () => { + const panel = new GetCsvReportPanelAction(core, mockLicense$()); + + await panel.execute(context); + + expect(core.http.post).toHaveBeenCalledWith( + '/api/reporting/v1/generate/immediate/csv_searchsource', + { + body: '{"searchSource":{},"columns":[],"browserTimezone":"America/New_York"}', + } + ); + }); + + it('translates embeddable context into job params', async () => { + // setup + mockSearchSource = { + createCopy: () => mockSearchSource, + removeField: jest.fn(), + setField: jest.fn(), + getField: jest.fn(), + getSerializedFields: jest.fn().mockImplementation(() => ({ testData: 'testDataValue' })), + }; + context.embeddable.getSavedSearch = () => { + return { + searchSource: mockSearchSource, + columns: ['column_a', 'column_b'], + }; + }; + + const panel = new GetCsvReportPanelAction(core, mockLicense$()); + + // test + await panel.execute(context); + + expect(core.http.post).toHaveBeenCalledWith( + '/api/reporting/v1/generate/immediate/csv_searchsource', + { + body: + '{"searchSource":{"testData":"testDataValue"},"columns":["column_a","column_b"],"browserTimezone":"America/New_York"}', + } + ); + }); + it('allows downloading for valid licenses', async () => { const panel = new GetCsvReportPanelAction(core, mockLicense$()); diff --git a/x-pack/plugins/reporting/public/panel_actions/get_csv_panel_action.tsx b/x-pack/plugins/reporting/public/panel_actions/get_csv_panel_action.tsx index d440edc3f3fe9f..95d193880975c8 100644 --- a/x-pack/plugins/reporting/public/panel_actions/get_csv_panel_action.tsx +++ b/x-pack/plugins/reporting/public/panel_actions/get_csv_panel_action.tsx @@ -7,21 +7,19 @@ import { i18n } from '@kbn/i18n'; import moment from 'moment-timezone'; -import { CoreSetup } from 'src/core/public'; +import type { CoreSetup } from 'src/core/public'; +import type { ISearchEmbeddable, SavedSearch } from '../../../../../src/plugins/discover/public'; import { loadSharingDataHelpers, - ISearchEmbeddable, - SavedSearch, SEARCH_EMBEDDABLE_TYPE, } from '../../../../../src/plugins/discover/public'; -import { IEmbeddable, ViewMode } from '../../../../../src/plugins/embeddable/public'; -import { - IncompatibleActionError, - UiActionsActionDefinition as ActionDefinition, -} from '../../../../../src/plugins/ui_actions/public'; -import { LicensingPluginSetup } from '../../../licensing/public'; +import type { IEmbeddable } from '../../../../../src/plugins/embeddable/public'; +import { ViewMode } from '../../../../../src/plugins/embeddable/public'; +import type { UiActionsActionDefinition as ActionDefinition } from '../../../../../src/plugins/ui_actions/public'; +import { IncompatibleActionError } from '../../../../../src/plugins/ui_actions/public'; +import type { LicensingPluginSetup } from '../../../licensing/public'; import { API_GENERATE_IMMEDIATE, CSV_REPORTING_ACTION } from '../../common/constants'; -import { JobParamsDownloadCSV } from '../../server/export_types/csv_searchsource_immediate/types'; +import type { JobParamsDownloadCSV } from '../../server/export_types/csv_searchsource_immediate/types'; import { checkLicense } from '../lib/license_check'; function isSavedSearchEmbeddable( @@ -64,14 +62,11 @@ export class GetCsvReportPanelAction implements ActionDefinition public async getSearchSource(savedSearch: SavedSearch, embeddable: ISearchEmbeddable) { const { getSharingData } = await loadSharingDataHelpers(); - const searchSource = savedSearch.searchSource.createCopy(); - const { searchSource: serializedSearchSource } = await getSharingData( - searchSource, + return await getSharingData( + savedSearch.searchSource, savedSearch, // TODO: get unsaved state (using embeddale.searchScope): https://github.com/elastic/kibana/issues/43977 this.core.uiSettings ); - - return serializedSearchSource; } public isCompatible = async (context: ActionContext) => { @@ -96,12 +91,13 @@ export class GetCsvReportPanelAction implements ActionDefinition } const savedSearch = embeddable.getSavedSearch(); - const searchSource = await this.getSearchSource(savedSearch, embeddable); + const { columns, searchSource } = await this.getSearchSource(savedSearch, embeddable); const kibanaTimezone = this.core.uiSettings.get('dateFormat:tz'); const browserTimezone = kibanaTimezone === 'Browser' ? moment.tz.guess() : kibanaTimezone; const immediateJobParams: JobParamsDownloadCSV = { searchSource, + columns, browserTimezone, title: savedSearch.title, }; diff --git a/x-pack/plugins/reporting/public/share_context_menu/register_csv_reporting.tsx b/x-pack/plugins/reporting/public/share_context_menu/register_csv_reporting.tsx index 97433f7a4f0c16..8995ef4739b094 100644 --- a/x-pack/plugins/reporting/public/share_context_menu/register_csv_reporting.tsx +++ b/x-pack/plugins/reporting/public/share_context_menu/register_csv_reporting.tsx @@ -8,14 +8,15 @@ import { i18n } from '@kbn/i18n'; import moment from 'moment-timezone'; import React from 'react'; -import { IUiSettingsClient, ToastsSetup } from 'src/core/public'; -import { ShareContext } from '../../../../../src/plugins/share/public'; -import { LicensingPluginSetup } from '../../../licensing/public'; +import type { IUiSettingsClient, ToastsSetup } from 'src/core/public'; +import type { SearchSourceFields } from 'src/plugins/data/common'; +import type { ShareContext } from '../../../../../src/plugins/share/public'; +import type { LicensingPluginSetup } from '../../../licensing/public'; import { CSV_JOB_TYPE } from '../../common/constants'; -import { JobParamsCSV } from '../../server/export_types/csv_searchsource/types'; +import type { JobParamsCSV } from '../../server/export_types/csv_searchsource/types'; import { ReportingPanelContent } from '../components/reporting_panel_content_lazy'; import { checkLicense } from '../lib/license_check'; -import { ReportingAPIClient } from '../lib/reporting_api_client'; +import type { ReportingAPIClient } from '../lib/reporting_api_client'; interface ReportingProvider { apiClient: ReportingAPIClient; @@ -65,7 +66,8 @@ export const csvReportingProvider = ({ browserTimezone, title: sharingData.title as string, objectType, - searchSource: sharingData.searchSource, + searchSource: sharingData.searchSource as SearchSourceFields, + columns: sharingData.columns as string[] | undefined, }; const getJobParams = () => jobParams; diff --git a/x-pack/plugins/reporting/public/share_context_menu/register_pdf_png_reporting.tsx b/x-pack/plugins/reporting/public/share_context_menu/register_pdf_png_reporting.tsx index 87011cc9185877..00ba167c50ae60 100644 --- a/x-pack/plugins/reporting/public/share_context_menu/register_pdf_png_reporting.tsx +++ b/x-pack/plugins/reporting/public/share_context_menu/register_pdf_png_reporting.tsx @@ -8,15 +8,15 @@ import { i18n } from '@kbn/i18n'; import moment from 'moment-timezone'; import React from 'react'; -import { IUiSettingsClient, ToastsSetup } from 'src/core/public'; -import { ShareContext } from '../../../../../src/plugins/share/public'; -import { LicensingPluginSetup } from '../../../licensing/public'; -import { LayoutParams } from '../../common/types'; -import { JobParamsPNG } from '../../server/export_types/png/types'; -import { JobParamsPDF } from '../../server/export_types/printable_pdf/types'; +import type { IUiSettingsClient, ToastsSetup } from 'src/core/public'; +import type { ShareContext } from '../../../../../src/plugins/share/public'; +import type { LicensingPluginSetup } from '../../../licensing/public'; +import type { LayoutParams } from '../../common/types'; +import type { JobParamsPNG } from '../../server/export_types/png/types'; +import type { JobParamsPDF } from '../../server/export_types/printable_pdf/types'; import { ScreenCapturePanelContent } from '../components/screen_capture_panel_content_lazy'; import { checkLicense } from '../lib/license_check'; -import { ReportingAPIClient } from '../lib/reporting_api_client'; +import type { ReportingAPIClient } from '../lib/reporting_api_client'; interface ReportingPDFPNGProvider { apiClient: ReportingAPIClient; diff --git a/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/__snapshots__/generate_csv.test.ts.snap b/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/__snapshots__/generate_csv.test.ts.snap index 62c9ecff830ffd..789b68a25ac423 100644 --- a/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/__snapshots__/generate_csv.test.ts.snap +++ b/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/__snapshots__/generate_csv.test.ts.snap @@ -1,18 +1,36 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`fields cells can be multi-value 1`] = ` +exports[`fields from job.columns (7.13+ generated) cells can be multi-value 1`] = ` +"product,category +coconut,\\"cool, rad\\" +" +`; + +exports[`fields from job.columns (7.13+ generated) columns can be top-level fields such as _id and _index 1`] = ` +"\\"_id\\",\\"_index\\",product,category +\\"my-cool-id\\",\\"my-cool-index\\",coconut,\\"cool, rad\\" +" +`; + +exports[`fields from job.columns (7.13+ generated) empty columns defaults to using searchSource.getFields() 1`] = ` +"product +coconut +" +`; + +exports[`fields from job.searchSource.getFields() (7.12 generated) cells can be multi-value 1`] = ` "\\"_id\\",sku \\"my-cool-id\\",\\"This is a cool SKU., This is also a cool SKU.\\" " `; -exports[`fields provides top-level underscored fields as columns 1`] = ` +exports[`fields from job.searchSource.getFields() (7.12 generated) provides top-level underscored fields as columns 1`] = ` "\\"_id\\",\\"_index\\",date,message \\"my-cool-id\\",\\"my-cool-index\\",\\"2020-12-31T00:14:28.000Z\\",\\"it's nice to see you\\" " `; -exports[`fields sorts the fields when they are to be used as table column names 1`] = ` +exports[`fields from job.searchSource.getFields() (7.12 generated) sorts the fields when they are to be used as table column names 1`] = ` "\\"_id\\",\\"_index\\",\\"_score\\",\\"_type\\",date,\\"message_t\\",\\"message_u\\",\\"message_v\\",\\"message_w\\",\\"message_x\\",\\"message_y\\",\\"message_z\\" \\"my-cool-id\\",\\"my-cool-index\\",\\"'-\\",\\"'-\\",\\"2020-12-31T00:14:28.000Z\\",\\"test field T\\",\\"test field U\\",\\"test field V\\",\\"test field W\\",\\"test field X\\",\\"test field Y\\",\\"test field Z\\" " diff --git a/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.test.ts b/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.test.ts index 0193eaaff2c8d9..8694eddce79677 100644 --- a/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.test.ts +++ b/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.test.ts @@ -326,7 +326,7 @@ it('uses the scrollId to page all the data', async () => { expect(csvResult.content).toMatchSnapshot(); }); -describe('fields', () => { +describe('fields from job.searchSource.getFields() (7.12 generated)', () => { it('cells can be multi-value', async () => { searchSourceMock.getField = jest.fn().mockImplementation((key: string) => { if (key === 'fields') { @@ -497,6 +497,140 @@ describe('fields', () => { }); }); +describe('fields from job.columns (7.13+ generated)', () => { + it('cells can be multi-value', async () => { + mockDataClient.search = jest.fn().mockImplementation(() => + Rx.of({ + rawResponse: { + hits: { + hits: [ + { + _id: 'my-cool-id', + _index: 'my-cool-index', + _version: 4, + fields: { + product: 'coconut', + category: [`cool`, `rad`], + }, + }, + ], + total: 1, + }, + }, + }) + ); + + const generateCsv = new CsvGenerator( + createMockJob({ searchSource: {}, columns: ['product', 'category'] }), + mockConfig, + { + es: mockEsClient, + data: mockDataClient, + uiSettings: uiSettingsClient, + }, + { + searchSourceStart: mockSearchSourceService, + fieldFormatsRegistry: mockFieldFormatsRegistry, + }, + new CancellationToken(), + logger + ); + const csvResult = await generateCsv.generateData(); + + expect(csvResult.content).toMatchSnapshot(); + }); + + it('columns can be top-level fields such as _id and _index', async () => { + mockDataClient.search = jest.fn().mockImplementation(() => + Rx.of({ + rawResponse: { + hits: { + hits: [ + { + _id: 'my-cool-id', + _index: 'my-cool-index', + _version: 4, + fields: { + product: 'coconut', + category: [`cool`, `rad`], + }, + }, + ], + total: 1, + }, + }, + }) + ); + + const generateCsv = new CsvGenerator( + createMockJob({ searchSource: {}, columns: ['_id', '_index', 'product', 'category'] }), + mockConfig, + { + es: mockEsClient, + data: mockDataClient, + uiSettings: uiSettingsClient, + }, + { + searchSourceStart: mockSearchSourceService, + fieldFormatsRegistry: mockFieldFormatsRegistry, + }, + new CancellationToken(), + logger + ); + const csvResult = await generateCsv.generateData(); + + expect(csvResult.content).toMatchSnapshot(); + }); + + it('empty columns defaults to using searchSource.getFields()', async () => { + searchSourceMock.getField = jest.fn().mockImplementation((key: string) => { + if (key === 'fields') { + return ['product']; + } + return mockSearchSourceGetFieldDefault(key); + }); + mockDataClient.search = jest.fn().mockImplementation(() => + Rx.of({ + rawResponse: { + hits: { + hits: [ + { + _id: 'my-cool-id', + _index: 'my-cool-index', + _version: 4, + fields: { + product: 'coconut', + category: [`cool`, `rad`], + }, + }, + ], + total: 1, + }, + }, + }) + ); + + const generateCsv = new CsvGenerator( + createMockJob({ searchSource: {}, columns: [] }), + mockConfig, + { + es: mockEsClient, + data: mockDataClient, + uiSettings: uiSettingsClient, + }, + { + searchSourceStart: mockSearchSourceService, + fieldFormatsRegistry: mockFieldFormatsRegistry, + }, + new CancellationToken(), + logger + ); + const csvResult = await generateCsv.generateData(); + + expect(csvResult.content).toMatchSnapshot(); + }); +}); + describe('formulas', () => { const TEST_FORMULA = '=SUM(A1:A2)'; diff --git a/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.ts b/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.ts index 01959ed08036da..7517396961c004 100644 --- a/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.ts +++ b/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.ts @@ -20,6 +20,7 @@ import { ISearchSource, ISearchStartSearchSource, SearchFieldValue, + SearchSourceFields, tabifyDocs, } from '../../../../../../../src/plugins/data/common'; import { KbnServerError } from '../../../../../../../src/plugins/kibana_utils/server'; @@ -60,7 +61,8 @@ function isPlainStringArray( } export class CsvGenerator { - private _formatters: Record | null = null; + private _columns?: string[]; + private _formatters?: Record; private csvContainsFormulas = false; private maxSizeReached = false; private csvRowCount = 0; @@ -135,27 +137,36 @@ export class CsvGenerator { }; } - // use fields/fieldsFromSource from the searchSource to get the ordering of columns - // otherwise use the table columns as they are - private getFields(searchSource: ISearchSource, table: Datatable): string[] { - const fieldValues: Record = { - fields: searchSource.getField('fields'), - fieldsFromSource: searchSource.getField('fieldsFromSource'), - }; - const fieldSource = fieldValues.fieldsFromSource ? 'fieldsFromSource' : 'fields'; - this.logger.debug(`Getting search source fields from: '${fieldSource}'`); - - const fields = fieldValues[fieldSource]; - // Check if field name values are string[] and if the fields are user-defined - if (isPlainStringArray(fields)) { - return fields; + private getColumns(searchSource: ISearchSource, table: Datatable) { + if (this._columns != null) { + return this._columns; } - // Default to using the table column IDs as the fields - const columnIds = table.columns.map((c) => c.id); - // Fields in the API response don't come sorted - they need to be sorted client-side - columnIds.sort(); - return columnIds; + // if columns is not provided in job params, + // default to use fields/fieldsFromSource from the searchSource to get the ordering of columns + const getFromSearchSource = (): string[] => { + const fieldValues: Pick = { + fields: searchSource.getField('fields'), + fieldsFromSource: searchSource.getField('fieldsFromSource'), + }; + const fieldSource = fieldValues.fieldsFromSource ? 'fieldsFromSource' : 'fields'; + this.logger.debug(`Getting columns from '${fieldSource}' in search source.`); + + const fields = fieldValues[fieldSource]; + // Check if field name values are string[] and if the fields are user-defined + if (isPlainStringArray(fields)) { + return fields; + } + + // Default to using the table column IDs as the fields + const columnIds = table.columns.map((c) => c.id); + // Fields in the API response don't come sorted - they need to be sorted client-side + columnIds.sort(); + return columnIds; + }; + this._columns = this.job.columns?.length ? this.job.columns : getFromSearchSource(); + + return this._columns; } private formatCellValues(formatters: Record) { @@ -202,16 +213,16 @@ export class CsvGenerator { } /* - * Use the list of fields to generate the header row + * Use the list of columns to generate the header row */ private generateHeader( - fields: string[], + columns: string[], table: Datatable, builder: MaxSizeStringBuilder, settings: CsvExportSettings ) { this.logger.debug(`Building CSV header row...`); - const header = fields.map(this.escapeValues(settings)).join(settings.separator) + '\n'; + const header = columns.map(this.escapeValues(settings)).join(settings.separator) + '\n'; if (!builder.tryAppend(header)) { return { @@ -227,7 +238,7 @@ export class CsvGenerator { * Format a Datatable into rows of CSV content */ private generateRows( - fields: string[], + columns: string[], table: Datatable, builder: MaxSizeStringBuilder, formatters: Record, @@ -240,7 +251,7 @@ export class CsvGenerator { } const row = - fields + columns .map((f) => ({ column: f, data: dataTableRow[f] })) .map(this.formatCellValues(formatters)) .map(this.escapeValues(settings)) @@ -338,11 +349,13 @@ export class CsvGenerator { break; } - const fields = this.getFields(searchSource, table); + // If columns exists in the job params, use it to order the CSV columns + // otherwise, get the ordering from the searchSource's fields / fieldsFromSource + const columns = this.getColumns(searchSource, table); if (first) { first = false; - this.generateHeader(fields, table, builder, settings); + this.generateHeader(columns, table, builder, settings); } if (table.rows.length < 1) { @@ -350,7 +363,7 @@ export class CsvGenerator { } const formatters = this.getFormatters(table); - this.generateRows(fields, table, builder, formatters, settings); + this.generateRows(columns, table, builder, formatters, settings); // update iterator currentRecord += table.rows.length; diff --git a/x-pack/plugins/reporting/server/export_types/csv_searchsource/types.d.ts b/x-pack/plugins/reporting/server/export_types/csv_searchsource/types.d.ts index f0ad4e00ebd5cc..d2a9e2b5bf783c 100644 --- a/x-pack/plugins/reporting/server/export_types/csv_searchsource/types.d.ts +++ b/x-pack/plugins/reporting/server/export_types/csv_searchsource/types.d.ts @@ -5,13 +5,15 @@ * 2.0. */ -import { BaseParams, BasePayload } from '../../types'; +import type { SearchSourceFields } from 'src/plugins/data/common'; +import type { BaseParams, BasePayload } from '../../types'; export type RawValue = string | object | null | undefined; interface BaseParamsCSV { browserTimezone: string; - searchSource: any; + searchSource: SearchSourceFields; + columns?: string[]; } export type JobParamsCSV = BaseParamsCSV & BaseParams; diff --git a/x-pack/plugins/reporting/server/export_types/csv_searchsource_immediate/types.d.ts b/x-pack/plugins/reporting/server/export_types/csv_searchsource_immediate/types.d.ts index 276016dd612332..cb1dd659ee2c21 100644 --- a/x-pack/plugins/reporting/server/export_types/csv_searchsource_immediate/types.d.ts +++ b/x-pack/plugins/reporting/server/export_types/csv_searchsource_immediate/types.d.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { TimeRangeParams } from '../common'; +import type { SearchSourceFields } from 'src/plugins/data/common'; export interface FakeRequest { headers: Record; @@ -14,7 +14,8 @@ export interface FakeRequest { export interface JobParamsDownloadCSV { browserTimezone: string; title: string; - searchSource: any; + searchSource: SearchSourceFields; + columns?: string[]; } export interface SavedObjectServiceError { diff --git a/x-pack/plugins/reporting/server/routes/csv_searchsource_immediate.ts b/x-pack/plugins/reporting/server/routes/csv_searchsource_immediate.ts index 55092b5236ce66..5d2b77c082ca56 100644 --- a/x-pack/plugins/reporting/server/routes/csv_searchsource_immediate.ts +++ b/x-pack/plugins/reporting/server/routes/csv_searchsource_immediate.ts @@ -44,6 +44,7 @@ export function registerGenerateCsvFromSavedObjectImmediate( path: `${API_BASE_GENERATE_V1}/immediate/csv_searchsource`, validate: { body: schema.object({ + columns: schema.maybe(schema.arrayOf(schema.string())), searchSource: schema.object({}, { unknowns: 'allow' }), browserTimezone: schema.string({ defaultValue: 'UTC' }), title: schema.string(), diff --git a/x-pack/plugins/rollup/server/routes/api/jobs/register_delete_route.ts b/x-pack/plugins/rollup/server/routes/api/jobs/register_delete_route.ts index f90a81f73823ed..7e22b5c4ead10a 100644 --- a/x-pack/plugins/rollup/server/routes/api/jobs/register_delete_route.ts +++ b/x-pack/plugins/rollup/server/routes/api/jobs/register_delete_route.ts @@ -37,7 +37,7 @@ export const registerDeleteRoute = ({ // Until then we'll modify the response here. if ( err?.meta && - err.body?.task_failures[0]?.reason?.reason?.includes( + err.body?.task_failures?.[0]?.reason?.reason?.includes( 'Job must be [STOPPED] before deletion' ) ) { diff --git a/x-pack/plugins/security/common/model/api_key.ts b/x-pack/plugins/security/common/model/api_key.ts index 08f8378d145cea..f2467468f8069b 100644 --- a/x-pack/plugins/security/common/model/api_key.ts +++ b/x-pack/plugins/security/common/model/api_key.ts @@ -5,6 +5,8 @@ * 2.0. */ +import type { Role } from './role'; + export interface ApiKey { id: string; name: string; @@ -19,3 +21,5 @@ export interface ApiKeyToInvalidate { id: string; name: string; } + +export type ApiKeyRoleDescriptors = Record; diff --git a/x-pack/plugins/security/common/model/index.ts b/x-pack/plugins/security/common/model/index.ts index bca8b69d03fca1..8eb341ef9bd371 100644 --- a/x-pack/plugins/security/common/model/index.ts +++ b/x-pack/plugins/security/common/model/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -export { ApiKey, ApiKeyToInvalidate } from './api_key'; +export { ApiKey, ApiKeyToInvalidate, ApiKeyRoleDescriptors } from './api_key'; export { User, EditUser, getUserDisplayName } from './user'; export { AuthenticatedUser, canUserChangePassword } from './authenticated_user'; export { AuthenticationProvider, shouldProviderUseLoginForm } from './authentication_provider'; diff --git a/x-pack/plugins/security/public/components/breadcrumb.tsx b/x-pack/plugins/security/public/components/breadcrumb.tsx index 4462e2bce6abc7..353f738501cbef 100644 --- a/x-pack/plugins/security/public/components/breadcrumb.tsx +++ b/x-pack/plugins/security/public/components/breadcrumb.tsx @@ -9,6 +9,8 @@ import type { EuiBreadcrumb } from '@elastic/eui'; import type { FunctionComponent } from 'react'; import React, { createContext, useContext, useEffect, useRef } from 'react'; +import type { ChromeStart } from 'src/core/public'; + import { useKibana } from '../../../../../src/plugins/kibana_react/public'; interface BreadcrumbsContext { @@ -81,8 +83,8 @@ export const BreadcrumbsProvider: FunctionComponent = if (onChange) { onChange(breadcrumbs); } else if (services.chrome) { - services.chrome.setBreadcrumbs(breadcrumbs); - services.chrome.docTitle.change(getDocTitle(breadcrumbs)); + const setBreadcrumbs = createBreadcrumbsChangeHandler(services.chrome); + setBreadcrumbs(breadcrumbs); } }; @@ -138,3 +140,17 @@ export function getDocTitle(breadcrumbs: BreadcrumbProps[], maxBreadcrumbs = 2) .reverse() .map(({ text }) => text); } + +export function createBreadcrumbsChangeHandler( + chrome: Pick, + setBreadcrumbs = chrome.setBreadcrumbs +) { + return (breadcrumbs: BreadcrumbProps[]) => { + setBreadcrumbs(breadcrumbs); + if (breadcrumbs.length === 0) { + chrome.docTitle.reset(); + } else { + chrome.docTitle.change(getDocTitle(breadcrumbs)); + } + }; +} diff --git a/x-pack/plugins/security/public/components/confirm_modal.tsx b/x-pack/plugins/security/public/components/confirm_modal.tsx deleted file mode 100644 index 80c2008642d049..00000000000000 --- a/x-pack/plugins/security/public/components/confirm_modal.tsx +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { EuiButtonProps, EuiModalProps } from '@elastic/eui'; -import { - EuiButton, - EuiButtonEmpty, - EuiFlexGroup, - EuiFlexItem, - EuiModal, - EuiModalBody, - EuiModalFooter, - EuiModalHeader, - EuiModalHeaderTitle, -} from '@elastic/eui'; -import type { FunctionComponent } from 'react'; -import React from 'react'; - -import { FormattedMessage } from '@kbn/i18n/react'; - -export interface ConfirmModalProps extends Omit { - confirmButtonText: string; - confirmButtonColor?: EuiButtonProps['color']; - isLoading?: EuiButtonProps['isLoading']; - isDisabled?: EuiButtonProps['isDisabled']; - onCancel(): void; - onConfirm(): void; -} - -/** - * Component that renders a confirmation modal similar to `EuiConfirmModal`, except that - * it adds `isLoading` prop, which renders a loading spinner and disables action buttons. - */ -export const ConfirmModal: FunctionComponent = ({ - children, - confirmButtonColor: buttonColor, - confirmButtonText, - isLoading, - isDisabled, - onCancel, - onConfirm, - title, - ...rest -}) => ( - - - {title} - - {children} - - - - - - - - - - {confirmButtonText} - - - - - -); diff --git a/x-pack/plugins/security/public/components/token_field.tsx b/x-pack/plugins/security/public/components/token_field.tsx new file mode 100644 index 00000000000000..98eee9352937c0 --- /dev/null +++ b/x-pack/plugins/security/public/components/token_field.tsx @@ -0,0 +1,140 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { EuiFieldTextProps } from '@elastic/eui'; +import { + EuiButtonEmpty, + EuiButtonIcon, + EuiContextMenuItem, + EuiContextMenuPanel, + EuiCopy, + EuiFormControlLayout, + EuiHorizontalRule, + EuiPopover, + EuiSpacer, + EuiText, +} from '@elastic/eui'; +import type { FunctionComponent, ReactElement } from 'react'; +import React from 'react'; + +import { i18n } from '@kbn/i18n'; +import { euiThemeVars } from '@kbn/ui-shared-deps/theme'; + +export interface TokenFieldProps extends Omit { + value: string; +} + +export const TokenField: FunctionComponent = (props) => { + return ( + + {(copyText) => ( + + )} + + } + style={{ backgroundColor: 'transparent' }} + readOnly + > + event.currentTarget.select()} + readOnly + /> + + ); +}; + +export interface SelectableTokenFieldOption { + key: string; + value: string; + icon?: string; + label: string; + description?: string; +} + +export interface SelectableTokenFieldProps extends Omit { + options: SelectableTokenFieldOption[]; +} + +export const SelectableTokenField: FunctionComponent = (props) => { + const { options, ...rest } = props; + const [isPopoverOpen, setIsPopoverOpen] = React.useState(false); + const [selectedOption, setSelectedOption] = React.useState( + options[0] + ); + const selectedIndex = options.findIndex((c) => c.key === selectedOption.key); + const closePopover = () => setIsPopoverOpen(false); + + return ( + setIsPopoverOpen(!isPopoverOpen)} + > + {selectedOption.label} + + } + isOpen={isPopoverOpen} + panelPaddingSize="none" + closePopover={closePopover} + > + ((items, option, i) => { + items.push( + { + closePopover(); + setSelectedOption(option); + }} + > + {option.label} + + +

{option.description}

+
+
+ ); + if (i < options.length - 1) { + items.push(); + } + return items; + }, [])} + /> + + } + value={selectedOption.value} + /> + ); +}; diff --git a/x-pack/plugins/security/public/components/use_initial_focus.ts b/x-pack/plugins/security/public/components/use_initial_focus.ts new file mode 100644 index 00000000000000..d8dd57f81070f8 --- /dev/null +++ b/x-pack/plugins/security/public/components/use_initial_focus.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { DependencyList } from 'react'; +import { useEffect, useRef } from 'react'; + +/** + * Creates a ref for an HTML element, which will be focussed on mount. + * + * @example + * ```typescript + * const firstInput = useInitialFocus(); + * + * + * ``` + * + * Pass in a dependency list to focus conditionally rendered components: + * + * @example + * ```typescript + * const firstInput = useInitialFocus([showField]); + * + * {showField ? : undefined} + * ``` + */ +export function useInitialFocus(deps: DependencyList = []) { + const inputRef = useRef(null); + useEffect(() => { + if (inputRef.current) { + inputRef.current.focus(); + } + }, deps); // eslint-disable-line react-hooks/exhaustive-deps + return inputRef; +} diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_api_client.mock.ts b/x-pack/plugins/security/public/management/api_keys/api_keys_api_client.mock.ts index cfb20229d3f6be..1ba35a20a5e5f6 100644 --- a/x-pack/plugins/security/public/management/api_keys/api_keys_api_client.mock.ts +++ b/x-pack/plugins/security/public/management/api_keys/api_keys_api_client.mock.ts @@ -10,5 +10,6 @@ export const apiKeysAPIClientMock = { checkPrivileges: jest.fn(), getApiKeys: jest.fn(), invalidateApiKeys: jest.fn(), + createApiKey: jest.fn(), }), }; diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_api_client.test.ts b/x-pack/plugins/security/public/management/api_keys/api_keys_api_client.test.ts index 8c79ee5bb0be50..03c256942ea5d3 100644 --- a/x-pack/plugins/security/public/management/api_keys/api_keys_api_client.test.ts +++ b/x-pack/plugins/security/public/management/api_keys/api_keys_api_client.test.ts @@ -84,4 +84,20 @@ describe('APIKeysAPIClient', () => { body: JSON.stringify({ apiKeys: mockAPIKeys, isAdmin: true }), }); }); + + it('createApiKey() queries correct endpoint', async () => { + const httpMock = httpServiceMock.createStartContract(); + + const mockResponse = Symbol('mockResponse'); + httpMock.post.mockResolvedValue(mockResponse); + + const apiClient = new APIKeysAPIClient(httpMock); + const mockAPIKeys = { name: 'name', expiration: '7d' }; + + await expect(apiClient.createApiKey(mockAPIKeys)).resolves.toBe(mockResponse); + expect(httpMock.post).toHaveBeenCalledTimes(1); + expect(httpMock.post).toHaveBeenCalledWith('/internal/security/api_key', { + body: JSON.stringify(mockAPIKeys), + }); + }); }); diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_api_client.ts b/x-pack/plugins/security/public/management/api_keys/api_keys_api_client.ts index 318837f0913279..65540fd7ebfc15 100644 --- a/x-pack/plugins/security/public/management/api_keys/api_keys_api_client.ts +++ b/x-pack/plugins/security/public/management/api_keys/api_keys_api_client.ts @@ -7,23 +7,36 @@ import type { HttpStart } from 'src/core/public'; -import type { ApiKey, ApiKeyToInvalidate } from '../../../common/model'; +import type { ApiKey, ApiKeyRoleDescriptors, ApiKeyToInvalidate } from '../../../common/model'; -interface CheckPrivilegesResponse { +export interface CheckPrivilegesResponse { areApiKeysEnabled: boolean; isAdmin: boolean; canManage: boolean; } -interface InvalidateApiKeysResponse { +export interface InvalidateApiKeysResponse { itemsInvalidated: ApiKeyToInvalidate[]; errors: any[]; } -interface GetApiKeysResponse { +export interface GetApiKeysResponse { apiKeys: ApiKey[]; } +export interface CreateApiKeyRequest { + name: string; + expiration?: string; + role_descriptors?: ApiKeyRoleDescriptors; +} + +export interface CreateApiKeyResponse { + id: string; + name: string; + expiration: number; + api_key: string; +} + const apiKeysUrl = '/internal/security/api_key'; export class APIKeysAPIClient { @@ -42,4 +55,10 @@ export class APIKeysAPIClient { body: JSON.stringify({ apiKeys, isAdmin }), }); } + + public async createApiKey(apiKey: CreateApiKeyRequest) { + return await this.http.post(apiKeysUrl, { + body: JSON.stringify(apiKey), + }); + } } diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/__snapshots__/api_keys_grid_page.test.tsx.snap b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/__snapshots__/api_keys_grid_page.test.tsx.snap deleted file mode 100644 index a743c4e610da3c..00000000000000 --- a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/__snapshots__/api_keys_grid_page.test.tsx.snap +++ /dev/null @@ -1,243 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`APIKeysGridPage renders a callout when API keys are not enabled 1`] = ` - - } -> -
-
- - - - API keys not enabled in Elasticsearch - - -
- -
- - - , - } - } - > - Contact your system administrator and refer to the - - - - docs - - - - - - - - (opens in a new tab or window) - - - - - - to enable API keys. - -
-
-
-
-`; - -exports[`APIKeysGridPage renders permission denied if user does not have required permissions 1`] = ` - - -
- - -
- - -

- } - iconType="securityApp" - title={ -

- -

- } - > -
- - - - -
- - - - -

- - You need permission to manage API keys - -

-
- -
- - -
-

- - Contact your system administrator. - -

-
-
- - -
- -
- - -
- - -`; diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_empty_prompt.tsx b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_empty_prompt.tsx new file mode 100644 index 00000000000000..eaded9a5c83ee1 --- /dev/null +++ b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_empty_prompt.tsx @@ -0,0 +1,148 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiAccordion, EuiEmptyPrompt, EuiErrorBoundary, EuiSpacer, EuiText } from '@elastic/eui'; +import type { FunctionComponent } from 'react'; +import React from 'react'; + +import { FormattedMessage } from '@kbn/i18n/react'; + +import { DocLink } from '../../../components/doc_link'; +import { useHtmlId } from '../../../components/use_html_id'; + +export interface ApiKeysEmptyPromptProps { + error?: Error; +} + +export const ApiKeysEmptyPrompt: FunctionComponent = ({ + error, + children, +}) => { + const accordionId = useHtmlId('apiKeysEmptyPrompt', 'accordion'); + + if (error) { + if (doesErrorIndicateAPIKeysAreDisabled(error)) { + return ( + +

+ +

+

+ + + +

+ + } + /> + ); + } + + if (doesErrorIndicateUserHasNoPermissionsToManageAPIKeys(error)) { + return ( + + +

+ } + /> + ); + } + + const ThrowError = () => { + throw error; + }; + + return ( + + +

+ } + actions={ + <> + {children} + + + + } + buttonProps={{ + style: { display: 'flex', justifyContent: 'center' }, + }} + arrowDisplay="right" + paddingSize="m" + > + + + + + + + + } + /> + ); + } + + return ( + + + + } + body={ +

+ +

+ } + actions={children} + /> + ); +}; + +function doesErrorIndicateAPIKeysAreDisabled(error: Record) { + const message = error.body?.message || ''; + return message.indexOf('disabled.feature="api_keys"') !== -1; +} + +function doesErrorIndicateUserHasNoPermissionsToManageAPIKeys(error: Record) { + return error.body?.statusCode === 403; +} diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_grid_page.test.tsx b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_grid_page.test.tsx index ff9fbad5c05b52..ba879e99f15981 100644 --- a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_grid_page.test.tsx +++ b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_grid_page.test.tsx @@ -5,182 +5,292 @@ * 2.0. */ -import { EuiCallOut } from '@elastic/eui'; -import type { ReactWrapper } from 'enzyme'; +import { + fireEvent, + render, + waitFor, + waitForElementToBeRemoved, + within, +} from '@testing-library/react'; +import { createMemoryHistory } from 'history'; import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; -import type { PublicMethodsOf } from '@kbn/utility-types'; -import { coreMock } from 'src/core/public/mocks'; -import { KibanaContextProvider } from 'src/plugins/kibana_react/public'; - -import type { APIKeysAPIClient } from '../api_keys_api_client'; +import { coreMock } from '../../../../../../../src/core/public/mocks'; +import { mockAuthenticatedUser } from '../../../../common/model/authenticated_user.mock'; +import { securityMock } from '../../../mocks'; +import { Providers } from '../api_keys_management_app'; import { apiKeysAPIClientMock } from '../index.mock'; import { APIKeysGridPage } from './api_keys_grid_page'; -import { NotEnabled } from './not_enabled'; -import { PermissionDenied } from './permission_denied'; - -const mock500 = () => ({ body: { error: 'Internal Server Error', message: '', statusCode: 500 } }); - -const waitForRender = async ( - wrapper: ReactWrapper, - condition: (wrapper: ReactWrapper) => boolean -) => { - return new Promise((resolve, reject) => { - const interval = setInterval(async () => { - await Promise.resolve(); - wrapper.update(); - if (condition(wrapper)) { - resolve(); - } - }, 10); - - setTimeout(() => { - clearInterval(interval); - reject(new Error('waitForRender timeout after 2000ms')); - }, 2000); - }); -}; -describe('APIKeysGridPage', () => { - let apiClientMock: jest.Mocked>; - beforeEach(() => { - apiClientMock = apiKeysAPIClientMock.create(); - apiClientMock.checkPrivileges.mockResolvedValue({ - isAdmin: true, - areApiKeysEnabled: true, - canManage: true, - }); - apiClientMock.getApiKeys.mockResolvedValue({ - apiKeys: [ - { - creation: 1571322182082, - expiration: 1571408582082, - id: '0QQZ2m0BO2XZwgJFuWTT', - invalidated: false, - name: 'my-api-key', - realm: 'reserved', - username: 'elastic', - }, - ], - }); - }); +jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({ + htmlIdGenerator: () => () => `id-${Math.random()}`, +})); + +jest.setTimeout(15000); + +const coreStart = coreMock.createStart(); + +const apiClientMock = apiKeysAPIClientMock.create(); +apiClientMock.checkPrivileges.mockResolvedValue({ + areApiKeysEnabled: true, + canManage: true, + isAdmin: true, +}); +apiClientMock.getApiKeys.mockResolvedValue({ + apiKeys: [ + { + creation: 1571322182082, + expiration: 1571408582082, + id: '0QQZ2m0BO2XZwgJFuWTT', + invalidated: false, + name: 'first-api-key', + realm: 'reserved', + username: 'elastic', + }, + { + creation: 1571322182082, + expiration: 1571408582082, + id: 'BO2XZwgJFuWTT0QQZ2m0', + invalidated: false, + name: 'second-api-key', + realm: 'reserved', + username: 'elastic', + }, + ], +}); - const coreStart = coreMock.createStart(); - const renderView = () => { - return mountWithIntl( - - - +const authc = securityMock.createSetup().authc; +authc.getCurrentUser.mockResolvedValue( + mockAuthenticatedUser({ + username: 'jdoe', + full_name: '', + email: '', + enabled: true, + roles: ['superuser'], + }) +); + +describe('APIKeysGridPage', () => { + it('loads and displays API keys', async () => { + const history = createMemoryHistory({ initialEntries: ['/'] }); + + const { getByText } = render( + + + ); - }; - it('renders a loading state when fetching API keys', async () => { - expect(renderView().find('[data-test-subj="apiKeysSectionLoading"]')).toHaveLength(1); + await waitForElementToBeRemoved(() => getByText(/Loading API keys/)); + getByText(/first-api-key/); + getByText(/second-api-key/); }); - it('renders a callout when API keys are not enabled', async () => { - apiClientMock.checkPrivileges.mockResolvedValue({ - isAdmin: true, - canManage: true, + it('displays callout when API keys are disabled', async () => { + const history = createMemoryHistory({ initialEntries: ['/'] }); + apiClientMock.checkPrivileges.mockResolvedValueOnce({ areApiKeysEnabled: false, + canManage: true, + isAdmin: true, }); - const wrapper = renderView(); - await waitForRender(wrapper, (updatedWrapper) => { - return updatedWrapper.find(NotEnabled).length > 0; - }); + const { getByText } = render( + + + + ); - expect(wrapper.find(NotEnabled).find(EuiCallOut)).toMatchSnapshot(); + await waitForElementToBeRemoved(() => getByText(/Loading API keys/)); + getByText(/API keys not enabled/); }); - it('renders permission denied if user does not have required permissions', async () => { - apiClientMock.checkPrivileges.mockResolvedValue({ + it('displays error when user does not have required permissions', async () => { + const history = createMemoryHistory({ initialEntries: ['/'] }); + apiClientMock.checkPrivileges.mockResolvedValueOnce({ + areApiKeysEnabled: true, canManage: false, isAdmin: false, - areApiKeysEnabled: true, }); - const wrapper = renderView(); - await waitForRender(wrapper, (updatedWrapper) => { - return updatedWrapper.find(PermissionDenied).length > 0; - }); + const { getByText } = render( + + + + ); - expect(wrapper.find(PermissionDenied)).toMatchSnapshot(); + await waitForElementToBeRemoved(() => getByText(/Loading API keys/)); + getByText(/You need permission to manage API keys/); }); - it('renders error callout if error fetching API keys', async () => { - apiClientMock.getApiKeys.mockRejectedValue(mock500()); - - const wrapper = renderView(); - await waitForRender(wrapper, (updatedWrapper) => { - return updatedWrapper.find(EuiCallOut).length > 0; + it('displays error when fetching API keys fails', async () => { + apiClientMock.getApiKeys.mockRejectedValueOnce({ + body: { error: 'Internal Server Error', message: '', statusCode: 500 }, }); + const history = createMemoryHistory({ initialEntries: ['/'] }); + + const { getByText } = render( + + + + ); - expect(wrapper.find('EuiCallOut[data-test-subj="apiKeysError"]')).toHaveLength(1); + await waitForElementToBeRemoved(() => getByText(/Loading API keys/)); + getByText(/Could not load API keys/); }); - describe('Admin view', () => { - let wrapper: ReactWrapper; - beforeEach(() => { - wrapper = renderView(); - }); + it('creates API key when submitting form, redirects back and displays base64', async () => { + const history = createMemoryHistory({ initialEntries: ['/create'] }); + coreStart.http.get.mockResolvedValue([{ name: 'superuser' }]); + coreStart.http.post.mockResolvedValue({ id: '1D', api_key: 'AP1_K3Y' }); + + const { findByRole, findByDisplayValue } = render( + + + + ); + expect(coreStart.http.get).toHaveBeenCalledWith('/api/security/role'); - it('renders a callout indicating the user is an administrator', async () => { - const calloutEl = 'EuiCallOut[data-test-subj="apiKeyAdminDescriptionCallOut"]'; + const dialog = await findByRole('dialog'); - await waitForRender(wrapper, (updatedWrapper) => { - return updatedWrapper.find(calloutEl).length > 0; - }); + fireEvent.click(await findByRole('button', { name: 'Create API key' })); + + const alert = await findByRole('alert'); + within(alert).getByText(/Enter a name/i); - expect(wrapper.find(calloutEl).text()).toEqual('You are an API Key administrator.'); + fireEvent.change(await within(dialog).findByLabelText('Name'), { + target: { value: 'Test' }, }); - it('renders the correct description text', async () => { - const descriptionEl = 'EuiText[data-test-subj="apiKeysDescriptionText"]'; + fireEvent.click(await findByRole('button', { name: 'Create API key' })); - await waitForRender(wrapper, (updatedWrapper) => { - return updatedWrapper.find(descriptionEl).length > 0; + await waitFor(() => { + expect(coreStart.http.post).toHaveBeenLastCalledWith('/internal/security/api_key', { + body: JSON.stringify({ name: 'Test' }), }); - - expect(wrapper.find(descriptionEl).text()).toEqual( - 'View and invalidate API keys. An API key sends requests on behalf of a user.' - ); + expect(history.location.pathname).toBe('/'); }); + + await findByDisplayValue(btoa('1D:AP1_K3Y')); }); - describe('Non-admin view', () => { - let wrapper: ReactWrapper; - beforeEach(() => { - apiClientMock.checkPrivileges.mockResolvedValue({ - isAdmin: false, - canManage: true, - areApiKeysEnabled: true, - }); + it('creates API key with optional expiration, redirects back and displays base64', async () => { + const history = createMemoryHistory({ initialEntries: ['/create'] }); + coreStart.http.get.mockResolvedValue([{ name: 'superuser' }]); + coreStart.http.post.mockResolvedValue({ id: '1D', api_key: 'AP1_K3Y' }); + + const { findByRole, findByDisplayValue } = render( + + + + ); + expect(coreStart.http.get).toHaveBeenCalledWith('/api/security/role'); - wrapper = renderView(); + const dialog = await findByRole('dialog'); + + fireEvent.change(await within(dialog).findByLabelText('Name'), { + target: { value: 'Test' }, }); - it('does NOT render a callout indicating the user is an administrator', async () => { - const descriptionEl = 'EuiText[data-test-subj="apiKeysDescriptionText"]'; - const calloutEl = 'EuiCallOut[data-test-subj="apiKeyAdminDescriptionCallOut"]'; + fireEvent.click(await within(dialog).findByLabelText('Expire after time')); - await waitForRender(wrapper, (updatedWrapper) => { - return updatedWrapper.find(descriptionEl).length > 0; - }); + fireEvent.click(await findByRole('button', { name: 'Create API key' })); - expect(wrapper.find(calloutEl).length).toEqual(0); + const alert = await findByRole('alert'); + within(alert).getByText(/Enter a valid duration or disable this option\./i); + + fireEvent.change(await within(dialog).findByLabelText('Lifetime (days)'), { + target: { value: '12' }, }); - it('renders the correct description text', async () => { - const descriptionEl = 'EuiText[data-test-subj="apiKeysDescriptionText"]'; + fireEvent.click(await findByRole('button', { name: 'Create API key' })); - await waitForRender(wrapper, (updatedWrapper) => { - return updatedWrapper.find(descriptionEl).length > 0; + await waitFor(() => { + expect(coreStart.http.post).toHaveBeenLastCalledWith('/internal/security/api_key', { + body: JSON.stringify({ name: 'Test', expiration: '12d' }), }); + expect(history.location.pathname).toBe('/'); + }); + + await findByDisplayValue(btoa('1D:AP1_K3Y')); + }); + + it('deletes api key using cta button', async () => { + const history = createMemoryHistory({ initialEntries: ['/'] }); + + const { findByRole, findAllByLabelText } = render( + + + + ); + + const [deleteButton] = await findAllByLabelText(/Delete/i); + fireEvent.click(deleteButton); + + const dialog = await findByRole('dialog'); + fireEvent.click(await within(dialog).findByRole('button', { name: 'Delete API key' })); + + await waitFor(() => { + expect(apiClientMock.invalidateApiKeys).toHaveBeenLastCalledWith( + [{ id: '0QQZ2m0BO2XZwgJFuWTT', name: 'first-api-key' }], + true + ); + }); + }); + + it('deletes multiple api keys using bulk select', async () => { + const history = createMemoryHistory({ initialEntries: ['/'] }); + + const { findByRole, findAllByRole } = render( + + + + ); + + const deleteCheckboxes = await findAllByRole('checkbox', { name: 'Select this row' }); + deleteCheckboxes.forEach((checkbox) => fireEvent.click(checkbox)); + fireEvent.click(await findByRole('button', { name: 'Delete API keys' })); + + const dialog = await findByRole('dialog'); + fireEvent.click(await within(dialog).findByRole('button', { name: 'Delete API keys' })); - expect(wrapper.find(descriptionEl).text()).toEqual( - 'View and invalidate your API keys. An API key sends requests on your behalf.' + await waitFor(() => { + expect(apiClientMock.invalidateApiKeys).toHaveBeenLastCalledWith( + [ + { id: '0QQZ2m0BO2XZwgJFuWTT', name: 'first-api-key' }, + { id: 'BO2XZwgJFuWTT0QQZ2m0', name: 'second-api-key' }, + ], + true ); }); }); diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_grid_page.tsx b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_grid_page.tsx index 62ca51be2ede8d..442c1d910f8142 100644 --- a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_grid_page.tsx +++ b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_grid_page.tsx @@ -9,10 +9,11 @@ import type { EuiBasicTableColumn, EuiInMemoryTableProps } from '@elastic/eui'; import { EuiBadge, EuiButton, - EuiButtonIcon, EuiCallOut, EuiFlexGroup, EuiFlexItem, + EuiHealth, + EuiIcon, EuiInMemoryTable, EuiPageContent, EuiPageContentBody, @@ -23,8 +24,10 @@ import { EuiTitle, EuiToolTip, } from '@elastic/eui'; +import type { History } from 'history'; import moment from 'moment-timezone'; import React, { Component } from 'react'; +import { Route } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -32,14 +35,20 @@ import type { PublicMethodsOf } from '@kbn/utility-types'; import type { NotificationsStart } from 'src/core/public'; import { SectionLoading } from '../../../../../../../src/plugins/es_ui_shared/public'; +import { reactRouterNavigate } from '../../../../../../../src/plugins/kibana_react/public'; import type { ApiKey, ApiKeyToInvalidate } from '../../../../common/model'; -import type { APIKeysAPIClient } from '../api_keys_api_client'; -import { EmptyPrompt } from './empty_prompt'; +import { Breadcrumb } from '../../../components/breadcrumb'; +import { SelectableTokenField } from '../../../components/token_field'; +import type { APIKeysAPIClient, CreateApiKeyResponse } from '../api_keys_api_client'; +import { ApiKeysEmptyPrompt } from './api_keys_empty_prompt'; +import { CreateApiKeyFlyout } from './create_api_key_flyout'; +import type { InvalidateApiKeys } from './invalidate_provider'; import { InvalidateProvider } from './invalidate_provider'; import { NotEnabled } from './not_enabled'; import { PermissionDenied } from './permission_denied'; interface Props { + history: History; notifications: NotificationsStart; apiKeysAPIClient: PublicMethodsOf; } @@ -50,9 +59,10 @@ interface State { isAdmin: boolean; canManage: boolean; areApiKeysEnabled: boolean; - apiKeys: ApiKey[]; + apiKeys?: ApiKey[]; selectedItems: ApiKey[]; error: any; + createdApiKey?: CreateApiKeyResponse; } const DATE_FORMAT = 'MMMM Do YYYY HH:mm:ss'; @@ -66,7 +76,7 @@ export class APIKeysGridPage extends Component { isAdmin: false, canManage: false, areApiKeysEnabled: false, - apiKeys: [], + apiKeys: undefined, selectedItems: [], error: undefined, }; @@ -77,6 +87,31 @@ export class APIKeysGridPage extends Component { } public render() { + return ( +
+ + + { + this.props.history.push({ pathname: '/' }); + this.reloadApiKeys(); + this.setState({ createdApiKey: apiKey }); + }} + onCancel={() => this.props.history.push({ pathname: '/' })} + /> + + + {this.renderContent()} +
+ ); + } + + public renderContent() { const { isLoadingApp, isLoadingTable, @@ -87,104 +122,191 @@ export class APIKeysGridPage extends Component { apiKeys, } = this.state; - if (isLoadingApp) { - return ( - - - - - - ); - } - - if (!canManage) { - return ; - } - - if (error) { - const { - body: { error: errorTitle, message, statusCode }, - } = error; - - return ( - - + - } - color="danger" - iconType="alert" - data-test-subj="apiKeysError" - > - {statusCode}: {errorTitle} - {message} - - - ); - } + + + ); + } - if (!areApiKeysEnabled) { - return ( - - - - ); + if (!canManage) { + return ; + } + + if (error) { + return ( + + + + + + + + ); + } + + if (!areApiKeysEnabled) { + return ( + + + + ); + } } if (!isLoadingTable && apiKeys && apiKeys.length === 0) { return ( - + + + + + ); } - const description = ( - -

- {isAdmin ? ( - - ) : ( - - )} -

-
- ); + const concatenated = `${this.state.createdApiKey?.id}:${this.state.createdApiKey?.api_key}`; return ( -

+

-

+
- {description} + +

+ {isAdmin ? ( + + ) : ( + + )} +

+
+
+ + + +
+ {this.state.createdApiKey && !this.state.isLoadingTable && ( + <> + +

+ +

+ +
+ + + )} + {this.renderTable()}
); } private renderTable = () => { - const { apiKeys, selectedItems, isLoadingTable, isAdmin } = this.state; + const { apiKeys, selectedItems, isLoadingTable, isAdmin, error } = this.state; const message = isLoadingTable ? ( { const sorting = { sort: { - field: 'expiration', - direction: 'asc', + field: 'creation', + direction: 'desc', }, } as const; @@ -234,7 +356,7 @@ export class APIKeysGridPage extends Component { > { }} ) : undefined, - toolsRight: ( - this.reloadApiKeys()} - data-test-subj="reloadButton" - > - - - ), box: { incremental: true, }, @@ -270,14 +379,23 @@ export class APIKeysGridPage extends Component { }), multiSelect: false, options: Object.keys( - apiKeys.reduce((apiKeysMap: any, apiKey) => { + apiKeys?.reduce((apiKeysMap: any, apiKey) => { apiKeysMap[apiKey.username] = true; return apiKeysMap; - }, {}) + }, {}) ?? {} ).map((username) => { return { value: username, - view: username, + view: ( + + + + + + {username} + + + ), }; }), }, @@ -289,10 +407,10 @@ export class APIKeysGridPage extends Component { }), multiSelect: false, options: Object.keys( - apiKeys.reduce((apiKeysMap: any, apiKey) => { + apiKeys?.reduce((apiKeysMap: any, apiKey) => { apiKeysMap[apiKey.realm] = true; return apiKeysMap; - }, {}) + }, {}) ?? {} ).map((realm) => { return { value: realm, @@ -306,52 +424,58 @@ export class APIKeysGridPage extends Component { return ( <> - {isAdmin ? ( + {!isAdmin ? ( <> } - color="success" + color="primary" iconType="user" - size="s" - data-test-subj="apiKeyAdminDescriptionCallOut" /> - - + ) : undefined} - { - { - return { - 'data-test-subj': 'apiKeyRow', - }; - }} - /> - } + + {(invalidateApiKeyPrompt) => ( + + )} + ); }; - private getColumnConfig = () => { - const { isAdmin } = this.state; + private getColumnConfig = (invalidateApiKeyPrompt: InvalidateApiKeys) => { + const { isAdmin, createdApiKey } = this.state; + + let config: Array> = []; - let config: Array> = [ + config = config.concat([ { field: 'name', name: i18n.translate('xpack.security.management.apiKeys.table.nameColumnName', { @@ -359,7 +483,7 @@ export class APIKeysGridPage extends Component { }), sortable: true, }, - ]; + ]); if (isAdmin) { config = config.concat([ @@ -369,6 +493,16 @@ export class APIKeysGridPage extends Component { defaultMessage: 'User', }), sortable: true, + render: (username: string) => ( + + + + + + {username} + + + ), }, { field: 'realm', @@ -387,91 +521,83 @@ export class APIKeysGridPage extends Component { defaultMessage: 'Created', }), sortable: true, - render: (creationDateMs: number) => moment(creationDateMs).format(DATE_FORMAT), - }, - { - field: 'expiration', - name: i18n.translate('xpack.security.management.apiKeys.table.expirationDateColumnName', { - defaultMessage: 'Expires', - }), - sortable: true, - render: (expirationDateMs: number) => { - if (expirationDateMs === undefined) { - return ( - - {i18n.translate( - 'xpack.security.management.apiKeys.table.expirationDateNeverMessage', - { - defaultMessage: 'Never', - } - )} - - ); - } - - return moment(expirationDateMs).format(DATE_FORMAT); + mobileOptions: { + show: false, }, + render: (creation: string, item: ApiKey) => ( + + {item.id === createdApiKey?.id ? ( + + + + ) : ( + {moment(creation).fromNow()} + )} + + ), }, { name: i18n.translate('xpack.security.management.apiKeys.table.statusColumnName', { defaultMessage: 'Status', }), render: ({ expiration }: any) => { - const now = Date.now(); + if (!expiration) { + return ( + + + + ); + } - if (now > expiration) { - return Expired; + if (Date.now() > expiration) { + return ( + + + + ); } - return Active; + return ( + + + + + + ); }, }, { - name: i18n.translate('xpack.security.management.apiKeys.table.actionsColumnName', { - defaultMessage: 'Actions', - }), actions: [ { - render: ({ name, id }: any) => { - return ( - - - - {(invalidateApiKeyPrompt) => { - return ( - - - invalidateApiKeyPrompt([{ id, name }], this.onApiKeysInvalidated) - } - /> - - ); - }} - - - - ); - }, + name: i18n.translate('xpack.security.management.apiKeys.table.deleteAction', { + defaultMessage: 'Delete', + }), + description: i18n.translate( + 'xpack.security.management.apiKeys.table.deleteDescription', + { + defaultMessage: 'Delete this API key', + } + ), + icon: 'trash', + type: 'icon', + color: 'danger', + onClick: (item) => + invalidateApiKeyPrompt([{ id: item.id, name: item.name }], this.onApiKeysInvalidated), }, ], }, @@ -498,7 +624,7 @@ export class APIKeysGridPage extends Component { if (!canManage || !areApiKeysEnabled) { this.setState({ isLoadingApp: false }); } else { - this.initiallyLoadApiKeys(); + this.loadApiKeys(); } } catch (e) { this.props.notifications.toasts.addDanger( @@ -510,13 +636,13 @@ export class APIKeysGridPage extends Component { } } - private initiallyLoadApiKeys = () => { - this.setState({ isLoadingApp: true, isLoadingTable: false }); - this.loadApiKeys(); - }; - private reloadApiKeys = () => { - this.setState({ apiKeys: [], isLoadingApp: false, isLoadingTable: true }); + this.setState({ + isLoadingApp: false, + isLoadingTable: true, + createdApiKey: undefined, + error: undefined, + }); this.loadApiKeys(); }; diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/create_api_key_flyout.tsx b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/create_api_key_flyout.tsx new file mode 100644 index 00000000000000..27385e4b29b009 --- /dev/null +++ b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/create_api_key_flyout.tsx @@ -0,0 +1,378 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + EuiCallOut, + EuiFieldNumber, + EuiFieldText, + EuiFlexGroup, + EuiFlexItem, + EuiForm, + EuiFormFieldset, + EuiFormRow, + EuiIcon, + EuiLoadingContent, + EuiSpacer, + EuiSwitch, + EuiText, +} from '@elastic/eui'; +import type { FunctionComponent } from 'react'; +import React, { useEffect } from 'react'; +import useAsyncFn from 'react-use/lib/useAsyncFn'; + +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { CodeEditorField, useKibana } from '../../../../../../../src/plugins/kibana_react/public'; +import type { ApiKeyRoleDescriptors } from '../../../../common/model'; +import { DocLink } from '../../../components/doc_link'; +import type { FormFlyoutProps } from '../../../components/form_flyout'; +import { FormFlyout } from '../../../components/form_flyout'; +import { useCurrentUser } from '../../../components/use_current_user'; +import { useForm } from '../../../components/use_form'; +import type { ValidationErrors } from '../../../components/use_form'; +import { useInitialFocus } from '../../../components/use_initial_focus'; +import { RolesAPIClient } from '../../roles/roles_api_client'; +import { APIKeysAPIClient } from '../api_keys_api_client'; +import type { CreateApiKeyRequest, CreateApiKeyResponse } from '../api_keys_api_client'; + +export interface ApiKeyFormValues { + name: string; + expiration: string; + customExpiration: boolean; + customPrivileges: boolean; + role_descriptors: string; +} + +export interface CreateApiKeyFlyoutProps { + defaultValues?: ApiKeyFormValues; + onSuccess?: (apiKey: CreateApiKeyResponse) => void; + onCancel: FormFlyoutProps['onCancel']; +} + +const defaultDefaultValues: ApiKeyFormValues = { + name: '', + expiration: '', + customExpiration: false, + customPrivileges: false, + role_descriptors: JSON.stringify( + { + 'role-a': { + cluster: ['all'], + indices: [ + { + names: ['index-a*'], + privileges: ['read'], + }, + ], + }, + 'role-b': { + cluster: ['all'], + indices: [ + { + names: ['index-b*'], + privileges: ['all'], + }, + ], + }, + }, + null, + 2 + ), +}; + +export const CreateApiKeyFlyout: FunctionComponent = ({ + onSuccess, + onCancel, + defaultValues = defaultDefaultValues, +}) => { + const { services } = useKibana(); + const { value: currentUser, loading: isLoadingCurrentUser } = useCurrentUser(); + const [{ value: roles, loading: isLoadingRoles }, getRoles] = useAsyncFn( + () => new RolesAPIClient(services.http!).getRoles(), + [services.http] + ); + const [form, eventHandlers] = useForm({ + onSubmit: async (values) => { + try { + const apiKey = await new APIKeysAPIClient(services.http!).createApiKey(mapValues(values)); + onSuccess?.(apiKey); + } catch (error) { + throw error; + } + }, + validate, + defaultValues, + }); + const isLoading = isLoadingCurrentUser || isLoadingRoles; + + useEffect(() => { + getRoles(); + }, []); // eslint-disable-line react-hooks/exhaustive-deps + + useEffect(() => { + if (currentUser && roles) { + const userPermissions = currentUser.roles.reduce( + (accumulator, roleName) => { + const role = roles.find((r) => r.name === roleName); + if (role) { + accumulator[role.name] = role.elasticsearch; + } + return accumulator; + }, + {} + ); + if (!form.touched.role_descriptors) { + form.setValue('role_descriptors', JSON.stringify(userPermissions, null, 2)); + } + } + }, [currentUser, roles]); // eslint-disable-line react-hooks/exhaustive-deps + + const firstFieldRef = useInitialFocus([isLoading]); + + return ( + + {form.submitError && ( + <> + + {(form.submitError as any).body?.message || form.submitError.message} + + + + )} + + {isLoading ? ( + + ) : ( + + + + + + + + + + {currentUser?.username} + + + + + + + + + + + + + form.setValue('customPrivileges', e.target.checked)} + /> + {form.values.customPrivileges && ( + <> + + + + + } + error={form.errors.role_descriptors} + isInvalid={form.touched.role_descriptors && !!form.errors.role_descriptors} + > + form.setValue('role_descriptors', value)} + languageId="xjson" + height={200} + /> + + + + )} + + + + + form.setValue('customExpiration', e.target.checked)} + /> + {form.values.customExpiration && ( + <> + + + + + + + )} + + + {/* Hidden submit button is required for enter key to trigger form submission */} + + + )} + + ); +}; + +export function validate(values: ApiKeyFormValues) { + const errors: ValidationErrors = {}; + + if (!values.name) { + errors.name = i18n.translate('xpack.security.management.apiKeys.createApiKey.nameRequired', { + defaultMessage: 'Enter a name.', + }); + } + + if (values.customExpiration) { + const parsedExpiration = parseFloat(values.expiration); + if (isNaN(parsedExpiration) || parsedExpiration <= 0) { + errors.expiration = i18n.translate( + 'xpack.security.management.apiKeys.createApiKey.expirationRequired', + { + defaultMessage: 'Enter a valid duration or disable this option.', + } + ); + } + } + + if (values.customPrivileges) { + if (!values.role_descriptors) { + errors.role_descriptors = i18n.translate( + 'xpack.security.management.apiKeys.createApiKey.roleDescriptorsRequired', + { + defaultMessage: 'Enter role descriptors or disable this option.', + } + ); + } else { + try { + JSON.parse(values.role_descriptors); + } catch (e) { + errors.role_descriptors = i18n.translate( + 'xpack.security.management.apiKeys.createApiKey.invalidJsonError', + { + defaultMessage: 'Enter valid JSON.', + } + ); + } + } + } + + return errors; +} + +export function mapValues(values: ApiKeyFormValues): CreateApiKeyRequest { + return { + name: values.name, + expiration: values.customExpiration && values.expiration ? `${values.expiration}d` : undefined, + role_descriptors: + values.customPrivileges && values.role_descriptors + ? JSON.parse(values.role_descriptors) + : undefined, + }; +} diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/empty_prompt/empty_prompt.tsx b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/empty_prompt/empty_prompt.tsx deleted file mode 100644 index 0987f43a3d14d7..00000000000000 --- a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/empty_prompt/empty_prompt.tsx +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { EuiButton, EuiEmptyPrompt, EuiLink } from '@elastic/eui'; -import React, { Fragment } from 'react'; - -import { FormattedMessage } from '@kbn/i18n/react'; - -import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; - -interface Props { - isAdmin: boolean; -} - -export const EmptyPrompt: React.FunctionComponent = ({ isAdmin }) => { - const { services } = useKibana(); - const application = services.application!; - const docLinks = services.docLinks!; - return ( - - {isAdmin ? ( - - ) : ( - - )} - - } - body={ - -

- - - - ), - }} - /> -

-
- } - actions={ - application.navigateToApp('dev_tools')} - data-test-subj="goToConsoleButton" - > - - - } - data-test-subj="emptyPrompt" - /> - ); -}; diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/invalidate_provider/index.ts b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/invalidate_provider/index.ts index 4eab1c881c2217..dc99861ce0a8db 100644 --- a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/invalidate_provider/index.ts +++ b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/invalidate_provider/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { InvalidateProvider } from './invalidate_provider'; +export { InvalidateProvider, InvalidateApiKeys } from './invalidate_provider'; diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/invalidate_provider/invalidate_provider.tsx b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/invalidate_provider/invalidate_provider.tsx index a68534db4fd85a..26d1e1f72d31f2 100644 --- a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/invalidate_provider/invalidate_provider.tsx +++ b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/invalidate_provider/invalidate_provider.tsx @@ -41,7 +41,7 @@ export const InvalidateProvider: React.FunctionComponent = ({ const invalidateApiKeyPrompt: InvalidateApiKeys = (keys, onSuccess = () => undefined) => { if (!keys || !keys.length) { - throw new Error('No API key IDs specified for invalidation'); + throw new Error('No API key IDs specified for deletion'); } setIsModalOpen(true); setApiKeys(keys); @@ -75,16 +75,16 @@ export const InvalidateProvider: React.FunctionComponent = ({ const hasMultipleSuccesses = itemsInvalidated.length > 1; const successMessage = hasMultipleSuccesses ? i18n.translate( - 'xpack.security.management.apiKeys.invalidateApiKey.successMultipleNotificationTitle', + 'xpack.security.management.apiKeys.deleteApiKey.successMultipleNotificationTitle', { - defaultMessage: 'Invalidated {count} API keys', + defaultMessage: 'Deleted {count} API keys', values: { count: itemsInvalidated.length }, } ) : i18n.translate( - 'xpack.security.management.apiKeys.invalidateApiKey.successSingleNotificationTitle', + 'xpack.security.management.apiKeys.deleteApiKey.successSingleNotificationTitle', { - defaultMessage: "Invalidated API key '{name}'", + defaultMessage: "Deleted API key '{name}'", values: { name: itemsInvalidated[0].name }, } ); @@ -102,7 +102,7 @@ export const InvalidateProvider: React.FunctionComponent = ({ const hasMultipleErrors = (errors && errors.length > 1) || (error && apiKeys.length > 1); const errorMessage = hasMultipleErrors ? i18n.translate( - 'xpack.security.management.apiKeys.invalidateApiKey.errorMultipleNotificationTitle', + 'xpack.security.management.apiKeys.deleteApiKey.errorMultipleNotificationTitle', { defaultMessage: 'Error deleting {count} apiKeys', values: { @@ -111,7 +111,7 @@ export const InvalidateProvider: React.FunctionComponent = ({ } ) : i18n.translate( - 'xpack.security.management.apiKeys.invalidateApiKey.errorSingleNotificationTitle', + 'xpack.security.management.apiKeys.deleteApiKey.errorSingleNotificationTitle', { defaultMessage: "Error deleting API key '{name}'", values: { name: (errors && errors[0].name) || apiKeys[0].name }, @@ -130,19 +130,20 @@ export const InvalidateProvider: React.FunctionComponent = ({ return ( = ({ onCancel={closeModal} onConfirm={invalidateApiKey} cancelButtonText={i18n.translate( - 'xpack.security.management.apiKeys.invalidateApiKey.confirmModal.cancelButtonLabel', + 'xpack.security.management.apiKeys.deleteApiKey.confirmModal.cancelButtonLabel', { defaultMessage: 'Cancel' } )} confirmButtonText={i18n.translate( - 'xpack.security.management.apiKeys.invalidateApiKey.confirmModal.confirmButtonLabel', + 'xpack.security.management.apiKeys.deleteApiKey.confirmModal.confirmButtonLabel', { - defaultMessage: 'Invalidate {count, plural, one {API key} other {API keys}}', + defaultMessage: 'Delete {count, plural, one {API key} other {API keys}}', values: { count: apiKeys.length }, } )} @@ -167,8 +168,8 @@ export const InvalidateProvider: React.FunctionComponent = ({

{i18n.translate( - 'xpack.security.management.apiKeys.invalidateApiKey.confirmModal.invalidateMultipleListDescription', - { defaultMessage: 'You are about to invalidate these API keys:' } + 'xpack.security.management.apiKeys.deleteApiKey.confirmModal.deleteMultipleListDescription', + { defaultMessage: 'You are about to delete these API keys:' } )}

    diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_management_app.test.tsx b/x-pack/plugins/security/public/management/api_keys/api_keys_management_app.test.tsx index bada8c5c7ce4cf..d2611864e77a2d 100644 --- a/x-pack/plugins/security/public/management/api_keys/api_keys_management_app.test.tsx +++ b/x-pack/plugins/security/public/management/api_keys/api_keys_management_app.test.tsx @@ -6,29 +6,36 @@ */ jest.mock('./api_keys_grid', () => ({ - APIKeysGridPage: (props: any) => `Page: ${JSON.stringify(props)}`, + APIKeysGridPage: (props: any) => JSON.stringify(props, null, 2), })); + +import { act } from '@testing-library/react'; + import { coreMock, scopedHistoryMock } from 'src/core/public/mocks'; +import type { Unmount } from 'src/plugins/management/public/types'; +import { securityMock } from '../../mocks'; import { apiKeysManagementApp } from './api_keys_management_app'; describe('apiKeysManagementApp', () => { it('create() returns proper management app descriptor', () => { const { getStartServices } = coreMock.createSetup(); + const { authc } = securityMock.createSetup(); - expect(apiKeysManagementApp.create({ getStartServices: getStartServices as any })) + expect(apiKeysManagementApp.create({ authc, getStartServices: getStartServices as any })) .toMatchInlineSnapshot(` Object { "id": "api_keys", "mount": [Function], "order": 30, - "title": "API Keys", + "title": "API keys", } `); }); it('mount() works for the `grid` page', async () => { const { getStartServices } = coreMock.createSetup(); + const { authc } = securityMock.createSetup(); const startServices = await getStartServices(); const docTitle = startServices[0].chrome.docTitle; @@ -36,28 +43,54 @@ describe('apiKeysManagementApp', () => { const container = document.createElement('div'); const setBreadcrumbs = jest.fn(); - const unmount = await apiKeysManagementApp - .create({ getStartServices: () => Promise.resolve(startServices) as any }) - .mount({ - basePath: '/some-base-path', - element: container, - setBreadcrumbs, - history: scopedHistoryMock.create(), - }); + let unmount: Unmount; + await act(async () => { + unmount = await apiKeysManagementApp + .create({ authc, getStartServices: () => Promise.resolve(startServices) as any }) + .mount({ + basePath: '/some-base-path', + element: container, + setBreadcrumbs, + history: scopedHistoryMock.create(), + }); + }); expect(setBreadcrumbs).toHaveBeenCalledTimes(1); - expect(setBreadcrumbs).toHaveBeenCalledWith([{ href: '/', text: 'API Keys' }]); - expect(docTitle.change).toHaveBeenCalledWith('API Keys'); + expect(setBreadcrumbs).toHaveBeenCalledWith([{ href: '/', text: 'API keys' }]); + expect(docTitle.change).toHaveBeenCalledWith(['API keys']); expect(docTitle.reset).not.toHaveBeenCalled(); expect(container).toMatchInlineSnapshot(`
    - Page: {"notifications":{"toasts":{}},"apiKeysAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{},"externalUrl":{}}}} + { + "history": { + "action": "PUSH", + "length": 1, + "location": { + "pathname": "/", + "search": "", + "hash": "" + } + }, + "notifications": { + "toasts": {} + }, + "apiKeysAPIClient": { + "http": { + "basePath": { + "basePath": "", + "serverBasePath": "" + }, + "anonymousPaths": {}, + "externalUrl": {} + } + } + }
    `); - unmount(); - expect(docTitle.reset).toHaveBeenCalledTimes(1); + unmount!(); + expect(docTitle.reset).toHaveBeenCalledTimes(1); expect(container).toMatchInlineSnapshot(`
    `); }); }); diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_management_app.tsx b/x-pack/plugins/security/public/management/api_keys/api_keys_management_app.tsx index 8fa52ba7e2edd2..68e06d38db4c86 100644 --- a/x-pack/plugins/security/public/management/api_keys/api_keys_management_app.tsx +++ b/x-pack/plugins/security/public/management/api_keys/api_keys_management_app.tsx @@ -5,63 +5,101 @@ * 2.0. */ +import type { History } from 'history'; +import type { FunctionComponent } from 'react'; import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; +import { Router } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; -import type { StartServicesAccessor } from 'src/core/public'; -import type { RegisterManagementAppArgs } from 'src/plugins/management/public'; +import { I18nProvider } from '@kbn/i18n/react'; +import type { CoreStart, StartServicesAccessor } from '../../../../../../src/core/public'; import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public'; +import type { RegisterManagementAppArgs } from '../../../../../../src/plugins/management/public'; +import type { AuthenticationServiceSetup } from '../../authentication'; +import type { BreadcrumbsChangeHandler } from '../../components/breadcrumb'; +import { + Breadcrumb, + BreadcrumbsProvider, + createBreadcrumbsChangeHandler, +} from '../../components/breadcrumb'; +import { AuthenticationProvider } from '../../components/use_current_user'; import type { PluginStartDependencies } from '../../plugin'; interface CreateParams { + authc: AuthenticationServiceSetup; getStartServices: StartServicesAccessor; } export const apiKeysManagementApp = Object.freeze({ id: 'api_keys', - create({ getStartServices }: CreateParams) { - const title = i18n.translate('xpack.security.management.apiKeysTitle', { - defaultMessage: 'API Keys', - }); + create({ authc, getStartServices }: CreateParams) { return { id: this.id, order: 30, - title, - async mount({ element, setBreadcrumbs }) { - setBreadcrumbs([ - { - text: title, - href: `/`, - }, - ]); - - const [[core], { APIKeysGridPage }, { APIKeysAPIClient }] = await Promise.all([ + title: i18n.translate('xpack.security.management.apiKeysTitle', { + defaultMessage: 'API keys', + }), + async mount({ element, setBreadcrumbs, history }) { + const [[coreStart], { APIKeysGridPage }, { APIKeysAPIClient }] = await Promise.all([ getStartServices(), import('./api_keys_grid'), import('./api_keys_api_client'), ]); - core.chrome.docTitle.change(title); - render( - - + + - - , + + , element ); return () => { - core.chrome.docTitle.reset(); unmountComponentAtNode(element); }; }, } as RegisterManagementAppArgs; }, }); + +export interface ProvidersProps { + services: CoreStart; + history: History; + authc: AuthenticationServiceSetup; + onChange?: BreadcrumbsChangeHandler; +} + +export const Providers: FunctionComponent = ({ + services, + history, + authc, + onChange, + children, +}) => ( + + + + + {children} + + + + +); diff --git a/x-pack/plugins/security/public/management/management_service.test.ts b/x-pack/plugins/security/public/management/management_service.test.ts index 694f3cc3880a21..b21897377d5eb2 100644 --- a/x-pack/plugins/security/public/management/management_service.test.ts +++ b/x-pack/plugins/security/public/management/management_service.test.ts @@ -68,7 +68,7 @@ describe('ManagementService', () => { id: 'api_keys', mount: expect.any(Function), order: 30, - title: 'API Keys', + title: 'API keys', }); expect(mockSection.registerApp).toHaveBeenCalledWith({ id: 'role_mappings', diff --git a/x-pack/plugins/security/public/management/management_service.ts b/x-pack/plugins/security/public/management/management_service.ts index 7809a45db16605..af1b05e64e37c9 100644 --- a/x-pack/plugins/security/public/management/management_service.ts +++ b/x-pack/plugins/security/public/management/management_service.ts @@ -47,7 +47,7 @@ export class ManagementService { this.securitySection.registerApp( rolesManagementApp.create({ fatalErrors, license, getStartServices }) ); - this.securitySection.registerApp(apiKeysManagementApp.create({ getStartServices })); + this.securitySection.registerApp(apiKeysManagementApp.create({ authc, getStartServices })); this.securitySection.registerApp(roleMappingsManagementApp.create({ getStartServices })); } diff --git a/x-pack/plugins/security/public/management/roles/roles_grid/__snapshots__/roles_grid_page.test.tsx.snap b/x-pack/plugins/security/public/management/roles/roles_grid/__snapshots__/roles_grid_page.test.tsx.snap index 60453d354f29e8..eb266ce93338c3 100644 --- a/x-pack/plugins/security/public/management/roles/roles_grid/__snapshots__/roles_grid_page.test.tsx.snap +++ b/x-pack/plugins/security/public/management/roles/roles_grid/__snapshots__/roles_grid_page.test.tsx.snap @@ -14,9 +14,11 @@ exports[` renders permission denied if required 1`] = `
    defaultValues, }); + const firstFieldRef = useInitialFocus([isLoading]); + return ( defaultValue={form.values.current_password} isInvalid={form.touched.current_password && !!form.errors.current_password} autoComplete="current-password" + inputRef={firstFieldRef} /> ) : null} @@ -263,6 +267,7 @@ export const ChangePasswordFlyout: FunctionComponent defaultValue={form.values.password} isInvalid={form.touched.password && !!form.errors.password} autoComplete="new-password" + inputRef={isCurrentUser ? undefined : firstFieldRef} /> = ({ }, [services.http]); return ( - = ({ values: { count: usernames.length, isLoading: state.loading }, } )} - confirmButtonColor="danger" + buttonColor="danger" isLoading={state.loading} > @@ -94,6 +100,6 @@ export const ConfirmDeleteUsers: FunctionComponent = ({ />

    -
    + ); }; diff --git a/x-pack/plugins/security/public/management/users/edit_user/confirm_disable_users.tsx b/x-pack/plugins/security/public/management/users/edit_user/confirm_disable_users.tsx index a3d36e19504e1b..e8779a3bb59b94 100644 --- a/x-pack/plugins/security/public/management/users/edit_user/confirm_disable_users.tsx +++ b/x-pack/plugins/security/public/management/users/edit_user/confirm_disable_users.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { EuiText } from '@elastic/eui'; +import { EuiConfirmModal, EuiText } from '@elastic/eui'; import type { FunctionComponent } from 'react'; import React from 'react'; import useAsyncFn from 'react-use/lib/useAsyncFn'; @@ -13,9 +13,8 @@ import useAsyncFn from 'react-use/lib/useAsyncFn'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; +import { UserAPIClient } from '..'; import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; -import { ConfirmModal } from '../../../components/confirm_modal'; -import { UserAPIClient } from '../user_api_client'; export interface ConfirmDisableUsersProps { usernames: string[]; @@ -58,13 +57,20 @@ export const ConfirmDisableUsers: FunctionComponent = }, [services.http]); return ( - = values: { count: usernames.length, isLoading: state.loading }, }) } - confirmButtonColor={isSystemUser ? 'danger' : undefined} + buttonColor={isSystemUser ? 'danger' : undefined} isLoading={state.loading} > {isSystemUser ? ( @@ -89,7 +95,7 @@ export const ConfirmDisableUsers: FunctionComponent =

    @@ -117,6 +123,6 @@ export const ConfirmDisableUsers: FunctionComponent = )} )} - + ); }; diff --git a/x-pack/plugins/security/public/management/users/edit_user/confirm_enable_users.tsx b/x-pack/plugins/security/public/management/users/edit_user/confirm_enable_users.tsx index 24364d7b56d99c..68c9a645eaa9a1 100644 --- a/x-pack/plugins/security/public/management/users/edit_user/confirm_enable_users.tsx +++ b/x-pack/plugins/security/public/management/users/edit_user/confirm_enable_users.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { EuiText } from '@elastic/eui'; +import { EuiConfirmModal, EuiText } from '@elastic/eui'; import type { FunctionComponent } from 'react'; import React from 'react'; import useAsyncFn from 'react-use/lib/useAsyncFn'; @@ -13,9 +13,8 @@ import useAsyncFn from 'react-use/lib/useAsyncFn'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; +import { UserAPIClient } from '..'; import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; -import { ConfirmModal } from '../../../components/confirm_modal'; -import { UserAPIClient } from '../user_api_client'; export interface ConfirmEnableUsersProps { usernames: string[]; @@ -54,13 +53,20 @@ export const ConfirmEnableUsers: FunctionComponent = ({ }, [services.http]); return ( - = ({

)} - +
); }; diff --git a/x-pack/plugins/security/public/management/users/users_management_app.tsx b/x-pack/plugins/security/public/management/users/users_management_app.tsx index 3e18734cbf3683..f6a2956c7ad43f 100644 --- a/x-pack/plugins/security/public/management/users/users_management_app.tsx +++ b/x-pack/plugins/security/public/management/users/users_management_app.tsx @@ -20,7 +20,11 @@ import type { RegisterManagementAppArgs } from 'src/plugins/management/public'; import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public'; import type { AuthenticationServiceSetup } from '../../authentication'; import type { BreadcrumbsChangeHandler } from '../../components/breadcrumb'; -import { Breadcrumb, BreadcrumbsProvider, getDocTitle } from '../../components/breadcrumb'; +import { + Breadcrumb, + BreadcrumbsProvider, + createBreadcrumbsChangeHandler, +} from '../../components/breadcrumb'; import { AuthenticationProvider } from '../../components/use_current_user'; import type { PluginStartDependencies } from '../../plugin'; import { tryDecodeURIComponent } from '../url_utils'; @@ -64,10 +68,7 @@ export const usersManagementApp = Object.freeze({ services={coreStart} history={history} authc={authc} - onChange={(breadcrumbs) => { - setBreadcrumbs(breadcrumbs); - coreStart.chrome.docTitle.change(getDocTitle(breadcrumbs)); - }} + onChange={createBreadcrumbsChangeHandler(coreStart.chrome, setBreadcrumbs)} > { aria-expanded="false" aria-haspopup="true" aria-label="Account menu" - class="euiButtonEmpty euiButtonEmpty--text euiHeaderSectionItem__button" + class="euiButtonEmpty euiButtonEmpty--text euiHeaderSectionItemButton" data-test-subj="userMenuButton" type="button" > @@ -80,18 +80,22 @@ describe('SecurityNavControlService', () => { -
- -
+ +
+ diff --git a/x-pack/plugins/security/server/authentication/api_keys/api_keys.ts b/x-pack/plugins/security/server/authentication/api_keys/api_keys.ts index 7396519acf9eaf..c5857b9c75909e 100644 --- a/x-pack/plugins/security/server/authentication/api_keys/api_keys.ts +++ b/x-pack/plugins/security/server/authentication/api_keys/api_keys.ts @@ -161,6 +161,9 @@ export class APIKeys { /** * Tries to create an API key for the current user. + * + * Returns newly created API key or `null` if API keys are disabled. + * * @param request Request instance. * @param params The params to create an API key */ diff --git a/x-pack/plugins/security/server/authorization/__snapshots__/reset_session_page.test.tsx.snap b/x-pack/plugins/security/server/authorization/__snapshots__/reset_session_page.test.tsx.snap index bcb8a6c975359a..785c57490e8efd 100644 --- a/x-pack/plugins/security/server/authorization/__snapshots__/reset_session_page.test.tsx.snap +++ b/x-pack/plugins/security/server/authorization/__snapshots__/reset_session_page.test.tsx.snap @@ -1,3 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`ResetSessionPage renders as expected 1`] = `"MockedFonts

You do not have permission to access the requested page

Either go back to the previous page or log in as a different user.

"`; +exports[`ResetSessionPage renders as expected 1`] = `"MockedFonts

You do not have permission to access the requested page

Either go back to the previous page or log in as a different user.

"`; diff --git a/x-pack/plugins/security/server/routes/api_keys/create.test.ts b/x-pack/plugins/security/server/routes/api_keys/create.test.ts new file mode 100644 index 00000000000000..8e34ededdd8fac --- /dev/null +++ b/x-pack/plugins/security/server/routes/api_keys/create.test.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import Boom from '@hapi/boom'; + +import type { DeeplyMockedKeys } from '@kbn/utility-types/jest'; +import type { RequestHandler } from 'src/core/server'; +import { kibanaResponseFactory } from 'src/core/server'; +import { httpServerMock } from 'src/core/server/mocks'; + +import type { AuthenticationServiceStart } from '../../authentication'; +import { authenticationServiceMock } from '../../authentication/authentication_service.mock'; +import type { SecurityRequestHandlerContext } from '../../types'; +import { routeDefinitionParamsMock } from '../index.mock'; +import { defineCreateApiKeyRoutes } from './create'; + +describe('Create API Key route', () => { + function getMockContext( + licenseCheckResult: { state: string; message?: string } = { state: 'valid' } + ) { + return ({ + licensing: { license: { check: jest.fn().mockReturnValue(licenseCheckResult) } }, + } as unknown) as SecurityRequestHandlerContext; + } + + let routeHandler: RequestHandler; + let authc: DeeplyMockedKeys; + beforeEach(() => { + authc = authenticationServiceMock.createStart(); + const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); + mockRouteDefinitionParams.getAuthenticationService.mockReturnValue(authc); + + defineCreateApiKeyRoutes(mockRouteDefinitionParams); + + const [, apiKeyRouteHandler] = mockRouteDefinitionParams.router.post.mock.calls.find( + ([{ path }]) => path === '/internal/security/api_key' + )!; + routeHandler = apiKeyRouteHandler; + }); + + describe('failure', () => { + test('returns result of license checker', async () => { + const mockContext = getMockContext({ state: 'invalid', message: 'test forbidden message' }); + const response = await routeHandler( + mockContext, + httpServerMock.createKibanaRequest(), + kibanaResponseFactory + ); + + expect(response.status).toBe(403); + expect(response.payload).toEqual({ message: 'test forbidden message' }); + expect(mockContext.licensing.license.check).toHaveBeenCalledWith('security', 'basic'); + }); + + test('returns error from cluster client', async () => { + const error = Boom.notAcceptable('test not acceptable message'); + authc.apiKeys.create.mockRejectedValue(error); + + const response = await routeHandler( + getMockContext(), + httpServerMock.createKibanaRequest(), + kibanaResponseFactory + ); + + expect(response.status).toBe(406); + expect(response.payload).toEqual(error); + }); + }); + + describe('success', () => { + test('allows an API Key to be created', async () => { + authc.apiKeys.create.mockResolvedValue({ + api_key: 'abc123', + id: 'key_id', + name: 'my api key', + }); + + const payload = { + name: 'my api key', + expires: '12d', + role_descriptors: { + role_1: {}, + }, + }; + + const request = httpServerMock.createKibanaRequest({ + body: { + ...payload, + }, + }); + + const response = await routeHandler(getMockContext(), request, kibanaResponseFactory); + expect(authc.apiKeys.create).toHaveBeenCalledWith(request, payload); + + expect(response.status).toBe(200); + expect(response.payload).toEqual({ + api_key: 'abc123', + id: 'key_id', + name: 'my api key', + }); + }); + + test('returns a message if API Keys are disabled', async () => { + authc.apiKeys.create.mockResolvedValue(null); + + const payload = { + name: 'my api key', + expires: '12d', + role_descriptors: { + role_1: {}, + }, + }; + + const request = httpServerMock.createKibanaRequest({ + body: { + ...payload, + }, + }); + + const response = await routeHandler(getMockContext(), request, kibanaResponseFactory); + expect(authc.apiKeys.create).toHaveBeenCalledWith(request, payload); + + expect(response.status).toBe(400); + expect(response.payload).toEqual({ + message: 'API Keys are not available', + }); + }); + }); +}); diff --git a/x-pack/plugins/security/server/routes/api_keys/create.ts b/x-pack/plugins/security/server/routes/api_keys/create.ts new file mode 100644 index 00000000000000..a309d3a0e3edba --- /dev/null +++ b/x-pack/plugins/security/server/routes/api_keys/create.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema } from '@kbn/config-schema'; + +import type { RouteDefinitionParams } from '..'; +import { wrapIntoCustomErrorResponse } from '../../errors'; +import { createLicensedRouteHandler } from '../licensed_route_handler'; + +export function defineCreateApiKeyRoutes({ + router, + getAuthenticationService, +}: RouteDefinitionParams) { + router.post( + { + path: '/internal/security/api_key', + validate: { + body: schema.object({ + name: schema.string(), + expiration: schema.maybe(schema.string()), + role_descriptors: schema.recordOf( + schema.string(), + schema.object({}, { unknowns: 'allow' }), + { + defaultValue: {}, + } + ), + }), + }, + }, + createLicensedRouteHandler(async (context, request, response) => { + try { + const apiKey = await getAuthenticationService().apiKeys.create(request, request.body); + + if (!apiKey) { + return response.badRequest({ body: { message: `API Keys are not available` } }); + } + + return response.ok({ body: apiKey }); + } catch (error) { + return response.customError(wrapIntoCustomErrorResponse(error)); + } + }) + ); +} diff --git a/x-pack/plugins/security/server/routes/api_keys/index.ts b/x-pack/plugins/security/server/routes/api_keys/index.ts index e6a8711bdf19e6..aa1e3b858ea582 100644 --- a/x-pack/plugins/security/server/routes/api_keys/index.ts +++ b/x-pack/plugins/security/server/routes/api_keys/index.ts @@ -6,6 +6,7 @@ */ import type { RouteDefinitionParams } from '../'; +import { defineCreateApiKeyRoutes } from './create'; import { defineEnabledApiKeysRoutes } from './enabled'; import { defineGetApiKeysRoutes } from './get'; import { defineInvalidateApiKeysRoutes } from './invalidate'; @@ -14,6 +15,7 @@ import { defineCheckPrivilegesRoutes } from './privileges'; export function defineApiKeysRoutes(params: RouteDefinitionParams) { defineEnabledApiKeysRoutes(params); defineGetApiKeysRoutes(params); + defineCreateApiKeyRoutes(params); defineCheckPrivilegesRoutes(params); defineInvalidateApiKeysRoutes(params); } diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts index 76ccfb0a433bd9..c61ab85f432709 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts @@ -494,7 +494,7 @@ export const threshold = t.intersection([ thresholdField, t.exact( t.partial({ - cardinality: t.union([t.array(thresholdCardinalityField), t.null]), + cardinality: t.array(thresholdCardinalityField), }) ), ]); @@ -507,7 +507,7 @@ export const thresholdNormalized = t.intersection([ thresholdFieldNormalized, t.exact( t.partial({ - cardinality: t.union([t.array(thresholdCardinalityField), t.null]), + cardinality: t.array(thresholdCardinalityField), }) ), ]); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.ts index 5cf2b6242b2f89..c7b33372e5953a 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.ts @@ -57,16 +57,16 @@ import { interval, enabled, updated_at, + updated_by, created_at, + created_by, job_status, status_date, last_success_at, last_success_message, last_failure_at, last_failure_message, - throttleOrNull, - createdByOrNull, - updatedByOrNull, + throttle, } from '../common/schemas'; const createSchema = < @@ -137,7 +137,7 @@ interface APIParams< defaultable: Defaultable; } -const commonParams = { +const baseParams = { required: { name, description, @@ -159,12 +159,11 @@ const commonParams = { tags, interval, enabled, - throttle: throttleOrNull, + throttle, actions, author, false_positives, from, - rule_id, // maxSignals not used in ML rules but probably should be used max_signals, risk_score_mapping, @@ -177,10 +176,26 @@ const commonParams = { }, }; const { - create: commonCreateParams, - patch: commonPatchParams, - response: commonResponseParams, -} = buildAPISchemas(commonParams); + create: baseCreateParams, + patch: basePatchParams, + response: baseResponseParams, +} = buildAPISchemas(baseParams); + +// "shared" types are the same across all rule types, and built from "baseParams" above +// with some variations for each route. These intersect with type specific schemas below +// to create the full schema for each route. +export const sharedCreateSchema = t.intersection([ + baseCreateParams, + t.exact(t.partial({ rule_id })), +]); +export type SharedCreateSchema = t.TypeOf; + +export const sharedUpdateSchema = t.intersection([ + baseCreateParams, + t.exact(t.partial({ rule_id })), + t.exact(t.partial({ id })), +]); +export type SharedUpdateSchema = t.TypeOf; const eqlRuleParams = { required: { @@ -318,74 +333,28 @@ const createTypeSpecific = t.union([ export type CreateTypeSpecific = t.TypeOf; // Convenience types for building specific types of rules -export const eqlCreateSchema = t.intersection([eqlCreateParams, commonCreateParams]); -export type EqlCreateSchema = t.TypeOf; - -export const threatMatchCreateSchema = t.intersection([ - threatMatchCreateParams, - commonCreateParams, -]); -export type ThreatMatchCreateSchema = t.TypeOf; - -export const queryCreateSchema = t.intersection([queryCreateParams, commonCreateParams]); -export type QueryCreateSchema = t.TypeOf; - -export const savedQueryCreateSchema = t.intersection([savedQueryCreateParams, commonCreateParams]); -export type SavedQueryCreateSchema = t.TypeOf; - -export const thresholdCreateSchema = t.intersection([thresholdCreateParams, commonCreateParams]); -export type ThresholdCreateSchema = t.TypeOf; - -export const machineLearningCreateSchema = t.intersection([ - machineLearningCreateParams, - commonCreateParams, -]); -export type MachineLearningCreateSchema = t.TypeOf; - -export const createRulesSchema = t.intersection([commonCreateParams, createTypeSpecific]); +type CreateSchema = SharedCreateSchema & T; +export type EqlCreateSchema = CreateSchema>; +export type ThreatMatchCreateSchema = CreateSchema>; +export type QueryCreateSchema = CreateSchema>; +export type SavedQueryCreateSchema = CreateSchema>; +export type ThresholdCreateSchema = CreateSchema>; +export type MachineLearningCreateSchema = CreateSchema< + t.TypeOf +>; + +export const createRulesSchema = t.intersection([sharedCreateSchema, createTypeSpecific]); export type CreateRulesSchema = t.TypeOf; -export const eqlUpdateSchema = t.intersection([ - eqlCreateParams, - commonCreateParams, - t.exact(t.partial({ id })), -]); -export type EqlUpdateSchema = t.TypeOf; - -export const threatMatchUpdateSchema = t.intersection([ - threatMatchCreateParams, - commonCreateParams, - t.exact(t.partial({ id })), -]); -export type ThreatMatchUpdateSchema = t.TypeOf; - -export const queryUpdateSchema = t.intersection([ - queryCreateParams, - commonCreateParams, - t.exact(t.partial({ id })), -]); -export type QueryUpdateSchema = t.TypeOf; - -export const savedQueryUpdateSchema = t.intersection([ - savedQueryCreateParams, - commonCreateParams, - t.exact(t.partial({ id })), -]); -export type SavedQueryUpdateSchema = t.TypeOf; - -export const thresholdUpdateSchema = t.intersection([ - thresholdCreateParams, - commonCreateParams, - t.exact(t.partial({ id })), -]); -export type ThresholdUpdateSchema = t.TypeOf; - -export const machineLearningUpdateSchema = t.intersection([ - machineLearningCreateParams, - commonCreateParams, - t.exact(t.partial({ id })), -]); -export type MachineLearningUpdateSchema = t.TypeOf; +type UpdateSchema = SharedUpdateSchema & T; +export type EqlUpdateSchema = UpdateSchema>; +export type ThreatMatchUpdateSchema = UpdateSchema>; +export type QueryUpdateSchema = UpdateSchema>; +export type SavedQueryUpdateSchema = UpdateSchema>; +export type ThresholdUpdateSchema = UpdateSchema>; +export type MachineLearningUpdateSchema = UpdateSchema< + t.TypeOf +>; const patchTypeSpecific = t.union([ eqlPatchParams, @@ -406,26 +375,23 @@ const responseTypeSpecific = t.union([ ]); export type ResponseTypeSpecific = t.TypeOf; -export const updateRulesSchema = t.intersection([ - commonCreateParams, - createTypeSpecific, - t.exact(t.partial({ id })), -]); +export const updateRulesSchema = t.intersection([createTypeSpecific, sharedUpdateSchema]); export type UpdateRulesSchema = t.TypeOf; export const fullPatchSchema = t.intersection([ - commonPatchParams, + basePatchParams, patchTypeSpecific, t.exact(t.partial({ id })), ]); const responseRequiredFields = { id, + rule_id, immutable, updated_at, - updated_by: updatedByOrNull, + updated_by, created_at, - created_by: createdByOrNull, + created_by, }; const responseOptionalFields = { status: job_status, @@ -437,7 +403,7 @@ const responseOptionalFields = { }; export const fullResponseSchema = t.intersection([ - commonResponseParams, + baseResponseParams, responseTypeSpecific, t.exact(t.type(responseRequiredFields)), t.exact(t.partial(responseOptionalFields)), diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/find_rules_schema.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/find_rules_schema.test.ts deleted file mode 100644 index f9cd405db935db..00000000000000 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/find_rules_schema.test.ts +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { findRulesSchema, FindRulesSchema } from './find_rules_schema'; -import { pipe } from 'fp-ts/lib/pipeable'; -import { left } from 'fp-ts/lib/Either'; -import { RulesSchema } from './rules_schema'; -import { exactCheck } from '../../../exact_check'; -import { foldLeftRight, getPaths } from '../../../test_utils'; -import { getRulesSchemaMock } from './rules_schema.mocks'; -import { getFindRulesSchemaMock } from './find_rules_schema.mocks'; - -describe('find_rules_schema', () => { - test('it should validate a typical single find rules response', () => { - const payload = getFindRulesSchemaMock(); - const decoded = findRulesSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(getFindRulesSchemaMock()); - }); - - test('it should validate an empty find rules response', () => { - const payload = getFindRulesSchemaMock(); - payload.data = []; - const decoded = findRulesSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - const expected = getFindRulesSchemaMock(); - expected.data = []; - - expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual(expected); - }); - - test('it should invalidate a typical single find rules response if it is has an extra property on it', () => { - const payload: FindRulesSchema & { invalid_data?: 'invalid' } = getFindRulesSchemaMock(); - payload.invalid_data = 'invalid'; - const decoded = findRulesSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['invalid keys "invalid_data"']); - expect(message.schema).toEqual({}); - }); - - test('it should invalidate a typical single find rules response if the rules are invalid within it', () => { - const payload = getFindRulesSchemaMock(); - const invalidRule: RulesSchema & { invalid_extra_data?: string } = getRulesSchemaMock(); - invalidRule.invalid_extra_data = 'invalid_data'; - payload.data = [invalidRule]; - const decoded = findRulesSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['invalid keys "invalid_extra_data"']); - expect(message.schema).toEqual({}); - }); - - test('it should invalidate a typical single find rules response if the rule is missing a required field such as name', () => { - const payload = getFindRulesSchemaMock(); - const invalidRule = getRulesSchemaMock(); - // @ts-expect-error - delete invalidRule.name; - payload.data = [invalidRule]; - const decoded = findRulesSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "name"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it should invalidate a typical single find rules response if it is missing perPage', () => { - const payload = getFindRulesSchemaMock(); - // @ts-expect-error - delete payload.perPage; - const decoded = findRulesSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual([ - 'Invalid value "undefined" supplied to "perPage"', - ]); - expect(message.schema).toEqual({}); - }); - - test('it should invalidate a typical single find rules response if it has a negative perPage number', () => { - const payload = getFindRulesSchemaMock(); - payload.perPage = -1; - const decoded = findRulesSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['Invalid value "-1" supplied to "perPage"']); - expect(message.schema).toEqual({}); - }); - - test('it should invalidate a typical single find rules response if it has a negative page number', () => { - const payload = getFindRulesSchemaMock(); - payload.page = -1; - const decoded = findRulesSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['Invalid value "-1" supplied to "page"']); - expect(message.schema).toEqual({}); - }); - - test('it should invalidate a typical single find rules response if it has a negative total', () => { - const payload = getFindRulesSchemaMock(); - payload.total = -1; - const decoded = findRulesSchema.decode(payload); - const checked = exactCheck(payload, decoded); - const message = pipe(checked, foldLeftRight); - - expect(getPaths(left(message.errors))).toEqual(['Invalid value "-1" supplied to "total"']); - expect(message.schema).toEqual({}); - }); -}); diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/find_rules_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/find_rules_schema.ts deleted file mode 100644 index c477bc108a7d23..00000000000000 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/find_rules_schema.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as t from 'io-ts'; - -import { rulesSchema } from './rules_schema'; -import { page, perPage, total } from '../common/schemas'; - -export const findRulesSchema = t.exact( - t.type({ - page, - perPage, - total, - data: t.array(rulesSchema), - }) -); - -export type FindRulesSchema = t.TypeOf; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/index.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/index.ts index 021cab086438ca..fa8ebaf597f47f 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/index.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/index.ts @@ -6,7 +6,6 @@ */ export * from './error_schema'; -export * from './find_rules_schema'; export * from './import_rules_schema'; export * from './prepackaged_rules_schema'; export * from './prepackaged_rules_status_schema'; diff --git a/x-pack/plugins/security_solution/common/detection_engine/utils.ts b/x-pack/plugins/security_solution/common/detection_engine/utils.ts index a2c362b08dc7a5..1f4e4e140ce186 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/utils.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/utils.ts @@ -12,7 +12,7 @@ import { EntriesArray, ExceptionListItemSchema, } from '../shared_imports'; -import { Type, JobStatus } from './schemas/common/schemas'; +import { Type, JobStatus, Threshold, ThresholdNormalized } from './schemas/common/schemas'; export const hasLargeValueItem = ( exceptionItems: Array @@ -55,5 +55,12 @@ export const normalizeThresholdField = ( : [thresholdField!]; }; +export const normalizeThresholdObject = (threshold: Threshold): ThresholdNormalized => { + return { + ...threshold, + field: normalizeThresholdField(threshold.field), + }; +}; + export const getRuleStatusText = (value: JobStatus | null | undefined): JobStatus | null => value === 'partial failure' ? 'warning' : value != null ? value : null; diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/matrix_histogram/events/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/matrix_histogram/events/index.ts index 53cdc7239f69d2..b2e0461b0b9b8a 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/matrix_histogram/events/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/matrix_histogram/events/index.ts @@ -26,10 +26,12 @@ export interface EventsActionGroupData { doc_count: number; } +export type Fields = Record; + export interface EventHit extends SearchHit { sort: string[]; _source: EventSource; - fields: Record; + fields: Fields; aggregations: { // eslint-disable-next-line @typescript-eslint/no-explicit-any [agg: string]: any; diff --git a/x-pack/plugins/security_solution/common/utils/mock_event_details.ts b/x-pack/plugins/security_solution/common/utils/mock_event_details.ts index 13b7fe70512460..7dc257ebb3feff 100644 --- a/x-pack/plugins/security_solution/common/utils/mock_event_details.ts +++ b/x-pack/plugins/security_solution/common/utils/mock_event_details.ts @@ -40,7 +40,7 @@ export const eventHit = { 'source.geo.location': [{ coordinates: [118.7778, 32.0617], type: 'Point' }], 'threat.indicator': [ { - 'matched.field': ['matched_field'], + 'matched.field': ['matched_field', 'other_matched_field'], first_seen: ['2021-02-22T17:29:25.195Z'], provider: ['yourself'], type: ['custom'], @@ -259,8 +259,8 @@ export const eventDetailsFormattedFields = [ { category: 'threat', field: 'threat.indicator.matched.field', - values: ['matched_field', 'matched_field_2'], - originalValue: ['matched_field', 'matched_field_2'], + values: ['matched_field', 'other_matched_field', 'matched_field_2'], + originalValue: ['matched_field', 'other_matched_field', 'matched_field_2'], isObjectArray: false, }, { diff --git a/x-pack/plugins/security_solution/common/validate.ts b/x-pack/plugins/security_solution/common/validate.ts index 79a0351b824e8d..1ac41ecbfb88b5 100644 --- a/x-pack/plugins/security_solution/common/validate.ts +++ b/x-pack/plugins/security_solution/common/validate.ts @@ -27,7 +27,7 @@ export const validate = ( }; export const validateNonExact = ( - obj: object, + obj: unknown, schema: T ): [t.TypeOf | null, string | null] => { const decoded = schema.decode(obj); diff --git a/x-pack/plugins/security_solution/cypress/objects/rule.ts b/x-pack/plugins/security_solution/cypress/objects/rule.ts index 68c7796f7ca3b8..e85b3f45b4ea62 100644 --- a/x-pack/plugins/security_solution/cypress/objects/rule.ts +++ b/x-pack/plugins/security_solution/cypress/objects/rule.ts @@ -332,5 +332,5 @@ export const editedRule = { export const expectedExportedRule = (ruleResponse: Cypress.Response) => { const jsonrule = ruleResponse.body; - return `{"author":[],"actions":[],"created_at":"${jsonrule.created_at}","updated_at":"${jsonrule.updated_at}","created_by":"elastic","description":"${jsonrule.description}","enabled":false,"false_positives":[],"from":"now-17520h","id":"${jsonrule.id}","immutable":false,"index":["exceptions-*"],"interval":"10s","rule_id":"rule_testing","language":"kuery","output_index":".siem-signals-default","max_signals":100,"risk_score":${jsonrule.risk_score},"risk_score_mapping":[],"name":"${jsonrule.name}","query":"${jsonrule.query}","references":[],"severity":"${jsonrule.severity}","severity_mapping":[],"updated_by":"elastic","tags":[],"to":"now","type":"query","threat":[],"throttle":"no_actions","version":1,"exceptions_list":[]}\n{"exported_count":1,"missing_rules":[],"missing_rules_count":0}\n`; + return `{"id":"${jsonrule.id}","updated_at":"${jsonrule.updated_at}","updated_by":"elastic","created_at":"${jsonrule.created_at}","created_by":"elastic","name":"${jsonrule.name}","tags":[],"interval":"10s","enabled":false,"description":"${jsonrule.description}","risk_score":${jsonrule.risk_score},"severity":"${jsonrule.severity}","output_index":".siem-signals-default","author":[],"false_positives":[],"from":"now-17520h","rule_id":"rule_testing","max_signals":100,"risk_score_mapping":[],"severity_mapping":[],"threat":[],"to":"now","references":[],"version":1,"exceptions_list":[],"immutable":false,"type":"query","language":"kuery","index":["exceptions-*"],"query":"${jsonrule.query}","throttle":"no_actions","actions":[]}\n{"exported_count":1,"missing_rules":[],"missing_rules_count":0}\n`; }; diff --git a/x-pack/plugins/security_solution/public/cases/components/create/flyout.tsx b/x-pack/plugins/security_solution/public/cases/components/create/flyout.tsx index e7bb0b25f391fd..8f76ee8f851738 100644 --- a/x-pack/plugins/security_solution/public/cases/components/create/flyout.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/create/flyout.tsx @@ -33,11 +33,25 @@ const StyledFlyout = styled(EuiFlyout)` z-index: ${theme.eui.euiZModal}; `} `; - // Adding bottom padding because timeline's // bottom bar gonna hide the submit button. +const StyledEuiFlyoutBody = styled(EuiFlyoutBody)` + ${({ theme }) => ` + && .euiFlyoutBody__overflow { + overflow-y: auto; + overflow-x: hidden; + } + + && .euiFlyoutBody__overflowContent { + display: block; + padding: ${theme.eui.paddingSizes.l} ${theme.eui.paddingSizes.l} 70px; + height: auto; + } + `} +`; + const FormWrapper = styled.div` - padding-bottom: 50px; + width: 100%; `; const CreateCaseFlyoutComponent: React.FC = ({ @@ -52,7 +66,7 @@ const CreateCaseFlyoutComponent: React.FC = ({

{i18n.CREATE_TITLE}

- + @@ -61,7 +75,7 @@ const CreateCaseFlyoutComponent: React.FC = ({ - + ); }; diff --git a/x-pack/plugins/security_solution/public/common/lib/clipboard/with_copy_to_clipboard.tsx b/x-pack/plugins/security_solution/public/common/lib/clipboard/with_copy_to_clipboard.tsx index bec1b296d48541..1baa57166de3fb 100644 --- a/x-pack/plugins/security_solution/public/common/lib/clipboard/with_copy_to_clipboard.tsx +++ b/x-pack/plugins/security_solution/public/common/lib/clipboard/with_copy_to_clipboard.tsx @@ -7,22 +7,12 @@ import { EuiToolTip } from '@elastic/eui'; import React from 'react'; -import styled from 'styled-components'; import { TooltipWithKeyboardShortcut } from '../../components/accessibility/tooltip_with_keyboard_shortcut'; import * as i18n from '../../components/drag_and_drop/translations'; import { Clipboard } from './clipboard'; -const WithCopyToClipboardContainer = styled.div` - align-items: center; - display: flex; - flex-direction: row; - user-select: text; -`; - -WithCopyToClipboardContainer.displayName = 'WithCopyToClipboardContainer'; - /** * Renders `children` with an adjacent icon that when clicked, copies `text` to * the clipboard and displays a confirmation toast @@ -31,7 +21,7 @@ export const WithCopyToClipboard = React.memo<{ keyboardShortcut?: string; text: string; titleSummary?: string; -}>(({ keyboardShortcut = '', text, titleSummary, children }) => ( +}>(({ keyboardShortcut = '', text, titleSummary }) => ( } > - - <>{children} - - + )); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/select_rule_type/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/select_rule_type/index.tsx index 64f0f5f65b1eef..5650c2c55488ed 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/select_rule_type/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/select_rule_type/index.tsx @@ -111,7 +111,6 @@ export const SelectRuleType: React.FC = ({ icon={} selectable={querySelectableConfig} layout="horizontal" - textAlign="left" /> )} @@ -131,7 +130,6 @@ export const SelectRuleType: React.FC = ({ isDisabled={mlSelectableConfig.isDisabled && !mlSelectableConfig.isSelected} selectable={mlSelectableConfig} layout="horizontal" - textAlign="left" /> )} @@ -145,7 +143,6 @@ export const SelectRuleType: React.FC = ({ icon={} selectable={thresholdSelectableConfig} layout="horizontal" - textAlign="left" /> )} @@ -159,7 +156,6 @@ export const SelectRuleType: React.FC = ({ icon={} selectable={eqlSelectableConfig} layout="horizontal" - textAlign="left" /> )} @@ -173,7 +169,6 @@ export const SelectRuleType: React.FC = ({ icon={} selectable={threatMatchSelectableConfig} layout="horizontal" - textAlign="left" /> )} diff --git a/x-pack/plugins/security_solution/public/detections/mitre/mitre_tactics_techniques.ts b/x-pack/plugins/security_solution/public/detections/mitre/mitre_tactics_techniques.ts index b0c02bdbfefc66..a5da747787ba6e 100644 --- a/x-pack/plugins/security_solution/public/detections/mitre/mitre_tactics_techniques.ts +++ b/x-pack/plugins/security_solution/public/detections/mitre/mitre_tactics_techniques.ts @@ -718,12 +718,6 @@ export const technique = [ reference: 'https://attack.mitre.org/techniques/T1061', tactics: ['execution'], }, - { - name: 'Group Policy Modification', - id: 'T1484', - reference: 'https://attack.mitre.org/techniques/T1484', - tactics: ['defense-evasion', 'privilege-escalation'], - }, { name: 'Hardware Additions', id: 'T1200', @@ -1354,6 +1348,18 @@ export const technique = [ reference: 'https://attack.mitre.org/techniques/T1220', tactics: ['defense-evasion'], }, + { + name: 'Domain Policy Modification', + id: 'T1484', + reference: 'https://attack.mitre.org/techniques/T1484', + tactics: ['defense-evasion', 'privilege-escalation'], + }, + { + name: 'Forge Web Credentials', + id: 'T1606', + reference: 'https://attack.mitre.org/techniques/T1606', + tactics: ['credential-access'], + }, ]; export const techniquesOptions: MitreTechniquesOptions[] = [ @@ -2259,17 +2265,6 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ tactics: 'execution', value: 'graphicalUserInterface', }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.groupPolicyModificationDescription', - { defaultMessage: 'Group Policy Modification (T1484)' } - ), - id: 'T1484', - name: 'Group Policy Modification', - reference: 'https://attack.mitre.org/techniques/T1484', - tactics: 'defense-evasion,privilege-escalation', - value: 'groupPolicyModification', - }, { label: i18n.translate( 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.hardwareAdditionsDescription', @@ -3425,6 +3420,28 @@ export const techniquesOptions: MitreTechniquesOptions[] = [ tactics: 'defense-evasion', value: 'xslScriptProcessing', }, + { + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.domainPolicyModificationDescription', + { defaultMessage: 'Domain Policy Modification (T1484)' } + ), + id: 'T1484', + name: 'Domain Policy Modification', + reference: 'https://attack.mitre.org/techniques/T1484', + tactics: 'defense-evasion,privilege-escalation', + value: 'domainPolicyModification', + }, + { + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackTechniques.forgeWebCredentialsDescription', + { defaultMessage: 'Forge Web Credentials (T1606)' } + ), + id: 'T1606', + name: 'Forge Web Credentials', + reference: 'https://attack.mitre.org/techniques/T1606', + tactics: 'credential-access', + value: 'forgeWebCredentials', + }, ]; export const subtechniques = [ @@ -3477,13 +3494,6 @@ export const subtechniques = [ tactics: ['persistence'], techniqueId: 'T1137', }, - { - name: 'Additional Cloud Credentials', - id: 'T1098.001', - reference: 'https://attack.mitre.org/techniques/T1098/001', - tactics: ['persistence'], - techniqueId: 'T1098', - }, { name: 'AppCert DLLs', id: 'T1546.009', @@ -5864,6 +5874,41 @@ export const subtechniques = [ tactics: ['persistence', 'privilege-escalation'], techniqueId: 'T1547', }, + { + name: 'Additional Cloud Credentials', + id: 'T1098.001', + reference: 'https://attack.mitre.org/techniques/T1098/001', + tactics: ['persistence'], + techniqueId: 'T1098', + }, + { + name: 'Group Policy Modification', + id: 'T1484.001', + reference: 'https://attack.mitre.org/techniques/T1484/001', + tactics: ['defense-evasion', 'privilege-escalation'], + techniqueId: 'T1484', + }, + { + name: 'Domain Trust Modification', + id: 'T1484.002', + reference: 'https://attack.mitre.org/techniques/T1484/002', + tactics: ['defense-evasion', 'privilege-escalation'], + techniqueId: 'T1484', + }, + { + name: 'Web Cookies', + id: 'T1606.001', + reference: 'https://attack.mitre.org/techniques/T1606/001', + tactics: ['credential-access'], + techniqueId: 'T1606', + }, + { + name: 'SAML Tokens', + id: 'T1606.002', + reference: 'https://attack.mitre.org/techniques/T1606/002', + tactics: ['credential-access'], + techniqueId: 'T1606', + }, ]; export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ @@ -5951,18 +5996,6 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ techniqueId: 'T1137', value: 'addIns', }, - { - label: i18n.translate( - 'xpack.securitySolution.detectionEngine.mitreAttackSubtechniques.additionalCloudCredentialsT1098Description', - { defaultMessage: 'Additional Cloud Credentials (T1098.001)' } - ), - id: 'T1098.001', - name: 'Additional Cloud Credentials', - reference: 'https://attack.mitre.org/techniques/T1098/001', - tactics: 'persistence', - techniqueId: 'T1098', - value: 'additionalCloudCredentials', - }, { label: i18n.translate( 'xpack.securitySolution.detectionEngine.mitreAttackSubtechniques.appCertDlLsT1546Description', @@ -10043,6 +10076,66 @@ export const subtechniquesOptions: MitreSubtechniquesOptions[] = [ techniqueId: 'T1547', value: 'winlogonHelperDll', }, + { + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackSubtechniques.additionalCloudCredentialsT1098Description', + { defaultMessage: 'Additional Cloud Credentials (T1098.001)' } + ), + id: 'T1098.001', + name: 'Additional Cloud Credentials', + reference: 'https://attack.mitre.org/techniques/T1098/001', + tactics: 'persistence', + techniqueId: 'T1098', + value: 'additionalCloudCredentials', + }, + { + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackSubtechniques.groupPolicyModificationT1484Description', + { defaultMessage: 'Group Policy Modification (T1484.001)' } + ), + id: 'T1484.001', + name: 'Group Policy Modification', + reference: 'https://attack.mitre.org/techniques/T1484/001', + tactics: 'defense-evasion,privilege-escalation', + techniqueId: 'T1484', + value: 'groupPolicyModification', + }, + { + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackSubtechniques.domainTrustModificationT1484Description', + { defaultMessage: 'Domain Trust Modification (T1484.002)' } + ), + id: 'T1484.002', + name: 'Domain Trust Modification', + reference: 'https://attack.mitre.org/techniques/T1484/002', + tactics: 'defense-evasion,privilege-escalation', + techniqueId: 'T1484', + value: 'domainTrustModification', + }, + { + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackSubtechniques.webCookiesT1606Description', + { defaultMessage: 'Web Cookies (T1606.001)' } + ), + id: 'T1606.001', + name: 'Web Cookies', + reference: 'https://attack.mitre.org/techniques/T1606/001', + tactics: 'credential-access', + techniqueId: 'T1606', + value: 'webCookies', + }, + { + label: i18n.translate( + 'xpack.securitySolution.detectionEngine.mitreAttackSubtechniques.samlTokensT1606Description', + { defaultMessage: 'SAML Tokens (T1606.002)' } + ), + id: 'T1606.002', + name: 'SAML Tokens', + reference: 'https://attack.mitre.org/techniques/T1606/002', + tactics: 'credential-access', + techniqueId: 'T1606', + value: 'samlTokens', + }, ]; /** diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/__snapshots__/index.test.tsx.snap index efae0a4b8b3aa9..220494b3a56944 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/__snapshots__/index.test.tsx.snap @@ -2919,7 +2919,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = `
`; @@ -44,7 +46,6 @@ exports[`renders with a space name entirely made of whitespace 1`] = ` = (props: Props) => { const spaceColor = getSpaceColor(space); + const spaceInitials = getSpaceInitials(space); + + const spaceImageUrl = getSpaceImageUrl(space); + + const avatarConfig: Partial = spaceImageUrl + ? { imageUrl: spaceImageUrl } + : { initials: spaceInitials, initialsLength: MAX_SPACE_INITIALS }; + return ( = (props: Props) => { 'aria-hidden': true, })} size={size || 'm'} - initialsLength={MAX_SPACE_INITIALS} - initials={getSpaceInitials(space)} color={isValidHex(spaceColor) ? spaceColor : ''} - imageUrl={getSpaceImageUrl(space)} + {...avatarConfig} {...rest} /> ); diff --git a/x-pack/plugins/timelines/.eslintrc.js b/x-pack/plugins/timelines/.eslintrc.js deleted file mode 100644 index b267018448ba62..00000000000000 --- a/x-pack/plugins/timelines/.eslintrc.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = { - root: true, - extends: ['@elastic/eslint-config-kibana', 'plugin:@elastic/eui/recommended'], - rules: { - '@kbn/eslint/require-license-header': 'off', - }, -}; diff --git a/x-pack/plugins/timelines/common/index.ts b/x-pack/plugins/timelines/common/index.ts index 2354c513f73b8d..c095b6c89627ea 100644 --- a/x-pack/plugins/timelines/common/index.ts +++ b/x-pack/plugins/timelines/common/index.ts @@ -1,2 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + export const PLUGIN_ID = 'timelines'; export const PLUGIN_NAME = 'timelines'; diff --git a/x-pack/plugins/timelines/public/components/index.tsx b/x-pack/plugins/timelines/public/components/index.tsx index 3388b3c44baff4..f44ad8052917f3 100644 --- a/x-pack/plugins/timelines/public/components/index.tsx +++ b/x-pack/plugins/timelines/public/components/index.tsx @@ -1,3 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + import React from 'react'; import { FormattedMessage, I18nProvider } from '@kbn/i18n/react'; diff --git a/x-pack/plugins/timelines/public/index.ts b/x-pack/plugins/timelines/public/index.ts index b535def809de3c..c3d24d49e2401f 100644 --- a/x-pack/plugins/timelines/public/index.ts +++ b/x-pack/plugins/timelines/public/index.ts @@ -1,3 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + import './index.scss'; import { PluginInitializerContext } from 'src/core/public'; diff --git a/x-pack/plugins/timelines/public/plugin.ts b/x-pack/plugins/timelines/public/plugin.ts index 7e90d9467fefdd..76a692cf8ed102 100644 --- a/x-pack/plugins/timelines/public/plugin.ts +++ b/x-pack/plugins/timelines/public/plugin.ts @@ -1,3 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + import { CoreSetup, Plugin, PluginInitializerContext } from '../../../../src/core/public'; import { TimelinesPluginSetup, TimelineProps } from './types'; import { getTimelineLazy } from './methods'; diff --git a/x-pack/plugins/timelines/public/types.ts b/x-pack/plugins/timelines/public/types.ts index b199b459027180..1fa6d33a6af602 100644 --- a/x-pack/plugins/timelines/public/types.ts +++ b/x-pack/plugins/timelines/public/types.ts @@ -1,3 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + import { ReactElement } from 'react'; export interface TimelinesPluginSetup { diff --git a/x-pack/plugins/timelines/server/config.ts b/x-pack/plugins/timelines/server/config.ts index 633a95b8f91a73..31be2566118039 100644 --- a/x-pack/plugins/timelines/server/config.ts +++ b/x-pack/plugins/timelines/server/config.ts @@ -1,7 +1,8 @@ /* * 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. + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. */ import { TypeOf, schema } from '@kbn/config-schema'; diff --git a/x-pack/plugins/timelines/server/index.ts b/x-pack/plugins/timelines/server/index.ts index 32de97be2704a8..65e2b6494c6f47 100644 --- a/x-pack/plugins/timelines/server/index.ts +++ b/x-pack/plugins/timelines/server/index.ts @@ -1,7 +1,8 @@ /* * 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. + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. */ import { PluginInitializerContext } from '../../../../src/core/server'; diff --git a/x-pack/plugins/timelines/server/plugin.ts b/x-pack/plugins/timelines/server/plugin.ts index 3e330b19b7fdb8..825d42994e0963 100644 --- a/x-pack/plugins/timelines/server/plugin.ts +++ b/x-pack/plugins/timelines/server/plugin.ts @@ -1,3 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + import { PluginInitializerContext, CoreSetup, diff --git a/x-pack/plugins/timelines/server/routes/index.ts b/x-pack/plugins/timelines/server/routes/index.ts index edb10c579b30be..1c651469b795a0 100644 --- a/x-pack/plugins/timelines/server/routes/index.ts +++ b/x-pack/plugins/timelines/server/routes/index.ts @@ -1,3 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + import { IRouter } from '../../../../../src/core/server'; export function defineRoutes(router: IRouter) { diff --git a/x-pack/plugins/timelines/server/types.ts b/x-pack/plugins/timelines/server/types.ts index cb544562b79b4e..5bcc90b48f0b99 100644 --- a/x-pack/plugins/timelines/server/types.ts +++ b/x-pack/plugins/timelines/server/types.ts @@ -1,3 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface TimelinesPluginSetup {} // eslint-disable-next-line @typescript-eslint/no-empty-interface diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 527f32828979ac..933bf512bdda03 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -669,8 +669,7 @@ "dashboard.panelStorageError.clearError": "保存されていない変更の消去中にエラーが発生しました。{message}", "dashboard.panelStorageError.getError": "保存されていない変更の取得中にエラーが発生しました。{message}", "dashboard.panelStorageError.setError": "保存されていない変更の設定中にエラーが発生しました。{message}", - "dashboard.panelToolbar.addPanelButtonLabel": "パネルの作成", - "dashboard.panelToolbar.libraryButtonLabel": "ライブラリから追加", + "dashboard.solutionToolbar.addPanelButtonLabel": "パネルの作成", "dashboard.placeholder.factory.displayName": "プレースホルダー", "dashboard.savedDashboard.newDashboardTitle": "新規ダッシュボード", "dashboard.stateManager.timeNotSavedWithDashboardErrorMessage": "このダッシュボードに時刻が保存されていないため、同期できません。", @@ -7344,7 +7343,6 @@ "xpack.data.searchSessionIndicator.canceledTitleText": "検索セッションが停止しました", "xpack.data.searchSessionIndicator.canceledTooltipText": "検索セッションが停止しました", "xpack.data.searchSessionIndicator.continueInBackgroundButtonText": "セッションの保存", - "xpack.data.searchSessionIndicator.disabledDueToAutoRefreshMessage": "自動更新が有効な場合は、検索セッションの保存を使用できません。", "xpack.data.searchSessionIndicator.disabledDueToDisabledGloballyMessage": "検索セッションを管理するアクセス権がありません", "xpack.data.searchSessionIndicator.disabledDueToTimeoutMessage": "検索セッション結果が期限切れです。", "xpack.data.searchSessionIndicator.loadingInTheBackgroundDescriptionText": "管理から完了した結果に戻ることができます。", @@ -12424,7 +12422,6 @@ "xpack.maps.es_geo_utils.convert.invalidGeometryCollectionErrorMessage": "GeometryCollectionを convertESShapeToGeojsonGeometryに渡さないでください", "xpack.maps.es_geo_utils.convert.unsupportedGeometryTypeErrorMessage": "{geometryType} ジオメトリから Geojson に変換できません。サポートされていません", "xpack.maps.es_geo_utils.distanceFilterAlias": "{pointLabel} の {distanceKm}km 以内にある {geoFieldName}", - "xpack.maps.es_geo_utils.shapeFilter.geoPointRelationLabel": "in", "xpack.maps.es_geo_utils.unsupportedFieldTypeErrorMessage": "サポートされていないフィールドタイプ、期待値:{expectedTypes}、提供された値:{fieldType}", "xpack.maps.es_geo_utils.unsupportedGeometryTypeErrorMessage": "サポートされていないジオメトリタイプ、期待値:{expectedTypes}、提供された値:{geometryType}", "xpack.maps.es_geo_utils.wkt.invalidWKTErrorMessage": "{wkt} を Geojson に変換できません。有効な WKT が必要です。", @@ -13497,8 +13494,6 @@ "xpack.ml.dataframe.analytics.rocChartSpec.yAxisTitle": "検出率 (TRP) (Recall) ", "xpack.ml.dataframe.analyticsList.analyticsDetails.tabs.analyticsMessagesLabel": "ジョブメッセージ", "xpack.ml.dataframe.analyticsList.analyticsDetails.tabs.analyticsStatsLabel": "ジョブ統計情報", - "xpack.ml.dataframe.analyticsList.betaBadgeLabel": "ベータ", - "xpack.ml.dataframe.analyticsList.betaBadgeTooltipContent": "データフレーム分析はベータ機能です。フィードバックをお待ちしています。", "xpack.ml.dataframe.analyticsList.cloneActionNameText": "クローンを作成", "xpack.ml.dataframe.analyticsList.cloneActionPermissionTooltip": "分析ジョブを複製する権限がありません。", "xpack.ml.dataframe.analyticsList.completeBatchAnalyticsToolTip": "{analyticsId}は完了済みの分析ジョブで、再度開始できません。", @@ -17385,7 +17380,6 @@ "xpack.security.components.sessionIdleTimeoutWarning.title": "警告", "xpack.security.components.sessionLifespanWarning.message": "セッションは最大時間制限{timeout}に達しました。もう一度ログインする必要があります。", "xpack.security.components.sessionLifespanWarning.title": "警告", - "xpack.security.confirmModal.cancelButton": "キャンセル", "xpack.security.conflictingSessionError": "申し訳ありません。すでに有効なKibanaセッションがあります。新しいセッションを開始する場合は、先に既存のセッションからログアウトしてください。", "xpack.security.formFlyout.cancelButton": "キャンセル", "xpack.security.loggedOut.login": "ログイン", @@ -17421,19 +17415,7 @@ "xpack.security.loginWithElasticsearchLabel": "Elasticsearchでログイン", "xpack.security.logoutAppTitle": "ログアウト", "xpack.security.management.apiKeys.deniedPermissionTitle": "API キーを管理するにはパーミッションが必要です", - "xpack.security.management.apiKeys.invalidateApiKey.confirmModal.cancelButtonLabel": "キャンセル", - "xpack.security.management.apiKeys.invalidateApiKey.confirmModal.invalidateMultipleListDescription": "これらの API キーを無効化しようとしています:", - "xpack.security.management.apiKeys.invalidateApiKey.confirmModal.invalidateMultipleTitle": "{count} API キーを無効にしますか?", - "xpack.security.management.apiKeys.invalidateApiKey.confirmModal.invalidateSingleTitle": "API キー「{name}」を無効にしますか?", - "xpack.security.management.apiKeys.invalidateApiKey.errorMultipleNotificationTitle": "{count} 件の API キーの削除中にエラーが発生", - "xpack.security.management.apiKeys.invalidateApiKey.errorSingleNotificationTitle": "API キー「{name}」の削除中にエラーが発生", - "xpack.security.management.apiKeys.invalidateApiKey.successMultipleNotificationTitle": "無効な {count} API キー", - "xpack.security.management.apiKeys.invalidateApiKey.successSingleNotificationTitle": "API キー「{name}」を無効にしました", "xpack.security.management.apiKeys.noPermissionToManageRolesDescription": "システム管理者にお問い合わせください。", - "xpack.security.management.apiKeys.table.actionDeleteAriaLabel": "「{name}」を無効にする", - "xpack.security.management.apiKeys.table.actionDeleteTooltip": "無効にする", - "xpack.security.management.apiKeys.table.actionsColumnName": "アクション", - "xpack.security.management.apiKeys.table.adminText": "あなたは API キー管理者です。", "xpack.security.management.apiKeys.table.apiKeysAllDescription": "API キーを表示して無効にします。API キーはユーザーの代わりにリクエストを送信します。", "xpack.security.management.apiKeys.table.apiKeysDisabledErrorDescription": "システム管理者に連絡し、{link}を参照して API キーを有効にしてください。", "xpack.security.management.apiKeys.table.apiKeysDisabledErrorLinkText": "ドキュメント", @@ -17442,20 +17424,10 @@ "xpack.security.management.apiKeys.table.apiKeysTableLoadingMessage": "API キーを読み込み中…", "xpack.security.management.apiKeys.table.apiKeysTitle": "API キー", "xpack.security.management.apiKeys.table.creationDateColumnName": "作成済み", - "xpack.security.management.apiKeys.table.emptyPromptAdminTitle": "API キーがありません", - "xpack.security.management.apiKeys.table.emptyPromptConsoleButtonMessage": "コンソールに移動してください", - "xpack.security.management.apiKeys.table.emptyPromptDescription": "コンソールで {link} を作成できます。", - "xpack.security.management.apiKeys.table.emptyPromptDocsLinkMessage": "API キー", - "xpack.security.management.apiKeys.table.emptyPromptNonAdminTitle": "まだ API キーがありません", - "xpack.security.management.apiKeys.table.expirationDateColumnName": "有効期限", - "xpack.security.management.apiKeys.table.expirationDateNeverMessage": "なし", "xpack.security.management.apiKeys.table.fetchingApiKeysErrorMessage": "権限の確認エラー:{message}", "xpack.security.management.apiKeys.table.loadingApiKeysDescription": "API キーを読み込み中…", - "xpack.security.management.apiKeys.table.loadingApiKeysErrorTitle": "API キーを読み込み中にエラーが発生", "xpack.security.management.apiKeys.table.nameColumnName": "名前", - "xpack.security.management.apiKeys.table.realmColumnName": "レルム", "xpack.security.management.apiKeys.table.realmFilterLabel": "レルム", - "xpack.security.management.apiKeys.table.reloadApiKeysButton": "再読み込み", "xpack.security.management.apiKeys.table.statusColumnName": "ステータス", "xpack.security.management.apiKeys.table.userFilterLabel": "ユーザー", "xpack.security.management.apiKeys.table.userNameColumnName": "ユーザー", @@ -19084,7 +19056,6 @@ "xpack.securitySolution.detectionEngine.mitreAttackTechniques.gatherVictimNetworkInformationDescription": "被害者ネットワーク情報の収集 (T1590) ", "xpack.securitySolution.detectionEngine.mitreAttackTechniques.gatherVictimOrgInformationDescription": "被害者組織情報の収集 (T1591) ", "xpack.securitySolution.detectionEngine.mitreAttackTechniques.graphicalUserInterfaceDescription": "グラフィカルユーザーインターフェイス (T1061) ", - "xpack.securitySolution.detectionEngine.mitreAttackTechniques.groupPolicyModificationDescription": "グループポリシー修正 (T1484) ", "xpack.securitySolution.detectionEngine.mitreAttackTechniques.hardwareAdditionsDescription": "ハードウェア追加 (T1200) ", "xpack.securitySolution.detectionEngine.mitreAttackTechniques.hideArtifactsDescription": "アーチファクトの非表示 (T1564) ", "xpack.securitySolution.detectionEngine.mitreAttackTechniques.hijackExecutionFlowDescription": "ハイジャック実行フロー (T1574) ", @@ -22622,14 +22593,9 @@ "xpack.uiActionsEnhanced.drilldowns.urlDrilldownValidation.urlFormatErrorMessage": "無効な形式:{message}", "xpack.uiActionsEnhanced.drilldowns.urlDrilldownValidation.urlFormatGeneralErrorMessage": "無効なフォーマット。例:{exampleUrl}", "xpack.upgradeAssistant.appTitle": "{version} アップグレードアシスタント", - "xpack.upgradeAssistant.checkupTab.backUpCallout.calloutBody.calloutDetail": "{snapshotRestoreDocsButton} でデータをバックアップします。", - "xpack.upgradeAssistant.checkupTab.backUpCallout.calloutBody.snapshotRestoreDocsButtonLabel": "API のスナップショットと復元", - "xpack.upgradeAssistant.checkupTab.backUpCallout.calloutTitle": "今すぐインデックをバックアップ", "xpack.upgradeAssistant.checkupTab.changeFiltersShowMoreLabel": "より多く表示させるにはフィルターを変更します。", - "xpack.upgradeAssistant.checkupTab.clusterTabLabel": "クラスター", "xpack.upgradeAssistant.checkupTab.controls.collapseAllButtonLabel": "すべて縮小", "xpack.upgradeAssistant.checkupTab.controls.expandAllButtonLabel": "すべて拡張", - "xpack.upgradeAssistant.checkupTab.controls.filterBar.allButtonLabel": "すべて", "xpack.upgradeAssistant.checkupTab.controls.filterBar.criticalButtonLabel": "致命的", "xpack.upgradeAssistant.checkupTab.controls.filterErrorMessageLabel": "フィルター無効:{searchTermError}", "xpack.upgradeAssistant.checkupTab.controls.groupByBar.byIndexLabel": "インデックス別", @@ -22644,12 +22610,9 @@ "xpack.upgradeAssistant.checkupTab.deprecations.indexTable.indexColumnLabel": "インデックス", "xpack.upgradeAssistant.checkupTab.deprecations.warningActionTooltip": "アップグレード前にこの問題を解決することをお勧めしますが、必須ではありません。", "xpack.upgradeAssistant.checkupTab.deprecations.warningLabel": "警告", - "xpack.upgradeAssistant.checkupTab.indexLabel": "インデックス", - "xpack.upgradeAssistant.checkupTab.indicesTabLabel": "インデックス", "xpack.upgradeAssistant.checkupTab.noDeprecationsLabel": "説明がありません", "xpack.upgradeAssistant.checkupTab.noIssues.nextStepsDetail": "{overviewTabButton} で次のステップを確認してください。", "xpack.upgradeAssistant.checkupTab.noIssues.nextStepsDetail.overviewTabButtonLabel": "概要タブ", - "xpack.upgradeAssistant.checkupTab.noIssues.noIssuesLabel": "{strongCheckupLabel} の問題がありません。", "xpack.upgradeAssistant.checkupTab.noIssues.noIssuesTitle": "完璧です!", "xpack.upgradeAssistant.checkupTab.numDeprecationsShownLabel": "{total} 件中 {numShown} 件を表示中", "xpack.upgradeAssistant.checkupTab.reindexing.flyout.checklistStep.cancelButtonLabel": "キャンセル", @@ -22692,45 +22655,12 @@ "xpack.upgradeAssistant.checkupTab.reindexing.reindexButton.loadingLabel": "読み込み中…", "xpack.upgradeAssistant.checkupTab.reindexing.reindexButton.pausedLabel": "一時停止中", "xpack.upgradeAssistant.checkupTab.reindexing.reindexButton.reindexLabel": "再インデックス", - "xpack.upgradeAssistant.checkupTab.tabDetail": "これらの {strongCheckupLabel} 問題に対応する必要があります。Elasticsearch {nextEsVersion} へのアップグレード前に解決してください。", - "xpack.upgradeAssistant.forbiddenErrorCallout.calloutTitle": "このページを表示するための権限がありません。", - "xpack.upgradeAssistant.genericErrorCallout.calloutTitle": "チェックアップの結果を取得中にエラーが発生しました。", - "xpack.upgradeAssistant.overviewTab.overviewTabTitle": "概要", - "xpack.upgradeAssistant.overviewTab.steps.clusterStep.issuesRemainingStepTitle": "クラスターの問題を確認してください", - "xpack.upgradeAssistant.overviewTab.steps.clusterStep.noIssuesRemainingStepTitle": "クラスターの設定は準備完了です", - "xpack.upgradeAssistant.overviewTab.steps.clusterStep.noRemainingIssuesLabel": "廃止された設定は残っていません。", - "xpack.upgradeAssistant.overviewTab.steps.clusterStep.remainingIssuesDetail": "{numIssues} 件の問題が解決されました。", - "xpack.upgradeAssistant.overviewTab.steps.clusterStep.todo.clusterTabButtonLabel": "クラスタータブ", - "xpack.upgradeAssistant.overviewTab.steps.clusterStep.todo.todoDetail": "{clusterTabButton} に移動して廃止された設定を更新してください。", - "xpack.upgradeAssistant.overviewTab.steps.deprecationLogsStep.deprecationLogs.deprecationLogsDocButtonLabel": "廃止ログ", - "xpack.upgradeAssistant.overviewTab.steps.deprecationLogsStep.deprecationLogs.logsDetail": "{deprecationLogsDocButton} で、アプリケーションが {nextEsVersion} で利用できない機能を使用していないか確認してください。廃止ログを有効にする必要があるかもしれません。", - "xpack.upgradeAssistant.overviewTab.steps.deprecationLogsStep.enableDeprecationLoggingLabel": "廃止ログを有効にしますか?", - "xpack.upgradeAssistant.overviewTab.steps.deprecationLogsStep.enableDeprecationLoggingToggleSwitch.disabledLabel": "オフ", "xpack.upgradeAssistant.overviewTab.steps.deprecationLogsStep.enableDeprecationLoggingToggleSwitch.enabledLabel": "オン", "xpack.upgradeAssistant.overviewTab.steps.deprecationLogsStep.enableDeprecationLoggingToggleSwitch.errorLabel": "ログステータスを読み込めませんでした", - "xpack.upgradeAssistant.overviewTab.steps.deprecationLogsStep.stepTitle": "Elasticsearch の廃止ログを確認してください", - "xpack.upgradeAssistant.overviewTab.steps.indicesStep.issuesRemainingStepTitle": "インデックスの問題を確認してください", - "xpack.upgradeAssistant.overviewTab.steps.indicesStep.noIssuesRemainingStepTitle": "インデックスの設定は準備完了です", - "xpack.upgradeAssistant.overviewTab.steps.indicesStep.noRemainingIssuesLabel": "廃止された設定は残っていません。", - "xpack.upgradeAssistant.overviewTab.steps.indicesStep.remainingIssuesDetail": "{numIssues} 件の問題が解決されました。", - "xpack.upgradeAssistant.overviewTab.steps.indicesStep.todo.indicesTabButtonLabel": "インデックスタブ", - "xpack.upgradeAssistant.overviewTab.steps.indicesStep.todo.todoDetail": "{indicesTabButton} に移動して廃止された設定を更新してください。", - "xpack.upgradeAssistant.overviewTab.steps.startUpgradeStep.stepTitle": "アップグレード開始", - "xpack.upgradeAssistant.overviewTab.steps.startUpgradeStepCloud.stepDetail.goToCloudDashboardDetail": "Elastic Cloud ダッシュボードのデプロイセクションに移動し、アップグレードを開始します。", - "xpack.upgradeAssistant.overviewTab.steps.startUpgradeStepOnPrem.stepDetail.followInstructionsDetail": "{instructionButton} に従い、アップグレードを開始します。", - "xpack.upgradeAssistant.overviewTab.steps.startUpgradeStepOnPrem.stepDetail.instructionButtonLabel": "これらの手順", - "xpack.upgradeAssistant.overviewTab.steps.waitForReleaseStep.stepDetail": "リリースされ次第最新の {currentEsMajorVersion} バージョンにアップグレードし、ここに戻って {nextEsMajorVersion} へのアップグレードを行ってください。", - "xpack.upgradeAssistant.overviewTab.steps.waitForReleaseStep.stepTitle": "Elasticsearch {nextEsVersion} のリリース待ち", - "xpack.upgradeAssistant.overviewTab.tabDetail": "このアシスタントは、クラスターとインデックスの Elasticsearch への準備に役立ちます {nextEsVersion} 対処が必要な他の問題に関しては、Elasticsearch のログをご覧ください。", "xpack.upgradeAssistant.reindex.reindexPrivilegesErrorBatch": "「{indexName}」に再インデックスするための権限が不十分です。", - "xpack.upgradeAssistant.tabs.checkupTab.clusterLabel": "クラスター", "xpack.upgradeAssistant.tabs.incompleteCallout.calloutBody.breackingChangesDocButtonLabel": "廃止と互換性を破る変更", "xpack.upgradeAssistant.tabs.incompleteCallout.calloutBody.calloutDetail": "Elasticsearch {nextEsVersion} の {breakingChangesDocButton} の完全なリストは、最終の {currentEsVersion} マイナーリリースで確認できます。この警告は、リストがすべて解決されると消えます。", "xpack.upgradeAssistant.tabs.incompleteCallout.calloutTitle": "リストの問題がすべて解決されていない可能性があります。", - "xpack.upgradeAssistant.tabs.upgradingInterstitial.upgradeCompleteDescription": "すべての Elasticsearch ノードがアップグレードされました。Kibana をアップデートする準備ができました。", - "xpack.upgradeAssistant.tabs.upgradingInterstitial.upgradeCompleteTitle": "クラスターがアップグレードされました", - "xpack.upgradeAssistant.tabs.upgradingInterstitial.upgradingDescription": "1 つまたは複数の Elasticsearch ノードに、 Kibana よりも新しいバージョンの Elasticsearch があります。すべてのノードがアップグレードされた後で Kibana をアップグレードしてください。", - "xpack.upgradeAssistant.tabs.upgradingInterstitial.upgradingTitle": "クラスターをアップグレード中です", "xpack.uptime.addDataButtonLabel": "データの追加", "xpack.uptime.alerts.anomaly.criteriaExpression.ariaLabel": "選択したモニターの条件を表示する式。", "xpack.uptime.alerts.anomaly.criteriaExpression.description": "監視するとき", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index f8c8ee753942cd..917c68913d4620 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -672,8 +672,7 @@ "dashboard.panelStorageError.clearError": "清除未保存更改时遇到错误:{message}", "dashboard.panelStorageError.getError": "获取未保存更改时遇到错误:{message}", "dashboard.panelStorageError.setError": "设置未保存更改时遇到错误:{message}", - "dashboard.panelToolbar.addPanelButtonLabel": "创建面板", - "dashboard.panelToolbar.libraryButtonLabel": "从库中添加", + "dashboard.solutionToolbar.addPanelButtonLabel": "创建面板", "dashboard.placeholder.factory.displayName": "占位符", "dashboard.savedDashboard.newDashboardTitle": "新建仪表板", "dashboard.stateManager.timeNotSavedWithDashboardErrorMessage": "时间未随此仪表板保存,因此无法同步。", @@ -7408,7 +7407,6 @@ "xpack.data.searchSessionIndicator.canceledTitleText": "搜索会话已停止", "xpack.data.searchSessionIndicator.canceledTooltipText": "搜索会话已停止", "xpack.data.searchSessionIndicator.continueInBackgroundButtonText": "保存会话", - "xpack.data.searchSessionIndicator.disabledDueToAutoRefreshMessage": "启用自动刷新时,保存搜索会话不可用。", "xpack.data.searchSessionIndicator.disabledDueToDisabledGloballyMessage": "您无权管理搜索会话", "xpack.data.searchSessionIndicator.disabledDueToTimeoutMessage": "搜索会话结果已过期。", "xpack.data.searchSessionIndicator.loadingInTheBackgroundDescriptionText": "可以从“管理”中返回至完成的结果。", @@ -12591,7 +12589,6 @@ "xpack.maps.es_geo_utils.convert.invalidGeometryCollectionErrorMessage": "不应将 GeometryCollection 传递给 convertESShapeToGeojsonGeometry", "xpack.maps.es_geo_utils.convert.unsupportedGeometryTypeErrorMessage": "无法将 {geometryType} 几何图形转换成 geojson,不支持", "xpack.maps.es_geo_utils.distanceFilterAlias": "{pointLabel} {distanceKm}km 内的 {geoFieldName}", - "xpack.maps.es_geo_utils.shapeFilter.geoPointRelationLabel": "于", "xpack.maps.es_geo_utils.unsupportedFieldTypeErrorMessage": "字段类型不受支持,应为 {expectedTypes},而提供的是 {fieldType}", "xpack.maps.es_geo_utils.unsupportedGeometryTypeErrorMessage": "几何类型不受支持,应为 {expectedTypes},而提供的是 {geometryType}", "xpack.maps.es_geo_utils.wkt.invalidWKTErrorMessage": "无法将 {wkt} 转换成 geojson。需要有效的 WKT。", @@ -13672,8 +13669,6 @@ "xpack.ml.dataframe.analytics.rocChartSpec.yAxisTitle": "真正类率 (TPR) (也称为查全率) ", "xpack.ml.dataframe.analyticsList.analyticsDetails.tabs.analyticsMessagesLabel": "作业消息", "xpack.ml.dataframe.analyticsList.analyticsDetails.tabs.analyticsStatsLabel": "作业统计信息", - "xpack.ml.dataframe.analyticsList.betaBadgeLabel": "公测版", - "xpack.ml.dataframe.analyticsList.betaBadgeTooltipContent": "数据帧分析是公测版功能。我们很乐意听取您的反馈意见。", "xpack.ml.dataframe.analyticsList.cloneActionNameText": "克隆", "xpack.ml.dataframe.analyticsList.cloneActionPermissionTooltip": "您无权克隆分析作业。", "xpack.ml.dataframe.analyticsList.completeBatchAnalyticsToolTip": "{analyticsId} 为已完成的分析作业,无法重新启动。", @@ -17625,7 +17620,6 @@ "xpack.security.components.sessionIdleTimeoutWarning.title": "警告", "xpack.security.components.sessionLifespanWarning.message": "您的会话将达到最大时间限制 {timeout}。您将需要重新登录。", "xpack.security.components.sessionLifespanWarning.title": "警告", - "xpack.security.confirmModal.cancelButton": "取消", "xpack.security.conflictingSessionError": "抱歉,您已有活动的 Kibana 会话。如果希望开始新的会话,请首先从现有会话注销。", "xpack.security.formFlyout.cancelButton": "取消", "xpack.security.loggedOut.login": "登录", @@ -17661,20 +17655,7 @@ "xpack.security.loginWithElasticsearchLabel": "通过 Elasticsearch 登录", "xpack.security.logoutAppTitle": "注销", "xpack.security.management.apiKeys.deniedPermissionTitle": "您需要管理 API 密钥的权限", - "xpack.security.management.apiKeys.invalidateApiKey.confirmModal.cancelButtonLabel": "取消", - "xpack.security.management.apiKeys.invalidateApiKey.confirmModal.confirmButtonLabel": "作废 {count, plural, other {API 密钥}}", - "xpack.security.management.apiKeys.invalidateApiKey.confirmModal.invalidateMultipleListDescription": "您即将作废以下 API 密钥:", - "xpack.security.management.apiKeys.invalidateApiKey.confirmModal.invalidateMultipleTitle": "作废 {count} 个 API 密钥?", - "xpack.security.management.apiKeys.invalidateApiKey.confirmModal.invalidateSingleTitle": "作废 API 密钥“{name}”?", - "xpack.security.management.apiKeys.invalidateApiKey.errorMultipleNotificationTitle": "删除 {count} 个 API 密钥时出错", - "xpack.security.management.apiKeys.invalidateApiKey.errorSingleNotificationTitle": "删除 API 密钥“{name}”时出错", - "xpack.security.management.apiKeys.invalidateApiKey.successMultipleNotificationTitle": "已作废 {count} 个 API 密钥", - "xpack.security.management.apiKeys.invalidateApiKey.successSingleNotificationTitle": "已作废 API 密钥“{name}”", "xpack.security.management.apiKeys.noPermissionToManageRolesDescription": "请联系您的系统管理员。", - "xpack.security.management.apiKeys.table.actionDeleteAriaLabel": "作废“{name}”", - "xpack.security.management.apiKeys.table.actionDeleteTooltip": "作废", - "xpack.security.management.apiKeys.table.actionsColumnName": "操作", - "xpack.security.management.apiKeys.table.adminText": "您是 API 密钥管理员。", "xpack.security.management.apiKeys.table.apiKeysAllDescription": "查看并作废 API 密钥。API 密钥代表用户发送请求。", "xpack.security.management.apiKeys.table.apiKeysDisabledErrorDescription": "请联系您的系统管理员并参阅{link}以启用 API 密钥。", "xpack.security.management.apiKeys.table.apiKeysDisabledErrorLinkText": "文档", @@ -17683,21 +17664,11 @@ "xpack.security.management.apiKeys.table.apiKeysTableLoadingMessage": "正在加载 API 密钥……", "xpack.security.management.apiKeys.table.apiKeysTitle": "API 密钥", "xpack.security.management.apiKeys.table.creationDateColumnName": "已创建", - "xpack.security.management.apiKeys.table.emptyPromptAdminTitle": "无 API 密钥", - "xpack.security.management.apiKeys.table.emptyPromptConsoleButtonMessage": "前往 Console", - "xpack.security.management.apiKeys.table.emptyPromptDescription": "您可以从 Console 创建 {link}。", - "xpack.security.management.apiKeys.table.emptyPromptDocsLinkMessage": "API 密钥", - "xpack.security.management.apiKeys.table.emptyPromptNonAdminTitle": "您未有任何 API 密钥", - "xpack.security.management.apiKeys.table.expirationDateColumnName": "过期", - "xpack.security.management.apiKeys.table.expirationDateNeverMessage": "永不", "xpack.security.management.apiKeys.table.fetchingApiKeysErrorMessage": "检查权限时出错:{message}", "xpack.security.management.apiKeys.table.invalidateApiKeyButton": "作废 {count, plural, other {API 密钥}}", "xpack.security.management.apiKeys.table.loadingApiKeysDescription": "正在加载 API 密钥……", - "xpack.security.management.apiKeys.table.loadingApiKeysErrorTitle": "加载 API 密钥时出错", "xpack.security.management.apiKeys.table.nameColumnName": "名称", - "xpack.security.management.apiKeys.table.realmColumnName": "Realm", "xpack.security.management.apiKeys.table.realmFilterLabel": "Realm", - "xpack.security.management.apiKeys.table.reloadApiKeysButton": "重新加载", "xpack.security.management.apiKeys.table.statusColumnName": "状态", "xpack.security.management.apiKeys.table.userFilterLabel": "用户", "xpack.security.management.apiKeys.table.userNameColumnName": "用户", @@ -19355,7 +19326,6 @@ "xpack.securitySolution.detectionEngine.mitreAttackTechniques.gatherVictimNetworkInformationDescription": "Gather Victim Network Information (T1590)", "xpack.securitySolution.detectionEngine.mitreAttackTechniques.gatherVictimOrgInformationDescription": "Gather Victim Org Information (T1591)", "xpack.securitySolution.detectionEngine.mitreAttackTechniques.graphicalUserInterfaceDescription": "Graphical User Interface (T1061)", - "xpack.securitySolution.detectionEngine.mitreAttackTechniques.groupPolicyModificationDescription": "Group Policy Modification (T1484)", "xpack.securitySolution.detectionEngine.mitreAttackTechniques.hardwareAdditionsDescription": "Hardware Additions (T1200)", "xpack.securitySolution.detectionEngine.mitreAttackTechniques.hideArtifactsDescription": "Hide Artifacts (T1564)", "xpack.securitySolution.detectionEngine.mitreAttackTechniques.hijackExecutionFlowDescription": "Hijack Execution Flow (T1574)", @@ -22980,14 +22950,9 @@ "xpack.uiActionsEnhanced.drilldowns.urlDrilldownValidation.urlFormatErrorMessage": "格式无效:{message}", "xpack.uiActionsEnhanced.drilldowns.urlDrilldownValidation.urlFormatGeneralErrorMessage": "格式无效。例如:{exampleUrl}", "xpack.upgradeAssistant.appTitle": "{version} 升级助手", - "xpack.upgradeAssistant.checkupTab.backUpCallout.calloutBody.calloutDetail": "使用 {snapshotRestoreDocsButton} 备份您的数据。", - "xpack.upgradeAssistant.checkupTab.backUpCallout.calloutBody.snapshotRestoreDocsButtonLabel": "快照和还原 API", - "xpack.upgradeAssistant.checkupTab.backUpCallout.calloutTitle": "立即备份索引", "xpack.upgradeAssistant.checkupTab.changeFiltersShowMoreLabel": "更改筛选以显示更多内容。", - "xpack.upgradeAssistant.checkupTab.clusterTabLabel": "集群", "xpack.upgradeAssistant.checkupTab.controls.collapseAllButtonLabel": "折叠全部", "xpack.upgradeAssistant.checkupTab.controls.expandAllButtonLabel": "展开全部", - "xpack.upgradeAssistant.checkupTab.controls.filterBar.allButtonLabel": "全部", "xpack.upgradeAssistant.checkupTab.controls.filterBar.criticalButtonLabel": "紧急", "xpack.upgradeAssistant.checkupTab.controls.filterErrorMessageLabel": "筛选无效:{searchTermError}", "xpack.upgradeAssistant.checkupTab.controls.groupByBar.byIndexLabel": "按索引", @@ -23002,13 +22967,10 @@ "xpack.upgradeAssistant.checkupTab.deprecations.indexTable.indexColumnLabel": "索引", "xpack.upgradeAssistant.checkupTab.deprecations.warningActionTooltip": "建议在升级之前先解决此问题,但这不是必需的。", "xpack.upgradeAssistant.checkupTab.deprecations.warningLabel": "警告", - "xpack.upgradeAssistant.checkupTab.indexLabel": "索引", "xpack.upgradeAssistant.checkupTab.indicesBadgeLabel": "{numIndices, plural, other { 个索引}}", - "xpack.upgradeAssistant.checkupTab.indicesTabLabel": "索引", "xpack.upgradeAssistant.checkupTab.noDeprecationsLabel": "无弃用内容", "xpack.upgradeAssistant.checkupTab.noIssues.nextStepsDetail": "选中 {overviewTabButton} 以执行后续步骤。", "xpack.upgradeAssistant.checkupTab.noIssues.nextStepsDetail.overviewTabButtonLabel": "“概述”选项卡", - "xpack.upgradeAssistant.checkupTab.noIssues.noIssuesLabel": "您没有 {strongCheckupLabel} 问题。", "xpack.upgradeAssistant.checkupTab.noIssues.noIssuesTitle": "全部清除!", "xpack.upgradeAssistant.checkupTab.numDeprecationsShownLabel": "显示 {numShown} 个,共 {total} 个", "xpack.upgradeAssistant.checkupTab.reindexing.flyout.checklistStep.cancelButtonLabel": "取消", @@ -23051,45 +23013,12 @@ "xpack.upgradeAssistant.checkupTab.reindexing.reindexButton.loadingLabel": "正在加载……", "xpack.upgradeAssistant.checkupTab.reindexing.reindexButton.pausedLabel": "已暂停", "xpack.upgradeAssistant.checkupTab.reindexing.reindexButton.reindexLabel": "重新索引", - "xpack.upgradeAssistant.checkupTab.tabDetail": "您需要注意这些 {strongCheckupLabel} 问题。在升级到 Elasticsearch {nextEsVersion} 之前先解决它们。", - "xpack.upgradeAssistant.forbiddenErrorCallout.calloutTitle": "您没有足够的权限来查看此页。", - "xpack.upgradeAssistant.genericErrorCallout.calloutTitle": "检索检查结果时出错。", - "xpack.upgradeAssistant.overviewTab.overviewTabTitle": "概览", - "xpack.upgradeAssistant.overviewTab.steps.clusterStep.issuesRemainingStepTitle": "检查集群是否存在问题", - "xpack.upgradeAssistant.overviewTab.steps.clusterStep.noIssuesRemainingStepTitle": "您的集群设置已就绪", - "xpack.upgradeAssistant.overviewTab.steps.clusterStep.noRemainingIssuesLabel": "没有其余已弃用设置。", - "xpack.upgradeAssistant.overviewTab.steps.clusterStep.remainingIssuesDetail": "必须解决 {numIssues} 个问题。", - "xpack.upgradeAssistant.overviewTab.steps.clusterStep.todo.clusterTabButtonLabel": "“集群”选项卡", - "xpack.upgradeAssistant.overviewTab.steps.clusterStep.todo.todoDetail": "转到 {clusterTabButton} 并更新已弃用的设置。", - "xpack.upgradeAssistant.overviewTab.steps.deprecationLogsStep.deprecationLogs.deprecationLogsDocButtonLabel": "弃用日志", - "xpack.upgradeAssistant.overviewTab.steps.deprecationLogsStep.deprecationLogs.logsDetail": "请参阅{deprecationLogsDocButton},了解您的应用程序是否使用未在 {nextEsVersion} 中提供的功能。您可能需要启用弃用日志。", - "xpack.upgradeAssistant.overviewTab.steps.deprecationLogsStep.enableDeprecationLoggingLabel": "是否启用弃用日志?", - "xpack.upgradeAssistant.overviewTab.steps.deprecationLogsStep.enableDeprecationLoggingToggleSwitch.disabledLabel": "关闭", "xpack.upgradeAssistant.overviewTab.steps.deprecationLogsStep.enableDeprecationLoggingToggleSwitch.enabledLabel": "开启", "xpack.upgradeAssistant.overviewTab.steps.deprecationLogsStep.enableDeprecationLoggingToggleSwitch.errorLabel": "无法加载日志状态", - "xpack.upgradeAssistant.overviewTab.steps.deprecationLogsStep.stepTitle": "查看 Elasticsearch 弃用日志", - "xpack.upgradeAssistant.overviewTab.steps.indicesStep.issuesRemainingStepTitle": "检查索引是否存在问题", - "xpack.upgradeAssistant.overviewTab.steps.indicesStep.noIssuesRemainingStepTitle": "您的索引设置已就绪", - "xpack.upgradeAssistant.overviewTab.steps.indicesStep.noRemainingIssuesLabel": "没有其余已弃用设置。", - "xpack.upgradeAssistant.overviewTab.steps.indicesStep.remainingIssuesDetail": "必须解决 {numIssues} 个问题。", - "xpack.upgradeAssistant.overviewTab.steps.indicesStep.todo.indicesTabButtonLabel": "“索引”选项卡", - "xpack.upgradeAssistant.overviewTab.steps.indicesStep.todo.todoDetail": "转到 {indicesTabButton} 并更新已弃用的设置。", - "xpack.upgradeAssistant.overviewTab.steps.startUpgradeStep.stepTitle": "开始升级", - "xpack.upgradeAssistant.overviewTab.steps.startUpgradeStepCloud.stepDetail.goToCloudDashboardDetail": "转到 Elastic Cloud 仪表板上的“部署”部分开始升级。", - "xpack.upgradeAssistant.overviewTab.steps.startUpgradeStepOnPrem.stepDetail.followInstructionsDetail": "按照 {instructionButton} 开始升级。", - "xpack.upgradeAssistant.overviewTab.steps.startUpgradeStepOnPrem.stepDetail.instructionButtonLabel": "以下说明", - "xpack.upgradeAssistant.overviewTab.steps.waitForReleaseStep.stepDetail": "版本发布后,请升级到最新的 {currentEsMajorVersion} 版本,然后返回此处,继续升级到 {nextEsMajorVersion}。", - "xpack.upgradeAssistant.overviewTab.steps.waitForReleaseStep.stepTitle": "等待 Elasticsearch {nextEsVersion} 发布版", - "xpack.upgradeAssistant.overviewTab.tabDetail": "此助理将帮助您为 Elasticsearch {nextEsVersion} 准备集群和索引。有关需要注意的其他问题,请参阅 Elasticsearch 日志。", "xpack.upgradeAssistant.reindex.reindexPrivilegesErrorBatch": "您没有足够的权限重新索引“{indexName}”。", - "xpack.upgradeAssistant.tabs.checkupTab.clusterLabel": "集群", "xpack.upgradeAssistant.tabs.incompleteCallout.calloutBody.breackingChangesDocButtonLabel": "弃用内容和重大更改", "xpack.upgradeAssistant.tabs.incompleteCallout.calloutBody.calloutDetail": "Elasticsearch {nextEsVersion} 中的 {breakingChangesDocButton} 完整列表将在最终的 {currentEsVersion} 次要版本中提供。完成列表后,此警告将消失。", "xpack.upgradeAssistant.tabs.incompleteCallout.calloutTitle": "问题列表可能不完整", - "xpack.upgradeAssistant.tabs.upgradingInterstitial.upgradeCompleteDescription": "所有 Elasticsearch 节点已升级。可以现在升级 Kibana。", - "xpack.upgradeAssistant.tabs.upgradingInterstitial.upgradeCompleteTitle": "您的集群已升级", - "xpack.upgradeAssistant.tabs.upgradingInterstitial.upgradingDescription": "一个或多个 Elasticsearch 节点的 Elasticsearch 版本比 Kibana 版本新。所有节点升级后,请升级 Kibana。", - "xpack.upgradeAssistant.tabs.upgradingInterstitial.upgradingTitle": "您的集群正在升级", "xpack.uptime.addDataButtonLabel": "添加数据", "xpack.uptime.alerts.anomaly.criteriaExpression.ariaLabel": "显示选定监测的条件的表达式。", "xpack.uptime.alerts.anomaly.criteriaExpression.description": "当监测", diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_notify_when.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_notify_when.tsx index 95fbe9c6ae6149..b774fd702fadc4 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_notify_when.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_notify_when.tsx @@ -49,7 +49,7 @@ const NOTIFY_WHEN_OPTIONS: Array> = [

@@ -62,7 +62,7 @@ const NOTIFY_WHEN_OPTIONS: Array> = [ inputDisplay: i18n.translate( 'xpack.triggersActionsUI.sections.alertForm.alertNotifyWhen.onActiveAlert.display', { - defaultMessage: 'Every time rule is active', + defaultMessage: 'Every time alert is active', } ), 'data-test-subj': 'onActiveAlert', @@ -70,14 +70,14 @@ const NOTIFY_WHEN_OPTIONS: Array> = [

diff --git a/x-pack/plugins/upgrade_assistant/kibana.json b/x-pack/plugins/upgrade_assistant/kibana.json index eda624dc422468..d9f4917fa0a6cd 100644 --- a/x-pack/plugins/upgrade_assistant/kibana.json +++ b/x-pack/plugins/upgrade_assistant/kibana.json @@ -6,5 +6,5 @@ "configPath": ["xpack", "upgrade_assistant"], "requiredPlugins": ["management", "licensing", "features"], "optionalPlugins": ["cloud", "usageCollection"], - "requiredBundles": ["esUiShared"] + "requiredBundles": ["esUiShared", "kibanaReact"] } diff --git a/x-pack/plugins/upgrade_assistant/public/application/app.tsx b/x-pack/plugins/upgrade_assistant/public/application/app.tsx index 1276198a528df0..7be723e335e8bf 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/app.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/app.tsx @@ -6,19 +6,48 @@ */ import React from 'react'; -import { I18nStart } from 'src/core/public'; -import { AppContextProvider, ContextValue } from './app_context'; -import { PageContent } from './components/page_content'; +import { Router, Switch, Route, Redirect } from 'react-router-dom'; +import { I18nStart, ScopedHistory } from 'src/core/public'; +import { AppContextProvider, ContextValue, useAppContext } from './app_context'; +import { ComingSoonPrompt } from './components/coming_soon_prompt'; +import { EsDeprecationsContent } from './components/es_deprecations'; +import { DeprecationsOverview } from './components/overview'; export interface AppDependencies extends ContextValue { i18n: I18nStart; + history: ScopedHistory; } -export const RootComponent = ({ i18n, ...contextValue }: AppDependencies) => { +const App: React.FunctionComponent = () => { + const { isReadOnlyMode } = useAppContext(); + + // Read-only mode will be enabled up until the last minor before the next major release + if (isReadOnlyMode) { + return ; + } + + return ( + + + + + + ); +}; + +export const AppWithRouter = ({ history }: { history: ScopedHistory }) => { + return ( + + + + ); +}; + +export const RootComponent = ({ i18n, history, ...contextValue }: AppDependencies) => { return ( - + ); diff --git a/x-pack/plugins/upgrade_assistant/public/application/app_context.tsx b/x-pack/plugins/upgrade_assistant/public/application/app_context.tsx index 2b49d1a5bca1f5..18df47d4cbd4ad 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/app_context.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/app_context.tsx @@ -5,9 +5,10 @@ * 2.0. */ -import { DocLinksStart, HttpSetup, NotificationsStart } from 'src/core/public'; +import { CoreStart, DocLinksStart, HttpSetup, NotificationsStart } from 'src/core/public'; import React, { createContext, useContext } from 'react'; import { ApiService } from './lib/api'; +import { BreadcrumbService } from './lib/breadcrumbs'; export interface KibanaVersionContext { currentMajor: number; @@ -23,6 +24,8 @@ export interface ContextValue { notifications: NotificationsStart; isReadOnlyMode: boolean; api: ApiService; + breadcrumbs: BreadcrumbService; + getUrlForApp: CoreStart['application']['getUrlForApp']; } export const AppContext = createContext({} as any); diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/error_banner.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/error_banner.tsx deleted file mode 100644 index 72e6c5c0702aff..00000000000000 --- a/x-pack/plugins/upgrade_assistant/public/application/components/error_banner.tsx +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; - -import { EuiCallOut } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; - -import { UpgradeAssistantTabProps } from './types'; - -export const LoadingErrorBanner: React.FunctionComponent< - Pick -> = ({ loadingError }) => { - if (loadingError?.statusCode === 403) { - return ( - - } - color="danger" - iconType="cross" - data-test-subj="permissionsError" - /> - ); - } - - return ( - - } - color="danger" - iconType="cross" - data-test-subj="upgradeStatusError" - > - {loadingError ? loadingError.message : null} - - ); -}; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/__snapshots__/filter_bar.test.tsx.snap b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/__snapshots__/filter_bar.test.tsx.snap index da9153f4a6c8d6..b88886b3641659 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/__snapshots__/filter_bar.test.tsx.snap +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/__snapshots__/filter_bar.test.tsx.snap @@ -6,20 +6,22 @@ exports[`FilterBar renders 1`] = ` > - all + critical - critical + warning diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_tab.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_tab.tsx deleted file mode 100644 index a5ae341f1e4248..00000000000000 --- a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_tab.tsx +++ /dev/null @@ -1,222 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { find } from 'lodash'; -import React, { FunctionComponent, useState } from 'react'; - -import { - EuiCallOut, - EuiEmptyPrompt, - EuiLink, - EuiPageContent, - EuiPageContentBody, - EuiSpacer, - EuiText, -} from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; - -import { LoadingErrorBanner } from '../error_banner'; -import { useAppContext } from '../../app_context'; -import { GroupByOption, LevelFilterOption, UpgradeAssistantTabProps } from '../types'; -import { CheckupControls } from './controls'; -import { GroupedDeprecations } from './deprecations/grouped'; - -export interface CheckupTabProps extends UpgradeAssistantTabProps { - checkupLabel: string; - showBackupWarning?: boolean; -} - -/** - * Displays a list of deprecations that filterable and groupable. Can be used for cluster, - * nodes, or indices checkups. - */ -export const DeprecationTab: FunctionComponent = ({ - alertBanner, - checkupLabel, - deprecations, - loadingError, - isLoading, - refreshCheckupData, - setSelectedTabIndex, - showBackupWarning = false, -}) => { - const [currentFilter, setCurrentFilter] = useState(LevelFilterOption.all); - const [search, setSearch] = useState(''); - const [currentGroupBy, setCurrentGroupBy] = useState(GroupByOption.message); - - const { docLinks, kibanaVersionInfo } = useAppContext(); - - const { DOC_LINK_VERSION, ELASTIC_WEBSITE_URL } = docLinks; - const esDocBasePath = `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/reference/${DOC_LINK_VERSION}`; - - const { nextMajor } = kibanaVersionInfo; - - const changeFilter = (filter: LevelFilterOption) => { - setCurrentFilter(filter); - }; - - const changeSearch = (newSearch: string) => { - setSearch(newSearch); - }; - - const changeGroupBy = (groupBy: GroupByOption) => { - setCurrentGroupBy(groupBy); - }; - - const availableGroupByOptions = () => { - if (!deprecations) { - return []; - } - - return Object.keys(GroupByOption).filter((opt) => find(deprecations, opt)) as GroupByOption[]; - }; - - const renderCheckupData = () => { - return ( - - ); - }; - - return ( - <> - - -

- {checkupLabel}, - nextEsVersion: `${nextMajor}.x`, - }} - /> -

-
- - - - {alertBanner && ( - <> - {alertBanner} - - - )} - - {showBackupWarning && ( - <> - - } - color="warning" - iconType="help" - > -

- - - - ), - }} - /> -

-
- - - )} - - - - {loadingError ? ( - - ) : deprecations && deprecations.length > 0 ? ( -
- - - {renderCheckupData()} -
- ) : ( - - - - } - body={ - <> -

- {checkupLabel}, - }} - /> -

-

- setSelectedTabIndex(0)}> - - - ), - }} - /> -

- - } - /> - )} -
-
- - ); -}; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_tab_content.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_tab_content.tsx new file mode 100644 index 00000000000000..9e8678fea0eb90 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_tab_content.tsx @@ -0,0 +1,138 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { find } from 'lodash'; +import React, { FunctionComponent, useState } from 'react'; + +import { EuiEmptyPrompt, EuiLink, EuiSpacer } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; + +import { SectionLoading } from '../../../shared_imports'; +import { GroupByOption, LevelFilterOption, UpgradeAssistantTabProps } from '../types'; +import { CheckupControls } from './controls'; +import { GroupedDeprecations } from './deprecations/grouped'; +import { EsDeprecationErrors } from './es_deprecation_errors'; + +const i18nTexts = { + isLoading: i18n.translate('xpack.upgradeAssistant.esDeprecations.loadingText', { + defaultMessage: 'Loading deprecations…', + }), +}; + +export interface CheckupTabProps extends UpgradeAssistantTabProps { + checkupLabel: string; +} + +/** + * Displays a list of deprecations that are filterable and groupable. Can be used for cluster, + * nodes, or indices deprecations. + */ +export const DeprecationTabContent: FunctionComponent = ({ + checkupLabel, + deprecations, + error, + isLoading, + refreshCheckupData, + navigateToOverviewPage, +}) => { + const [currentFilter, setCurrentFilter] = useState(LevelFilterOption.all); + const [search, setSearch] = useState(''); + const [currentGroupBy, setCurrentGroupBy] = useState(GroupByOption.message); + + const availableGroupByOptions = () => { + if (!deprecations) { + return []; + } + + return Object.keys(GroupByOption).filter((opt) => find(deprecations, opt)) as GroupByOption[]; + }; + + if (deprecations && deprecations.length === 0) { + return ( + + + + } + body={ + <> +

+ +

+

+ + + + ), + }} + /> +

+ + } + /> + ); + } + + let content: React.ReactNode; + + if (isLoading) { + content = {i18nTexts.isLoading}; + } else if (deprecations?.length) { + content = ( +
+ + + + + +
+ ); + } else if (error) { + content = ; + } + + return ( +
+ + + {content} +
+ ); +}; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/es_deprecation_errors.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/es_deprecation_errors.tsx new file mode 100644 index 00000000000000..239433808c5aff --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/es_deprecation_errors.tsx @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { EuiCallOut } from '@elastic/eui'; + +import { ResponseError } from '../../lib/api'; +import { getEsDeprecationError } from '../../lib/es_deprecation_errors'; +interface Props { + error: ResponseError; +} + +export const EsDeprecationErrors: React.FunctionComponent = ({ error }) => { + const { code: errorType, message } = getEsDeprecationError(error); + + switch (errorType) { + case 'unauthorized_error': + return ( + + ); + case 'partially_upgraded_error': + return ( + + ); + case 'upgraded_error': + return ; + case 'request_error': + default: + return ( + + {error.message} + + ); + } +}; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/es_deprecations.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/es_deprecations.tsx new file mode 100644 index 00000000000000..0da4a4877a7ec5 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/es_deprecations.tsx @@ -0,0 +1,212 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useMemo, useEffect, useState } from 'react'; +import { withRouter, RouteComponentProps } from 'react-router-dom'; + +import { + EuiButton, + EuiButtonEmpty, + EuiPageBody, + EuiPageHeader, + EuiTabbedContent, + EuiTabbedContentTab, + EuiPageContent, + EuiPageContentBody, + EuiToolTip, + EuiNotificationBadge, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import { useAppContext } from '../../app_context'; +import { UpgradeAssistantTabProps, EsTabs, TelemetryState } from '../types'; +import { DeprecationTabContent } from './deprecation_tab_content'; + +const i18nTexts = { + pageTitle: i18n.translate('xpack.upgradeAssistant.esDeprecations.pageTitle', { + defaultMessage: 'Elasticsearch', + }), + pageDescription: i18n.translate('xpack.upgradeAssistant.esDeprecations.pageDescription', { + defaultMessage: + 'Review the deprecated cluster and index settings. You must resolve any critical issues before upgrading.', + }), + docLinkText: i18n.translate('xpack.upgradeAssistant.esDeprecations.docLinkText', { + defaultMessage: 'Documentation', + }), + backupDataButton: { + label: i18n.translate('xpack.upgradeAssistant.esDeprecations.backupDataButtonLabel', { + defaultMessage: 'Back up your data', + }), + tooltipText: i18n.translate('xpack.upgradeAssistant.esDeprecations.backupDataTooltipText', { + defaultMessage: 'Take a snapshot before you make any changes.', + }), + }, + clusterTab: { + tabName: i18n.translate('xpack.upgradeAssistant.esDeprecations.clusterTabLabel', { + defaultMessage: 'Cluster', + }), + deprecationType: i18n.translate('xpack.upgradeAssistant.esDeprecations.clusterLabel', { + defaultMessage: 'cluster', + }), + }, + indicesTab: { + tabName: i18n.translate('xpack.upgradeAssistant.esDeprecations.indicesTabLabel', { + defaultMessage: 'Indices', + }), + deprecationType: i18n.translate('xpack.upgradeAssistant.esDeprecations.indexLabel', { + defaultMessage: 'index', + }), + }, +}; + +interface MatchParams { + tabName: EsTabs; +} + +export const EsDeprecationsContent = withRouter( + ({ + match: { + params: { tabName }, + }, + history, + }: RouteComponentProps) => { + const [telemetryState, setTelemetryState] = useState(TelemetryState.Complete); + + const { api, breadcrumbs, getUrlForApp, docLinks } = useAppContext(); + + const { data: checkupData, isLoading, error, resendRequest } = api.useLoadUpgradeStatus(); + + const onTabClick = (selectedTab: EuiTabbedContentTab) => { + history.push(`/es_deprecations/${selectedTab.id}`); + }; + + const tabs = useMemo(() => { + const commonTabProps: UpgradeAssistantTabProps = { + error, + isLoading, + refreshCheckupData: resendRequest, + navigateToOverviewPage: () => history.push('/overview'), + }; + + return [ + { + id: 'cluster', + 'data-test-subj': 'upgradeAssistantClusterTab', + name: ( + + {i18nTexts.clusterTab.tabName} + {checkupData && checkupData.cluster.length > 0 && ( + <> + {' '} + {checkupData.cluster.length} + + )} + + ), + content: ( + + ), + }, + { + id: 'indices', + 'data-test-subj': 'upgradeAssistantIndicesTab', + name: ( + + {i18nTexts.indicesTab.tabName} + {checkupData && checkupData.indices.length > 0 && ( + <> + {' '} + {checkupData.indices.length} + + )} + + ), + content: ( + + ), + }, + ]; + }, [checkupData, error, history, isLoading, resendRequest]); + + useEffect(() => { + breadcrumbs.setBreadcrumbs('esDeprecations'); + }, [breadcrumbs]); + + useEffect(() => { + if (isLoading === false) { + setTelemetryState(TelemetryState.Running); + + async function sendTelemetryData() { + await api.sendTelemetryData({ + [tabName]: true, + }); + setTelemetryState(TelemetryState.Complete); + } + + sendTelemetryData(); + } + }, [api, tabName, isLoading]); + + return ( + + + + {i18nTexts.docLinkText} + , + ]} + > + + + {i18nTexts.backupDataButton.label} + + + + + + tab.id === tabName)} + /> + + + + ); + } +); diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/filter_bar.test.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/filter_bar.test.tsx index feac88cf4a5251..4888efda97bd05 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/filter_bar.test.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/filter_bar.test.tsx @@ -17,7 +17,7 @@ const defaultProps = { { level: LevelFilterOption.critical }, { level: LevelFilterOption.critical }, ] as DeprecationInfo[], - currentFilter: LevelFilterOption.critical, + currentFilter: LevelFilterOption.all, onFilterChange: jest.fn(), }; @@ -28,7 +28,7 @@ describe('FilterBar', () => { test('clicking button calls onFilterChange', () => { const wrapper = mount(); - wrapper.find('button.euiFilterButton-hasActiveFilters').simulate('click'); + wrapper.find('button[data-test-subj="criticalLevelFilter"]').simulate('click'); expect(defaultProps.onFilterChange).toHaveBeenCalledTimes(1); expect(defaultProps.onFilterChange.mock.calls[0][0]).toEqual(LevelFilterOption.critical); }); diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/filter_bar.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/filter_bar.tsx index 7ef3ae2fc93325..848ac3b14a817f 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/filter_bar.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/filter_bar.tsx @@ -15,17 +15,18 @@ import { DeprecationInfo } from '../../../../common/types'; import { LevelFilterOption } from '../types'; const LocalizedOptions: { [option: string]: string } = { - all: i18n.translate('xpack.upgradeAssistant.checkupTab.controls.filterBar.allButtonLabel', { - defaultMessage: 'all', - }), + warning: i18n.translate( + 'xpack.upgradeAssistant.checkupTab.controls.filterBar.warningButtonLabel', + { + defaultMessage: 'warning', + } + ), critical: i18n.translate( 'xpack.upgradeAssistant.checkupTab.controls.filterBar.criticalButtonLabel', { defaultMessage: 'critical' } ), }; -const allFilterOptions = Object.keys(LevelFilterOption) as LevelFilterOption[]; - interface FilterBarProps { allDeprecations?: DeprecationInfo[]; currentFilter: LevelFilterOption; @@ -43,23 +44,40 @@ export const FilterBar: React.FunctionComponent = ({ return counts; }, {} as { [level: string]: number }); - const allCount = allDeprecations.length; - return ( - {allFilterOptions.map((option) => ( - - {LocalizedOptions[option]} - - ))} + { + onFilterChange( + currentFilter !== LevelFilterOption.critical + ? LevelFilterOption.critical + : LevelFilterOption.all + ); + }} + hasActiveFilters={currentFilter === LevelFilterOption.critical} + numFilters={levelCounts[LevelFilterOption.critical] || undefined} + data-test-subj="criticalLevelFilter" + > + {LocalizedOptions[LevelFilterOption.critical]} + + { + onFilterChange( + currentFilter !== LevelFilterOption.warning + ? LevelFilterOption.warning + : LevelFilterOption.all + ); + }} + hasActiveFilters={currentFilter === LevelFilterOption.warning} + numFilters={levelCounts[LevelFilterOption.warning] || undefined} + data-test-subj="warningLevelFilter" + > + {LocalizedOptions[LevelFilterOption.warning]} + ); diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/index.ts b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/index.ts index 8b7435b94b2c1a..0e69259adc6096 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/index.ts +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { DeprecationTab } from './deprecation_tab'; +export { EsDeprecationsContent } from './es_deprecations'; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/overview/deprecation_logging_toggle.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/overview/deprecation_logging_toggle.tsx index 5ed46c25ecf17c..6be7793f0bd4a4 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/overview/deprecation_logging_toggle.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/overview/deprecation_logging_toggle.tsx @@ -13,8 +13,35 @@ import { i18n } from '@kbn/i18n'; import { useAppContext } from '../../app_context'; import { ResponseError } from '../../lib/api'; +const i18nTexts = { + toggleErrorLabel: i18n.translate( + 'xpack.upgradeAssistant.overviewTab.steps.deprecationLogsStep.enableDeprecationLoggingToggleSwitch.errorLabel', + { + defaultMessage: 'Could not load logging state', + } + ), + toggleLabel: i18n.translate( + 'xpack.upgradeAssistant.overviewTab.steps.deprecationLogsStep.enableDeprecationLoggingToggleSwitch.enabledLabel', + { + defaultMessage: 'Enable deprecation logging', + } + ), + enabledMessage: i18n.translate( + 'xpack.upgradeAssistant.overviewTab.steps.deprecationLogsStep.enableDeprecationLoggingToggleSwitch.enabledToastMessage', + { + defaultMessage: 'Log deprecated actions.', + } + ), + disabledMessage: i18n.translate( + 'xpack.upgradeAssistant.overviewTab.steps.deprecationLogsStep.enableDeprecationLoggingToggleSwitch.disabledToastMessage', + { + defaultMessage: 'Do not log deprecated actions.', + } + ), +}; + export const DeprecationLoggingToggle: React.FunctionComponent = () => { - const { api } = useAppContext(); + const { api, notifications } = useAppContext(); const [isEnabled, setIsEnabled] = useState(true); const [isLoading, setIsLoading] = useState(false); @@ -44,27 +71,10 @@ export const DeprecationLoggingToggle: React.FunctionComponent = () => { const renderLoggingState = () => { if (error) { - return i18n.translate( - 'xpack.upgradeAssistant.overviewTab.steps.deprecationLogsStep.enableDeprecationLoggingToggleSwitch.errorLabel', - { - defaultMessage: 'Could not load logging state', - } - ); - } else if (isEnabled) { - return i18n.translate( - 'xpack.upgradeAssistant.overviewTab.steps.deprecationLogsStep.enableDeprecationLoggingToggleSwitch.enabledLabel', - { - defaultMessage: 'On', - } - ); - } else { - return i18n.translate( - 'xpack.upgradeAssistant.overviewTab.steps.deprecationLogsStep.enableDeprecationLoggingToggleSwitch.disabledLabel', - { - defaultMessage: 'Off', - } - ); + return i18nTexts.toggleErrorLabel; } + + return i18nTexts.toggleLabel; }; const toggleLogging = async () => { @@ -82,6 +92,9 @@ export const DeprecationLoggingToggle: React.FunctionComponent = () => { setError(updateError); } else if (data) { setIsEnabled(data.isEnabled); + notifications.toasts.addSuccess( + data.isEnabled ? i18nTexts.enabledMessage : i18nTexts.disabledMessage + ); } }; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/overview/es_stats.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/overview/es_stats.tsx new file mode 100644 index 00000000000000..51a66bdd353956 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/application/components/overview/es_stats.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { FunctionComponent } from 'react'; + +import { + EuiLink, + EuiPanel, + EuiStat, + EuiTitle, + EuiSpacer, + EuiFlexGroup, + EuiFlexItem, + EuiIconTip, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import { RouteComponentProps } from 'react-router-dom'; +import { reactRouterNavigate } from '../../../../../../../src/plugins/kibana_react/public'; +import { useAppContext } from '../../app_context'; +import { EsStatsErrors } from './es_stats_error'; + +const i18nTexts = { + statsTitle: i18n.translate('xpack.upgradeAssistant.esDeprecationStats.statsTitle', { + defaultMessage: 'Elasticsearch', + }), + totalDeprecationsTitle: i18n.translate( + 'xpack.upgradeAssistant.esDeprecationStats.totalDeprecationsTitle', + { + defaultMessage: 'Deprecations', + } + ), + criticalDeprecationsTitle: i18n.translate( + 'xpack.upgradeAssistant.esDeprecationStats.criticalDeprecationsTitle', + { + defaultMessage: 'Critical', + } + ), + viewDeprecationsLink: i18n.translate( + 'xpack.upgradeAssistant.esDeprecationStats.viewDeprecationsLinkText', + { + defaultMessage: 'View deprecations', + } + ), + getTotalDeprecationsTooltip: (clusterCount: number, indexCount: number) => + i18n.translate('xpack.upgradeAssistant.esDeprecationStats.totalDeprecationsTooltip', { + defaultMessage: + 'This cluster is using {clusterCount} deprecated cluster settings and {indexCount} deprecated index settings', + values: { + clusterCount, + indexCount, + }, + }), +}; + +interface Props { + history: RouteComponentProps['history']; +} + +export const ESDeprecationStats: FunctionComponent = ({ history }) => { + const { api } = useAppContext(); + + const { data: esDeprecations, isLoading, error } = api.useLoadUpgradeStatus(); + + const allDeprecations = esDeprecations?.cluster?.concat(esDeprecations?.indices) ?? []; + const criticalDeprecations = allDeprecations.filter( + (deprecation) => deprecation.level === 'critical' + ); + + return ( + + + + +

{i18nTexts.statsTitle}

+
+
+ + + {i18nTexts.viewDeprecationsLink} + + +
+ + + + + + + {i18nTexts.totalDeprecationsTitle}{' '} + + + } + isLoading={isLoading} + /> + + + + + {error && } + + + +
+ ); +}; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/overview/es_stats_error.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/overview/es_stats_error.tsx new file mode 100644 index 00000000000000..dda7d16599e0c2 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/application/components/overview/es_stats_error.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { EuiIconTip, EuiSpacer } from '@elastic/eui'; +import { ResponseError } from '../../lib/api'; +import { getEsDeprecationError } from '../../lib/es_deprecation_errors'; + +interface Props { + error: ResponseError; +} + +export const EsStatsErrors: React.FunctionComponent = ({ error }) => { + let iconContent: React.ReactNode; + + const { code: errorType, message } = getEsDeprecationError(error); + + switch (errorType) { + case 'unauthorized_error': + iconContent = ( + + ); + break; + case 'partially_upgraded_error': + iconContent = ( + + ); + break; + case 'upgraded_error': + iconContent = ( + + ); + break; + case 'request_error': + default: + iconContent = ( + + ); + } + + return ( + <> + + {iconContent} + + ); +}; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/overview/index.ts b/x-pack/plugins/upgrade_assistant/public/application/components/overview/index.ts index c43c1415f6f7c2..a64d7b0d449158 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/overview/index.ts +++ b/x-pack/plugins/upgrade_assistant/public/application/components/overview/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { OverviewTab } from './overview'; +export { DeprecationsOverview } from './overview'; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/overview/overview.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/overview/overview.tsx index 01677e7394a872..0784fbc102805c 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/overview/overview.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/overview/overview.tsx @@ -5,70 +5,133 @@ * 2.0. */ -import React, { FunctionComponent } from 'react'; +import React, { FunctionComponent, useEffect } from 'react'; import { - EuiFlexGroup, - EuiFlexItem, - EuiLoadingSpinner, EuiPageContent, EuiPageContentBody, - EuiSpacer, EuiText, + EuiPageHeader, + EuiPageBody, + EuiButtonEmpty, + EuiFlexItem, + EuiFlexGroup, + EuiSpacer, + EuiLink, + EuiFormRow, } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; +import { RouteComponentProps } from 'react-router-dom'; import { useAppContext } from '../../app_context'; -import { LoadingErrorBanner } from '../error_banner'; -import { UpgradeAssistantTabProps } from '../types'; -import { Steps } from './steps'; +import { LatestMinorBanner } from '../latest_minor_banner'; +import { ESDeprecationStats } from './es_stats'; +import { DeprecationLoggingToggle } from './deprecation_logging_toggle'; -export const OverviewTab: FunctionComponent = (props) => { - const { kibanaVersionInfo } = useAppContext(); +const i18nTexts = { + pageTitle: i18n.translate('xpack.upgradeAssistant.pageTitle', { + defaultMessage: 'Upgrade Assistant', + }), + getPageDescription: (nextMajor: string) => + i18n.translate('xpack.upgradeAssistant.pageDescription', { + defaultMessage: + 'Prepare to upgrade by identifying deprecated settings and updating your configuration. Enable deprecation logging to see if your are using deprecated features that will not be available after you upgrade to Elastic {nextMajor}.', + values: { + nextMajor, + }, + }), + getDeprecationLoggingLabel: (href: string) => ( + + {i18n.translate('xpack.upgradeAssistant.deprecationLoggingDescription.learnMoreLink', { + defaultMessage: 'Learn more.', + })} + + ), + }} + /> + ), + docLink: i18n.translate('xpack.upgradeAssistant.documentationLinkText', { + defaultMessage: 'Documentation', + }), +}; + +interface Props { + history: RouteComponentProps['history']; +} + +export const DeprecationsOverview: FunctionComponent = ({ history }) => { + const { kibanaVersionInfo, breadcrumbs, docLinks, api } = useAppContext(); const { nextMajor } = kibanaVersionInfo; + useEffect(() => { + async function sendTelemetryData() { + await api.sendTelemetryData({ + overview: true, + }); + } + + sendTelemetryData(); + }, [api]); + + useEffect(() => { + breadcrumbs.setBreadcrumbs('overview'); + }, [breadcrumbs]); + return ( - <> - - - -

- -

-
- - - - {props.alertBanner && ( - <> - {props.alertBanner} - - - - )} - - + + + + {i18nTexts.docLink} + , + ]} + /> + - {props.isLoading && ( - - - - - - )} + <> + +

{i18nTexts.getPageDescription(`${nextMajor}.x`)}

+
+ + - {props.checkupData && } + {/* Remove this in last minor of the current major (e.g., 7.15) */} + - {props.loadingError && } + + + + + + + + + + + + + +
- +
); }; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/overview/steps.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/overview/steps.tsx deleted file mode 100644 index 095960ae93562d..00000000000000 --- a/x-pack/plugins/upgrade_assistant/public/application/components/overview/steps.tsx +++ /dev/null @@ -1,293 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { Fragment, FunctionComponent } from 'react'; - -import { - EuiFormRow, - EuiLink, - EuiNotificationBadge, - EuiSpacer, - // @ts-ignore - EuiStat, - EuiSteps, - EuiText, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; - -import { useAppContext } from '../../app_context'; -import { UpgradeAssistantTabProps } from '../types'; -import { DeprecationLoggingToggle } from './deprecation_logging_toggle'; - -// Leaving these here even if unused so they are picked up for i18n static analysis -// Keep this until last minor release (when next major is also released). -const WAIT_FOR_RELEASE_STEP = (majorVersion: number, nextMajorVersion: number) => ({ - title: i18n.translate('xpack.upgradeAssistant.overviewTab.steps.waitForReleaseStep.stepTitle', { - defaultMessage: 'Wait for the Elasticsearch {nextEsVersion} release', - values: { - nextEsVersion: `${nextMajorVersion}.0`, - }, - }), - 'data-test-subj': 'waitForReleaseStep', - children: ( - <> - -

- -

-
- - ), -}); - -// Swap in this step for the one above it on the last minor release. -// @ts-ignore -const START_UPGRADE_STEP = (isCloudEnabled: boolean, esDocBasePath: string) => ({ - title: i18n.translate('xpack.upgradeAssistant.overviewTab.steps.startUpgradeStep.stepTitle', { - defaultMessage: 'Start your upgrade', - }), - 'data-test-subj': 'startUpgradeStep', - children: ( - - -

- {isCloudEnabled ? ( - - ) : ( - - - - ), - }} - /> - )} -

-
-
- ), -}); - -export const Steps: FunctionComponent = ({ - checkupData, - setSelectedTabIndex, -}) => { - const checkupDataTyped = (checkupData! as unknown) as { [checkupType: string]: any[] }; - const countByType = Object.keys(checkupDataTyped).reduce((counts, checkupType) => { - counts[checkupType] = checkupDataTyped[checkupType].length; - return counts; - }, {} as { [checkupType: string]: number }); - - // Uncomment when START_UPGRADE_STEP is in use! - const { kibanaVersionInfo, docLinks /* , isCloudEnabled */ } = useAppContext(); - - const { DOC_LINK_VERSION, ELASTIC_WEBSITE_URL } = docLinks; - const esDocBasePath = `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/reference/${DOC_LINK_VERSION}`; - - const { currentMajor, nextMajor } = kibanaVersionInfo; - - return ( - - {countByType.cluster ? ( - -

- setSelectedTabIndex(1)}> - - - ), - }} - /> -

-

- {countByType.cluster} - ), - }} - /> -

-
- ) : ( -

- -

- )} -
- ), - }, - { - title: countByType.indices - ? i18n.translate( - 'xpack.upgradeAssistant.overviewTab.steps.indicesStep.issuesRemainingStepTitle', - { - defaultMessage: 'Check for issues with your indices', - } - ) - : i18n.translate( - 'xpack.upgradeAssistant.overviewTab.steps.indicesStep.noIssuesRemainingStepTitle', - { - defaultMessage: 'Your index settings are ready', - } - ), - status: countByType.indices ? 'warning' : 'complete', - 'data-test-subj': 'indicesIssuesStep', - children: ( - - {countByType.indices ? ( - -

- setSelectedTabIndex(2)}> - - - ), - }} - /> -

-

- {countByType.indices} - ), - }} - /> -

-
- ) : ( -

- -

- )} -
- ), - }, - { - title: i18n.translate( - 'xpack.upgradeAssistant.overviewTab.steps.deprecationLogsStep.stepTitle', - { - defaultMessage: 'Review the Elasticsearch deprecation logs', - } - ), - 'data-test-subj': 'deprecationLoggingStep', - children: ( - - -

- - - - ), - nextEsVersion: `${nextMajor}.0`, - }} - /> -

-
- - - - - - -
- ), - }, - - // Swap in START_UPGRADE_STEP on the last minor release. - WAIT_FOR_RELEASE_STEP(currentMajor, nextMajor), - // START_UPGRADE_STEP(isCloudEnabled, esDocBasePath), - ]} - /> - ); -}; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/page_content.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/page_content.tsx deleted file mode 100644 index db515f0c123a85..00000000000000 --- a/x-pack/plugins/upgrade_assistant/public/application/components/page_content.tsx +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { EuiPageHeader, EuiPageHeaderSection, EuiTitle } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; - -import { useAppContext } from '../app_context'; -import { ComingSoonPrompt } from './coming_soon_prompt'; -import { UpgradeAssistantTabs } from './tabs'; - -export const PageContent: React.FunctionComponent = () => { - const { kibanaVersionInfo, isReadOnlyMode } = useAppContext(); - const { nextMajor } = kibanaVersionInfo; - - // Read-only mode will be enabled up until the last minor before the next major release - if (isReadOnlyMode) { - return ; - } - - return ( - <> - - - -

- -

-
-
-
- - - - ); -}; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/tabs.tsx deleted file mode 100644 index 231d9705bd0d91..00000000000000 --- a/x-pack/plugins/upgrade_assistant/public/application/components/tabs.tsx +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { findIndex } from 'lodash'; -import React, { useEffect, useState, useMemo } from 'react'; - -import { - EuiEmptyPrompt, - EuiPageContent, - EuiPageContentBody, - EuiTabbedContent, - EuiTabbedContentTab, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; - -import { LatestMinorBanner } from './latest_minor_banner'; -import { DeprecationTab } from './es_deprecations'; -import { OverviewTab } from './overview'; -import { TelemetryState, UpgradeAssistantTabProps } from './types'; -import { useAppContext } from '../app_context'; - -export const UpgradeAssistantTabs: React.FunctionComponent = () => { - const [selectedTabIndex, setSelectedTabIndex] = useState(0); - const [telemetryState, setTelemetryState] = useState(TelemetryState.Complete); - - const { api } = useAppContext(); - - const { data: checkupData, isLoading, error, resendRequest } = api.useLoadUpgradeStatus(); - - const tabs = useMemo(() => { - const commonTabProps: UpgradeAssistantTabProps = { - loadingError: error, - isLoading, - refreshCheckupData: resendRequest, - setSelectedTabIndex, - // Remove this in last minor of the current major (e.g., 7.15) - alertBanner: , - }; - - return [ - { - id: 'overview', - 'data-test-subj': 'upgradeAssistantOverviewTab', - name: i18n.translate('xpack.upgradeAssistant.overviewTab.overviewTabTitle', { - defaultMessage: 'Overview', - }), - content: , - }, - { - id: 'cluster', - 'data-test-subj': 'upgradeAssistantClusterTab', - name: i18n.translate('xpack.upgradeAssistant.checkupTab.clusterTabLabel', { - defaultMessage: 'Cluster', - }), - content: ( - - ), - }, - { - id: 'indices', - 'data-test-subj': 'upgradeAssistantIndicesTab', - name: i18n.translate('xpack.upgradeAssistant.checkupTab.indicesTabLabel', { - defaultMessage: 'Indices', - }), - content: ( - - ), - }, - ]; - }, [checkupData, error, isLoading, resendRequest]); - - const tabName = tabs[selectedTabIndex].id; - - useEffect(() => { - if (isLoading === false) { - setTelemetryState(TelemetryState.Running); - - async function sendTelemetryData() { - await api.sendTelemetryData({ - [tabName]: true, - }); - setTelemetryState(TelemetryState.Complete); - } - - sendTelemetryData(); - } - }, [api, selectedTabIndex, tabName, isLoading]); - - const onTabClick = (selectedTab: EuiTabbedContentTab) => { - const newSelectedTabIndex = findIndex(tabs, { id: selectedTab.id }); - if (selectedTabIndex === -1) { - throw new Error('Clicked tab did not exist in tabs array'); - } - setSelectedTabIndex(newSelectedTabIndex); - }; - - if (error?.statusCode === 426 && error.attributes?.allNodesUpgraded === false) { - return ( - - - - - - } - body={ -

- -

- } - /> -
-
- ); - } else if (error?.statusCode === 426 && error.attributes?.allNodesUpgraded === true) { - return ( - - - - - - } - body={ -

- -

- } - /> -
-
- ); - } - - return ( - - ); -}; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/types.ts b/x-pack/plugins/upgrade_assistant/public/application/components/types.ts index 8be2fe3e0b0aba..d82b779110a89e 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/types.ts +++ b/x-pack/plugins/upgrade_assistant/public/application/components/types.ts @@ -15,9 +15,9 @@ export interface UpgradeAssistantTabProps { checkupData?: UpgradeAssistantStatus | null; deprecations?: EnrichedDeprecationInfo[]; refreshCheckupData: () => void; - loadingError: ResponseError | null; + error: ResponseError | null; isLoading: boolean; - setSelectedTabIndex: (tabIndex: number) => void; + navigateToOverviewPage: () => void; } // eslint-disable-next-line react/prefer-stateless-function @@ -35,6 +35,7 @@ export enum LoadingState { export enum LevelFilterOption { all = 'all', critical = 'critical', + warning = 'warning', } export enum GroupByOption { @@ -47,3 +48,5 @@ export enum TelemetryState { Running, Complete, } + +export type EsTabs = 'cluster' | 'indices'; diff --git a/x-pack/plugins/upgrade_assistant/public/application/lib/breadcrumbs.ts b/x-pack/plugins/upgrade_assistant/public/application/lib/breadcrumbs.ts new file mode 100644 index 00000000000000..3f2ee4fa33657a --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/application/lib/breadcrumbs.ts @@ -0,0 +1,66 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import { ManagementAppMountParams } from '../../../../../../src/plugins/management/public'; + +type SetBreadcrumbs = ManagementAppMountParams['setBreadcrumbs']; + +const i18nTexts = { + breadcrumbs: { + overview: i18n.translate('xpack.upgradeAssistant.breadcrumb.overviewLabel', { + defaultMessage: 'Upgrade Assistant', + }), + esDeprecations: i18n.translate('xpack.upgradeAssistant.breadcrumb.esDeprecationsLabel', { + defaultMessage: 'Elasticsearch deprecations', + }), + }, +}; + +export class BreadcrumbService { + private breadcrumbs: { + [key: string]: Array<{ + text: string; + href?: string; + }>; + } = { + overview: [ + { + text: i18nTexts.breadcrumbs.overview, + }, + ], + esDeprecations: [ + { + text: i18nTexts.breadcrumbs.overview, + href: '/', + }, + { + text: i18nTexts.breadcrumbs.esDeprecations, + }, + ], + }; + + private setBreadcrumbsHandler?: SetBreadcrumbs; + + public setup(setBreadcrumbsHandler: SetBreadcrumbs): void { + this.setBreadcrumbsHandler = setBreadcrumbsHandler; + } + + public setBreadcrumbs(type: 'overview' | 'esDeprecations'): void { + if (!this.setBreadcrumbsHandler) { + throw new Error('Breadcrumb service has not been initialized'); + } + + const newBreadcrumbs = this.breadcrumbs[type] + ? [...this.breadcrumbs[type]] + : [...this.breadcrumbs.home]; + + this.setBreadcrumbsHandler(newBreadcrumbs); + } +} + +export const breadcrumbService = new BreadcrumbService(); diff --git a/x-pack/plugins/upgrade_assistant/public/application/lib/es_deprecation_errors.ts b/x-pack/plugins/upgrade_assistant/public/application/lib/es_deprecation_errors.ts new file mode 100644 index 00000000000000..4220f0eef8d42b --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/public/application/lib/es_deprecation_errors.ts @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import { ResponseError } from './api'; + +const i18nTexts = { + permissionsError: i18n.translate( + 'xpack.upgradeAssistant.esDeprecationErrors.permissionsErrorMessage', + { + defaultMessage: 'You are not authorized to view Elasticsearch deprecations.', + } + ), + partiallyUpgradedWarning: i18n.translate( + 'xpack.upgradeAssistant.esDeprecationErrors.partiallyUpgradedWarningMessage', + { + defaultMessage: + 'Upgrade Kibana to the same version as your Elasticsearch cluster. One or more nodes in the cluster is running a different version than Kibana.', + } + ), + upgradedMessage: i18n.translate( + 'xpack.upgradeAssistant.esDeprecationErrors.upgradedWarningMessage', + { + defaultMessage: + 'Your configuration is up to date. Kibana and all Elasticsearch nodes are running the same version.', + } + ), + loadingError: i18n.translate('xpack.upgradeAssistant.esDeprecationErrors.loadingErrorMessage', { + defaultMessage: 'Could not retrieve Elasticsearch deprecations.', + }), +}; + +export const getEsDeprecationError = (error: ResponseError) => { + if (error.statusCode === 403) { + return { + code: 'unauthorized_error', + message: i18nTexts.permissionsError, + }; + } else if (error?.statusCode === 426 && error.attributes?.allNodesUpgraded === false) { + return { + code: 'partially_upgraded_error', + message: i18nTexts.partiallyUpgradedWarning, + }; + } else if (error?.statusCode === 426 && error.attributes?.allNodesUpgraded === true) { + return { + code: 'upgraded_error', + message: i18nTexts.upgradedMessage, + }; + } else { + return { + code: 'request_error', + message: i18nTexts.loadingError, + }; + } +}; diff --git a/x-pack/plugins/upgrade_assistant/public/application/mount_management_section.ts b/x-pack/plugins/upgrade_assistant/public/application/mount_management_section.ts index 681beefdfd00cd..575c85bb33ec01 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/mount_management_section.ts +++ b/x-pack/plugins/upgrade_assistant/public/application/mount_management_section.ts @@ -11,6 +11,7 @@ import { UA_READONLY_MODE } from '../../common/constants'; import { renderApp } from './render_app'; import { KibanaVersionContext } from './app_context'; import { apiService } from './lib/api'; +import { breadcrumbService } from './lib/breadcrumbs'; export async function mountManagementSection( coreSetup: CoreSetup, @@ -18,13 +19,15 @@ export async function mountManagementSection( params: ManagementAppMountParams, kibanaVersionInfo: KibanaVersionContext ) { - const [{ i18n, docLinks, notifications }] = await coreSetup.getStartServices(); + const [{ i18n, docLinks, notifications, application }] = await coreSetup.getStartServices(); + const { element, history, setBreadcrumbs } = params; const { http } = coreSetup; apiService.setup(http); + breadcrumbService.setup(setBreadcrumbs); return renderApp({ - element: params.element, + element, isCloudEnabled, http, i18n, @@ -32,6 +35,9 @@ export async function mountManagementSection( kibanaVersionInfo, notifications, isReadOnlyMode: UA_READONLY_MODE, + history, api: apiService, + breadcrumbs: breadcrumbService, + getUrlForApp: application.getUrlForApp, }); } diff --git a/x-pack/plugins/upgrade_assistant/public/application/render_app.tsx b/x-pack/plugins/upgrade_assistant/public/application/render_app.tsx index a393ae433c5af4..248e6961a74e57 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/render_app.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/render_app.tsx @@ -8,11 +8,9 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { AppDependencies, RootComponent } from './app'; -import { ApiService } from './lib/api'; interface BootDependencies extends AppDependencies { element: HTMLElement; - api: ApiService; } export const renderApp = (deps: BootDependencies) => { diff --git a/x-pack/plugins/upgrade_assistant/public/shared_imports.ts b/x-pack/plugins/upgrade_assistant/public/shared_imports.ts index 6d3984fac68a6c..9007fdc5db04d1 100644 --- a/x-pack/plugins/upgrade_assistant/public/shared_imports.ts +++ b/x-pack/plugins/upgrade_assistant/public/shared_imports.ts @@ -11,4 +11,5 @@ export { SendRequestResponse, useRequest, UseRequestConfig, + SectionLoading, } from '../../../../src/plugins/es_ui_shared/public/'; diff --git a/x-pack/plugins/upgrade_assistant/tests_client_integration/helpers/indices.helpers.ts b/x-pack/plugins/upgrade_assistant/tests_client_integration/helpers/indices.helpers.ts index 5ab5c88cce4bcc..a59aa009a912ba 100644 --- a/x-pack/plugins/upgrade_assistant/tests_client_integration/helpers/indices.helpers.ts +++ b/x-pack/plugins/upgrade_assistant/tests_client_integration/helpers/indices.helpers.ts @@ -6,10 +6,14 @@ */ import { registerTestBed, TestBed, TestBedConfig } from '@kbn/test/jest'; -import { PageContent } from '../../public/application/components/page_content'; +import { EsDeprecationsContent } from '../../public/application/components/es_deprecations'; import { WithAppDependencies } from './setup_environment'; const testBedConfig: TestBedConfig = { + memoryRouter: { + initialEntries: ['/es_deprecations/indices'], + componentRoutePath: '/es_deprecations/:tabName', + }, doMountAsync: true, }; @@ -46,7 +50,10 @@ const createActions = (testBed: TestBed) => { }; export const setup = async (overrides?: Record): Promise => { - const initTestBed = registerTestBed(WithAppDependencies(PageContent, overrides), testBedConfig); + const initTestBed = registerTestBed( + WithAppDependencies(EsDeprecationsContent, overrides), + testBedConfig + ); const testBed = await initTestBed(); return { @@ -60,6 +67,9 @@ export type IndicesTestSubjects = | 'removeIndexSettingsButton' | 'deprecationsContainer' | 'permissionsError' - | 'upgradeStatusError' + | 'requestError' + | 'indexCount' + | 'upgradedCallout' + | 'partiallyUpgradedWarning' | 'noDeprecationsPrompt' | string; diff --git a/x-pack/plugins/upgrade_assistant/tests_client_integration/helpers/overview.helpers.ts b/x-pack/plugins/upgrade_assistant/tests_client_integration/helpers/overview.helpers.ts index 22d00290842f4f..161364f6d45ce7 100644 --- a/x-pack/plugins/upgrade_assistant/tests_client_integration/helpers/overview.helpers.ts +++ b/x-pack/plugins/upgrade_assistant/tests_client_integration/helpers/overview.helpers.ts @@ -6,27 +6,40 @@ */ import { registerTestBed, TestBed, TestBedConfig } from '@kbn/test/jest'; -import { PageContent } from '../../public/application/components/page_content'; +import { DeprecationsOverview } from '../../public/application/components/overview'; import { WithAppDependencies } from './setup_environment'; const testBedConfig: TestBedConfig = { + memoryRouter: { + initialEntries: [`/overview`], + componentRoutePath: '/overview', + }, doMountAsync: true, }; export type OverviewTestBed = TestBed; -export const setup = async (overrides?: any): Promise => { - const initTestBed = registerTestBed(WithAppDependencies(PageContent, overrides), testBedConfig); +export const setup = async (overrides?: Record): Promise => { + const initTestBed = registerTestBed( + WithAppDependencies(DeprecationsOverview, overrides), + testBedConfig + ); const testBed = await initTestBed(); return testBed; }; export type OverviewTestSubjects = - | 'comingSoonPrompt' - | 'upgradeAssistantPageContent' + | 'overviewPageContent' + | 'esStatsPanel' + | 'esStatsPanel.totalDeprecations' + | 'esStatsPanel.criticalDeprecations' + | 'deprecationLoggingFormRow' + | 'requestErrorIconTip' + | 'partiallyUpgradedErrorIconTip' + | 'upgradedErrorIconTip' + | 'unauthorizedErrorIconTip' | 'upgradedPrompt' | 'partiallyUpgradedPrompt' | 'upgradeAssistantDeprecationToggle' - | 'deprecationLoggingStep' | 'upgradeStatusError'; diff --git a/x-pack/plugins/upgrade_assistant/tests_client_integration/helpers/setup_environment.tsx b/x-pack/plugins/upgrade_assistant/tests_client_integration/helpers/setup_environment.tsx index fb0afef8cf5879..7ee6114cd86a80 100644 --- a/x-pack/plugins/upgrade_assistant/tests_client_integration/helpers/setup_environment.tsx +++ b/x-pack/plugins/upgrade_assistant/tests_client_integration/helpers/setup_environment.tsx @@ -17,14 +17,15 @@ import { mockKibanaSemverVersion, UA_READONLY_MODE } from '../../common/constant import { AppContextProvider } from '../../public/application/app_context'; import { init as initHttpRequests } from './http_requests'; import { apiService } from '../../public/application/lib/api'; +import { breadcrumbService } from '../../public/application/lib/breadcrumbs'; const mockHttpClient = axios.create({ adapter: axiosXhrAdapter }); -export const WithAppDependencies = ( - Comp: React.FunctionComponent>, - overrides: Record = {} -) => (props: Record) => { +export const WithAppDependencies = (Comp: any, overrides: Record = {}) => ( + props: Record +) => { apiService.setup((mockHttpClient as unknown) as HttpSetup); + breadcrumbService.setup(() => ''); const contextValue = { http: (mockHttpClient as unknown) as HttpSetup, @@ -38,6 +39,8 @@ export const WithAppDependencies = ( isReadOnlyMode: UA_READONLY_MODE, notifications: notificationServiceMock.createStartContract(), api: apiService, + breadcrumbs: breadcrumbService, + getUrlForApp: () => '', }; return ( diff --git a/x-pack/plugins/upgrade_assistant/tests_client_integration/indices.test.ts b/x-pack/plugins/upgrade_assistant/tests_client_integration/indices.test.ts index 01d95f117827ee..6363e57903c27e 100644 --- a/x-pack/plugins/upgrade_assistant/tests_client_integration/indices.test.ts +++ b/x-pack/plugins/upgrade_assistant/tests_client_integration/indices.test.ts @@ -124,14 +124,7 @@ describe('Indices tab', () => { testBed = await setupIndicesPage({ isReadOnlyMode: false }); }); - const { actions, component } = testBed; - - component.update(); - - // Navigate to the indices tab - await act(async () => { - actions.clickTab('indices'); - }); + const { component } = testBed; component.update(); }); @@ -139,7 +132,7 @@ describe('Indices tab', () => { test('renders prompt', () => { const { exists, find } = testBed; expect(exists('noDeprecationsPrompt')).toBe(true); - expect(find('noDeprecationsPrompt').text()).toContain('All clear!'); + expect(find('noDeprecationsPrompt').text()).toContain('Ready to upgrade!'); }); }); @@ -163,7 +156,59 @@ describe('Indices tab', () => { expect(exists('permissionsError')).toBe(true); expect(find('permissionsError').text()).toContain( - 'You do not have sufficient privileges to view this page.' + 'You are not authorized to view Elasticsearch deprecations.' + ); + }); + + test('handles upgrade error', async () => { + const error = { + statusCode: 426, + error: 'Upgrade required', + message: 'There are some nodes running a different version of Elasticsearch', + attributes: { + allNodesUpgraded: true, + }, + }; + + httpRequestsMockHelpers.setLoadStatusResponse(undefined, error); + + await act(async () => { + testBed = await setupIndicesPage({ isReadOnlyMode: false }); + }); + + const { component, exists, find } = testBed; + + component.update(); + + expect(exists('upgradedCallout')).toBe(true); + expect(find('upgradedCallout').text()).toContain( + 'Your configuration is up to date. Kibana and all Elasticsearch nodes are running the same version.' + ); + }); + + test('handles partially upgrade error', async () => { + const error = { + statusCode: 426, + error: 'Upgrade required', + message: 'There are some nodes running a different version of Elasticsearch', + attributes: { + allNodesUpgraded: false, + }, + }; + + httpRequestsMockHelpers.setLoadStatusResponse(undefined, error); + + await act(async () => { + testBed = await setupIndicesPage({ isReadOnlyMode: false }); + }); + + const { component, exists, find } = testBed; + + component.update(); + + expect(exists('partiallyUpgradedWarning')).toBe(true); + expect(find('partiallyUpgradedWarning').text()).toContain( + 'Upgrade Kibana to the same version as your Elasticsearch cluster. One or more nodes in the cluster is running a different version than Kibana.' ); }); @@ -184,9 +229,9 @@ describe('Indices tab', () => { component.update(); - expect(exists('upgradeStatusError')).toBe(true); - expect(find('upgradeStatusError').text()).toContain( - 'An error occurred while retrieving the checkup results.' + expect(exists('requestError')).toBe(true); + expect(find('requestError').text()).toContain( + 'Could not retrieve Elasticsearch deprecations.' ); }); }); diff --git a/x-pack/plugins/upgrade_assistant/tests_client_integration/overview.test.ts b/x-pack/plugins/upgrade_assistant/tests_client_integration/overview.test.ts index 139c4ecb5a75d3..cdbbd0a36cbdd3 100644 --- a/x-pack/plugins/upgrade_assistant/tests_client_integration/overview.test.ts +++ b/x-pack/plugins/upgrade_assistant/tests_client_integration/overview.test.ts @@ -11,25 +11,9 @@ import { OverviewTestBed, setupOverviewPage, setupEnvironment } from './helpers' describe('Overview page', () => { let testBed: OverviewTestBed; + const { server, httpRequestsMockHelpers } = setupEnvironment(); beforeEach(async () => { - await act(async () => { - testBed = await setupOverviewPage(); - }); - }); - - describe('Coming soon prompt', () => { - // Default behavior up until the last minor before the next major release - test('renders the coming soon prompt by default', () => { - const { exists } = testBed; - - expect(exists('comingSoonPrompt')).toBe(true); - }); - }); - - describe('Overview content', () => { - const { server, httpRequestsMockHelpers } = setupEnvironment(); - const upgradeStatusMockResponse = { readyForUpgrade: false, cluster: [], @@ -39,148 +23,163 @@ describe('Overview page', () => { httpRequestsMockHelpers.setLoadStatusResponse(upgradeStatusMockResponse); httpRequestsMockHelpers.setLoadDeprecationLoggingResponse({ isEnabled: true }); - beforeEach(async () => { - await act(async () => { - // Override the default context value to verify tab content renders as expected - // This will be the default behavior on the last minor before the next major release (e.g., v7.15) - testBed = await setupOverviewPage({ isReadOnlyMode: false }); - }); - - testBed.component.update(); - }); - - afterAll(() => { - server.restore(); + await act(async () => { + testBed = await setupOverviewPage(); }); - test('renders the overview tab', () => { - const { exists } = testBed; + const { component } = testBed; + component.update(); + }); - expect(exists('comingSoonPrompt')).toBe(false); - expect(exists('upgradeAssistantPageContent')).toBe(true); - }); + afterAll(() => { + server.restore(); + }); - describe('Deprecation logging', () => { - test('toggles deprecation logging', async () => { - const { form, find, component } = testBed; + test('renders the overview page', () => { + const { exists, find } = testBed; - httpRequestsMockHelpers.setUpdateDeprecationLoggingResponse({ isEnabled: false }); + expect(exists('overviewPageContent')).toBe(true); + // Verify ES stats + expect(exists('esStatsPanel')).toBe(true); + expect(find('esStatsPanel.totalDeprecations').text()).toContain('0'); + expect(find('esStatsPanel.criticalDeprecations').text()).toContain('0'); + }); - expect(find('upgradeAssistantDeprecationToggle').props()['aria-checked']).toBe(true); - expect(find('upgradeAssistantDeprecationToggle').props().disabled).toBe(false); - expect(find('deprecationLoggingStep').find('.euiSwitch__label').text()).toContain('On'); + describe('Deprecation logging', () => { + test('toggles deprecation logging', async () => { + const { form, find, component } = testBed; - await act(async () => { - form.toggleEuiSwitch('upgradeAssistantDeprecationToggle'); - }); + httpRequestsMockHelpers.setUpdateDeprecationLoggingResponse({ isEnabled: false }); - component.update(); + expect(find('upgradeAssistantDeprecationToggle').props()['aria-checked']).toBe(true); + expect(find('upgradeAssistantDeprecationToggle').props().disabled).toBe(false); - expect(find('upgradeAssistantDeprecationToggle').props()['aria-checked']).toBe(false); - expect(find('upgradeAssistantDeprecationToggle').props().disabled).toBe(false); - expect(find('deprecationLoggingStep').find('.euiSwitch__label').text()).toContain('Off'); + await act(async () => { + form.toggleEuiSwitch('upgradeAssistantDeprecationToggle'); }); - test('handles network error', async () => { - const error = { - statusCode: 500, - error: 'Internal server error', - message: 'Internal server error', - }; + component.update(); - const { form, find, component } = testBed; + expect(find('upgradeAssistantDeprecationToggle').props()['aria-checked']).toBe(false); + expect(find('upgradeAssistantDeprecationToggle').props().disabled).toBe(false); + }); - httpRequestsMockHelpers.setUpdateDeprecationLoggingResponse(undefined, error); + test('handles network error', async () => { + const error = { + statusCode: 500, + error: 'Internal server error', + message: 'Internal server error', + }; - expect(find('upgradeAssistantDeprecationToggle').props()['aria-checked']).toBe(true); - expect(find('upgradeAssistantDeprecationToggle').props().disabled).toBe(false); - expect(find('deprecationLoggingStep').find('.euiSwitch__label').text()).toContain('On'); + const { form, find, component } = testBed; - await act(async () => { - form.toggleEuiSwitch('upgradeAssistantDeprecationToggle'); - }); + httpRequestsMockHelpers.setUpdateDeprecationLoggingResponse(undefined, error); - component.update(); + expect(find('upgradeAssistantDeprecationToggle').props()['aria-checked']).toBe(true); + expect(find('upgradeAssistantDeprecationToggle').props().disabled).toBe(false); + expect(find('deprecationLoggingFormRow').find('.euiSwitch__label').text()).toContain( + 'Enable deprecation logging' + ); - expect(find('upgradeAssistantDeprecationToggle').props()['aria-checked']).toBe(true); - expect(find('upgradeAssistantDeprecationToggle').props().disabled).toBe(true); - expect(find('deprecationLoggingStep').find('.euiSwitch__label').text()).toContain( - 'Could not load logging state' - ); + await act(async () => { + form.toggleEuiSwitch('upgradeAssistantDeprecationToggle'); }); + + component.update(); + + expect(find('upgradeAssistantDeprecationToggle').props()['aria-checked']).toBe(true); + expect(find('upgradeAssistantDeprecationToggle').props().disabled).toBe(true); + expect(find('deprecationLoggingFormRow').find('.euiSwitch__label').text()).toContain( + 'Could not load logging state' + ); }); + }); + + describe('Error handling', () => { + test('handles network failure', async () => { + const error = { + statusCode: 500, + error: 'Internal server error', + message: 'Internal server error', + }; + + httpRequestsMockHelpers.setLoadStatusResponse(undefined, error); + + await act(async () => { + testBed = await setupOverviewPage(); + }); - describe('Error handling', () => { - test('handles network failure', async () => { - const error = { - statusCode: 500, - error: 'Internal server error', - message: 'Internal server error', - }; + const { component, exists } = testBed; - httpRequestsMockHelpers.setLoadStatusResponse(undefined, error); + component.update(); - await act(async () => { - testBed = await setupOverviewPage({ isReadOnlyMode: false }); - }); + expect(exists('requestErrorIconTip')).toBe(true); + }); - const { component, exists, find } = testBed; + test('handles unauthorized error', async () => { + const error = { + statusCode: 403, + error: 'Forbidden', + message: 'Forbidden', + }; - component.update(); + httpRequestsMockHelpers.setLoadStatusResponse(undefined, error); - expect(exists('upgradeStatusError')).toBe(true); - expect(find('upgradeStatusError').text()).toContain( - 'An error occurred while retrieving the checkup results.' - ); + await act(async () => { + testBed = await setupOverviewPage(); }); - test('handles partially upgraded error', async () => { - const error = { - statusCode: 426, - error: 'Upgrade required', - message: 'There are some nodes running a different version of Elasticsearch', - attributes: { - allNodesUpgraded: false, - }, - }; + const { component, exists } = testBed; - httpRequestsMockHelpers.setLoadStatusResponse(undefined, error); + component.update(); - await act(async () => { - testBed = await setupOverviewPage({ isReadOnlyMode: false }); - }); + expect(exists('unauthorizedErrorIconTip')).toBe(true); + }); - const { component, exists, find } = testBed; + test('handles partially upgraded error', async () => { + const error = { + statusCode: 426, + error: 'Upgrade required', + message: 'There are some nodes running a different version of Elasticsearch', + attributes: { + allNodesUpgraded: false, + }, + }; - component.update(); + httpRequestsMockHelpers.setLoadStatusResponse(undefined, error); - expect(exists('partiallyUpgradedPrompt')).toBe(true); - expect(find('partiallyUpgradedPrompt').text()).toContain('Your cluster is upgrading'); + await act(async () => { + testBed = await setupOverviewPage({ isReadOnlyMode: false }); }); - test('handles upgrade error', async () => { - const error = { - statusCode: 426, - error: 'Upgrade required', - message: 'There are some nodes running a different version of Elasticsearch', - attributes: { - allNodesUpgraded: true, - }, - }; + const { component, exists } = testBed; - httpRequestsMockHelpers.setLoadStatusResponse(undefined, error); + component.update(); - await act(async () => { - testBed = await setupOverviewPage({ isReadOnlyMode: false }); - }); + expect(exists('partiallyUpgradedErrorIconTip')).toBe(true); + }); - const { component, exists, find } = testBed; + test('handles upgrade error', async () => { + const error = { + statusCode: 426, + error: 'Upgrade required', + message: 'There are some nodes running a different version of Elasticsearch', + attributes: { + allNodesUpgraded: true, + }, + }; - component.update(); + httpRequestsMockHelpers.setLoadStatusResponse(undefined, error); - expect(exists('upgradedPrompt')).toBe(true); - expect(find('upgradedPrompt').text()).toContain('Your cluster has been upgraded'); + await act(async () => { + testBed = await setupOverviewPage({ isReadOnlyMode: false }); }); + + const { component, exists } = testBed; + + component.update(); + + expect(exists('upgradedErrorIconTip')).toBe(true); }); }); }); diff --git a/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/__snapshots__/location_status_tags.test.tsx.snap b/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/__snapshots__/location_status_tags.test.tsx.snap index 8e2a4b1bd17777..44a2021cce611e 100644 --- a/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/__snapshots__/location_status_tags.test.tsx.snap +++ b/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/__snapshots__/location_status_tags.test.tsx.snap @@ -996,7 +996,7 @@ exports[`LocationStatusTags component renders when there are many location 1`] = aria-controls="generated-id" aria-current="true" aria-label="Page 1 of 2" - class="euiButtonEmpty euiButtonEmpty--text euiButtonEmpty--xSmall euiButtonEmpty-isDisabled euiPaginationButton euiPaginationButton-isActive euiPaginationButton--hideOnMobile" + class="euiButtonEmpty euiButtonEmpty--text euiButtonEmpty--small euiButtonEmpty-isDisabled euiPaginationButton euiPaginationButton-isActive euiPaginationButton--hideOnMobile" data-test-subj="pagination-button-0" disabled="" type="button" @@ -1018,7 +1018,7 @@ exports[`LocationStatusTags component renders when there are many location 1`] =
{ + describe('Upgrade Assistant', () => { before(async () => { await PageObjects.upgradeAssistant.navigateToPage(); }); - it('Overview page', async () => { - await retry.waitFor('Upgrade Assistant overview page to be visible', async () => { + it('Coming soon prompt', async () => { + await retry.waitFor('Upgrade Assistant coming soon prompt to be visible', async () => { return testSubjects.exists('comingSoonPrompt'); }); await a11y.testAppSnapshot(); }); // These tests will be skipped until the last minor of the next major release - describe.skip('tabs', () => { - it('Overview Tab', async () => { - await retry.waitFor('Upgrade Assistant overview tab to be visible', async () => { - return testSubjects.exists('upgradeAssistantOverviewTabDetail'); + describe.skip('Upgrade Assistant content', () => { + it('Overview page', async () => { + await retry.waitFor('Upgrade Assistant overview page to be visible', async () => { + return testSubjects.exists('overviewPageContent'); }); await a11y.testAppSnapshot(); }); - it('Cluster Tab', async () => { - await testSubjects.click('upgradeAssistantClusterTab'); + it('Elasticsearch cluster tab', async () => { + await testSubjects.click('esDeprecationsLink'); await retry.waitFor('Upgrade Assistant Cluster tab to be visible', async () => { - return testSubjects.exists('upgradeAssistantClusterTabDetail'); + return testSubjects.exists('clusterTabContent'); }); await a11y.testAppSnapshot(); }); - it('Indices Tab', async () => { + it('Elasticsearch indices tab', async () => { await testSubjects.click('upgradeAssistantIndicesTab'); - await retry.waitFor('Upgrade Assistant Cluster tab to be visible', async () => { - return testSubjects.exists('upgradeAssistantIndexTabDetail'); + await retry.waitFor('Upgrade Assistant Indices tab to be visible', async () => { + return testSubjects.exists('indexTabContent'); }); await a11y.testAppSnapshot(); }); diff --git a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/index.ts b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/index.ts index 700aee6bfd49d6..027ea50a8ae6a2 100644 --- a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/index.ts +++ b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/index.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { PluginInitializerContext } from 'kibana/server'; import { FixturePlugin } from './plugin'; -export const plugin = () => new FixturePlugin(); +export const plugin = (initContext: PluginInitializerContext) => new FixturePlugin(initContext); diff --git a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/lib/retry_if_conflicts.ts b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/lib/retry_if_conflicts.ts new file mode 100644 index 00000000000000..776686bcd1c0a3 --- /dev/null +++ b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/lib/retry_if_conflicts.ts @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +// This module provides a helper to perform retries on a function if the +// function ends up throwing a SavedObject 409 conflict. This can happen +// when alert SO's are updated in the background, and will avoid having to +// have the caller make explicit conflict checks, where the conflict was +// caused by a background update. + +import { Logger } from 'kibana/server'; + +type RetryableForConflicts = () => Promise; + +// number of times to retry when conflicts occur +export const RetryForConflictsAttempts = 2; + +// milliseconds to wait before retrying when conflicts occur +// note: we considered making this random, to help avoid a stampede, but +// with 1 retry it probably doesn't matter, and adding randomness could +// make it harder to diagnose issues +const RetryForConflictsDelay = 250; + +// retry an operation if it runs into 409 Conflict's, up to a limit +export async function retryIfConflicts( + logger: Logger, + name: string, + operation: RetryableForConflicts, + retries: number = RetryForConflictsAttempts +): Promise { + // run the operation, return if no errors or throw if not a conflict error + try { + return await operation(); + } catch (err) { + if (!isConflictError(err)) { + throw err; + } + + // must be a conflict; if no retries left, throw it + if (retries <= 0) { + logger.warn(`${name} conflict, exceeded retries`); + throw err; + } + + // delay a bit before retrying + logger.debug(`${name} conflict, retrying ...`); + await waitBeforeNextRetry(); + return await retryIfConflicts(logger, name, operation, retries - 1); + } +} + +async function waitBeforeNextRetry(): Promise { + await new Promise((resolve) => setTimeout(resolve, RetryForConflictsDelay)); +} + +// This is a workaround to avoid having to add more code to compile for tests via +// packages/kbn-test/src/functional_tests/lib/babel_register_for_test_plugins.js +// to use SavedObjectsErrorHelpers.isConflictError. +function isConflictError(error: any): boolean { + return error.isBoom === true && error.output.statusCode === 409; +} diff --git a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/plugin.ts b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/plugin.ts index 972cb05c997663..bf5d05ee4624a8 100644 --- a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/plugin.ts +++ b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/plugin.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { Plugin, CoreSetup } from 'kibana/server'; +import { Plugin, CoreSetup, Logger, PluginInitializerContext } from 'kibana/server'; import { PluginSetupContract as ActionsPluginSetup } from '../../../../../../../plugins/actions/server/plugin'; import { PluginSetupContract as AlertingPluginSetup } from '../../../../../../../plugins/alerting/server/plugin'; import { EncryptedSavedObjectsPluginStart } from '../../../../../../../plugins/encrypted_saved_objects/server'; @@ -29,6 +29,12 @@ export interface FixtureStartDeps { } export class FixturePlugin implements Plugin { + private readonly logger: Logger; + + constructor(initializerContext: PluginInitializerContext) { + this.logger = initializerContext.logger.get('fixtures', 'plugins', 'alerts'); + } + public setup( core: CoreSetup, { features, actions, alerting }: FixtureSetupDeps @@ -109,7 +115,7 @@ export class FixturePlugin implements Plugin) { +export function defineRoutes(core: CoreSetup, { logger }: { logger: Logger }) { const router = core.http.createRouter(); router.put( { @@ -84,28 +86,35 @@ export function defineRoutes(core: CoreSetup) { throw new Error('Failed to grant an API Key'); } - const result = await savedObjectsWithAlerts.update( - 'alert', - id, - { - ...( - await encryptedSavedObjectsWithAlerts.getDecryptedAsInternalUser( - 'alert', - id, - { - namespace, - } - ) - ).attributes, - apiKey: Buffer.from(`${createAPIKeyResult.id}:${createAPIKeyResult.api_key}`).toString( - 'base64' - ), - apiKeyOwner: user.username, - }, - { - namespace, + const result = await retryIfConflicts( + logger, + `/api/alerts_fixture/${id}/replace_api_key`, + async () => { + return await savedObjectsWithAlerts.update( + 'alert', + id, + { + ...( + await encryptedSavedObjectsWithAlerts.getDecryptedAsInternalUser( + 'alert', + id, + { + namespace, + } + ) + ).attributes, + apiKey: Buffer.from( + `${createAPIKeyResult.id}:${createAPIKeyResult.api_key}` + ).toString('base64'), + apiKeyOwner: user.username, + }, + { + namespace, + } + ); } ); + return res.ok({ body: result }); } ); @@ -147,11 +156,17 @@ export function defineRoutes(core: CoreSetup) { includedHiddenTypes: ['alert'], }); const savedAlert = await savedObjectsWithAlerts.get(type, id); - const result = await savedObjectsWithAlerts.update( - type, - id, - { ...savedAlert.attributes, ...attributes }, - options + const result = await retryIfConflicts( + logger, + `/api/alerts_fixture/saved_object/${type}/${id}`, + async () => { + return await savedObjectsWithAlerts.update( + type, + id, + { ...savedAlert.attributes, ...attributes }, + options + ); + } ); return res.ok({ body: result }); } @@ -182,10 +197,16 @@ export function defineRoutes(core: CoreSetup) { includedHiddenTypes: ['task', 'alert'], }); const alert = await savedObjectsWithTasksAndAlerts.get('alert', id); - const result = await savedObjectsWithTasksAndAlerts.update( - 'task', - alert.attributes.scheduledTaskId!, - { runAt } + const result = await retryIfConflicts( + logger, + `/api/alerts_fixture/${id}/reschedule_task`, + async () => { + return await savedObjectsWithTasksAndAlerts.update( + 'task', + alert.attributes.scheduledTaskId!, + { runAt } + ); + } ); return res.ok({ body: result }); } @@ -216,10 +237,16 @@ export function defineRoutes(core: CoreSetup) { includedHiddenTypes: ['task', 'alert'], }); const alert = await savedObjectsWithTasksAndAlerts.get('alert', id); - const result = await savedObjectsWithTasksAndAlerts.update( - 'task', - alert.attributes.scheduledTaskId!, - { status } + const result = await retryIfConflicts( + logger, + `/api/alerts_fixture/{id}/reset_task_status`, + async () => { + return await savedObjectsWithTasksAndAlerts.update( + 'task', + alert.attributes.scheduledTaskId!, + { status } + ); + } ); return res.ok({ body: result }); } diff --git a/x-pack/test/api_integration/apis/management/ingest_pipelines/ingest_pipelines.ts b/x-pack/test/api_integration/apis/management/ingest_pipelines/ingest_pipelines.ts index 41d37cb798833c..2df2727ed869b2 100644 --- a/x-pack/test/api_integration/apis/management/ingest_pipelines/ingest_pipelines.ts +++ b/x-pack/test/api_integration/apis/management/ingest_pipelines/ingest_pipelines.ts @@ -204,7 +204,8 @@ export default function ({ getService }: FtrProviderContext) { expect(body).to.eql({ statusCode: 404, error: 'Not Found', - message: 'Not Found', + message: 'Response Error', + attributes: {}, }); }); }); @@ -339,24 +340,16 @@ export default function ({ getService }: FtrProviderContext) { { name: PIPELINE_DOES_NOT_EXIST, error: { - msg: '[resource_not_found_exception] pipeline [pipeline_does_not_exist] is missing', - path: '/_ingest/pipeline/pipeline_does_not_exist', - query: {}, - statusCode: 404, - response: JSON.stringify({ - error: { - root_cause: [ - { - type: 'resource_not_found_exception', - reason: 'pipeline [pipeline_does_not_exist] is missing', - }, - ], + root_cause: [ + { type: 'resource_not_found_exception', reason: 'pipeline [pipeline_does_not_exist] is missing', }, - status: 404, - }), + ], + type: 'resource_not_found_exception', + reason: 'pipeline [pipeline_does_not_exist] is missing', }, + status: 404, }, ], }); @@ -501,8 +494,9 @@ export default function ({ getService }: FtrProviderContext) { expect(body).to.eql({ error: 'Not Found', - message: 'Not Found', + message: 'Response Error', statusCode: 404, + attributes: {}, }); }); }); diff --git a/x-pack/test/api_integration/apis/management/ingest_pipelines/lib/elasticsearch.ts b/x-pack/test/api_integration/apis/management/ingest_pipelines/lib/elasticsearch.ts index ce11707dbe32b9..5a4459fced6248 100644 --- a/x-pack/test/api_integration/apis/management/ingest_pipelines/lib/elasticsearch.ts +++ b/x-pack/test/api_integration/apis/management/ingest_pipelines/lib/elasticsearch.ts @@ -30,17 +30,18 @@ interface Pipeline { export const registerEsHelpers = (getService: FtrProviderContext['getService']) => { let pipelinesCreated: string[] = []; - const es = getService('legacyEs'); + const es = getService('es'); const createPipeline = (pipeline: Pipeline, cachePipeline?: boolean) => { if (cachePipeline) { pipelinesCreated.push(pipeline.id); } - return es.ingest.putPipeline(pipeline); + return es.ingest.putPipeline(pipeline).then(({ body }) => body); }; - const deletePipeline = (pipelineId: string) => es.ingest.deletePipeline({ id: pipelineId }); + const deletePipeline = (pipelineId: string) => + es.ingest.deletePipeline({ id: pipelineId }).then(({ body }) => body); const cleanupPipelines = () => Promise.all(pipelinesCreated.map(deletePipeline)) @@ -53,11 +54,11 @@ export const registerEsHelpers = (getService: FtrProviderContext['getService']) }); const createIndex = (index: { index: string; id: string; body: object }) => { - return es.index(index); + return es.index(index).then(({ body }) => body); }; const deleteIndex = (indexName: string) => { - return es.indices.delete({ index: indexName }); + return es.indices.delete({ index: indexName }).then(({ body }) => body); }; return { diff --git a/x-pack/test/api_integration/apis/ml/modules/get_module.ts b/x-pack/test/api_integration/apis/ml/modules/get_module.ts index bd35bdddc33992..59aa6102b54e21 100644 --- a/x-pack/test/api_integration/apis/ml/modules/get_module.ts +++ b/x-pack/test/api_integration/apis/ml/modules/get_module.ts @@ -11,6 +11,8 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; import { USER } from '../../../../functional/services/ml/security_common'; import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api'; +import { isPopulatedObject } from '../../../../../plugins/ml/common/util/object_utils'; + const moduleIds = [ 'apache_ecs', 'apm_jsbase', @@ -27,6 +29,7 @@ const moduleIds = [ 'sample_data_ecommerce', 'sample_data_weblogs', 'security_linux', + 'security_network', 'security_windows', 'siem_auditbeat', 'siem_auditbeat_auth', @@ -69,6 +72,32 @@ export default ({ getService }: FtrProviderContext) => { const rspBody = await executeGetModuleRequest(moduleId, USER.ML_POWERUSER, 200); expect(rspBody).to.be.an(Object); + expect(rspBody).to.have.property('id').a('string'); + expect(rspBody).to.have.property('title').a('string'); + expect(rspBody).to.have.property('description').a('string'); + expect(rspBody).to.have.property('type').a('string'); + if (isPopulatedObject(rspBody, ['logoFile'])) { + expect(rspBody).to.have.property('logoFile').a('string'); + } + if (isPopulatedObject(rspBody, ['logo'])) { + expect(rspBody).to.have.property('logo').an(Object); + } + if (isPopulatedObject(rspBody, ['defaultIndexPattern'])) { + expect(rspBody).to.have.property('defaultIndexPattern').a('string'); + } + if (isPopulatedObject(rspBody, ['query'])) { + expect(rspBody).to.have.property('query').an(Object); + } + if (isPopulatedObject(rspBody, ['jobs'])) { + expect(rspBody).to.have.property('jobs').an(Object); + } + if (isPopulatedObject(rspBody, ['datafeeds'])) { + expect(rspBody).to.have.property('datafeeds').an(Object); + } + if (isPopulatedObject(rspBody, ['kibana'])) { + expect(rspBody).to.have.property('kibana').an(Object); + } + expect(rspBody.id).to.eql(moduleId); }); } diff --git a/x-pack/test/api_integration/apis/ml/modules/recognize_module.ts b/x-pack/test/api_integration/apis/ml/modules/recognize_module.ts index d7ba410dd5dd88..d6020e17801fd7 100644 --- a/x-pack/test/api_integration/apis/ml/modules/recognize_module.ts +++ b/x-pack/test/api_integration/apis/ml/modules/recognize_module.ts @@ -143,7 +143,7 @@ export default ({ getService }: FtrProviderContext) => { user: USER.ML_POWERUSER, expected: { responseCode: 200, - moduleIds: ['security_linux', 'security_windows'], + moduleIds: ['security_linux', 'security_network', 'security_windows'], }, }, ]; diff --git a/x-pack/test/api_integration/apis/security/api_keys.ts b/x-pack/test/api_integration/apis/security/api_keys.ts index 596a0b038cfb3f..c6513fa800c1c2 100644 --- a/x-pack/test/api_integration/apis/security/api_keys.ts +++ b/x-pack/test/api_integration/apis/security/api_keys.ts @@ -25,5 +25,27 @@ export default function ({ getService }: FtrProviderContext) { }); }); }); + + describe('POST /internal/security/api_key', () => { + it('should allow an API Key to be created', async () => { + await supertest + .post('/internal/security/api_key') + .set('kbn-xsrf', 'xxx') + .send({ + name: 'test_api_key', + expiration: '12d', + role_descriptors: { + role_1: { + cluster: ['monitor'], + }, + }, + }) + .expect(200) + .then((response: Record) => { + const { name } = response.body; + expect(name).to.eql('test_api_key'); + }); + }); + }); }); } diff --git a/x-pack/test/api_integration/apis/security_solution/timeline_details.ts b/x-pack/test/api_integration/apis/security_solution/timeline_details.ts index d653528fd47e26..61b75931c3c145 100644 --- a/x-pack/test/api_integration/apis/security_solution/timeline_details.ts +++ b/x-pack/test/api_integration/apis/security_solution/timeline_details.ts @@ -668,6 +668,7 @@ const EXPECTED_KPI_COUNTS = { }; export default function ({ getService }: FtrProviderContext) { + const retry = getService('retry'); const esArchiver = getService('esArchiver'); const supertest = getService('supertest'); @@ -676,41 +677,45 @@ export default function ({ getService }: FtrProviderContext) { after(() => esArchiver.unload('filebeat/default')); it('Make sure that we get Event Details data', async () => { - const { - body: { data: detailsData }, - } = await supertest - .post('/internal/search/securitySolutionTimelineSearchStrategy/') - .set('kbn-xsrf', 'true') - .send({ - factoryQueryType: TimelineEventsQueries.details, - docValueFields: [], - indexName: INDEX_NAME, - inspect: false, - eventId: ID, - wait_for_completion_timeout: '10s', - }) - .expect(200); - expect(sortBy(detailsData, 'field')).to.eql(sortBy(EXPECTED_DATA, 'field')); + await retry.try(async () => { + const { + body: { data: detailsData }, + } = await supertest + .post('/internal/search/securitySolutionTimelineSearchStrategy/') + .set('kbn-xsrf', 'true') + .send({ + factoryQueryType: TimelineEventsQueries.details, + docValueFields: [], + indexName: INDEX_NAME, + inspect: false, + eventId: ID, + wait_for_completion_timeout: '10s', + }) + .expect(200); + expect(sortBy(detailsData, 'field')).to.eql(sortBy(EXPECTED_DATA, 'field')); + }); }); it('Make sure that we get kpi data', async () => { - const { - body: { destinationIpCount, hostCount, processCount, sourceIpCount, userCount }, - } = await supertest - .post('/internal/search/securitySolutionTimelineSearchStrategy/') - .set('kbn-xsrf', 'true') - .send({ - factoryQueryType: TimelineEventsQueries.kpi, - docValueFields: [], - indexName: INDEX_NAME, - inspect: false, - eventId: ID, - wait_for_completion_timeout: '10s', - }) - .expect(200); - expect({ destinationIpCount, hostCount, processCount, sourceIpCount, userCount }).to.eql( - EXPECTED_KPI_COUNTS - ); + await retry.try(async () => { + const { + body: { destinationIpCount, hostCount, processCount, sourceIpCount, userCount }, + } = await supertest + .post('/internal/search/securitySolutionTimelineSearchStrategy/') + .set('kbn-xsrf', 'true') + .send({ + factoryQueryType: TimelineEventsQueries.kpi, + docValueFields: [], + indexName: INDEX_NAME, + inspect: false, + eventId: ID, + wait_for_completion_timeout: '10s', + }) + .expect(200); + expect({ destinationIpCount, hostCount, processCount, sourceIpCount, userCount }).to.eql( + EXPECTED_KPI_COUNTS + ); + }); }); }); } diff --git a/x-pack/test/api_integration/apis/security_solution/users.ts b/x-pack/test/api_integration/apis/security_solution/users.ts index 77b2dc4092b017..5afb2bba745a90 100644 --- a/x-pack/test/api_integration/apis/security_solution/users.ts +++ b/x-pack/test/api_integration/apis/security_solution/users.ts @@ -20,6 +20,7 @@ const TO = '3000-01-01T00:00:00.000Z'; const IP = '0.0.0.0'; export default function ({ getService }: FtrProviderContext) { + const retry = getService('retry'); const esArchiver = getService('esArchiver'); const supertest = getService('supertest'); describe('Users', () => { @@ -28,42 +29,44 @@ export default function ({ getService }: FtrProviderContext) { after(() => esArchiver.unload('auditbeat/users')); it('Ensure data is returned from auditbeat', async () => { - const { body: users } = await supertest - .post('/internal/search/securitySolutionSearchStrategy/') - .set('kbn-xsrf', 'true') - .send({ - factoryQueryType: NetworkQueries.users, - sourceId: 'default', - timerange: { - interval: '12h', - to: TO, - from: FROM, - }, - defaultIndex: ['auditbeat-users'], - docValueFields: [], - ip: IP, - flowTarget: FlowTarget.destination, - sort: { field: NetworkUsersFields.name, direction: Direction.asc }, - pagination: { - activePage: 0, - cursorStart: 0, - fakePossibleCount: 30, - querySize: 10, - }, - inspect: false, - /* We need a very long timeout to avoid returning just partial data. - ** https://github.com/elastic/kibana/blob/master/x-pack/test/api_integration/apis/search/search.ts#L18 - */ - wait_for_completion_timeout: '10s', - }) - .expect(200); - expect(users.edges.length).to.be(1); - expect(users.totalCount).to.be(1); - expect(users.edges[0].node.user!.id).to.eql(['0']); - expect(users.edges[0].node.user!.name).to.be('root'); - expect(users.edges[0].node.user!.groupId).to.eql(['0']); - expect(users.edges[0].node.user!.groupName).to.eql(['root']); - expect(users.edges[0].node.user!.count).to.be(1); + await retry.try(async () => { + const { body: users } = await supertest + .post('/internal/search/securitySolutionSearchStrategy/') + .set('kbn-xsrf', 'true') + .send({ + factoryQueryType: NetworkQueries.users, + sourceId: 'default', + timerange: { + interval: '12h', + to: TO, + from: FROM, + }, + defaultIndex: ['auditbeat-users'], + docValueFields: [], + ip: IP, + flowTarget: FlowTarget.destination, + sort: { field: NetworkUsersFields.name, direction: Direction.asc }, + pagination: { + activePage: 0, + cursorStart: 0, + fakePossibleCount: 30, + querySize: 10, + }, + inspect: false, + /* We need a very long timeout to avoid returning just partial data. + ** https://github.com/elastic/kibana/blob/master/x-pack/test/api_integration/apis/search/search.ts#L18 + */ + wait_for_completion_timeout: '10s', + }) + .expect(200); + expect(users.edges.length).to.be(1); + expect(users.totalCount).to.be(1); + expect(users.edges[0].node.user!.id).to.eql(['0']); + expect(users.edges[0].node.user!.name).to.be('root'); + expect(users.edges[0].node.user!.groupId).to.eql(['0']); + expect(users.edges[0].node.user!.groupName).to.eql(['root']); + expect(users.edges[0].node.user!.count).to.be(1); + }); }); }); }); diff --git a/x-pack/test/fleet_api_integration/apis/agents/reassign.ts b/x-pack/test/fleet_api_integration/apis/agents/reassign.ts index 627cb299f0909d..5737794eefeab0 100644 --- a/x-pack/test/fleet_api_integration/apis/agents/reassign.ts +++ b/x-pack/test/fleet_api_integration/apis/agents/reassign.ts @@ -19,11 +19,13 @@ export default function (providerContext: FtrProviderContext) { await esArchiver.load('fleet/empty_fleet_server'); }); beforeEach(async () => { + await esArchiver.unload('fleet/empty_fleet_server'); await esArchiver.load('fleet/agents'); }); setupFleetAndAgents(providerContext); afterEach(async () => { await esArchiver.unload('fleet/agents'); + await esArchiver.load('fleet/empty_fleet_server'); }); after(async () => { await esArchiver.unload('fleet/empty_fleet_server'); diff --git a/x-pack/test/fleet_api_integration/apis/agents/unenroll.ts b/x-pack/test/fleet_api_integration/apis/agents/unenroll.ts index ab765eae18ca5e..d7e16b7e7224bf 100644 --- a/x-pack/test/fleet_api_integration/apis/agents/unenroll.ts +++ b/x-pack/test/fleet_api_integration/apis/agents/unenroll.ts @@ -23,10 +23,12 @@ export default function (providerContext: FtrProviderContext) { let accessAPIKeyId: string; let outputAPIKeyId: string; before(async () => { - await esArchiver.load('fleet/agents'); + await esArchiver.load('fleet/empty_fleet_server'); }); setupFleetAndAgents(providerContext); beforeEach(async () => { + await esArchiver.unload('fleet/empty_fleet_server'); + await esArchiver.load('fleet/agents'); const { body: accessAPIKeyBody } = await esClient.security.createApiKey({ body: { name: `test access api key: ${uuid.v4()}`, @@ -63,8 +65,12 @@ export default function (providerContext: FtrProviderContext) { }, }); }); - after(async () => { + afterEach(async () => { await esArchiver.unload('fleet/agents'); + await esArchiver.load('fleet/empty_fleet_server'); + }); + after(async () => { + await esArchiver.unload('fleet/empty_fleet_server'); }); it('/agents/{agent_id}/unenroll should fail for managed policy', async () => { diff --git a/x-pack/test/fleet_api_integration/apis/agents/upgrade.ts b/x-pack/test/fleet_api_integration/apis/agents/upgrade.ts index 41232f73efa5c9..008614f075514a 100644 --- a/x-pack/test/fleet_api_integration/apis/agents/upgrade.ts +++ b/x-pack/test/fleet_api_integration/apis/agents/upgrade.ts @@ -26,12 +26,15 @@ export default function (providerContext: FtrProviderContext) { describe('fleet upgrade', () => { skipIfNoDockerRegistry(providerContext); before(async () => { - await esArchiver.loadIfNeeded('fleet/agents'); + await esArchiver.load('fleet/agents'); }); setupFleetAndAgents(providerContext); beforeEach(async () => { await esArchiver.load('fleet/agents'); }); + afterEach(async () => { + await esArchiver.unload('fleet/agents'); + }); after(async () => { await esArchiver.unload('fleet/agents'); }); diff --git a/x-pack/test/fleet_api_integration/apis/agents_setup.ts b/x-pack/test/fleet_api_integration/apis/agents_setup.ts index 700a06750d2f47..25b4e16535fdae 100644 --- a/x-pack/test/fleet_api_integration/apis/agents_setup.ts +++ b/x-pack/test/fleet_api_integration/apis/agents_setup.ts @@ -24,7 +24,7 @@ export default function (providerContext: FtrProviderContext) { after(async () => { await esArchiver.unload('empty_kibana'); - await esArchiver.load('fleet/empty_fleet_server'); + await esArchiver.unload('fleet/empty_fleet_server'); }); beforeEach(async () => { diff --git a/x-pack/test/fleet_api_integration/apis/epm/list.ts b/x-pack/test/fleet_api_integration/apis/epm/list.ts index 5a991e52bdba4e..c482f4012d2e5a 100644 --- a/x-pack/test/fleet_api_integration/apis/epm/list.ts +++ b/x-pack/test/fleet_api_integration/apis/epm/list.ts @@ -26,7 +26,7 @@ export default function (providerContext: FtrProviderContext) { }); setupFleetAndAgents(providerContext); after(async () => { - await esArchiver.load('fleet/empty_fleet_server'); + await esArchiver.unload('fleet/empty_fleet_server'); }); describe('list api tests', async () => { diff --git a/x-pack/test/fleet_api_integration/apis/fleet_setup.ts b/x-pack/test/fleet_api_integration/apis/fleet_setup.ts index c9709475d182d9..4c16a4fbd1cfa6 100644 --- a/x-pack/test/fleet_api_integration/apis/fleet_setup.ts +++ b/x-pack/test/fleet_api_integration/apis/fleet_setup.ts @@ -24,7 +24,7 @@ export default function (providerContext: FtrProviderContext) { after(async () => { await esArchiver.unload('empty_kibana'); - await esArchiver.load('fleet/empty_fleet_server'); + await esArchiver.unload('fleet/empty_fleet_server'); }); beforeEach(async () => { try { diff --git a/x-pack/test/functional/apps/api_keys/home_page.ts b/x-pack/test/functional/apps/api_keys/home_page.ts index 6191a2b8dbcfc5..be8f128359345f 100644 --- a/x-pack/test/functional/apps/api_keys/home_page.ts +++ b/x-pack/test/functional/apps/api_keys/home_page.ts @@ -5,7 +5,6 @@ * 2.0. */ -import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; export default ({ getPageObjects, getService }: FtrProviderContext) => { @@ -13,6 +12,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const log = getService('log'); const security = getService('security'); const testSubjects = getService('testSubjects'); + const find = getService('find'); describe('Home page', function () { before(async () => { @@ -31,17 +31,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { it('Loads the app', async () => { await security.testUser.setRoles(['test_api_keys']); - log.debug('Checking for section header'); - const headers = await testSubjects.findAll('noApiKeysHeader'); - if (headers.length > 0) { - expect(await headers[0].getVisibleText()).to.be('No API keys'); - const goToConsoleButton = await pageObjects.apiKeys.getGoToConsoleButton(); - expect(await goToConsoleButton.isDisplayed()).to.be(true); - } else { - // page may already contain EiTable with data, then check API Key Admin text - const description = await pageObjects.apiKeys.getApiKeyAdminDesc(); - expect(description).to.be('You are an API Key administrator.'); - } + log.debug('Checking for create API key call to action'); + await find.existsByLinkText('Create API key'); }); }); }; diff --git a/x-pack/test/functional/apps/dashboard/reporting/download_csv.ts b/x-pack/test/functional/apps/dashboard/reporting/download_csv.ts index c437cfaa8f5dc7..d4a909f6a04741 100644 --- a/x-pack/test/functional/apps/dashboard/reporting/download_csv.ts +++ b/x-pack/test/functional/apps/dashboard/reporting/download_csv.ts @@ -50,8 +50,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await testSubjects.existOrFail('csvDownloadStarted'); // validate toast panel }; - // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/96000 - describe.skip('Download CSV', () => { + describe('Download CSV', () => { before('initialize tests', async () => { log.debug('ReportingPage:initTests'); await browser.setWindowSize(1600, 850); diff --git a/x-pack/test/functional/apps/ingest_pipelines/ingest_pipelines.ts b/x-pack/test/functional/apps/ingest_pipelines/ingest_pipelines.ts index 2a51983a990bbc..3c0cdf4c8060c5 100644 --- a/x-pack/test/functional/apps/ingest_pipelines/ingest_pipelines.ts +++ b/x-pack/test/functional/apps/ingest_pipelines/ingest_pipelines.ts @@ -17,7 +17,7 @@ const PIPELINE = { export default ({ getPageObjects, getService }: FtrProviderContext) => { const pageObjects = getPageObjects(['common', 'ingestPipelines']); const log = getService('log'); - const es = getService('legacyEs'); + const es = getService('es'); describe('Ingest Pipelines', function () { this.tags('smoke'); diff --git a/x-pack/test/functional/apps/maps/embeddable/dashboard.js b/x-pack/test/functional/apps/maps/embeddable/dashboard.js index e1181119bee09a..860273bc23cc10 100644 --- a/x-pack/test/functional/apps/maps/embeddable/dashboard.js +++ b/x-pack/test/functional/apps/maps/embeddable/dashboard.js @@ -35,6 +35,7 @@ export default function ({ getPageObjects, getService }) { }); await PageObjects.common.navigateToApp('dashboard'); await PageObjects.dashboard.loadSavedDashboard('map embeddable example'); + await PageObjects.dashboard.waitForRenderComplete(); }); after(async () => { diff --git a/x-pack/test/functional/es_archives/fleet/agents/mappings.json b/x-pack/test/functional/es_archives/fleet/agents/mappings.json index 5b35fa05a51bfd..72a7368e4d0a82 100644 --- a/x-pack/test/functional/es_archives/fleet/agents/mappings.json +++ b/x-pack/test/functional/es_archives/fleet/agents/mappings.json @@ -3089,7 +3089,7 @@ ".fleet-actions": { } }, - "index": ".fleet-actions_1", + "index": ".fleet-actions-7", "mappings": { "_meta": { "migrationHash": "6527beea5a4a2f33acb599585ed4710442ece7f2" @@ -3136,7 +3136,7 @@ ".fleet-agents": { } }, - "index": ".fleet-agents_1", + "index": ".fleet-agent-7", "mappings": { "_meta": { "migrationHash": "87cab95ac988d78a78d0d66bbf05361b65dcbacf" @@ -3373,7 +3373,7 @@ ".fleet-enrollment-api-keys": { } }, - "index": ".fleet-enrollment-api-keys_1", + "index": ".fleet-enrollment-api-keys-7", "mappings": { "_meta": { "migrationHash": "06bef724726f3bea9f474a09be0a7f7881c28d4a" @@ -3422,7 +3422,7 @@ ".fleet-policies": { } }, - "index": ".fleet-policies_1", + "index": ".fleet-policies-7", "mappings": { "_meta": { "migrationHash": "c2c2a49b19562942fa7c1ff1537e66e751cdb4fa" @@ -3466,7 +3466,7 @@ ".fleet-servers": { } }, - "index": ".fleet-servers_1", + "index": ".fleet-servers-7", "mappings": { "_meta": { "migrationHash": "e2782448c7235ec9af66ca7997e867d715ac379c" diff --git a/x-pack/test/functional/es_archives/fleet/empty_fleet_server/mappings.json b/x-pack/test/functional/es_archives/fleet/empty_fleet_server/mappings.json index 73f090b6103dc4..a04b7a7dc21c7e 100644 --- a/x-pack/test/functional/es_archives/fleet/empty_fleet_server/mappings.json +++ b/x-pack/test/functional/es_archives/fleet/empty_fleet_server/mappings.json @@ -5,7 +5,7 @@ ".fleet-actions": { } }, - "index": ".fleet-actions_1", + "index": ".fleet-actions-7", "mappings": { "_meta": { "migrationHash": "6527beea5a4a2f33acb599585ed4710442ece7f2" @@ -52,7 +52,7 @@ ".fleet-agents": { } }, - "index": ".fleet-agents_1", + "index": ".fleet-agents-7", "mappings": { "_meta": { "migrationHash": "87cab95ac988d78a78d0d66bbf05361b65dcbacf" @@ -289,7 +289,7 @@ ".fleet-enrollment-api-keys": { } }, - "index": ".fleet-enrollment-api-keys_1", + "index": ".fleet-enrollment-api-keys-7", "mappings": { "_meta": { "migrationHash": "06bef724726f3bea9f474a09be0a7f7881c28d4a" @@ -338,7 +338,7 @@ ".fleet-policies": { } }, - "index": ".fleet-policies_1", + "index": ".fleet-policies-7", "mappings": { "_meta": { "migrationHash": "c2c2a49b19562942fa7c1ff1537e66e751cdb4fa" @@ -382,7 +382,7 @@ ".fleet-servers": { } }, - "index": ".fleet-servers_1", + "index": ".fleet-servers-7", "mappings": { "_meta": { "migrationHash": "e2782448c7235ec9af66ca7997e867d715ac379c" diff --git a/x-pack/test/functional/page_objects/index_lifecycle_management_page.ts b/x-pack/test/functional/page_objects/index_lifecycle_management_page.ts index f47e79260e61c8..525e0d91e2f4d8 100644 --- a/x-pack/test/functional/page_objects/index_lifecycle_management_page.ts +++ b/x-pack/test/functional/page_objects/index_lifecycle_management_page.ts @@ -22,18 +22,25 @@ export function IndexLifecycleManagementPageProvider({ getService }: FtrProvider policyName: string, warmEnabled: boolean = false, coldEnabled: boolean = false, - deletePhaseEnabled: boolean = false + deletePhaseEnabled: boolean = false, + minAges: { [key: string]: { value: string; unit: string } } = { + warm: { value: '10', unit: 'd' }, + cold: { value: '15', unit: 'd' }, + frozen: { value: '20', unit: 'd' }, + } ) { await testSubjects.setValue('policyNameField', policyName); if (warmEnabled) { await retry.try(async () => { await testSubjects.click('enablePhaseSwitch-warm'); }); + await testSubjects.setValue('warm-selectedMinimumAge', minAges.warm.value); } if (coldEnabled) { await retry.try(async () => { await testSubjects.click('enablePhaseSwitch-cold'); }); + await testSubjects.setValue('cold-selectedMinimumAge', minAges.cold.value); } if (deletePhaseEnabled) { await retry.try(async () => { @@ -48,10 +55,17 @@ export function IndexLifecycleManagementPageProvider({ getService }: FtrProvider policyName: string, warmEnabled: boolean = false, coldEnabled: boolean = false, - deletePhaseEnabled: boolean = false + deletePhaseEnabled: boolean = false, + minAges?: { [key: string]: { value: string; unit: string } } ) { await testSubjects.click('createPolicyButton'); - await this.fillNewPolicyForm(policyName, warmEnabled, coldEnabled, deletePhaseEnabled); + await this.fillNewPolicyForm( + policyName, + warmEnabled, + coldEnabled, + deletePhaseEnabled, + minAges + ); await this.saveNewPolicy(); }, diff --git a/x-pack/test/reporting_api_integration/ftr_provider_context.d.ts b/x-pack/test/reporting_api_integration/ftr_provider_context.d.ts index 809f464289ff26..671866cad6ff5d 100644 --- a/x-pack/test/reporting_api_integration/ftr_provider_context.d.ts +++ b/x-pack/test/reporting_api_integration/ftr_provider_context.d.ts @@ -6,7 +6,6 @@ */ import { GenericFtrProviderContext } from '@kbn/test/types/ftr'; -import { pageObjects } from '../functional/page_objects'; // Reporting APIs depend on UI functionality import { services } from './services'; -export type FtrProviderContext = GenericFtrProviderContext; +export type FtrProviderContext = GenericFtrProviderContext; diff --git a/x-pack/test/reporting_api_integration/reporting_and_security.config.ts b/x-pack/test/reporting_api_integration/reporting_and_security.config.ts index ddd6fe046dd31b..623799c84d8600 100644 --- a/x-pack/test/reporting_api_integration/reporting_and_security.config.ts +++ b/x-pack/test/reporting_api_integration/reporting_and_security.config.ts @@ -5,16 +5,14 @@ * 2.0. */ -// @ts-expect-error https://github.com/elastic/kibana/issues/95679 -import { esTestConfig, kbnTestConfig, kibanaServerTestUser } from '@kbn/test'; import { FtrConfigProviderContext } from '@kbn/test/types/ftr'; -import { format as formatUrl } from 'url'; +import { resolve } from 'path'; import { ReportingAPIProvider } from './services'; export default async function ({ readConfigFile }: FtrConfigProviderContext) { const apiConfig = await readConfigFile(require.resolve('../api_integration/config')); - const functionalConfig = await readConfigFile(require.resolve('../functional/config')); // Reporting API tests need a fully working UI + // config for testing network policy const testPolicyRules = [ { allow: true, protocol: 'http:' }, { allow: false, host: 'via.placeholder.com' }, @@ -24,9 +22,9 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { ]; return { - servers: apiConfig.get('servers'), + ...apiConfig.getAll(), junit: { reportName: 'X-Pack Reporting API Integration Tests' }, - testFiles: [require.resolve('./reporting_and_security')], + testFiles: [resolve(__dirname, './reporting_and_security')], services: { ...apiConfig.get('services'), reportingAPI: ReportingAPIProvider, @@ -34,22 +32,11 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { kbnTestServer: { ...apiConfig.get('kbnTestServer'), serverArgs: [ - ...functionalConfig.get('kbnTestServer.serverArgs'), - - `--elasticsearch.hosts=${formatUrl(esTestConfig.getUrlParts())}`, - `--elasticsearch.password=${kibanaServerTestUser.password}`, - `--elasticsearch.username=${kibanaServerTestUser.username}`, - `--logging.json=false`, - `--server.maxPayloadBytes=1679958`, - `--server.port=${kbnTestConfig.getPort()}`, + ...apiConfig.get('kbnTestServer.serverArgs'), + `--xpack.reporting.capture.networkPolicy.rules=${JSON.stringify(testPolicyRules)}`, `--xpack.reporting.capture.maxAttempts=1`, `--xpack.reporting.csv.maxSizeBytes=6000`, - `--xpack.reporting.queue.pollInterval=3000`, - `--xpack.security.session.idleTimeout=3600000`, - `--xpack.reporting.capture.networkPolicy.rules=${JSON.stringify(testPolicyRules)}`, ], }, - esArchiver: apiConfig.get('esArchiver'), - esTestCluster: apiConfig.get('esTestCluster'), }; } diff --git a/x-pack/test/reporting_api_integration/reporting_and_security/__snapshots__/csv_searchsource_immediate.snap b/x-pack/test/reporting_api_integration/reporting_and_security/__snapshots__/download_csv_dashboard.snap similarity index 100% rename from x-pack/test/reporting_api_integration/reporting_and_security/__snapshots__/csv_searchsource_immediate.snap rename to x-pack/test/reporting_api_integration/reporting_and_security/__snapshots__/download_csv_dashboard.snap diff --git a/x-pack/test/reporting_api_integration/reporting_and_security/constants.ts b/x-pack/test/reporting_api_integration/reporting_and_security/constants.ts deleted file mode 100644 index f765046bce9b13..00000000000000 --- a/x-pack/test/reporting_api_integration/reporting_and_security/constants.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { REPO_ROOT } from '@kbn/utils'; -import path from 'path'; - -export const OSS_KIBANA_ARCHIVE_PATH = path.resolve( - REPO_ROOT, - 'test/functional/fixtures/es_archiver/dashboard/current/kibana' -); -export const OSS_DATA_ARCHIVE_PATH = path.resolve( - REPO_ROOT, - 'test/functional/fixtures/es_archiver/dashboard/current/data' -); diff --git a/x-pack/test/reporting_api_integration/reporting_and_security/csv_searchsource_immediate.ts b/x-pack/test/reporting_api_integration/reporting_and_security/download_csv_dashboard.ts similarity index 92% rename from x-pack/test/reporting_api_integration/reporting_and_security/csv_searchsource_immediate.ts rename to x-pack/test/reporting_api_integration/reporting_and_security/download_csv_dashboard.ts index ebc7badd88f427..7f642f171b9fca 100644 --- a/x-pack/test/reporting_api_integration/reporting_and_security/csv_searchsource_immediate.ts +++ b/x-pack/test/reporting_api_integration/reporting_and_security/download_csv_dashboard.ts @@ -10,9 +10,9 @@ import supertest from 'supertest'; import { JobParamsDownloadCSV } from '../../../plugins/reporting/server/export_types/csv_searchsource_immediate/types'; import { FtrProviderContext } from '../ftr_provider_context'; -const getMockJobParams = (obj: Partial): JobParamsDownloadCSV => ({ +const getMockJobParams = (obj: any): JobParamsDownloadCSV => ({ title: `Mock CSV Title`, - ...(obj as any), + ...obj, }); // eslint-disable-next-line import/no-default-export @@ -31,23 +31,21 @@ export default function ({ getService }: FtrProviderContext) { }, }; - // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/96000 - describe.skip('CSV Generation from SearchSource', () => { + describe('CSV Generation from SearchSource', () => { before(async () => { await kibanaServer.uiSettings.update({ 'csv:quoteValues': false, 'dateFormat:tz': 'UTC', defaultIndex: 'logstash-*', }); + await reportingAPI.initEcommerce(); }); after(async () => { + await reportingAPI.teardownEcommerce(); await reportingAPI.deleteAllReports(); }); it('Exports CSV with almost all fields when using fieldsFromSource', async () => { - await esArchiver.load('reporting/ecommerce'); - await esArchiver.load('reporting/ecommerce_kibana'); - const { status: resStatus, text: resText, @@ -146,15 +144,9 @@ export default function ({ getService }: FtrProviderContext) { expect(resStatus).to.eql(200); expect(resType).to.eql('text/csv'); expectSnapshot(resText).toMatch(); - - await esArchiver.unload('reporting/ecommerce'); - await esArchiver.unload('reporting/ecommerce_kibana'); }); it('Exports CSV with all fields when using defaults', async () => { - await esArchiver.load('reporting/ecommerce'); - await esArchiver.load('reporting/ecommerce_kibana'); - const { status: resStatus, text: resText, @@ -193,15 +185,9 @@ export default function ({ getService }: FtrProviderContext) { expect(resStatus).to.eql(200); expect(resType).to.eql('text/csv'); expectSnapshot(resText).toMatch(); - - await esArchiver.unload('reporting/ecommerce'); - await esArchiver.unload('reporting/ecommerce_kibana'); }); it('Logs the error explanation if the search query returns an error', async () => { - await esArchiver.load('reporting/ecommerce'); - await esArchiver.load('reporting/ecommerce_kibana'); - const { status: resStatus, text: resText } = (await generateAPI.getCSVFromSearchSource( getMockJobParams({ searchSource: { @@ -235,9 +221,6 @@ export default function ({ getService }: FtrProviderContext) { )) as supertest.Response; expect(resStatus).to.eql(500); expectSnapshot(resText).toMatch(); - - await esArchiver.unload('reporting/ecommerce'); - await esArchiver.unload('reporting/ecommerce_kibana'); }); describe('date formatting', () => { @@ -387,9 +370,9 @@ export default function ({ getService }: FtrProviderContext) { version: true, index: '907bc200-a294-11e9-a900-ef10e0ac769e', sort: [{ date: 'desc' }], - fields: ['date', 'message', '_id', '_index'], filter: [], }, + columns: ['date', 'message', '_id', '_index'], }) ); const { status: resStatus, text: resText, type: resType } = res; @@ -435,6 +418,9 @@ export default function ({ getService }: FtrProviderContext) { }); describe('validation', () => { + after(async () => { + await reportingAPI.deleteAllReports(); + }); it('Return a 404', async () => { const { body } = (await generateAPI.getCSVFromSearchSource( getMockJobParams({ @@ -452,8 +438,7 @@ export default function ({ getService }: FtrProviderContext) { }); it(`Searches large amount of data, stops at Max Size Reached`, async () => { - await esArchiver.load('reporting/ecommerce'); - await esArchiver.load('reporting/ecommerce_kibana'); + await reportingAPI.initEcommerce(); const { status: resStatus, @@ -505,8 +490,7 @@ export default function ({ getService }: FtrProviderContext) { expect(resType).to.eql('text/csv'); expectSnapshot(resText).toMatch(); - await esArchiver.unload('reporting/ecommerce'); - await esArchiver.unload('reporting/ecommerce_kibana'); + await reportingAPI.teardownEcommerce(); }); }); }); diff --git a/x-pack/test/reporting_api_integration/reporting_and_security/csv_job_params.ts b/x-pack/test/reporting_api_integration/reporting_and_security/generate_csv_discover.ts similarity index 97% rename from x-pack/test/reporting_api_integration/reporting_and_security/csv_job_params.ts rename to x-pack/test/reporting_api_integration/reporting_and_security/generate_csv_discover.ts index b3fa9ebe46f8cb..3370eb0bb398bf 100644 --- a/x-pack/test/reporting_api_integration/reporting_and_security/csv_job_params.ts +++ b/x-pack/test/reporting_api_integration/reporting_and_security/generate_csv_discover.ts @@ -7,7 +7,7 @@ import expect from '@kbn/expect'; import supertest from 'supertest'; -import { JOB_PARAMS_RISON_CSV_DEPRECATED } from '../fixtures'; +import { JOB_PARAMS_RISON_CSV_DEPRECATED } from '../services/fixtures'; import { FtrProviderContext } from '../ftr_provider_context'; // eslint-disable-next-line import/no-default-export diff --git a/x-pack/test/reporting_api_integration/reporting_and_security/index.ts b/x-pack/test/reporting_api_integration/reporting_and_security/index.ts index b4e05e37d3fda2..78873f2097e80f 100644 --- a/x-pack/test/reporting_api_integration/reporting_and_security/index.ts +++ b/x-pack/test/reporting_api_integration/reporting_and_security/index.ts @@ -8,11 +8,20 @@ import { FtrProviderContext } from '../ftr_provider_context'; // eslint-disable-next-line import/no-default-export -export default function ({ loadTestFile }: FtrProviderContext) { +export default function ({ getService, loadTestFile }: FtrProviderContext) { describe('Reporting APIs', function () { this.tags('ciGroup2'); - loadTestFile(require.resolve('./csv_job_params')); - loadTestFile(require.resolve('./csv_searchsource_immediate')); + + before(async () => { + const reportingAPI = getService('reportingAPI'); + await reportingAPI.createDataAnalystRole(); + await reportingAPI.createDataAnalyst(); + await reportingAPI.createTestReportingUser(); + }); + + loadTestFile(require.resolve('./security_roles_privileges')); + loadTestFile(require.resolve('./download_csv_dashboard')); + loadTestFile(require.resolve('./generate_csv_discover')); loadTestFile(require.resolve('./network_policy')); loadTestFile(require.resolve('./spaces')); loadTestFile(require.resolve('./usage')); diff --git a/x-pack/test/reporting_api_integration/reporting_and_security/security_roles_privileges.ts b/x-pack/test/reporting_api_integration/reporting_and_security/security_roles_privileges.ts new file mode 100644 index 00000000000000..4dbf1b6fa5ebb8 --- /dev/null +++ b/x-pack/test/reporting_api_integration/reporting_and_security/security_roles_privileges.ts @@ -0,0 +1,192 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import supertest from 'supertest'; +import { FtrProviderContext } from '../ftr_provider_context'; + +// eslint-disable-next-line import/no-default-export +export default function ({ getService }: FtrProviderContext) { + const reportingAPI = getService('reportingAPI'); + + describe('Security Roles and Privileges for Applications', () => { + before(async () => { + await reportingAPI.initEcommerce(); + }); + after(async () => { + await reportingAPI.teardownEcommerce(); + await reportingAPI.deleteAllReports(); + }); + + describe('Dashboard: CSV download file', () => { + it('does not allow user that does not have the role-based privilege', async () => { + const res = (await reportingAPI.downloadCsv( + reportingAPI.DATA_ANALYST_USERNAME, + reportingAPI.DATA_ANALYST_PASSWORD, + { + searchSource: { + query: { query: '', language: 'kuery' }, + index: '5193f870-d861-11e9-a311-0fa548c5f953', + filter: [], + }, + browserTimezone: 'UTC', + title: 'testfooyu78yt90-', + } as any + )) as supertest.Response; + expect(res.status).to.eql(403); + }); + + it('does allow user with the role privilege', async () => { + const res = (await reportingAPI.downloadCsv( + reportingAPI.REPORTING_USER_USERNAME, + reportingAPI.REPORTING_USER_PASSWORD, + { + searchSource: { + query: { query: '', language: 'kuery' }, + index: '5193f870-d861-11e9-a311-0fa548c5f953', + filter: [], + }, + browserTimezone: 'UTC', + title: 'testfooyu78yt90-', + } as any + )) as supertest.Response; + expect(res.status).to.eql(200); + }); + }); + + describe('Dashboard: Generate PDF report', () => { + it('does not allow user that does not have the role-based privilege', async () => { + const res = await reportingAPI.generatePdf( + reportingAPI.DATA_ANALYST_USERNAME, + reportingAPI.DATA_ANALYST_PASSWORD, + { + browserTimezone: 'UTC', + title: 'test PDF disallowed', + layout: { id: 'preserve' }, + relativeUrls: ['/fooyou'], + objectType: 'dashboard', + } + ); + expect(res.status).to.eql(403); + }); + + it('does allow user with the role-based privilege', async () => { + const res = await reportingAPI.generatePdf( + reportingAPI.REPORTING_USER_USERNAME, + reportingAPI.REPORTING_USER_PASSWORD, + { + browserTimezone: 'UTC', + title: 'test PDF allowed', + layout: { id: 'preserve' }, + relativeUrls: ['/fooyou'], + objectType: 'dashboard', + } + ); + expect(res.status).to.eql(200); + }); + }); + + describe('Visualize: Generate PDF report', () => { + it('does not allow user that does not have the role-based privilege', async () => { + const res = await reportingAPI.generatePdf( + reportingAPI.DATA_ANALYST_USERNAME, + reportingAPI.DATA_ANALYST_PASSWORD, + { + browserTimezone: 'UTC', + title: 'test PDF disallowed', + layout: { id: 'preserve' }, + relativeUrls: ['/fooyou'], + objectType: 'visualization', + } + ); + expect(res.status).to.eql(403); + }); + + it('does allow user with the role-based privilege', async () => { + const res = await reportingAPI.generatePdf( + reportingAPI.REPORTING_USER_USERNAME, + reportingAPI.REPORTING_USER_PASSWORD, + { + browserTimezone: 'UTC', + title: 'test PDF allowed', + layout: { id: 'preserve' }, + relativeUrls: ['/fooyou'], + objectType: 'visualization', + } + ); + expect(res.status).to.eql(200); + }); + }); + + describe('Canvas: Generate PDF report', () => { + it('does not allow user that does not have the role-based privilege', async () => { + const res = await reportingAPI.generatePdf( + reportingAPI.DATA_ANALYST_USERNAME, + reportingAPI.DATA_ANALYST_PASSWORD, + { + browserTimezone: 'UTC', + title: 'test PDF disallowed', + layout: { id: 'preserve' }, + relativeUrls: ['/fooyou'], + objectType: 'canvas', + } + ); + expect(res.status).to.eql(403); + }); + + it('does allow user with the role-based privilege', async () => { + const res = await reportingAPI.generatePdf( + reportingAPI.REPORTING_USER_USERNAME, + reportingAPI.REPORTING_USER_PASSWORD, + { + browserTimezone: 'UTC', + title: 'test PDF allowed', + layout: { id: 'preserve' }, + relativeUrls: ['/fooyou'], + objectType: 'canvas', + } + ); + expect(res.status).to.eql(200); + }); + }); + + describe('Discover: Generate CSV report', () => { + it('does not allow user that does not have the role-based privilege', async () => { + const res = await reportingAPI.generateCsv( + reportingAPI.DATA_ANALYST_USERNAME, + reportingAPI.DATA_ANALYST_PASSWORD, + { + browserTimezone: 'UTC', + searchSource: {}, + objectType: 'search', + title: 'test disallowed', + } + ); + expect(res.status).to.eql(403); + }); + + it('does allow user with the role-based privilege', async () => { + const res = await reportingAPI.generateCsv( + reportingAPI.REPORTING_USER_USERNAME, + reportingAPI.REPORTING_USER_PASSWORD, + { + browserTimezone: 'UTC', + title: 'allowed search', + objectType: 'search', + searchSource: { + version: true, + fields: [{ field: '*', include_unmapped: 'true' }], + index: '5193f870-d861-11e9-a311-0fa548c5f953', + } as any, + columns: [], + } + ); + expect(res.status).to.eql(200); + }); + }); + }); +} diff --git a/x-pack/test/reporting_api_integration/reporting_and_security/usage.ts b/x-pack/test/reporting_api_integration/reporting_and_security/usage.ts index 2a6bf95023fb44..a69534cfc4df74 100644 --- a/x-pack/test/reporting_api_integration/reporting_and_security/usage.ts +++ b/x-pack/test/reporting_api_integration/reporting_and_security/usage.ts @@ -6,10 +6,20 @@ */ import expect from '@kbn/expect'; +import { REPO_ROOT } from '@kbn/utils'; +import path from 'path'; import { FtrProviderContext } from '../ftr_provider_context'; -import * as GenerationUrls from '../generation_urls'; -import { ReportingUsageStats } from '../services'; -import { OSS_DATA_ARCHIVE_PATH, OSS_KIBANA_ARCHIVE_PATH } from './constants'; +import * as GenerationUrls from '../services/generation_urls'; +import { ReportingUsageStats } from '../services/usage'; + +const OSS_KIBANA_ARCHIVE_PATH = path.resolve( + REPO_ROOT, + 'test/functional/fixtures/es_archiver/dashboard/current/kibana' +); +const OSS_DATA_ARCHIVE_PATH = path.resolve( + REPO_ROOT, + 'test/functional/fixtures/es_archiver/dashboard/current/data' +); interface UsageStats { reporting: ReportingUsageStats; @@ -20,6 +30,7 @@ export default function ({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const kibanaServer = getService('kibanaServer'); const reportingAPI = getService('reportingAPI'); + const retry = getService('retry'); const usageAPI = getService('usageAPI'); describe('Usage', () => { @@ -46,7 +57,10 @@ export default function ({ getService }: FtrProviderContext) { let usage: UsageStats; before(async () => { - usage = (await usageAPI.getUsageStats()) as UsageStats; + await retry.try(async () => { + // use retry for stability - usage API could return 503 + usage = (await usageAPI.getUsageStats()) as UsageStats; + }); }); it('shows reporting as available and enabled', async () => { diff --git a/x-pack/test/reporting_api_integration/reporting_without_security.config.ts b/x-pack/test/reporting_api_integration/reporting_without_security.config.ts index 20f9ff1b10592f..b962ab30876a51 100644 --- a/x-pack/test/reporting_api_integration/reporting_without_security.config.ts +++ b/x-pack/test/reporting_api_integration/reporting_without_security.config.ts @@ -5,24 +5,15 @@ * 2.0. */ -// @ts-expect-error https://github.com/elastic/kibana/issues/95679 -import { esTestConfig, kbnTestConfig } from '@kbn/test'; import { FtrConfigProviderContext } from '@kbn/test/types/ftr'; -import { format as formatUrl } from 'url'; -import { pageObjects } from '../functional/page_objects'; // Reporting APIs depend on UI functionality -import { services } from './services'; export default async function ({ readConfigFile }: FtrConfigProviderContext) { - const apiConfig = await readConfigFile(require.resolve('../api_integration/config')); + const apiConfig = await readConfigFile(require.resolve('./reporting_and_security.config')); return { - apps: { reporting: { pathname: '/app/management/insightsAndAlerting/reporting' } }, - servers: apiConfig.get('servers'), - junit: { reportName: 'X-Pack Reporting Without Security API Integration Tests' }, + ...apiConfig.getAll(), + junit: { reportName: 'X-Pack Reporting API Integration Tests Without Security Enabled' }, testFiles: [require.resolve('./reporting_without_security')], - services, - pageObjects, - esArchiver: apiConfig.get('esArchiver'), esTestCluster: { ...apiConfig.get('esTestCluster'), serverArgs: [ @@ -33,15 +24,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { }, kbnTestServer: { ...apiConfig.get('kbnTestServer'), - serverArgs: [ - `--elasticsearch.hosts=${formatUrl(esTestConfig.getUrlParts())}`, - `--logging.json=false`, - `--server.maxPayloadBytes=1679958`, - `--server.port=${kbnTestConfig.getPort()}`, - `--xpack.reporting.capture.maxAttempts=1`, - `--xpack.reporting.csv.maxSizeBytes=2850`, - `--xpack.security.enabled=false`, - ], + serverArgs: [...apiConfig.get('kbnTestServer.serverArgs'), `--xpack.security.enabled=false`], }, }; } diff --git a/x-pack/test/reporting_api_integration/reporting_without_security/index.ts b/x-pack/test/reporting_api_integration/reporting_without_security/index.ts index eb0a349df7d3e0..15960e45d4a62a 100644 --- a/x-pack/test/reporting_api_integration/reporting_without_security/index.ts +++ b/x-pack/test/reporting_api_integration/reporting_without_security/index.ts @@ -9,9 +9,8 @@ import { FtrProviderContext } from '../ftr_provider_context'; // eslint-disable-next-line import/no-default-export export default function ({ loadTestFile }: FtrProviderContext) { - describe('Reporting APIs', function () { + describe('Reporting API Integration Tests with Security disabled', function () { this.tags('ciGroup13'); loadTestFile(require.resolve('./job_apis')); - loadTestFile(require.resolve('./management')); }); } diff --git a/x-pack/test/reporting_api_integration/reporting_without_security/job_apis.ts b/x-pack/test/reporting_api_integration/reporting_without_security/job_apis.ts index 8d827f02dfd16f..194a3d6d1f5bc0 100644 --- a/x-pack/test/reporting_api_integration/reporting_without_security/job_apis.ts +++ b/x-pack/test/reporting_api_integration/reporting_without_security/job_apis.ts @@ -7,7 +7,7 @@ import expect from '@kbn/expect'; import { forOwn } from 'lodash'; -import { JOB_PARAMS_RISON_CSV_DEPRECATED } from '../fixtures'; +import { JOB_PARAMS_RISON_CSV_DEPRECATED } from '../services/fixtures'; import { FtrProviderContext } from '../ftr_provider_context'; // eslint-disable-next-line import/no-default-export @@ -16,7 +16,7 @@ export default function ({ getService }: FtrProviderContext) { const supertestNoAuth = getService('supertestWithoutAuth'); const reportingAPI = getService('reportingAPI'); - describe('Job Listing APIs, Without Security', () => { + describe('Job Listing APIs', () => { before(async () => { await esArchiver.load('reporting/logs'); await esArchiver.load('logstash_functional'); diff --git a/x-pack/test/reporting_api_integration/fixtures.ts b/x-pack/test/reporting_api_integration/services/fixtures.ts similarity index 100% rename from x-pack/test/reporting_api_integration/fixtures.ts rename to x-pack/test/reporting_api_integration/services/fixtures.ts diff --git a/x-pack/test/reporting_api_integration/generation_urls.ts b/x-pack/test/reporting_api_integration/services/generation_urls.ts similarity index 100% rename from x-pack/test/reporting_api_integration/generation_urls.ts rename to x-pack/test/reporting_api_integration/services/generation_urls.ts diff --git a/x-pack/test/reporting_api_integration/services/index.ts b/x-pack/test/reporting_api_integration/services/index.ts new file mode 100644 index 00000000000000..c0c3da4dd6ba15 --- /dev/null +++ b/x-pack/test/reporting_api_integration/services/index.ts @@ -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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { services as xpackServices } from '../../functional/services'; +import { services as apiIntegrationServices } from '../../api_integration/services'; +import { FtrProviderContext } from '../ftr_provider_context'; +import { createUsageServices } from './usage'; +import { createScenarios } from './scenarios'; + +export function ReportingAPIProvider(context: FtrProviderContext) { + return { + ...createScenarios(context), + ...createUsageServices(context), + }; +} + +export const services = { + ...xpackServices, + supertestWithoutAuth: apiIntegrationServices.supertestWithoutAuth, + usageAPI: apiIntegrationServices.usageAPI, + reportingAPI: ReportingAPIProvider, +}; diff --git a/x-pack/test/reporting_api_integration/services/scenarios.ts b/x-pack/test/reporting_api_integration/services/scenarios.ts new file mode 100644 index 00000000000000..d13deac3578baa --- /dev/null +++ b/x-pack/test/reporting_api_integration/services/scenarios.ts @@ -0,0 +1,154 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import rison, { RisonValue } from 'rison-node'; +import { JobParamsCSV } from '../../../plugins/reporting/server/export_types/csv_searchsource/types'; +import { JobParamsDownloadCSV } from '../../../plugins/reporting/server/export_types/csv_searchsource_immediate/types'; +import { JobParamsPNG } from '../../../plugins/reporting/server/export_types/png/types'; +import { JobParamsPDF } from '../../../plugins/reporting/server/export_types/printable_pdf/types'; +import { FtrProviderContext } from '../ftr_provider_context'; + +function removeWhitespace(str: string) { + return str.replace(/\s/g, ''); +} + +export function createScenarios({ getService }: Pick) { + const security = getService('security'); + const esArchiver = getService('esArchiver'); + const log = getService('log'); + const supertest = getService('supertest'); + const esSupertest = getService('esSupertest'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); + const retry = getService('retry'); + + const DATA_ANALYST_USERNAME = 'data_analyst'; + const DATA_ANALYST_PASSWORD = 'data_analyst-password'; + const REPORTING_USER_USERNAME = 'reporting_user'; + const REPORTING_USER_PASSWORD = 'reporting_user-password'; + + const initEcommerce = async () => { + await esArchiver.load('reporting/ecommerce'); + await esArchiver.load('reporting/ecommerce_kibana'); + }; + const teardownEcommerce = async () => { + await esArchiver.unload('reporting/ecommerce'); + await esArchiver.unload('reporting/ecommerce_kibana'); + await deleteAllReports(); + }; + + const createDataAnalystRole = async () => { + await security.role.create('data_analyst', { + metadata: {}, + elasticsearch: { + cluster: [], + indices: [ + { + names: ['ecommerce'], + privileges: ['read', 'view_index_metadata'], + allow_restricted_indices: false, + }, + ], + run_as: [], + }, + kibana: [{ base: ['read'], feature: {}, spaces: ['*'] }], + }); + }; + + const createDataAnalyst = async () => { + await security.user.create('data_analyst', { + password: 'data_analyst-password', + roles: ['data_analyst'], + full_name: 'Data Analyst User', + }); + }; + + const createTestReportingUser = async () => { + await security.user.create('reporting_user', { + password: 'reporting_user-password', + roles: ['data_analyst', 'reporting_user'], + full_name: 'Reporting User', + }); + }; + + const downloadCsv = async (username: string, password: string, job: JobParamsDownloadCSV) => { + return await supertestWithoutAuth + .post(`/api/reporting/v1/generate/immediate/csv_searchsource`) + .auth(username, password) + .set('kbn-xsrf', 'xxx') + .send(job); + }; + const generatePdf = async (username: string, password: string, job: JobParamsPDF) => { + const jobParams = rison.encode((job as object) as RisonValue); + return await supertestWithoutAuth + .post(`/api/reporting/generate/printablePdf`) + .auth(username, password) + .set('kbn-xsrf', 'xxx') + .send({ jobParams }); + }; + const generatePng = async (username: string, password: string, job: JobParamsPNG) => { + const jobParams = rison.encode((job as object) as RisonValue); + return await supertestWithoutAuth + .post(`/api/reporting/generate/png`) + .auth(username, password) + .set('kbn-xsrf', 'xxx') + .send({ jobParams }); + }; + const generateCsv = async (username: string, password: string, job: JobParamsCSV) => { + const jobParams = rison.encode((job as object) as RisonValue); + return await supertestWithoutAuth + .post(`/api/reporting/generate/csv_searchsource`) + .auth(username, password) + .set('kbn-xsrf', 'xxx') + .send({ jobParams }); + }; + + const postJob = async (apiPath: string): Promise => { + log.debug(`ReportingAPI.postJob(${apiPath})`); + const { body } = await supertest + .post(removeWhitespace(apiPath)) + .set('kbn-xsrf', 'xxx') + .expect(200); + return body.path; + }; + + const postJobJSON = async (apiPath: string, jobJSON: object = {}): Promise => { + log.debug(`ReportingAPI.postJobJSON((${apiPath}): ${JSON.stringify(jobJSON)})`); + const { body } = await supertest.post(apiPath).set('kbn-xsrf', 'xxx').send(jobJSON); + return body.path; + }; + + const deleteAllReports = async () => { + log.debug('ReportingAPI.deleteAllReports'); + + // ignores 409 errs and keeps retrying + await retry.tryForTime(5000, async () => { + await esSupertest + .post('/.reporting*/_delete_by_query') + .send({ query: { match_all: {} } }) + .expect(200); + }); + }; + + return { + initEcommerce, + teardownEcommerce, + DATA_ANALYST_USERNAME, + DATA_ANALYST_PASSWORD, + REPORTING_USER_USERNAME, + REPORTING_USER_PASSWORD, + createDataAnalystRole, + createDataAnalyst, + createTestReportingUser, + downloadCsv, + generatePdf, + generatePng, + generateCsv, + postJob, + postJobJSON, + deleteAllReports, + }; +} diff --git a/x-pack/test/reporting_api_integration/services.ts b/x-pack/test/reporting_api_integration/services/usage.ts similarity index 53% rename from x-pack/test/reporting_api_integration/services.ts rename to x-pack/test/reporting_api_integration/services/usage.ts index b451a6b65fc913..ababbbf03e4c10 100644 --- a/x-pack/test/reporting_api_integration/services.ts +++ b/x-pack/test/reporting_api_integration/services/usage.ts @@ -6,10 +6,7 @@ */ import expect from '@kbn/expect'; -import { indexTimestamp } from '../../plugins/reporting/server/lib/store/index_timestamp'; -import { services as xpackServices } from '../functional/services'; -import { services as apiIntegrationServices } from '../api_integration/services'; -import { FtrProviderContext } from './ftr_provider_context'; +import { FtrProviderContext } from '../ftr_provider_context'; interface PDFAppCounts { app: { @@ -38,15 +35,9 @@ interface UsageStats { reporting: ReportingUsageStats; } -function removeWhitespace(str: string) { - return str.replace(/\s/g, ''); -} - -export function ReportingAPIProvider({ getService }: FtrProviderContext) { +export function createUsageServices({ getService }: FtrProviderContext) { const log = getService('log'); const supertest = getService('supertest'); - const esSupertest = getService('esSupertest'); - const retry = getService('retry'); return { async waitForJobToFinish(downloadReportPath: string) { @@ -84,69 +75,6 @@ export function ReportingAPIProvider({ getService }: FtrProviderContext) { ); }, - async postJob(apiPath: string): Promise { - log.debug(`ReportingAPI.postJob(${apiPath})`); - const { body } = await supertest - .post(removeWhitespace(apiPath)) - .set('kbn-xsrf', 'xxx') - .expect(200); - return body.path; - }, - - async postJobJSON(apiPath: string, jobJSON: object = {}): Promise { - log.debug(`ReportingAPI.postJobJSON((${apiPath}): ${JSON.stringify(jobJSON)})`); - const { body } = await supertest.post(apiPath).set('kbn-xsrf', 'xxx').send(jobJSON); - return body.path; - }, - - /** - * - * @return {Promise} A function to call to clean up the index alias that was added. - */ - async coerceReportsIntoExistingIndex(indexName: string) { - log.debug(`ReportingAPI.coerceReportsIntoExistingIndex(${indexName})`); - - // Adding an index alias coerces the report to be generated on an existing index which means any new - // index schema won't be applied. This is important if a point release updated the schema. Reports may still - // be inserted into an existing index before the new schema is applied. - const timestampForIndex = indexTimestamp('week', '.'); - await esSupertest - .post('/_aliases') - .send({ - actions: [ - { - add: { index: indexName, alias: `.reporting-${timestampForIndex}` }, - }, - ], - }) - .expect(200); - - return async () => { - await esSupertest - .post('/_aliases') - .send({ - actions: [ - { - remove: { index: indexName, alias: `.reporting-${timestampForIndex}` }, - }, - ], - }) - .expect(200); - }; - }, - - async deleteAllReports() { - log.debug('ReportingAPI.deleteAllReports'); - - // ignores 409 errs and keeps retrying - await retry.tryForTime(5000, async () => { - await esSupertest - .post('/.reporting*/_delete_by_query') - .send({ query: { match_all: {} } }) - .expect(200); - }); - }, - expectRecentPdfAppStats(stats: UsageStats, app: string, count: number) { expect(stats.reporting.last_7_days.printable_pdf.app[app]).to.be(count); }, @@ -180,10 +108,3 @@ export function ReportingAPIProvider({ getService }: FtrProviderContext) { }, }; } - -export const services = { - ...xpackServices, - supertestWithoutAuth: apiIntegrationServices.supertestWithoutAuth, - usageAPI: apiIntegrationServices.usageAPI, - reportingAPI: ReportingAPIProvider, -}; diff --git a/x-pack/test/reporting_functional/ftr_provider_context.d.ts b/x-pack/test/reporting_functional/ftr_provider_context.d.ts new file mode 100644 index 00000000000000..58ebd710861303 --- /dev/null +++ b/x-pack/test/reporting_functional/ftr_provider_context.d.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { GenericFtrProviderContext } from '@kbn/test/types/ftr'; +import { pageObjects } from '../functional/page_objects'; +import { services } from './services'; + +export type FtrProviderContext = GenericFtrProviderContext; diff --git a/x-pack/test/reporting_functional/reporting_and_security.config.ts b/x-pack/test/reporting_functional/reporting_and_security.config.ts new file mode 100644 index 00000000000000..1f9ec5754e0bd8 --- /dev/null +++ b/x-pack/test/reporting_functional/reporting_and_security.config.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrConfigProviderContext } from '@kbn/test/types/ftr'; +import { resolve } from 'path'; +import { ReportingAPIProvider } from '../reporting_api_integration/services'; +import { ReportingFunctionalProvider } from './services'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const functionalConfig = await readConfigFile(require.resolve('../functional/config')); // Reporting API tests need a fully working UI + const apiConfig = await readConfigFile(require.resolve('../api_integration/config')); + + return { + ...apiConfig.getAll(), + ...functionalConfig.getAll(), + junit: { reportName: 'X-Pack Reporting Functional Tests' }, + testFiles: [resolve(__dirname, './reporting_and_security')], + kbnTestServer: { + ...functionalConfig.get('kbnTestServer'), + serverArgs: [ + ...functionalConfig.get('kbnTestServer.serverArgs'), + `--xpack.reporting.capture.maxAttempts=1`, + `--xpack.reporting.csv.maxSizeBytes=6000`, + ], + }, + services: { + ...apiConfig.get('services'), + ...functionalConfig.get('services'), + reportingAPI: ReportingAPIProvider, + reportingFunctional: ReportingFunctionalProvider, + }, + }; +} diff --git a/x-pack/test/reporting_functional/reporting_and_security/index.ts b/x-pack/test/reporting_functional/reporting_and_security/index.ts new file mode 100644 index 00000000000000..f3e01453b0a59b --- /dev/null +++ b/x-pack/test/reporting_functional/reporting_and_security/index.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../ftr_provider_context'; + +// eslint-disable-next-line import/no-default-export +export default function ({ getService, loadTestFile }: FtrProviderContext) { + const security = getService('security'); + const createDataAnalystRole = async () => { + await security.role.create('data_analyst', { + metadata: {}, + elasticsearch: { + cluster: [], + indices: [ + { + names: ['ecommerce'], + privileges: ['read', 'view_index_metadata'], + allow_restricted_indices: false, + }, + ], + run_as: [], + }, + kibana: [{ base: ['all'], feature: {}, spaces: ['*'] }], + }); + }; + const createDataAnalyst = async () => { + await security.user.create('data_analyst', { + password: 'data_analyst-password', + roles: ['data_analyst', 'kibana_user'], + full_name: 'a kibana user called data_a', + }); + }; + const createReportingUser = async () => { + await security.user.create('reporting_user', { + password: 'reporting_user-password', + roles: ['reporting_user', 'data_analyst', 'kibana_user'], + full_name: 'a reporting user', + }); + }; + + describe('Reporting Functional Tests with Role-based Security configuration enabled', function () { + this.tags('ciGroup2'); + + before(async () => { + await createDataAnalystRole(); + await createDataAnalyst(); + await createReportingUser(); + }); + + loadTestFile(require.resolve('./security_roles_privileges')); + loadTestFile(require.resolve('./management')); + }); +} diff --git a/x-pack/test/reporting_functional/reporting_and_security/management.ts b/x-pack/test/reporting_functional/reporting_and_security/management.ts new file mode 100644 index 00000000000000..dba16c798d4ffb --- /dev/null +++ b/x-pack/test/reporting_functional/reporting_and_security/management.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../ftr_provider_context'; + +// eslint-disable-next-line import/no-default-export +export default ({ getService, getPageObjects }: FtrProviderContext) => { + const PageObjects = getPageObjects(['common', 'reporting', 'discover']); + + const testSubjects = getService('testSubjects'); + const reportingFunctional = getService('reportingFunctional'); + + describe('Access to Management > Reporting', () => { + before(async () => { + await reportingFunctional.initEcommerce(); + }); + after(async () => { + await reportingFunctional.teardownEcommerce(); + }); + + it('does not allow user that does not have reporting_user role', async () => { + await reportingFunctional.loginDataAnalyst(); + await PageObjects.common.navigateToApp('reporting'); + await testSubjects.missingOrFail('reportJobListing'); + }); + + it('does allow user with reporting_user role', async () => { + await reportingFunctional.loginReportingUser(); + await PageObjects.common.navigateToApp('reporting'); + await testSubjects.existOrFail('reportJobListing'); + }); + }); +}; diff --git a/x-pack/test/reporting_functional/reporting_and_security/security_roles_privileges.ts b/x-pack/test/reporting_functional/reporting_and_security/security_roles_privileges.ts new file mode 100644 index 00000000000000..76ccb014778568 --- /dev/null +++ b/x-pack/test/reporting_functional/reporting_and_security/security_roles_privileges.ts @@ -0,0 +1,109 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../ftr_provider_context'; + +const DASHBOARD_TITLE = 'Ecom Dashboard'; +const SAVEDSEARCH_TITLE = 'Ecommerce Data'; +const VIS_TITLE = 'e-commerce pie chart'; +const CANVAS_TITLE = 'The Very Cool Workpad for PDF Tests'; + +// eslint-disable-next-line import/no-default-export +export default function ({ getService }: FtrProviderContext) { + const reportingFunctional = getService('reportingFunctional'); + + describe('Security with `reporting_user` built-in role', () => { + before(async () => { + await reportingFunctional.initEcommerce(); + }); + after(async () => { + await reportingFunctional.teardownEcommerce(); + }); + + describe('Dashboard: Download CSV file', () => { + it('does not allow user that does not have reporting_user role', async () => { + await reportingFunctional.loginDataAnalyst(); + await reportingFunctional.openSavedDashboard(DASHBOARD_TITLE); + await reportingFunctional.tryDashboardDownloadCsvFail('Ecommerce Data'); + }); + + it('does allow user with reporting_user role', async () => { + await reportingFunctional.loginDataAnalyst(); + await reportingFunctional.openSavedDashboard(DASHBOARD_TITLE); + await reportingFunctional.tryDashboardDownloadCsvSuccess('Ecommerce Data'); + }); + }); + + describe('Dashboard: Generate Screenshot', () => { + it('does not allow user that does not have reporting_user role', async () => { + await reportingFunctional.loginDataAnalyst(); + await reportingFunctional.openSavedDashboard(DASHBOARD_TITLE); + await reportingFunctional.tryGeneratePdfFail(); + }); + + it('does allow user with reporting_user role', async () => { + await reportingFunctional.loginReportingUser(); + await reportingFunctional.openSavedDashboard(DASHBOARD_TITLE); + await reportingFunctional.tryGeneratePdfSuccess(); + }); + }); + + describe('Discover: Generate CSV', () => { + it('does not allow user that does not have reporting_user role', async () => { + await reportingFunctional.loginDataAnalyst(); + await reportingFunctional.openSavedSearch(SAVEDSEARCH_TITLE); + await reportingFunctional.tryDiscoverCsvFail(); + }); + + it('does allow user with reporting_user role', async () => { + await reportingFunctional.loginReportingUser(); + await reportingFunctional.openSavedSearch(SAVEDSEARCH_TITLE); + await reportingFunctional.tryDiscoverCsvSuccess(); + }); + }); + + describe('Canvas: Generate PDF', () => { + const esArchiver = getService('esArchiver'); + const reportingApi = getService('reportingAPI'); + before('initialize tests', async () => { + await esArchiver.load('canvas/reports'); + }); + + after('teardown tests', async () => { + await esArchiver.unload('canvas/reports'); + await reportingApi.deleteAllReports(); + await reportingFunctional.initEcommerce(); + }); + + it('does not allow user that does not have reporting_user role', async () => { + await reportingFunctional.loginDataAnalyst(); + await reportingFunctional.openCanvasWorkpad(CANVAS_TITLE); + await reportingFunctional.tryGeneratePdfFail(); + }); + + it('does allow user with reporting_user role', async () => { + await reportingFunctional.loginReportingUser(); + await reportingFunctional.openCanvasWorkpad(CANVAS_TITLE); + await reportingFunctional.tryGeneratePdfSuccess(); + }); + }); + + describe('Visualize Editor: Generate Screenshot', () => { + it('does not allow user that does not have reporting_user role', async () => { + await reportingFunctional.loginDataAnalyst(); + await reportingFunctional.openSavedVisualization(VIS_TITLE); + await reportingFunctional.tryGeneratePdfFail(); + }); + + it('does allow user with reporting_user role', async () => { + await reportingFunctional.loginReportingUser(); + await reportingFunctional.openSavedVisualization(VIS_TITLE); + await reportingFunctional.tryGeneratePdfSuccess(); + }); + }); + }); +} diff --git a/x-pack/test/reporting_functional/reporting_without_security.config.ts b/x-pack/test/reporting_functional/reporting_without_security.config.ts new file mode 100644 index 00000000000000..b88c6115439536 --- /dev/null +++ b/x-pack/test/reporting_functional/reporting_without_security.config.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrConfigProviderContext } from '@kbn/test/types/ftr'; +import { resolve } from 'path'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const reportingConfig = await readConfigFile(require.resolve('./reporting_and_security.config')); + + return { + ...reportingConfig.getAll(), + junit: { reportName: 'X-Pack Reporting Functional Tests Without Security Enabled' }, + testFiles: [resolve(__dirname, './reporting_without_security')], + kbnTestServer: { + ...reportingConfig.get('kbnTestServer'), + serverArgs: [ + ...reportingConfig.get('kbnTestServer.serverArgs'), + `--xpack.security.enabled=false`, + ], + }, + esTestCluster: { + ...reportingConfig.get('esTestCluster'), + serverArgs: [ + ...reportingConfig.get('esTestCluster.serverArgs'), + 'node.name=UnsecuredClusterNode01', + 'xpack.security.enabled=false', + ], + }, + }; +} diff --git a/x-pack/test/reporting_functional/reporting_without_security/index.ts b/x-pack/test/reporting_functional/reporting_without_security/index.ts new file mode 100644 index 00000000000000..d1801b7e3e2e6c --- /dev/null +++ b/x-pack/test/reporting_functional/reporting_without_security/index.ts @@ -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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../ftr_provider_context'; + +// eslint-disable-next-line import/no-default-export +export default function ({ loadTestFile, getService }: FtrProviderContext) { + describe('Reporting Functional Tests with Security disabled', function () { + this.tags('ciGroup2'); + loadTestFile(require.resolve('./management')); + }); +} diff --git a/x-pack/test/reporting_api_integration/reporting_without_security/management.ts b/x-pack/test/reporting_functional/reporting_without_security/management.ts similarity index 96% rename from x-pack/test/reporting_api_integration/reporting_without_security/management.ts rename to x-pack/test/reporting_functional/reporting_without_security/management.ts index f6db20c75639da..b116bb5fe201c0 100644 --- a/x-pack/test/reporting_api_integration/reporting_without_security/management.ts +++ b/x-pack/test/reporting_functional/reporting_without_security/management.ts @@ -6,7 +6,7 @@ */ import expect from '@kbn/expect'; -import { JOB_PARAMS_ECOM_MARKDOWN } from '../fixtures'; +import { JOB_PARAMS_ECOM_MARKDOWN } from '../../reporting_api_integration/services/fixtures'; import { FtrProviderContext } from '../ftr_provider_context'; // eslint-disable-next-line import/no-default-export diff --git a/x-pack/test/reporting_functional/services/index.ts b/x-pack/test/reporting_functional/services/index.ts new file mode 100644 index 00000000000000..458ddc7c734201 --- /dev/null +++ b/x-pack/test/reporting_functional/services/index.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { services as apiServices } from '../../reporting_api_integration/services'; +import { FtrProviderContext } from '../ftr_provider_context'; +import { createScenarios } from './scenarios'; + +export function ReportingFunctionalProvider(context: FtrProviderContext) { + return createScenarios(context); +} + +export const services = { + ...apiServices, + reportingFunctional: ReportingFunctionalProvider, +}; diff --git a/x-pack/test/reporting_functional/services/scenarios.ts b/x-pack/test/reporting_functional/services/scenarios.ts new file mode 100644 index 00000000000000..a1387127ffc0ac --- /dev/null +++ b/x-pack/test/reporting_functional/services/scenarios.ts @@ -0,0 +1,165 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../ftr_provider_context'; +import { createScenarios as createAPIScenarios } from '../../reporting_api_integration/services/scenarios'; + +export function createScenarios( + context: Pick +) { + const { getService, getPageObjects } = context; + const log = getService('log'); + const testSubjects = getService('testSubjects'); + const dashboardPanelActions = getService('dashboardPanelActions'); + + const PageObjects = getPageObjects([ + 'reporting', + 'security', + 'common', + 'share', + 'visualize', + 'dashboard', + 'discover', + 'canvas', + ]); + const scenariosAPI = createAPIScenarios(context); + + const { + DATA_ANALYST_USERNAME, + DATA_ANALYST_PASSWORD, + REPORTING_USER_USERNAME, + REPORTING_USER_PASSWORD, + } = scenariosAPI; + + const loginDataAnalyst = async () => { + await PageObjects.security.forceLogout(); + await PageObjects.security.login(DATA_ANALYST_USERNAME, DATA_ANALYST_PASSWORD, { + expectSpaceSelector: false, + }); + }; + + const loginReportingUser = async () => { + await PageObjects.security.forceLogout(); + await PageObjects.security.login(REPORTING_USER_USERNAME, REPORTING_USER_PASSWORD, { + expectSpaceSelector: false, + }); + }; + + const openSavedVisualization = async (title: string) => { + log.debug(`Opening saved visualizatiton: ${title}`); + await PageObjects.common.navigateToApp('visualize'); + await PageObjects.visualize.openSavedVisualization(title); + }; + + const openSavedDashboard = async (title: string) => { + log.debug(`Opening saved dashboard: ${title}`); + await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.loadSavedDashboard(title); + }; + + const openSavedSearch = async (title: string) => { + log.debug(`Opening saved search: ${title}`); + await PageObjects.common.navigateToApp('discover'); + await PageObjects.discover.loadSavedSearch(title); + }; + + const openCanvasWorkpad = async (title: string) => { + log.debug(`Opening saved canvas workpad: ${title}`); + await PageObjects.common.navigateToApp('canvas'); + await PageObjects.canvas.loadFirstWorkpad(title); + }; + + const getSavedSearchPanel = async (savedSearchTitle: string) => { + return await testSubjects.find(`embeddablePanelHeading-${savedSearchTitle.replace(' ', '')}`); + }; + const tryDashboardDownloadCsvFail = async (savedSearchTitle: string) => { + const savedSearchPanel = await getSavedSearchPanel(savedSearchTitle); + await dashboardPanelActions.toggleContextMenu(savedSearchPanel); + await dashboardPanelActions.clickContextMenuMoreItem(); + const actionItemTestSubj = 'embeddablePanelAction-downloadCsvReport'; + await testSubjects.existOrFail(actionItemTestSubj); + /* wait for the full panel to display or else the test runner could click the wrong option! */ await testSubjects.click( + actionItemTestSubj + ); + await testSubjects.existOrFail('downloadCsvFail'); + }; + const tryDashboardDownloadCsvNotAvailable = async (savedSearchTitle: string) => { + const savedSearchPanel = await getSavedSearchPanel(savedSearchTitle); + await dashboardPanelActions.toggleContextMenu(savedSearchPanel); + await dashboardPanelActions.clickContextMenuMoreItem(); + await testSubjects.missingOrFail('embeddablePanelAction-downloadCsvReport'); + }; + const tryDashboardDownloadCsvSuccess = async (savedSearchTitle: string) => { + const savedSearchPanel = await getSavedSearchPanel(savedSearchTitle); + await dashboardPanelActions.toggleContextMenu(savedSearchPanel); + await dashboardPanelActions.clickContextMenuMoreItem(); + const actionItemTestSubj = 'embeddablePanelAction-downloadCsvReport'; + await testSubjects.existOrFail(actionItemTestSubj); + /* wait for the full panel to display or else the test runner could click the wrong option! */ await testSubjects.click( + actionItemTestSubj + ); + await testSubjects.existOrFail('csvDownloadStarted'); /* validate toast panel */ + }; + const tryDiscoverCsvFail = async () => { + await PageObjects.reporting.openCsvReportingPanel(); + await PageObjects.reporting.clickGenerateReportButton(); + const queueReportError = await PageObjects.reporting.getQueueReportError(); + expect(queueReportError).to.be(true); + }; + const tryDiscoverCsvNotAvailable = async () => { + await PageObjects.share.clickShareTopNavButton(); + await testSubjects.missingOrFail('sharePanel-CSVReports'); + }; + const tryDiscoverCsvSuccess = async () => { + await PageObjects.reporting.openCsvReportingPanel(); + expect(await PageObjects.reporting.canReportBeCreated()).to.be(true); + }; + const tryGeneratePdfFail = async () => { + await PageObjects.reporting.openPdfReportingPanel(); + await PageObjects.reporting.clickGenerateReportButton(); + const queueReportError = await PageObjects.reporting.getQueueReportError(); + expect(queueReportError).to.be(true); + }; + const tryGeneratePdfNotAvailable = async () => { + PageObjects.share.clickShareTopNavButton(); + await testSubjects.missingOrFail(`sharePanel-PDFReports`); + }; + const tryGeneratePdfSuccess = async () => { + await PageObjects.reporting.openPdfReportingPanel(); + expect(await PageObjects.reporting.canReportBeCreated()).to.be(true); + }; + const tryGeneratePngSuccess = async () => { + await PageObjects.reporting.openPngReportingPanel(); + expect(await PageObjects.reporting.canReportBeCreated()).to.be(true); + }; + const tryReportsNotAvailable = async () => { + await PageObjects.share.clickShareTopNavButton(); + await testSubjects.missingOrFail('sharePanel-Reports'); + }; + + return { + ...scenariosAPI, + openSavedVisualization, + openSavedDashboard, + openSavedSearch, + openCanvasWorkpad, + tryDashboardDownloadCsvFail, + tryDashboardDownloadCsvNotAvailable, + tryDashboardDownloadCsvSuccess, + tryDiscoverCsvFail, + tryDiscoverCsvNotAvailable, + tryDiscoverCsvSuccess, + tryGeneratePdfFail, + tryGeneratePdfNotAvailable, + tryGeneratePdfSuccess, + tryGeneratePngSuccess, + tryReportsNotAvailable, + loginDataAnalyst, + loginReportingUser, + }; +} diff --git a/yarn.lock b/yarn.lock index 0e6427d2e265ed..2aaf94250b966b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1417,10 +1417,10 @@ resolved "https://registry.yarnpkg.com/@elastic/eslint-plugin-eui/-/eslint-plugin-eui-0.0.2.tgz#56b9ef03984a05cc213772ae3713ea8ef47b0314" integrity sha512-IoxURM5zraoQ7C8f+mJb9HYSENiZGgRVcG4tLQxE61yHNNRDXtGDWTZh8N1KIHcsqN1CEPETjuzBXkJYF/fDiQ== -"@elastic/eui@31.10.0": - version "31.10.0" - resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-31.10.0.tgz#07c1ca88bc042d124e0a4d3bc38b05bb0c6dd697" - integrity sha512-OsFs3WyFNWM1Uvs0y/h1MA50Pnf1c9KBX8A7sBmcFbMRpbD033AKOzH67crK8xmqRHB3vD23Yc+IVQdB6SMEGg== +"@elastic/eui@32.0.4": + version "32.0.4" + resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-32.0.4.tgz#46c001abb162e494e2c11ea48def840b5520f1dc" + integrity sha512-NL+bzzxAB6t/BPwaXqELIAWT0wZMcHyciAq+dGS44n7ZYbGzlDgTf77hlvwUsdDhFPhpMyFHJ55rE6ZtqBX/+w== dependencies: "@types/chroma-js" "^2.0.0" "@types/lodash" "^4.14.160" @@ -1433,7 +1433,7 @@ chroma-js "^2.1.0" classnames "^2.2.6" highlight.js "^9.18.5" - lodash "^4.17.20" + lodash "^4.17.21" numeral "^2.0.6" prop-types "^15.6.0" react-ace "^7.0.5" @@ -1454,7 +1454,7 @@ tabbable "^3.0.0" text-diff "^1.0.1" unified "^9.2.0" - url-parse "^1.4.7" + url-parse "^1.5.0" uuid "^8.3.0" vfile "^4.2.0" @@ -2616,7 +2616,7 @@ version "0.0.0" uid "" -"@kbn/apm-utils@link:packages/kbn-apm-utils": +"@kbn/apm-utils@link:bazel-bin/packages/kbn-apm-utils/npm_module": version "0.0.0" uid "" @@ -2632,7 +2632,7 @@ version "0.0.0" uid "" -"@kbn/config-schema@link:packages/kbn-config-schema": +"@kbn/config-schema@link:bazel-bin/packages/kbn-config-schema/npm_module": version "0.0.0" uid "" @@ -2740,7 +2740,7 @@ version "0.0.0" uid "" -"@kbn/tinymath@link:packages/kbn-tinymath": +"@kbn/tinymath@link:bazel-bin/packages/kbn-tinymath/npm_module": version "0.0.0" uid "" @@ -9186,20 +9186,10 @@ caniuse-api@^3.0.0: lodash.memoize "^4.1.2" lodash.uniq "^4.5.0" -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001181: - version "1.0.30001202" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001202.tgz#4cb3bd5e8a808e8cd89e4e66c549989bc8137201" - integrity sha512-ZcijQNqrcF8JNLjzvEiXqX4JUYxoZa7Pvcsd9UD8Kz4TvhTonOSNRsK+qtvpVL4l6+T1Rh4LFtLfnNWg6BGWCQ== - -caniuse-lite@^1.0.30001043, caniuse-lite@^1.0.30001097, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001125, caniuse-lite@^1.0.30001135, caniuse-lite@^1.0.30001173: - version "1.0.30001179" - resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001179.tgz" - integrity sha512-blMmO0QQujuUWZKyVrD1msR4WNDAqb/UPO1Sw2WWsQ7deoM5bJiicKnWJ1Y0NS/aGINSnKPIWBMw5luX+NDUCA== - -caniuse-lite@^1.0.30001157: - version "1.0.30001164" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001164.tgz#5bbfd64ca605d43132f13cc7fdabb17c3036bfdc" - integrity sha512-G+A/tkf4bu0dSp9+duNiXc7bGds35DioCyC6vgK2m/rjA4Krpy5WeZgZyfH2f0wj2kI6yAWWucyap6oOwmY1mg== +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001043, caniuse-lite@^1.0.30001097, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001125, caniuse-lite@^1.0.30001135, caniuse-lite@^1.0.30001157, caniuse-lite@^1.0.30001173, caniuse-lite@^1.0.30001181: + version "1.0.30001208" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001208.tgz" + integrity sha512-OE5UE4+nBOro8Dyvv0lfx+SRtfVIOM9uhKqFmJeUbGriqhhStgp1A0OyBpgy3OUF8AhYCT+PVwPC1gMl2ZcQMA== capture-exit@^2.0.0: version "2.0.0" @@ -15851,14 +15841,14 @@ hooker@~0.2.3: integrity sha1-uDT3I8xKJCqmWWNFnfbZhMXT2Vk= hosted-git-info@^2.1.4: - version "2.5.0" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.5.0.tgz#6d60e34b3abbc8313062c3b798ef8d901a07af3c" - integrity sha512-pNgbURSuab90KbTqvRPsseaTxOJCZBD0a7t+haSN33piP9cCM4l0CqdzAif2hUqm716UovKB2ROmiabGAKVXyg== + version "2.8.9" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" + integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== hosted-git-info@^3.0.6: - version "3.0.7" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-3.0.7.tgz#a30727385ea85acfcee94e0aad9e368c792e036c" - integrity sha512-fWqc0IcuXs+BmE9orLDyVykAG9GJtGLGuZAAqgcckPgv5xad4AcXGIv8galtQvlwutxSlaMcdw7BUtq2EIvqCQ== + version "3.0.8" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-3.0.8.tgz#6e35d4cc87af2c5f816e4cb9ce350ba87a3f370d" + integrity sha512-aXpmwoOhRBrw6X3j0h5RloK4x1OzsxMPyxqIHyNfSe2pypkVTZFpEiRoSipPEPlMrh0HW/XsjkJ5WgnCirpNUw== dependencies: lru-cache "^6.0.0" @@ -28675,7 +28665,7 @@ url-parse-lax@^3.0.0: dependencies: prepend-http "^2.0.0" -url-parse@^1.4.3, url-parse@^1.4.7: +url-parse@^1.4.3, url-parse@^1.5.0: version "1.5.1" resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.1.tgz#d5fa9890af8a5e1f274a2c98376510f6425f6e3b" integrity sha512-HOfCOUJt7iSYzEx/UqgtwKRMC6EU91NFhsCHMv9oM03VJcVo2Qrp8T8kI9D7amFf1cu+/3CEhgb3rF9zL7k85Q==