mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 01:13:23 -04:00
Merge remote-tracking branch 'origin/master' into feature/merge-code
This commit is contained in:
commit
f48000022c
458 changed files with 19239 additions and 5348 deletions
18
.github/ISSUE_TEMPLATE/Accessibility.md
vendored
18
.github/ISSUE_TEMPLATE/Accessibility.md
vendored
|
@ -1,7 +1,8 @@
|
|||
---
|
||||
name: Accessibility Issue
|
||||
about: Issues to help Elastic meet WCAG / Section 508 compliance
|
||||
|
||||
about: Issues to help Kibana be as keyboard-navigable, screen-readable, and accessible to all, with a focus on WCAG / Section 508 compliance
|
||||
labels: accessibility
|
||||
title: (Accessibility)
|
||||
---
|
||||
|
||||
**Steps to reproduce (assumes [ChromeVox](https://chrome.google.com/webstore/detail/chromevox/kgejglhpjiefppelpmljglcjbhoiplfn) or similar)**
|
||||
|
@ -9,17 +10,20 @@ about: Issues to help Elastic meet WCAG / Section 508 compliance
|
|||
1.
|
||||
2.
|
||||
3.
|
||||
4.
|
||||
|
||||
[Screenshot here]
|
||||
|
||||
**Actual Result**
|
||||
5.
|
||||
|
||||
4.
|
||||
|
||||
**Expected Result**
|
||||
5.
|
||||
|
||||
[Link to meta issues here]
|
||||
4.
|
||||
|
||||
**Meta Issue**
|
||||
|
||||
**Kibana Version:**
|
||||
**Relevant WCAG Criteria:** (link to https://www.w3.org/WAI/WCAG21/quickref/?versions=2.0)
|
||||
|
||||
**Relevant WCAG Criteria:** [#.#.# WCAG Criterion](link to https://www.w3.org/WAI/WCAG21/quickref/?versions=2.0)
|
||||
|
||||
|
|
|
@ -1,13 +1,7 @@
|
|||
files:
|
||||
include:
|
||||
- '{src,x-pack}/**/*.s+(a|c)ss'
|
||||
ignore:
|
||||
# _only include_ rollup and security files
|
||||
- '**/x-pack/plugins/!(rollup|security)/**'
|
||||
# ignore all of src
|
||||
- '**/src/**/*'
|
||||
# ignore all node_modules
|
||||
- '**/node_modules/**'
|
||||
- 'x-pack/plugins/rollup/**/*.s+(a|c)ss'
|
||||
- 'x-pack/plugins/security/**/*.s+(a|c)ss'
|
||||
rules:
|
||||
quotes:
|
||||
- 2
|
||||
|
|
BIN
docs/images/management-upgrade-assistant-8.0.png
Normal file
BIN
docs/images/management-upgrade-assistant-8.0.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 230 KiB |
|
@ -78,3 +78,12 @@ You must first stop a rollup job before deleting it.
|
|||
|
||||
[role="screenshot"]
|
||||
image::images/management_rollup_job_details.png[][Rollup job details]
|
||||
|
||||
You can start, stop, and delete an existing rollup job, but edits are not supported.
|
||||
If you want to make any changes, delete the existing job and create a new one with
|
||||
the updated specifications. Be sure to use a different name for the new rollup job;
|
||||
reusing the same name could lead to problems with mismatched job configurations.
|
||||
More about logistical details for the {ref}/rollup-job-config.html[rollup job configuration]
|
||||
can be found in the {es} documentation.
|
||||
|
||||
|
||||
|
|
|
@ -15,10 +15,15 @@ an item for creating a rollup index pattern, if a rollup index is detected in th
|
|||
image::images/management_create_rollup_menu.png[Create index pattern menu]
|
||||
|
||||
You can match an index pattern to only rolled up data, or mix both rolled up
|
||||
and raw data to visualize all data together. An index
|
||||
pattern can match only one rolled up index, not multiple. There is no restriction
|
||||
on the number of standard indices that an index pattern can match. To match multiple indices, use a comma
|
||||
to separate the names, with no space after the comma.
|
||||
and raw data to visualize all data together. An index pattern can match only one
|
||||
rolled up index, not multiple. There is no restriction on the number of standard
|
||||
indices that an index pattern can match.
|
||||
|
||||
Combination index patterns use the same
|
||||
notation as other multiple indices in {es}. To match multiple indices to create a
|
||||
combination index pattern, use a comma to separate the names, with no space after the comma.
|
||||
The notation for wildcards (`*`) and the ability to "exclude" (`-`) also apply
|
||||
(for example, `test*,-test3`).
|
||||
|
||||
When creating an index pattern, you’re asked to set a time field for filtering.
|
||||
With a rollup index, the time filter field is the same field used for
|
||||
|
|
|
@ -1,70 +1,14 @@
|
|||
[role="xpack"]
|
||||
[[xpack-upgrade-assistant]]
|
||||
[[upgrade-assistant]]
|
||||
== Upgrade Assistant
|
||||
|
||||
The Upgrade Assistant helps you prepare to upgrade from Elasticsearch 5.x to
|
||||
Elasticsearch 6.0. It identifies deprecated settings, simplifies reindexing
|
||||
your pre-5.x indices, and upgrades the internal indices used by Kibana and
|
||||
X-Pack to the format required in 6.0.
|
||||
The Upgrade Assistant helps you prepare for your upgrade to {es} 8.0.
|
||||
To access the assistant, go to *Management > 8.0 Upgrade Assistant*.
|
||||
|
||||
To access the Upgrade Assistant, go to **Management** and click the **Upgrade
|
||||
Assistant** link in the Elasticsearch section.
|
||||
The assistant identifies the deprecated settings in your cluster and indices
|
||||
and guides you through the process of resolving issues, including reindexing.
|
||||
|
||||
[float]
|
||||
[[cluster-checkup]]
|
||||
=== Cluster Checkup
|
||||
Before upgrading to Elasticsearch 8.0, make sure that you are using the final
|
||||
7.x minor release to see the most up-to-date deprecation issues.
|
||||
|
||||
The first step in preparing to upgrade is to identify any deprecated settings
|
||||
or features you are using. The Cluster Checkup runs a series of checks
|
||||
against your cluster and indices and generates a report identifying
|
||||
any issues that you need to resolve.
|
||||
|
||||
To run the Cluster Checkup, go to the **Cluster Checkup** tab in the
|
||||
Upgrade Assistant. Issues that **must** be resolved before you can upgrade to
|
||||
Elasticsearch 6.0 appear in red as errors.
|
||||
|
||||
If the checkup finds indices that need to be reindexed, you can
|
||||
manage that process with the Reindex Helper. You can also manually reindex or
|
||||
simply delete old indices if you are sure you no longer need them.
|
||||
|
||||
[float]
|
||||
[[reindex-helper]]
|
||||
=== Reindex Helper
|
||||
If you have indices created in 2.x, you must reindex them before
|
||||
upgrading to Elasticsearch 6.0. In addition, the internal Kibana and X-Pack
|
||||
indices must be reindexed to upgrade them to the format required in 6.0.
|
||||
|
||||
To reindex indices with the Reindex Helper:
|
||||
|
||||
. **Back up your indices using {ref}/modules-snapshots.html[Snapshot and Restore].**
|
||||
. Go to the **Reindex Helper** tab in the Upgrade Assistant.
|
||||
. Click the **Reindex** button to reindex an index.
|
||||
. Monitor the reindex task that is kicked off.
|
||||
|
||||
You can run any number of reindex tasks simultaneously. The processing status
|
||||
is displayed for each index. You can stop a task by clicking **Cancel**. If
|
||||
any step in the reindex process fails, you can reset the index by clicking
|
||||
**Reset**.
|
||||
|
||||
You can also click **Refresh Indices** to remove any indices that have been
|
||||
successfully reindexed from the list.
|
||||
|
||||
Reindexing tasks continue to run when you leave the Reindex Helper. When you
|
||||
come back, click **Refresh Indices** to get the latest status of your indices.
|
||||
|
||||
NOTE: When you come back to the Reindex Helper, it shows any tasks that are
|
||||
still running. You can cancel those tasks if you need to, but you won't have
|
||||
access to the task progress and are blocked from resetting indices that fail
|
||||
reindexing. This is because the index might have been modified outside of
|
||||
your Kibana instance.
|
||||
|
||||
[float]
|
||||
[[toggle-deprecation-logger]]
|
||||
=== Toggle Deprecation Logger
|
||||
|
||||
To see the current deprecation logging status, go to the **Toggle Deprecation
|
||||
Logging** tab in the Upgrade Assistant. Deprecation Logging is enabled by
|
||||
default from Elasticsearch 5.x onward. If you have disabled deprecation logging, you
|
||||
can click **Toggle Deprecation Logging** to re-enable it. This logs any
|
||||
deprecated actions to your log directory so you can see what changes you need
|
||||
to make to your code before upgrading.
|
||||
[role="screenshot"]
|
||||
image::images/management-upgrade-assistant-8.0.png[]
|
||||
|
|
|
@ -23,6 +23,30 @@ Kibana 7.0 will only use the Node.js distribution included in the package.
|
|||
|
||||
*Impact:* There is no expected impact unless Kibana is installed in a non-standard way.
|
||||
|
||||
[float]
|
||||
=== Removed support for using PhantomJS browser for screenshots in Reporting
|
||||
*Details:* Since the first release of Kibana Reporting, PhantomJS was used as
|
||||
the headless browser to capture screenshots of Kibana dashboards and
|
||||
visualizations. In that short time, Chromium has started offering a new
|
||||
headless browser library and the PhantomJS maintainers abandoned their project.
|
||||
We started planning for a transition in 6.5.0, when we made Chromium the
|
||||
default option, but allowed users to continue using Phantom with the
|
||||
`xpack.reporting.capture.browser.type: phantom` setting. In 7.0, that setting
|
||||
will still exist for compatibility, but the only valid option will be
|
||||
`chromium`.
|
||||
|
||||
*Impact:* Before upgrading to 7.0, if you have `xpack.reporting.capture.browser.type`
|
||||
set in kibana.yml, make sure it is set to `chromium`.
|
||||
|
||||
[NOTE]
|
||||
============
|
||||
Reporting 7.0 uses a version of the Chromium headless browser that RHEL 6,
|
||||
CentOS 6.x, and other old versions of Linux derived from RHEL 6. This change
|
||||
effectively removes RHEL 6 OS server support from Kibana Reporting. Users with
|
||||
RHEL 6 must upgrade to RHEL 7 to use Kibana Reporting starting with version
|
||||
7.0.0 of the Elastic stack.
|
||||
============
|
||||
|
||||
[float]
|
||||
=== Advanced setting query:queryString:options no longer applies to filters
|
||||
*Details:* In previous versions of Kibana the Advanced Setting `query:queryString:options` was applied to both queries
|
||||
|
|
|
@ -13,17 +13,10 @@ However, these docs will be kept up-to-date to reflect the current implementatio
|
|||
|
||||
[float]
|
||||
[[reporting-nav-bar-extensions]]
|
||||
=== Nav Bar Extensions
|
||||
X-Pack uses the `NavBarExtensionsRegistryProvider` to register a navigation bar item for the
|
||||
Dashboard/Discover/Visualize applications. These all use the `export-config` AngularJS directive to display the
|
||||
Reporting options. Each of the different exportTypes require the AngularJS controller that the contains the
|
||||
`export-config` directive to implement certain methods/properties that are used when creating the {reporting} job.
|
||||
=== Share Menu Extensions
|
||||
X-Pack uses the `ShareContextMenuExtensionsRegistryProvider` to register actions in the share menu.
|
||||
|
||||
If you wish to add Reporting integration via the navigation bar that emulates the way these options are chosen for
|
||||
Dashboards, Visualize and Discover, you can reuse the `export-config` directive for the time being. Otherwise, you can
|
||||
provide a custom UI that generates the URL that is used to generate the {reporting} job.
|
||||
|
||||
This integration will likely be changing in the near future as we move away from AngularJS towards React.
|
||||
This integration will likely be changing in the near future as we move towards a unified actions abstraction across {kib}.
|
||||
|
||||
[float]
|
||||
=== Generate Job URL
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
[[reporting-getting-started]]
|
||||
== Getting Started
|
||||
|
||||
{reporting} is automatically enabled in {kib}.
|
||||
{reporting} is automatically enabled in {kib}.
|
||||
|
||||
To manually generate a report:
|
||||
|
||||
|
@ -14,20 +14,24 @@ information, see <<secure-reporting>>.
|
|||
. Open the dashboard, visualization, or saved search you want to include
|
||||
in the report.
|
||||
|
||||
. Click *Reporting* in the {kib} toolbar:
|
||||
. Click *Share* in the {kib} toolbar:
|
||||
+
|
||||
--
|
||||
[role="screenshot"]
|
||||
image:reporting/images/reporting-button.png["Reporting Button",link="reporting-button.png"]
|
||||
image:reporting/images/share-button.png["Reporting Button",link="share-button.png"]
|
||||
--
|
||||
|
||||
. Depending on the {kib} application, choose the appropriate options:
|
||||
|
||||
.. If you're on Discover, click the *Generate CSV* button.
|
||||
.. If you're on Discover:
|
||||
... Select *CSV Reports*
|
||||
|
||||
... Click the *Generate CSV* button.
|
||||
|
||||
.. If you're on Visualize or Dashboard:
|
||||
... Select *PDF Reports*
|
||||
|
||||
... Select either *Optimize PDF for printing* or *Preserve existing layout in PDF*. For an explanation of the different layout modes, see <<pdf-layout-modes, PDF Layout Modes>>.
|
||||
... Choose to enable *Optimize for printing* layout mode. For an explanation of the different layout modes, see <<pdf-layout-modes, PDF Layout Modes>>.
|
||||
|
||||
... Click the *Generate PDF* button.
|
||||
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 111 KiB |
Binary file not shown.
Before Width: | Height: | Size: 145 KiB |
BIN
docs/reporting/images/preserve-layout-switch.png
Normal file
BIN
docs/reporting/images/preserve-layout-switch.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 86 KiB |
Binary file not shown.
Before Width: | Height: | Size: 55 KiB |
BIN
docs/reporting/images/share-button.png
Normal file
BIN
docs/reporting/images/share-button.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 213 KiB |
|
@ -13,10 +13,10 @@ NOTE: On Linux, the `libfontconfig` and `libfreetype6` packages and system
|
|||
fonts are required to generate PDF reports. If no system fonts are available,
|
||||
labels are not rendered correctly in the reports.
|
||||
|
||||
The following Reporting button is found in the {kib} toolbar:
|
||||
Reporting is located in the share menu from the {kib} toolbar:
|
||||
|
||||
[role="screenshot"]
|
||||
image::reporting/images/reporting-button.png["Reporting"]
|
||||
image::reporting/images/share-button.png["Share"]
|
||||
|
||||
You can also <<automating-report-generation, generate reports automatically>>.
|
||||
|
||||
|
@ -30,4 +30,4 @@ include::pdf-layout-modes.asciidoc[]
|
|||
include::configuring-reporting.asciidoc[]
|
||||
include::chromium-sandbox.asciidoc[]
|
||||
include::reporting-troubleshooting.asciidoc[]
|
||||
include::development/index.asciidoc[]
|
||||
include::development/index.asciidoc[]
|
||||
|
|
|
@ -5,7 +5,7 @@ When creating a PDF report, there are two layout modes *Optimize PDF for printin
|
|||
|
||||
--
|
||||
[role="screenshot"]
|
||||
image:reporting/images/pdf-reporting.png["PDF Reporting",link="pdf-reporting.png"]
|
||||
image:reporting/images/preserve-layout-switch.png["PDF Reporting",link="preserve-layout-switch.png"]
|
||||
--
|
||||
|
||||
[float]
|
||||
|
@ -26,4 +26,4 @@ This will create a PDF preserving the existing layout and size of the Visualizat
|
|||
--
|
||||
[role="screenshot"]
|
||||
image:reporting/images/preserve-layout.png["Preserve existing layout in PDF",link="preserve-layout.png"]
|
||||
--
|
||||
--
|
||||
|
|
|
@ -22,8 +22,9 @@ There is currently a known limitation with the Data Table visualization that onl
|
|||
|
||||
[float]
|
||||
==== `You must install fontconfig and freetype for Reporting to work'`
|
||||
Reporting using PhantomJS, the default browser, relies on system packages. Install the appropriate fontconfig and freetype
|
||||
packages for your distribution.
|
||||
Reporting uses a headless browser on the Kibana server, which relies on some
|
||||
system packages. Install the appropriate fontconfig and freetype packages for
|
||||
your distribution.
|
||||
|
||||
[float]
|
||||
==== `Max attempts reached (3)`
|
||||
|
@ -54,11 +55,6 @@ the CAP_SYS_ADMIN capability.
|
|||
Elastic recommends that you research the feasibility of enabling unprivileged user namespaces before disabling the sandbox. An exception
|
||||
is if you are running Kibana in Docker because the container runs in a user namespace with the built-in seccomp/bpf filters.
|
||||
|
||||
[float]
|
||||
==== `spawn EACCES`
|
||||
Ensure that the `phantomjs` binary in your Kibana data directory is owned by the user who is running Kibana, that the user has the execute permission,
|
||||
and if applicable, that the filesystem is mounted with the `exec` option.
|
||||
|
||||
[float]
|
||||
==== `Caught error spawning Chromium`
|
||||
Ensure that the `headless_shell` binary located in your Kibana data directory is owned by the user who is running Kibana, that the user has the execute permission,
|
||||
|
|
|
@ -91,16 +91,8 @@ visualizations, try increasing this value.
|
|||
Defaults to `3000` (3 seconds).
|
||||
|
||||
[[xpack-reporting-browser]]`xpack.reporting.capture.browser.type`::
|
||||
Specifies the browser to use to capture screenshots. Valid options are `phantom`
|
||||
and `chromium`. When `chromium` is set, the settings specified in the <<reporting-chromium-settings, Chromium settings>>
|
||||
are respected. Defaults to `chromium`.
|
||||
|
||||
[NOTE]
|
||||
============
|
||||
Starting in 7.0, Phantom support will be removed from Kibana, and `chromium`
|
||||
will be the only valid option for the `xpack.reporting.capture.browser.type` setting.
|
||||
============
|
||||
|
||||
Specifies the browser to use to capture screenshots. This setting exists for
|
||||
backward compatibility. The only valid option is `chromium`.
|
||||
|
||||
[float]
|
||||
[[reporting-chromium-settings]]
|
||||
|
|
20
package.json
20
package.json
|
@ -37,16 +37,15 @@
|
|||
"preinstall": "node ./preinstall_check",
|
||||
"kbn": "node scripts/kbn",
|
||||
"es": "node scripts/es",
|
||||
"elasticsearch": "echo 'use `yarn es snapshot -E path.data=../data/`'",
|
||||
"test": "grunt test",
|
||||
"test:dev": "grunt test:dev",
|
||||
"test:quick": "grunt test:quick",
|
||||
"test:browser": "grunt test:browser",
|
||||
"test:jest": "node scripts/jest",
|
||||
"test:mocha": "grunt test:mocha",
|
||||
"test:ui": "echo 'use `node scripts/functional_tests`' && false",
|
||||
"test:ui:server": "echo 'use `node scripts/functional_tests_server`' && false",
|
||||
"test:ui:runner": "echo 'use `node scripts/functional_test_runner`' && false",
|
||||
"test:ui": "node scripts/functional_tests",
|
||||
"test:ui:server": "node scripts/functional_tests_server",
|
||||
"test:ui:runner": "node scripts/functional_test_runner",
|
||||
"test:server": "grunt test:server",
|
||||
"test:coverage": "grunt test:coverage",
|
||||
"checkLicenses": "grunt licenses --dev",
|
||||
|
@ -56,11 +55,12 @@
|
|||
"debug-break": "node --nolazy --inspect-brk scripts/kibana --dev",
|
||||
"precommit": "node scripts/precommit_hook",
|
||||
"karma": "karma start",
|
||||
"lint": "echo 'use `node scripts/eslint` and/or `node scripts/tslint`' && false",
|
||||
"lintroller": "echo 'use `node scripts/eslint --fix` and/or `node scripts/tslint --fix`' && false",
|
||||
"makelogs": "echo 'use `node scripts/makelogs`' && false",
|
||||
"mocha": "echo 'use `node scripts/mocha`' && false",
|
||||
"sterilize": "grunt sterilize",
|
||||
"lint": "yarn run lint:es && yarn run lint:ts && yarn run lint:sass",
|
||||
"lint:es": "node scripts/eslint",
|
||||
"lint:ts": "node scripts/tslint",
|
||||
"lint:sass": "node scripts/sasslint",
|
||||
"makelogs": "node scripts/makelogs",
|
||||
"mocha": "node scripts/mocha",
|
||||
"uiFramework:start": "cd packages/kbn-ui-framework && yarn docSiteStart",
|
||||
"uiFramework:build": "cd packages/kbn-ui-framework && yarn docSiteBuild",
|
||||
"uiFramework:createComponent": "cd packages/kbn-ui-framework && yarn createComponent",
|
||||
|
@ -397,8 +397,8 @@
|
|||
"ts-node": "^7.0.1",
|
||||
"tslint": "^5.11.0",
|
||||
"tslint-config-prettier": "^1.15.0",
|
||||
"tslint-plugin-prettier": "^2.0.0",
|
||||
"tslint-microsoft-contrib": "^6.0.0",
|
||||
"tslint-plugin-prettier": "^2.0.0",
|
||||
"typescript": "^3.0.3",
|
||||
"vinyl-fs": "^3.0.2",
|
||||
"xml2js": "^0.4.19",
|
||||
|
|
|
@ -26,7 +26,7 @@ module.exports = {
|
|||
browsers: [
|
||||
'last 2 versions',
|
||||
'> 5%',
|
||||
'Safari 7', // for PhantomJS support
|
||||
'Safari 7', // for PhantomJS support: https://github.com/elastic/kibana/issues/27136
|
||||
],
|
||||
},
|
||||
useBuiltIns: true,
|
||||
|
|
|
@ -52,6 +52,8 @@ function fromExpression(expression, parseOptions = {}, parse = parseKuery) {
|
|||
return parse(expression, parseOptions);
|
||||
}
|
||||
|
||||
// indexPattern isn't required, but if you pass one in, we can be more intelligent
|
||||
// about how we craft the queries (e.g. scripted fields)
|
||||
export function toElasticsearchQuery(node, indexPattern) {
|
||||
if (!node || !node.type || !nodeTypes[node.type]) {
|
||||
return toElasticsearchQuery(nodeTypes.function.buildNode('and', []));
|
||||
|
|
|
@ -57,12 +57,21 @@ describe('kuery functions', function () {
|
|||
expect(_.isEqual(expected, result)).to.be(true);
|
||||
});
|
||||
|
||||
it('should return an ES exists query without an index pattern', function () {
|
||||
const expected = {
|
||||
exists: { field: 'response' }
|
||||
};
|
||||
|
||||
const existsNode = nodeTypes.function.buildNode('exists', 'response');
|
||||
const result = exists.toElasticsearchQuery(existsNode);
|
||||
expect(_.isEqual(expected, result)).to.be(true);
|
||||
});
|
||||
|
||||
it('should throw an error for scripted fields', function () {
|
||||
const existsNode = nodeTypes.function.buildNode('exists', 'script string');
|
||||
expect(exists.toElasticsearchQuery)
|
||||
.withArgs(existsNode, indexPattern).to.throwException(/Exists query does not support scripted fields/);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -83,6 +83,14 @@ describe('kuery functions', function () {
|
|||
expect(result.geo_bounding_box.geo).to.have.property('bottom_right', '50.73, -135.35');
|
||||
});
|
||||
|
||||
it('should return an ES geo_bounding_box query without an index pattern', function () {
|
||||
const node = nodeTypes.function.buildNode('geoBoundingBox', 'geo', params);
|
||||
const result = geoBoundingBox.toElasticsearchQuery(node);
|
||||
expect(result).to.have.property('geo_bounding_box');
|
||||
expect(result.geo_bounding_box.geo).to.have.property('top_left', '73.12, -174.37');
|
||||
expect(result.geo_bounding_box.geo).to.have.property('bottom_right', '50.73, -135.35');
|
||||
});
|
||||
|
||||
it('should use the ignore_unmapped parameter', function () {
|
||||
const node = nodeTypes.function.buildNode('geoBoundingBox', 'geo', params);
|
||||
const result = geoBoundingBox.toElasticsearchQuery(node, indexPattern);
|
||||
|
|
|
@ -91,6 +91,17 @@ describe('kuery functions', function () {
|
|||
});
|
||||
});
|
||||
|
||||
it('should return an ES geo_polygon query without an index pattern', function () {
|
||||
const node = nodeTypes.function.buildNode('geoPolygon', 'geo', points);
|
||||
const result = geoPolygon.toElasticsearchQuery(node);
|
||||
expect(result).to.have.property('geo_polygon');
|
||||
expect(result.geo_polygon.geo).to.have.property('points');
|
||||
|
||||
result.geo_polygon.geo.points.forEach((point, index) => {
|
||||
const expectedLatLon = `${points[index].lat}, ${points[index].lon}`;
|
||||
expect(point).to.be(expectedLatLon);
|
||||
});
|
||||
});
|
||||
|
||||
it('should use the ignore_unmapped parameter', function () {
|
||||
const node = nodeTypes.function.buildNode('geoPolygon', 'geo', points);
|
||||
|
|
|
@ -143,6 +143,21 @@ describe('kuery functions', function () {
|
|||
expect(result).to.eql(expected);
|
||||
});
|
||||
|
||||
it('should return an ES match query when a concrete fieldName and value are provided without an index pattern', function () {
|
||||
const expected = {
|
||||
bool: {
|
||||
should: [
|
||||
{ match: { extension: 'jpg' } },
|
||||
],
|
||||
minimum_should_match: 1
|
||||
}
|
||||
};
|
||||
|
||||
const node = nodeTypes.function.buildNode('is', 'extension', 'jpg');
|
||||
const result = is.toElasticsearchQuery(node);
|
||||
expect(result).to.eql(expected);
|
||||
});
|
||||
|
||||
it('should support creation of phrase queries', function () {
|
||||
const expected = {
|
||||
bool: {
|
||||
|
|
|
@ -86,6 +86,28 @@ describe('kuery functions', function () {
|
|||
expect(result).to.eql(expected);
|
||||
});
|
||||
|
||||
it('should return an ES range query without an index pattern', function () {
|
||||
const expected = {
|
||||
bool: {
|
||||
should: [
|
||||
{
|
||||
range: {
|
||||
bytes: {
|
||||
gt: 1000,
|
||||
lt: 8000
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
minimum_should_match: 1
|
||||
}
|
||||
};
|
||||
|
||||
const node = nodeTypes.function.buildNode('range', 'bytes', { gt: 1000, lt: 8000 });
|
||||
const result = range.toElasticsearchQuery(node);
|
||||
expect(result).to.eql(expected);
|
||||
});
|
||||
|
||||
it('should support wildcard field names', function () {
|
||||
const expected = {
|
||||
bool: {
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { get } from 'lodash';
|
||||
import * as literal from '../node_types/literal';
|
||||
|
||||
export function buildNodeParams(fieldName) {
|
||||
|
@ -28,7 +29,7 @@ export function buildNodeParams(fieldName) {
|
|||
export function toElasticsearchQuery(node, indexPattern) {
|
||||
const { arguments: [ fieldNameArg ] } = node;
|
||||
const fieldName = literal.toElasticsearchQuery(fieldNameArg);
|
||||
const field = indexPattern.fields.find(field => field.name === fieldName);
|
||||
const field = get(indexPattern, 'fields', []).find(field => field.name === fieldName);
|
||||
|
||||
if (field && field.scripted) {
|
||||
throw new Error(`Exists query does not support scripted fields`);
|
||||
|
|
|
@ -37,7 +37,7 @@ export function buildNodeParams(fieldName, params) {
|
|||
export function toElasticsearchQuery(node, indexPattern) {
|
||||
const [ fieldNameArg, ...args ] = node.arguments;
|
||||
const fieldName = nodeTypes.literal.toElasticsearchQuery(fieldNameArg);
|
||||
const field = indexPattern.fields.find(field => field.name === fieldName);
|
||||
const field = _.get(indexPattern, 'fields', []).find(field => field.name === fieldName);
|
||||
const queryParams = args.reduce((acc, arg) => {
|
||||
const snakeArgName = _.snakeCase(arg.name);
|
||||
return {
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { get } from 'lodash';
|
||||
import { nodeTypes } from '../node_types';
|
||||
import * as ast from '../ast';
|
||||
|
||||
|
@ -35,7 +36,7 @@ export function buildNodeParams(fieldName, points) {
|
|||
export function toElasticsearchQuery(node, indexPattern) {
|
||||
const [ fieldNameArg, ...points ] = node.arguments;
|
||||
const fieldName = nodeTypes.literal.toElasticsearchQuery(fieldNameArg);
|
||||
const field = indexPattern.fields.find(field => field.name === fieldName);
|
||||
const field = get(indexPattern, 'fields', []).find(field => field.name === fieldName);
|
||||
const queryParams = {
|
||||
points: points.map(ast.toElasticsearchQuery)
|
||||
};
|
||||
|
|
|
@ -44,6 +44,7 @@ export function buildNodeParams(fieldName, value, isPhrase = false) {
|
|||
export function toElasticsearchQuery(node, indexPattern) {
|
||||
const { arguments: [ fieldNameArg, valueArg, isPhraseArg ] } = node;
|
||||
|
||||
const fieldName = ast.toElasticsearchQuery(fieldNameArg);
|
||||
const value = !_.isUndefined(valueArg) ? ast.toElasticsearchQuery(valueArg) : valueArg;
|
||||
const type = isPhraseArg.value ? 'phrase' : 'best_fields';
|
||||
|
||||
|
@ -65,7 +66,7 @@ export function toElasticsearchQuery(node, indexPattern) {
|
|||
};
|
||||
}
|
||||
|
||||
const fields = getFields(fieldNameArg, indexPattern);
|
||||
const fields = indexPattern ? getFields(fieldNameArg, indexPattern) : [];
|
||||
|
||||
// If no fields are found in the index pattern we send through the given field name as-is. We do this to preserve
|
||||
// the behaviour of lucene on dashboards where there are panels based on different index patterns that have different
|
||||
|
@ -80,7 +81,10 @@ export function toElasticsearchQuery(node, indexPattern) {
|
|||
}
|
||||
|
||||
const isExistsQuery = valueArg.type === 'wildcard' && value === '*';
|
||||
const isMatchAllQuery = isExistsQuery && fields && fields.length === indexPattern.fields.length;
|
||||
const isAllFieldsQuery =
|
||||
(fieldNameArg.type === 'wildcard' && fieldName === '*')
|
||||
|| (fields && indexPattern && fields.length === indexPattern.fields.length);
|
||||
const isMatchAllQuery = isExistsQuery && isAllFieldsQuery;
|
||||
|
||||
if (isMatchAllQuery) {
|
||||
return { match_all: {} };
|
||||
|
|
|
@ -37,7 +37,7 @@ export function buildNodeParams(fieldName, params) {
|
|||
|
||||
export function toElasticsearchQuery(node, indexPattern) {
|
||||
const [ fieldNameArg, ...args ] = node.arguments;
|
||||
const fields = getFields(fieldNameArg, indexPattern);
|
||||
const fields = indexPattern ? getFields(fieldNameArg, indexPattern) : [];
|
||||
const namedArgs = extractArguments(args);
|
||||
const queryParams = _.mapValues(namedArgs, ast.toElasticsearchQuery);
|
||||
|
||||
|
|
|
@ -31,10 +31,16 @@ const canvasPluginDirectoryName = 'canvas_plugin';
|
|||
const isDirectory = path =>
|
||||
lstat(path)
|
||||
.then(stat => stat.isDirectory())
|
||||
.catch(() => false);
|
||||
.catch(() => false); // if lstat fails, it doesn't exist and is not a directory
|
||||
|
||||
const isDirname = (p, name) => path.basename(p) === name;
|
||||
|
||||
const filterDirectories = (paths, { exclude = false } = {}) => {
|
||||
return Promise.all(paths.map(p => isDirectory(p))).then(directories => {
|
||||
return paths.filter((p, i) => (exclude ? !directories[i] : directories[i]));
|
||||
});
|
||||
};
|
||||
|
||||
const getPackagePluginPath = () => {
|
||||
let basePluginPath = path.resolve(__dirname, '..');
|
||||
|
||||
|
@ -90,19 +96,15 @@ export const getPluginPaths = type => {
|
|||
return list.concat(dir);
|
||||
}, [])
|
||||
)
|
||||
.then(possibleCanvasPlugins => {
|
||||
// Check how many are directories. If lstat fails it doesn't exist anyway.
|
||||
return Promise.all(
|
||||
// An array
|
||||
possibleCanvasPlugins.map(pluginPath => isDirectory(pluginPath))
|
||||
).then(isDirectory => possibleCanvasPlugins.filter((pluginPath, i) => isDirectory[i]));
|
||||
})
|
||||
.then(possibleCanvasPlugins => filterDirectories(possibleCanvasPlugins, { exclude: false }))
|
||||
.then(canvasPluginDirectories => {
|
||||
return Promise.all(
|
||||
canvasPluginDirectories.map(dir =>
|
||||
// Get the full path of all files in the directory
|
||||
readdir(dir).then(files => files.map(file => path.resolve(dir, file)))
|
||||
)
|
||||
).then(flatten);
|
||||
)
|
||||
.then(flatten)
|
||||
.then(files => filterDirectories(files, { exclude: true }));
|
||||
});
|
||||
};
|
||||
|
|
22
packages/kbn-ui-framework/dist/ui_framework.css
vendored
22
packages/kbn-ui-framework/dist/ui_framework.css
vendored
|
@ -2487,17 +2487,17 @@ main {
|
|||
* 1. Make seamless transition from ToolBar to Table header and contained Menu.
|
||||
* 1. Make seamless transition from Table to ToolBarFooter header.
|
||||
*/
|
||||
.kuiControlledTable .kuiTable {
|
||||
border-top: none;
|
||||
/* 1 */ }
|
||||
|
||||
.kuiControlledTable .kuiToolBarFooter {
|
||||
border-top: none;
|
||||
/* 2 */ }
|
||||
|
||||
.kuiControlledTable .kuiMenu--contained {
|
||||
border-top: none;
|
||||
/* 1 */ }
|
||||
.kuiControlledTable {
|
||||
background: #FFF; }
|
||||
.kuiControlledTable .kuiTable {
|
||||
border-top: none;
|
||||
/* 1 */ }
|
||||
.kuiControlledTable .kuiToolBarFooter {
|
||||
border-top: none;
|
||||
/* 2 */ }
|
||||
.kuiControlledTable .kuiMenu--contained {
|
||||
border-top: none;
|
||||
/* 1 */ }
|
||||
|
||||
/**
|
||||
* 1. Prevent cells from expanding based on content size. This substitutes for table-layout: fixed.
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
* 1. Make seamless transition from Table to ToolBarFooter header.
|
||||
*/
|
||||
.kuiControlledTable {
|
||||
background: $tableBackgroundColor;
|
||||
.kuiTable {
|
||||
border-top: none; /* 1 */
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
padding: 10px;
|
||||
height: 40px;
|
||||
background-color: #ffffff;
|
||||
border: $kuiBorderThin;
|
||||
}
|
||||
|
||||
.kuiToolBarFooterSection {
|
||||
|
|
|
@ -195,27 +195,21 @@ export const CleanExtraBrowsersTask = {
|
|||
async run(config, log, build) {
|
||||
const getBrowserPathsForPlatform = platform => {
|
||||
const reportingDir = 'node_modules/x-pack/plugins/reporting';
|
||||
const phantomDir = '.phantom';
|
||||
const chromiumDir = '.chromium';
|
||||
const phantomPath = p =>
|
||||
build.resolvePathForPlatform(platform, reportingDir, phantomDir, p);
|
||||
const chromiumPath = p =>
|
||||
build.resolvePathForPlatform(platform, reportingDir, chromiumDir, p);
|
||||
return platforms => {
|
||||
const paths = [];
|
||||
if (platforms.windows) {
|
||||
paths.push(phantomPath('phantomjs-*-windows.zip'));
|
||||
paths.push(chromiumPath('chromium-*-win32.zip'));
|
||||
paths.push(chromiumPath('chromium-*-windows.zip'));
|
||||
}
|
||||
|
||||
if (platforms.darwin) {
|
||||
paths.push(phantomPath('phantomjs-*-macosx.zip'));
|
||||
paths.push(chromiumPath('chromium-*-darwin.zip'));
|
||||
}
|
||||
|
||||
if (platforms.linux) {
|
||||
paths.push(phantomPath('phantomjs-*-linux-x86_64.tar.bz2'));
|
||||
paths.push(chromiumPath('chromium-*-linux.zip'));
|
||||
}
|
||||
return paths;
|
||||
|
|
|
@ -48,6 +48,10 @@ export class File {
|
|||
return this.ext === '.ts' || this.ext === '.tsx';
|
||||
}
|
||||
|
||||
public isSass() {
|
||||
return this.ext === '.sass' || this.ext === '.scss';
|
||||
}
|
||||
|
||||
public isFixture() {
|
||||
return this.relativePath.split(sep).includes('__fixtures__');
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
import { run, combineErrors } from './run';
|
||||
import * as Eslint from './eslint';
|
||||
import * as Tslint from './tslint';
|
||||
import * as Sasslint from './sasslint';
|
||||
import { getFilesForCommit, checkFileCasing } from './precommit_hook';
|
||||
|
||||
run(async ({ log }) => {
|
||||
|
@ -32,7 +33,7 @@ run(async ({ log }) => {
|
|||
errors.push(error);
|
||||
}
|
||||
|
||||
for (const Linter of [Eslint, Tslint]) {
|
||||
for (const Linter of [Eslint, Tslint, Sasslint]) {
|
||||
const filesToLint = Linter.pickFilesToLint(log, files);
|
||||
if (filesToLint.length > 0) {
|
||||
try {
|
||||
|
|
|
@ -17,10 +17,5 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import chrome from 'ui/chrome';
|
||||
const apiPrefix = chrome.addBasePath('/api/kibana');
|
||||
|
||||
export async function getRemoteClusters($http) {
|
||||
const response = await $http.get(`${apiPrefix}/clusters`);
|
||||
return response.data;
|
||||
}
|
||||
export { pickFilesToLint } from './pick_files_to_lint';
|
||||
export { lintFiles } from './lint_files';
|
59
src/dev/sasslint/lint_files.js
Normal file
59
src/dev/sasslint/lint_files.js
Normal file
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import sassLint from 'sass-lint';
|
||||
import path from 'path';
|
||||
import { createFailError } from '../run';
|
||||
|
||||
/**
|
||||
* Lints a list of files with eslint. eslint reports are written to the log
|
||||
* and a FailError is thrown when linting errors occur.
|
||||
*
|
||||
* @param {ToolingLog} log
|
||||
* @param {Array<File>} files
|
||||
* @return {undefined}
|
||||
*/
|
||||
export function lintFiles(log, files) {
|
||||
const paths = files.map(file => file.getRelativePath());
|
||||
|
||||
const report = sassLint.lintFiles(
|
||||
paths.join(', '),
|
||||
{},
|
||||
path.resolve(__dirname, '..', '..', '..', '.sass-lint.yml')
|
||||
);
|
||||
|
||||
const failTypes = Object.keys(
|
||||
report.reduce(
|
||||
(failTypes, reportEntry) => {
|
||||
if (reportEntry.warningCount > 0) failTypes.warning = true;
|
||||
if (reportEntry.errorCount > 0) failTypes.errors = true;
|
||||
return failTypes;
|
||||
},
|
||||
{}
|
||||
)
|
||||
);
|
||||
|
||||
if (!failTypes.length) {
|
||||
log.success('[sasslint] %d files linted successfully', files.length);
|
||||
return;
|
||||
}
|
||||
|
||||
log.error(sassLint.format(report));
|
||||
throw createFailError(`[sasslint] ${failTypes.join(' & ')}`, 1);
|
||||
}
|
44
src/dev/sasslint/pick_files_to_lint.js
Normal file
44
src/dev/sasslint/pick_files_to_lint.js
Normal file
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import fs from 'fs';
|
||||
import { safeLoad } from 'js-yaml';
|
||||
import { makeRe } from 'minimatch';
|
||||
import path from 'path';
|
||||
|
||||
// load the include globs from .sass-lint.yml and convert them to regular expressions for filtering files
|
||||
const sassLintPath = path.resolve(__dirname, '..', '..', '..', '.sass-lint.yml');
|
||||
const sassLintConfig = safeLoad(fs.readFileSync(sassLintPath));
|
||||
const { files: { include: includeGlobs } } = sassLintConfig;
|
||||
const includeRegex = includeGlobs.map(glob => makeRe(glob));
|
||||
|
||||
function matchesInclude(file) {
|
||||
for (let i = 0; i < includeRegex.length; i++) {
|
||||
if (includeRegex[i].test(file.relativePath)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function pickFilesToLint(log, files) {
|
||||
return files
|
||||
.filter(file => file.isSass())
|
||||
.filter(matchesInclude);
|
||||
}
|
|
@ -48,8 +48,7 @@ const buildUiExports = _.once(async () => {
|
|||
* Deletes all indices that start with `.kibana`
|
||||
*/
|
||||
export async function deleteKibanaIndices({ client, stats, log }) {
|
||||
const kibanaIndices = await client.cat.indices({ index: '.kibana*', format: 'json' });
|
||||
const indexNames = kibanaIndices.map(x => x.index);
|
||||
const indexNames = await fetchKibanaIndices(client);
|
||||
if (!indexNames.length) {
|
||||
return;
|
||||
}
|
||||
|
@ -152,6 +151,20 @@ export async function createDefaultSpace({ index, client }) {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrations mean that the Kibana index will look something like:
|
||||
* .kibana, .kibana_1, .kibana_323, etc. This finds all indices starting
|
||||
* with .kibana, then filters out any that aren't actually Kibana's core
|
||||
* index (e.g. we don't want to remove .kibana_task_manager or the like).
|
||||
*
|
||||
* @param {string} index
|
||||
*/
|
||||
async function fetchKibanaIndices(client) {
|
||||
const kibanaIndices = await client.cat.indices({ index: '.kibana*', format: 'json' });
|
||||
const isKibanaIndex = (index) => (/^\.kibana(:?_\d*)?$/).test(index);
|
||||
return kibanaIndices.map(x => x.index).filter(isKibanaIndex);
|
||||
}
|
||||
|
||||
export async function cleanKibanaIndices({ client, stats, log, kibanaUrl }) {
|
||||
if (!await isSpacesEnabled({ kibanaUrl })) {
|
||||
return await deleteKibanaIndices({
|
||||
|
|
|
@ -75,7 +75,6 @@ describe('server createHandlers', () => {
|
|||
it('provides helper methods and properties', () => {
|
||||
expect(handlers).to.have.property('environment', 'server');
|
||||
expect(handlers).to.have.property('serverUri');
|
||||
expect(handlers).to.have.property('httpHeaders', mockRequest.headers);
|
||||
expect(handlers).to.have.property('elasticsearchClient');
|
||||
});
|
||||
|
||||
|
|
|
@ -27,14 +27,10 @@ export const createHandlers = (request, server) => {
|
|||
|
||||
return {
|
||||
environment: 'server',
|
||||
// TODO: https://github.com/elastic/kibana/issues/27437 - A temporary measure to allow the timelion data source to negotiate secure connections to the Kibana server, to be removed by 6.7
|
||||
// See https://github.com/elastic/kibana/pull/26809 and https://github.com/elastic/kibana/issues/26812
|
||||
__dangerouslyUnsupportedSslConfig: server.config().get('server.ssl'),
|
||||
serverUri:
|
||||
config.has('server.rewriteBasePath') && config.get('server.rewriteBasePath')
|
||||
? `${server.info.uri}${config.get('server.basePath')}`
|
||||
: server.info.uri,
|
||||
httpHeaders: request.headers,
|
||||
elasticsearchClient: async (...args) => {
|
||||
// check if the session is valid because continuing to use it
|
||||
if (isSecurityEnabled(server)) {
|
||||
|
|
|
@ -31,7 +31,6 @@ import { managementApi } from './server/routes/api/management';
|
|||
import { scriptsApi } from './server/routes/api/scripts';
|
||||
import { registerSuggestionsApi } from './server/routes/api/suggestions';
|
||||
import { registerKqlTelemetryApi } from './server/routes/api/kql_telemetry';
|
||||
import { registerClustersRoute } from './server/routes/api/remote_info';
|
||||
import { registerFieldFormats } from './server/field_formats/register';
|
||||
import { registerTutorials } from './server/tutorials/register';
|
||||
import * as systemApi from './server/lib/system_api';
|
||||
|
@ -190,7 +189,6 @@ export default function (kibana) {
|
|||
registerFieldFormats(server);
|
||||
registerTutorials(server);
|
||||
makeKQLUsageCollector(server);
|
||||
registerClustersRoute(server);
|
||||
server.expose('systemApi', systemApi);
|
||||
server.expose('handleEsError', handleEsError);
|
||||
server.injectUiAppVars('kibana', () => injectVars(server));
|
||||
|
|
|
@ -11,14 +11,14 @@ kbn-management-objects-view {
|
|||
}
|
||||
|
||||
// SASSTODO: Remove when Kibana has a proper background color
|
||||
.tab-account, .tab-management {
|
||||
background-color: $euiColorEmptyShade;
|
||||
kbn-management-objects, kbn-management-app, .tab-management {
|
||||
background: $euiColorLightestShade;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
// SASSTODO: Remove when Kibana has a proper background color
|
||||
kbn-management-objects, kbn-management-app {
|
||||
background: $euiColorLightestShade;
|
||||
min-height: 100vh;
|
||||
#management-landing {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.kbn-management-tab:first-letter {
|
||||
|
|
|
@ -1,42 +1,4 @@
|
|||
<div class="app-container">
|
||||
<!-- Local nav. -->
|
||||
<kbn-top-nav name="management-subnav" data-test-subj="managementNav">
|
||||
<!-- Transcluded elements. -->
|
||||
<div data-transclude-slots>
|
||||
<!-- Breadcrumbs. -->
|
||||
<bread-crumbs
|
||||
data-transclude-slot="topLeftCorner"
|
||||
omit-current-page="true"
|
||||
use-links="true"
|
||||
omit-pages="omitPages"
|
||||
page-title="pageTitle"
|
||||
></bread-crumbs>
|
||||
|
||||
<!-- Tabs. -->
|
||||
<div data-transclude-slot="bottomRow" class="kuiLocalTabs" role="tablist" ng-show="!sectionName || section.visibleItems.length > 0">
|
||||
<h2 class="kuiLocalTab" ng-if="!sectionName" id="tabHeader" tabindex="0" role="tab">
|
||||
{{::section.display}}
|
||||
</h2>
|
||||
<a
|
||||
role="tab"
|
||||
ng-if="sectionName"
|
||||
ng-repeat="item in section.visibleItems"
|
||||
class="kuiLocalTab"
|
||||
ng-class="{ 'kuiLocalTab-isSelected': item.active, 'kuiLocalTab-isDisabled': !item.active && (item.disabled || !item.url) }"
|
||||
kbn-href="{{::item.disabled ? '' : item.url}}"
|
||||
data-test-subj="{{::item.name}}"
|
||||
tooltip="{{::item.tooltip}}"
|
||||
tooltip-placement="bottom"
|
||||
tooltip-popup-delay="400"
|
||||
tooltip-append-to-body="1"
|
||||
aria-selected="{{!!item.active}}"
|
||||
aria-disabled="{{!!item.disabled}}"
|
||||
>
|
||||
{{::item.display}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</kbn-top-nav>
|
||||
|
||||
<main class="management-container" ng-transclude></main>
|
||||
</div>
|
||||
<div class="app-container euiPage">
|
||||
<div id="management-sidenav" class="euiPageSideBar" style="position: static;"></div>
|
||||
<main class="management-container euiPageBody euiPageBody--restrictWidth-default" ng-transclude></main>
|
||||
</div>
|
|
@ -17,6 +17,10 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { render, unmountComponentAtNode } from 'react-dom';
|
||||
import { I18nProvider, FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
import './sections';
|
||||
import 'ui/filters/start_from';
|
||||
import 'ui/field_editor';
|
||||
|
@ -24,11 +28,15 @@ import uiRoutes from 'ui/routes';
|
|||
import { uiModules } from 'ui/modules';
|
||||
import appTemplate from './app.html';
|
||||
import landingTemplate from './landing.html';
|
||||
import { management, MANAGEMENT_BREADCRUMB } from 'ui/management';
|
||||
import { management, SidebarNav, MANAGEMENT_BREADCRUMB } from 'ui/management';
|
||||
import { FeatureCatalogueRegistryProvider, FeatureCatalogueCategory } from 'ui/registry/feature_catalogue';
|
||||
import { timefilter } from 'ui/timefilter';
|
||||
import { EuiPageContent, EuiTitle, EuiText, EuiSpacer, EuiIcon, EuiHorizontalRule } from '@elastic/eui';
|
||||
import 'ui/kbn_top_nav';
|
||||
|
||||
const SIDENAV_ID = 'management-sidenav';
|
||||
const LANDING_ID = 'management-landing';
|
||||
|
||||
uiRoutes
|
||||
.when('/management', {
|
||||
template: landingTemplate,
|
||||
|
@ -46,6 +54,78 @@ require('ui/index_patterns/route_setup/load_default')({
|
|||
whenMissingRedirectTo: '/management/kibana/index'
|
||||
});
|
||||
|
||||
export function updateLandingPage(version) {
|
||||
const node = document.getElementById(LANDING_ID);
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
|
||||
render(
|
||||
<EuiPageContent verticalPosition="center" horizontalPosition="center">
|
||||
<I18nProvider>
|
||||
<div>
|
||||
<div className="eui-textCenter">
|
||||
<EuiIcon type="managementApp" size="xxl" />
|
||||
<EuiSpacer />
|
||||
<EuiTitle>
|
||||
<h1>
|
||||
<FormattedMessage
|
||||
id="kbn.management.landing.header"
|
||||
defaultMessage="Kibana {version} management"
|
||||
values={{ version }}
|
||||
/>
|
||||
</h1>
|
||||
</EuiTitle>
|
||||
<EuiText>
|
||||
<FormattedMessage
|
||||
id="kbn.management.landing.subhead"
|
||||
defaultMessage="Manage your indices, index patterns, saved objects, Kibana settings, and more."
|
||||
/>
|
||||
</EuiText>
|
||||
</div>
|
||||
|
||||
<EuiHorizontalRule />
|
||||
|
||||
<EuiText color="subdued" size="s" textAlign="center">
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="kbn.management.landing.text"
|
||||
defaultMessage="A full list of tools can be found in the left menu"
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
</div>
|
||||
</I18nProvider>
|
||||
</EuiPageContent>,
|
||||
node,
|
||||
);
|
||||
}
|
||||
|
||||
export function updateSidebar(
|
||||
items, id
|
||||
) {
|
||||
const node = document.getElementById(SIDENAV_ID);
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
|
||||
render(
|
||||
<I18nProvider>
|
||||
<SidebarNav
|
||||
sections={items}
|
||||
selectedId={id}
|
||||
style={{ width: 192 }}
|
||||
/>
|
||||
</I18nProvider>,
|
||||
node,
|
||||
);
|
||||
}
|
||||
|
||||
export const destroyReact = id => {
|
||||
const node = document.getElementById(id);
|
||||
node && unmountComponentAtNode(node);
|
||||
};
|
||||
|
||||
uiModules
|
||||
.get('apps/management')
|
||||
.directive('kbnManagementApp', function (Private, $location) {
|
||||
|
@ -70,6 +150,13 @@ uiModules
|
|||
item.active = `#${$location.path()}`.indexOf(item.url) > -1;
|
||||
});
|
||||
}
|
||||
|
||||
updateSidebar($scope.sections, $scope.section.id);
|
||||
$scope.$on('$destroy', () => destroyReact(SIDENAV_ID));
|
||||
management.addListener(() => updateSidebar(management.items.inOrder, $scope.section.id));
|
||||
|
||||
updateLandingPage($scope.$root.chrome.getKibanaVersion());
|
||||
$scope.$on('$destroy', () => destroyReact(LANDING_ID));
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
@ -1,50 +1,3 @@
|
|||
<kbn-management-app>
|
||||
<kbn-management-landing>
|
||||
<!-- General info -->
|
||||
<div class="page-row">
|
||||
<div class="page-row-text">Version: {{::kbnVersion}}</div>
|
||||
</div>
|
||||
|
||||
<!-- Management sections for the ES stack -->
|
||||
<div
|
||||
ng-if="section.visibleItems.length > 0"
|
||||
ng-repeat="section in sections"
|
||||
class="page-row"
|
||||
>
|
||||
<div class="kuiPanel mgtPanel" role="group">
|
||||
<div class="kuiPanelHeader">
|
||||
<div class="kuiPanelHeaderSection">
|
||||
<icon type="'{{::section.icon}}'" size="'l'"></icon>
|
||||
<h3 class="kuiPanelHeader__title">
|
||||
{{::section.display}}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="kuiPanelBody mgtPanel__body">
|
||||
<div class="row">
|
||||
<ul class="list-unstyled">
|
||||
<li
|
||||
class="col-xs-4 col-md-3 mgtPanel__item"
|
||||
ng-repeat="item in section.visibleItems"
|
||||
>
|
||||
<a
|
||||
data-test-subj="{{::item.id}}"
|
||||
class="euiLink euiLink--primary mgtPanel__link"
|
||||
ng-class="{ 'mgtPanel__link--disabled': item.disabled || !item.url }"
|
||||
kbn-href="{{::item.disabled ? '' : item.url}}"
|
||||
tooltip="{{::item.tooltip}}"
|
||||
tooltip-placement="bottom"
|
||||
tooltip-popup-delay="400"
|
||||
tooltip-append-to-body="1"
|
||||
>
|
||||
{{::item.display}}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</kbn-management-landing>
|
||||
<div id="management-landing"></div>
|
||||
</kbn-management-app>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<kbn-management-app section="kibana">
|
||||
<kbn-management-app section="kibana/indices">
|
||||
<kbn-management-indices>
|
||||
<div id="createIndexPatternReact"></div>
|
||||
</kbn-management-indices>
|
||||
|
|
|
@ -1,131 +1,75 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`EmptyState should render normally 1`] = `
|
||||
<EuiPanel
|
||||
grow={true}
|
||||
hasShadow={false}
|
||||
paddingSize="l"
|
||||
>
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
component="div"
|
||||
direction="row"
|
||||
gutterSize="l"
|
||||
justifyContent="center"
|
||||
responsive={true}
|
||||
wrap={false}
|
||||
<div>
|
||||
<EuiCallOut
|
||||
color="warning"
|
||||
size="m"
|
||||
title={
|
||||
<FormattedMessage
|
||||
defaultMessage="Couldn't find any Elasticsearch data"
|
||||
id="kbn.management.createIndexPattern.emptyStateHeader"
|
||||
values={Object {}}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiFlexItem
|
||||
component="div"
|
||||
grow={false}
|
||||
<p>
|
||||
<FormattedMessage
|
||||
defaultMessage="{needToIndex} {learnHowLink} or {getStartedLink}"
|
||||
id="kbn.management.createIndexPattern.emptyStateLabel.emptyStateDetail"
|
||||
values={
|
||||
Object {
|
||||
"getStartedLink": <EuiLink
|
||||
color="primary"
|
||||
href="#/home/tutorial_directory/sampleData"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="get started with some sample data sets."
|
||||
id="kbn.management.createIndexPattern.emptyStateLabel.getStartedLink"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiLink>,
|
||||
"learnHowLink": <EuiLink
|
||||
color="primary"
|
||||
href="#/home/tutorial_directory"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Learn how"
|
||||
id="kbn.management.createIndexPattern.emptyStateLabel.learnHowLink"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiLink>,
|
||||
"needToIndex": <EuiTextColor
|
||||
color="subdued"
|
||||
component="span"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="You'll need to index some data into Elasticsearch before you can create an index pattern."
|
||||
id="kbn.management.createIndexPattern.emptyStateLabel.needToIndexLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiTextColor>,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</p>
|
||||
<EuiButton
|
||||
color="warning"
|
||||
data-test-subj="refreshIndicesButton"
|
||||
fill={false}
|
||||
iconSide="left"
|
||||
iconType="refresh"
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<EuiTitle
|
||||
size="m"
|
||||
textTransform="none"
|
||||
>
|
||||
<EuiTextColor
|
||||
color="subdued"
|
||||
component="span"
|
||||
>
|
||||
<h2
|
||||
style={
|
||||
Object {
|
||||
"textAlign": "center",
|
||||
}
|
||||
}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Couldn't find any Elasticsearch data"
|
||||
id="kbn.management.createIndexPattern.emptyStateHeader"
|
||||
values={Object {}}
|
||||
/>
|
||||
</h2>
|
||||
</EuiTextColor>
|
||||
</EuiTitle>
|
||||
<EuiSpacer
|
||||
size="s"
|
||||
<FormattedMessage
|
||||
defaultMessage="Check for new data"
|
||||
id="kbn.management.createIndexPattern.emptyState.checkDataButton"
|
||||
values={Object {}}
|
||||
/>
|
||||
<EuiText
|
||||
grow={true}
|
||||
size="m"
|
||||
>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
defaultMessage="{needToIndex} {learnHowLink} or {getStartedLink}"
|
||||
id="kbn.management.createIndexPattern.emptyStateLabel.emptyStateDetail"
|
||||
values={
|
||||
Object {
|
||||
"getStartedLink": <EuiLink
|
||||
color="primary"
|
||||
href="#/home/tutorial_directory/sampleData"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="get started with some sample data sets."
|
||||
id="kbn.management.createIndexPattern.emptyStateLabel.getStartedLink"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiLink>,
|
||||
"learnHowLink": <EuiLink
|
||||
color="primary"
|
||||
href="#/home/tutorial_directory"
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Learn how"
|
||||
id="kbn.management.createIndexPattern.emptyStateLabel.learnHowLink"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiLink>,
|
||||
"needToIndex": <EuiTextColor
|
||||
color="subdued"
|
||||
component="span"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="You'll need to index some data into Elasticsearch before you can create an index pattern."
|
||||
id="kbn.management.createIndexPattern.emptyStateLabel.needToIndexLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiTextColor>,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
<EuiSpacer
|
||||
size="m"
|
||||
/>
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
component="div"
|
||||
direction="row"
|
||||
gutterSize="l"
|
||||
justifyContent="center"
|
||||
responsive={true}
|
||||
wrap={false}
|
||||
>
|
||||
<EuiFlexItem
|
||||
component="div"
|
||||
grow={false}
|
||||
>
|
||||
<EuiButton
|
||||
color="primary"
|
||||
data-test-subj="refreshIndicesButton"
|
||||
fill={false}
|
||||
iconSide="left"
|
||||
iconType="refresh"
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Check for new data"
|
||||
id="kbn.management.createIndexPattern.emptyState.checkDataButton"
|
||||
values={Object {}}
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
</EuiButton>
|
||||
</EuiCallOut>
|
||||
</div>
|
||||
`;
|
||||
|
|
|
@ -21,13 +21,8 @@ import React from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
|
||||
import {
|
||||
EuiPanel,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiTitle,
|
||||
EuiText,
|
||||
EuiCallOut,
|
||||
EuiTextColor,
|
||||
EuiSpacer,
|
||||
EuiLink,
|
||||
EuiButton,
|
||||
} from '@elastic/eui';
|
||||
|
@ -37,74 +32,63 @@ import { FormattedMessage } from '@kbn/i18n/react';
|
|||
export const EmptyState = ({
|
||||
onRefresh,
|
||||
}) => (
|
||||
<EuiPanel paddingSize="l">
|
||||
<EuiFlexGroup justifyContent="center" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiTitle>
|
||||
<EuiTextColor color="subdued">
|
||||
<h2 style={{ textAlign: 'center' }}>
|
||||
<FormattedMessage
|
||||
id="kbn.management.createIndexPattern.emptyStateHeader"
|
||||
defaultMessage="Couldn't find any Elasticsearch data"
|
||||
/>
|
||||
</h2>
|
||||
</EuiTextColor>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="s"/>
|
||||
<EuiText>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="kbn.management.createIndexPattern.emptyStateLabel.emptyStateDetail"
|
||||
defaultMessage="{needToIndex} {learnHowLink} or {getStartedLink}"
|
||||
values={{
|
||||
needToIndex: (
|
||||
<EuiTextColor color="subdued">
|
||||
<FormattedMessage
|
||||
id="kbn.management.createIndexPattern.emptyStateLabel.needToIndexLabel"
|
||||
defaultMessage="You'll need to index some data into Elasticsearch before you can create an index pattern."
|
||||
/>
|
||||
</EuiTextColor>
|
||||
),
|
||||
learnHowLink: (
|
||||
<EuiLink href="#/home/tutorial_directory">
|
||||
<FormattedMessage
|
||||
id="kbn.management.createIndexPattern.emptyStateLabel.learnHowLink"
|
||||
defaultMessage="Learn how"
|
||||
/>
|
||||
</EuiLink>
|
||||
),
|
||||
getStartedLink: (
|
||||
<EuiLink href="#/home/tutorial_directory/sampleData">
|
||||
<FormattedMessage
|
||||
id="kbn.management.createIndexPattern.emptyStateLabel.getStartedLink"
|
||||
defaultMessage="get started with some sample data sets."
|
||||
/>
|
||||
</EuiLink>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
<div>
|
||||
<EuiCallOut
|
||||
color="warning"
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="kbn.management.createIndexPattern.emptyStateHeader"
|
||||
defaultMessage="Couldn't find any Elasticsearch data"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="kbn.management.createIndexPattern.emptyStateLabel.emptyStateDetail"
|
||||
defaultMessage="{needToIndex} {learnHowLink} or {getStartedLink}"
|
||||
values={{
|
||||
needToIndex: (
|
||||
<EuiTextColor color="subdued">
|
||||
<FormattedMessage
|
||||
id="kbn.management.createIndexPattern.emptyStateLabel.needToIndexLabel"
|
||||
defaultMessage="You'll need to index some data into Elasticsearch before you can create an index pattern."
|
||||
/>
|
||||
</EuiTextColor>
|
||||
),
|
||||
learnHowLink: (
|
||||
<EuiLink href="#/home/tutorial_directory">
|
||||
<FormattedMessage
|
||||
id="kbn.management.createIndexPattern.emptyStateLabel.learnHowLink"
|
||||
defaultMessage="Learn how"
|
||||
/>
|
||||
</EuiLink>
|
||||
),
|
||||
getStartedLink: (
|
||||
<EuiLink href="#/home/tutorial_directory/sampleData">
|
||||
<FormattedMessage
|
||||
id="kbn.management.createIndexPattern.emptyStateLabel.getStartedLink"
|
||||
defaultMessage="get started with some sample data sets."
|
||||
/>
|
||||
</EuiLink>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
|
||||
<EuiSpacer size="m"/>
|
||||
<EuiButton
|
||||
iconType="refresh"
|
||||
onClick={onRefresh}
|
||||
data-test-subj="refreshIndicesButton"
|
||||
color="warning"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="kbn.management.createIndexPattern.emptyState.checkDataButton"
|
||||
defaultMessage="Check for new data"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiCallOut>
|
||||
|
||||
<EuiFlexGroup justifyContent="center" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
iconType="refresh"
|
||||
onClick={onRefresh}
|
||||
data-test-subj="refreshIndicesButton"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="kbn.management.createIndexPattern.emptyState.checkDataButton"
|
||||
defaultMessage="Check for new data"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
</div>
|
||||
);
|
||||
|
||||
EmptyState.propTypes = {
|
||||
|
|
|
@ -1,56 +1,45 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`LoadingState should render normally 1`] = `
|
||||
<EuiPanel
|
||||
grow={true}
|
||||
hasShadow={false}
|
||||
paddingSize="l"
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
component="div"
|
||||
direction="column"
|
||||
gutterSize="s"
|
||||
justifyContent="center"
|
||||
responsive={true}
|
||||
wrap={false}
|
||||
>
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
<EuiFlexItem
|
||||
component="div"
|
||||
direction="column"
|
||||
gutterSize="s"
|
||||
justifyContent="center"
|
||||
responsive={true}
|
||||
wrap={false}
|
||||
grow={false}
|
||||
>
|
||||
<EuiFlexItem
|
||||
component="div"
|
||||
grow={false}
|
||||
<EuiTitle
|
||||
size="s"
|
||||
textTransform="none"
|
||||
>
|
||||
<EuiTitle
|
||||
size="s"
|
||||
textTransform="none"
|
||||
<h2
|
||||
style={
|
||||
Object {
|
||||
"textAlign": "center",
|
||||
}
|
||||
}
|
||||
>
|
||||
<EuiTextColor
|
||||
color="subdued"
|
||||
component="span"
|
||||
>
|
||||
<h2
|
||||
style={
|
||||
Object {
|
||||
"textAlign": "center",
|
||||
}
|
||||
}
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Checking for Elasticsearch data"
|
||||
id="kbn.management.createIndexPattern.loadingState.checkingLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
</h2>
|
||||
</EuiTextColor>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
component="div"
|
||||
grow={false}
|
||||
>
|
||||
<EuiLoadingSpinner
|
||||
size="l"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
<FormattedMessage
|
||||
defaultMessage="Checking for Elasticsearch data"
|
||||
id="kbn.management.createIndexPattern.loadingState.checkingLabel"
|
||||
values={Object {}}
|
||||
/>
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
component="div"
|
||||
grow={false}
|
||||
>
|
||||
<EuiLoadingSpinner
|
||||
size="l"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
`;
|
||||
|
|
|
@ -23,32 +23,26 @@ import {
|
|||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiLoadingSpinner,
|
||||
EuiPanel,
|
||||
EuiTextColor,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
export const LoadingState = () => (
|
||||
<EuiPanel paddingSize="l">
|
||||
<EuiFlexGroup justifyContent="center" alignItems="center" direction="column" gutterSize="s">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiTitle size="s">
|
||||
<EuiTextColor color="subdued">
|
||||
<h2 style={{ textAlign: 'center' }}>
|
||||
<FormattedMessage
|
||||
id="kbn.management.createIndexPattern.loadingState.checkingLabel"
|
||||
defaultMessage="Checking for Elasticsearch data"
|
||||
/>
|
||||
</h2>
|
||||
</EuiTextColor>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexGroup justifyContent="center" alignItems="center" direction="column" gutterSize="s">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiTitle size="s">
|
||||
<h2 style={{ textAlign: 'center' }}>
|
||||
<FormattedMessage
|
||||
id="kbn.management.createIndexPattern.loadingState.checkingLabel"
|
||||
defaultMessage="Checking for Elasticsearch data"
|
||||
/>
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiLoadingSpinner size="l"/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiLoadingSpinner size="l"/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
|
|
|
@ -35,7 +35,6 @@ import { MAX_SEARCH_SIZE } from './constants';
|
|||
import {
|
||||
ensureMinimumTime,
|
||||
getIndices,
|
||||
getRemoteClusters
|
||||
} from './lib';
|
||||
|
||||
export class CreateIndexPatternWizard extends Component {
|
||||
|
@ -104,17 +103,18 @@ export class CreateIndexPatternWizard extends Component {
|
|||
defaultMessage="Failed to load remote clusters"
|
||||
/>);
|
||||
|
||||
const [allIndices, remoteClusters] = await ensureMinimumTime([
|
||||
this.catchAndWarn(getIndices(services.es, this.indexPatternCreationType, `*`, MAX_SEARCH_SIZE), [], indicesFailMsg),
|
||||
this.catchAndWarn(getRemoteClusters(services.$http), [], clustersFailMsg)
|
||||
]);
|
||||
// query local and remote indices, updating state independently
|
||||
ensureMinimumTime(
|
||||
this.catchAndWarn(
|
||||
getIndices(services.es, this.indexPatternCreationType, `*`, MAX_SEARCH_SIZE), [], indicesFailMsg)
|
||||
).then(allIndices => this.setState({ allIndices, isInitiallyLoadingIndices: false }));
|
||||
|
||||
this.setState({
|
||||
allIndices,
|
||||
isInitiallyLoadingIndices: false,
|
||||
remoteClustersExist: remoteClusters.length !== 0
|
||||
});
|
||||
}
|
||||
this.catchAndWarn(
|
||||
// if we get an error from remote cluster query, supply fallback value that allows user entry.
|
||||
// ['a'] is fallback value
|
||||
getIndices(services.es, this.indexPatternCreationType, `*:*`, 1), ['a'], clustersFailMsg
|
||||
).then(remoteIndices => this.setState({ remoteClustersExist: !!remoteIndices.length }));
|
||||
};
|
||||
|
||||
createIndexPattern = async (timeFieldName, indexPatternId) => {
|
||||
const { services } = this.props;
|
||||
|
|
|
@ -46,6 +46,7 @@ export async function getIndices(es, indexPatternCreationType, rawPattern, limit
|
|||
}
|
||||
|
||||
const params = {
|
||||
ignoreUnavailable: true,
|
||||
index: pattern,
|
||||
ignore: [404],
|
||||
body: {
|
||||
|
|
|
@ -28,5 +28,3 @@ export { getMatchedIndices } from './get_matched_indices';
|
|||
export { containsIllegalCharacters } from './contains_illegal_characters';
|
||||
|
||||
export { extractTimeFields } from './extract_time_fields';
|
||||
|
||||
export { getRemoteClusters } from './get_remote_clusters';
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<kbn-management-app section="kibana">
|
||||
<kbn-management-app section="kibana/indices">
|
||||
<kbn-management-indices>
|
||||
<div class="kuiViewContent">
|
||||
<kbn-management-index-header
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<kbn-management-app section="kibana" omit-breadcrumb-pages="['indices']">
|
||||
<kbn-management-app section="kibana/indices" omit-breadcrumb-pages="['indices']">
|
||||
<kbn-management-indices>
|
||||
<div
|
||||
ng-controller="managementIndicesEdit"
|
||||
|
@ -14,32 +14,49 @@
|
|||
delete="removePattern()"
|
||||
></kbn-management-index-header>
|
||||
|
||||
<p class="kuiText kuiVerticalRhythm" ng-if="::(indexPattern.timeFieldName || (indexPattern.tags && indexPattern.tags.length))">
|
||||
<div class="euiSpacer euiSpacer--s"></div>
|
||||
<p ng-if="::(indexPattern.timeFieldName || (indexPattern.tags && indexPattern.tags.length))">
|
||||
<span ng-if="::indexPattern.timeFieldName">
|
||||
<span class="label label-success">
|
||||
<span class="kuiIcon fa-clock-o"></span>
|
||||
<span i18n-id="kbn.management.editIndexPattern.timeFilterHeader"
|
||||
<span class="euiBadge euiBadge--warning">
|
||||
<span class="euiBadge__content">
|
||||
<span class="euiBadge__text">
|
||||
<span
|
||||
i18n-id="kbn.management.editIndexPattern.timeFilterHeader"
|
||||
i18n-default-message="Time Filter field name: {timeFieldName}"
|
||||
i18n-values="{ timeFieldName: indexPattern.timeFieldName }"></span>
|
||||
i18n-values="{ timeFieldName: indexPattern.timeFieldName }">
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
</span>
|
||||
<span ng-repeat="tag in indexPattern.tags">
|
||||
<span class="label label-info">{{tag.name}}</span>
|
||||
<span class="euiBadge euiBadge--hollow">
|
||||
<span class="euiBadge__content">
|
||||
<span class="euiBadge__text">
|
||||
{{tag.name}}
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</p>
|
||||
|
||||
<div class="euiSpacer euiSpacer--m"></div>
|
||||
|
||||
<p class="kuiText kuiVerticalRhythm">
|
||||
<span i18n-id="kbn.management.editIndexPattern.timeFilterLabel.timeFilterDetail"
|
||||
i18n-default-message="This page lists every field in the {indexPatternTitle} index and the field's associated core type as recorded by Elasticsearch. To change a field type, use the Elasticsearch"
|
||||
i18n-values="{ html_indexPatternTitle: '<strong>' + indexPattern.title + '</strong>' }"></span>
|
||||
<a target="_window" class="euiLink euiLink--primary" href="http://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html">
|
||||
<span i18n-id="kbn.management.editIndexPattern.timeFilterLabel.mappingAPILink"
|
||||
i18n-default-message="Mapping API"></span>
|
||||
<i aria-hidden="true" class="fa-link fa"></i>
|
||||
</a>
|
||||
</p>
|
||||
<div class="euiText">
|
||||
<p>
|
||||
<span i18n-id="kbn.management.editIndexPattern.timeFilterLabel.timeFilterDetail"
|
||||
i18n-default-message="This page lists every field in the {indexPatternTitle} index and the field's associated core type as recorded by Elasticsearch. To change a field type, use the Elasticsearch"
|
||||
i18n-values="{ html_indexPatternTitle: '<strong>' + indexPattern.title + '</strong>' }"></span>
|
||||
<a target="_window" class="euiLink euiLink--primary" href="http://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html">
|
||||
<span i18n-id="kbn.management.editIndexPattern.timeFilterLabel.mappingAPILink"
|
||||
i18n-default-message="Mapping API"></span>
|
||||
<i aria-hidden="true" class="fa-link fa"></i>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="euiSpacer euiSpacer--m"></div>
|
||||
|
||||
<!-- Alerts -->
|
||||
<div
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<div class="kuiBarSection">
|
||||
<!-- Index pattern name -->
|
||||
<h1
|
||||
class="kuiTitle kuiVerticalRhythm"
|
||||
class="euiTitle euiTitle--medium"
|
||||
data-test-subj="indexPatternTitle"
|
||||
>
|
||||
<span
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
<div class="euiPage">
|
||||
<div class="col-md-2 sidebar-container" role="region" aria-label="{{::'kbn.management.editIndexPatternAria' | i18n: { defaultMessage: 'Index patterns' } }}">
|
||||
<div class="sidebar-list">
|
||||
<div id="indexPatternListReact" role="region" aria-label="{{'kbn.management.editIndexPatternLiveRegionAriaLabel' | i18n: { defaultMessage: 'Index patterns' } }}"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-10">
|
||||
<div class="euiPanel euiPanel--paddingLarge">
|
||||
<div ng-transclude></div>
|
||||
</div>
|
||||
<div class="col-md-2 sidebar-container" role="region" aria-label="{{::'kbn.management.editIndexPatternAria' | i18n: { defaultMessage: 'Index patterns' } }}">
|
||||
<div class="sidebar-list">
|
||||
<div id="indexPatternListReact" role="region" aria-label="{{'kbn.management.editIndexPatternLiveRegionAriaLabel' | i18n: { defaultMessage: 'Index patterns' } }}"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-10">
|
||||
<div class="euiPanel euiPanel--paddingLarge">
|
||||
<div ng-transclude></div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -24,7 +24,7 @@ import { injectI18n, FormattedMessage } from '@kbn/i18n/react';
|
|||
import {
|
||||
EuiButtonEmpty,
|
||||
EuiBadge,
|
||||
EuiCallOut,
|
||||
EuiText,
|
||||
EuiSpacer,
|
||||
} from '@elastic/eui';
|
||||
|
||||
|
@ -105,17 +105,14 @@ class ListUi extends Component {
|
|||
const { defaultIndex } = this.props;
|
||||
return !defaultIndex ? (
|
||||
<div className="indexPatternList__headerWrapper">
|
||||
<EuiCallOut
|
||||
color="warning"
|
||||
size="s"
|
||||
iconType="alert"
|
||||
title={(
|
||||
<EuiText size="xs" color="subdued">
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="kbn.management.indexPatternList.noDefaultIndexPatternTitle"
|
||||
defaultMessage="No default index pattern. You must select or create one to continue."
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
</div>
|
||||
) : null;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<kbn-management-app section="kibana" class="kuiView">
|
||||
<kbn-management-app section="kibana/objects">
|
||||
<kbn-management-objects>
|
||||
<div id="reactSavedObjectsTable"></div>
|
||||
</kbn-management-objects>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<kbn-management-app section="kibana" class="kuiView">
|
||||
<kbn-management-app section="kibana/objects" class="kuiView">
|
||||
<kbn-management-objects-view class="kuiViewContent kuiViewContent--constrainedWidth">
|
||||
<!-- Header -->
|
||||
<div class="kuiViewContentItem kuiBar kuiVerticalRhythm">
|
||||
|
|
|
@ -171,106 +171,96 @@ exports[`ObjectsTable relationships should show the flyout 1`] = `
|
|||
`;
|
||||
|
||||
exports[`ObjectsTable should render normally 1`] = `
|
||||
<EuiPage
|
||||
restrictWidth={false}
|
||||
<EuiPageContent
|
||||
horizontalPosition="center"
|
||||
panelPaddingSize="l"
|
||||
style={
|
||||
Object {
|
||||
"maxWidth": 1000,
|
||||
}
|
||||
}
|
||||
verticalPosition="center"
|
||||
>
|
||||
<EuiPageBody
|
||||
restrictWidth={false}
|
||||
>
|
||||
<EuiPageContent
|
||||
horizontalPosition="center"
|
||||
panelPaddingSize="l"
|
||||
style={
|
||||
<Header
|
||||
filteredCount={4}
|
||||
onExportAll={[Function]}
|
||||
onImport={[Function]}
|
||||
onRefresh={[Function]}
|
||||
/>
|
||||
<EuiSpacer
|
||||
size="xs"
|
||||
/>
|
||||
<InjectIntl(TableUI)
|
||||
filterOptions={
|
||||
Array [
|
||||
Object {
|
||||
"marginBottom": 16,
|
||||
"marginTop": 16,
|
||||
"maxWidth": 1000,
|
||||
}
|
||||
"name": "index-pattern",
|
||||
"value": "index-pattern",
|
||||
"view": "index-pattern (0)",
|
||||
},
|
||||
Object {
|
||||
"name": "visualization",
|
||||
"value": "visualization",
|
||||
"view": "visualization (0)",
|
||||
},
|
||||
Object {
|
||||
"name": "dashboard",
|
||||
"value": "dashboard",
|
||||
"view": "dashboard (0)",
|
||||
},
|
||||
Object {
|
||||
"name": "search",
|
||||
"value": "search",
|
||||
"view": "search (0)",
|
||||
},
|
||||
]
|
||||
}
|
||||
getEditUrl={[Function]}
|
||||
goInApp={[Function]}
|
||||
isSearching={false}
|
||||
itemId="id"
|
||||
items={
|
||||
Array [
|
||||
Object {
|
||||
"icon": "indexPatternApp",
|
||||
"id": "1",
|
||||
"title": "MyIndexPattern*",
|
||||
"type": "index-pattern",
|
||||
},
|
||||
Object {
|
||||
"icon": "search",
|
||||
"id": "2",
|
||||
"title": "MySearch",
|
||||
"type": "search",
|
||||
},
|
||||
Object {
|
||||
"icon": "dashboardApp",
|
||||
"id": "3",
|
||||
"title": "MyDashboard",
|
||||
"type": "dashboard",
|
||||
},
|
||||
Object {
|
||||
"icon": "visualizeApp",
|
||||
"id": "4",
|
||||
"title": "MyViz",
|
||||
"type": "visualization",
|
||||
},
|
||||
]
|
||||
}
|
||||
onDelete={[Function]}
|
||||
onExport={[Function]}
|
||||
onQueryChange={[Function]}
|
||||
onShowRelationships={[Function]}
|
||||
onTableChange={[Function]}
|
||||
pageIndex={0}
|
||||
pageSize={15}
|
||||
selectedSavedObjects={Array []}
|
||||
selectionConfig={
|
||||
Object {
|
||||
"onSelectionChange": [Function],
|
||||
}
|
||||
verticalPosition="center"
|
||||
>
|
||||
<Header
|
||||
filteredCount={4}
|
||||
onExportAll={[Function]}
|
||||
onImport={[Function]}
|
||||
onRefresh={[Function]}
|
||||
/>
|
||||
<EuiSpacer
|
||||
size="xs"
|
||||
/>
|
||||
<InjectIntl(TableUI)
|
||||
filterOptions={
|
||||
Array [
|
||||
Object {
|
||||
"name": "index-pattern",
|
||||
"value": "index-pattern",
|
||||
"view": "index-pattern (0)",
|
||||
},
|
||||
Object {
|
||||
"name": "visualization",
|
||||
"value": "visualization",
|
||||
"view": "visualization (0)",
|
||||
},
|
||||
Object {
|
||||
"name": "dashboard",
|
||||
"value": "dashboard",
|
||||
"view": "dashboard (0)",
|
||||
},
|
||||
Object {
|
||||
"name": "search",
|
||||
"value": "search",
|
||||
"view": "search (0)",
|
||||
},
|
||||
]
|
||||
}
|
||||
getEditUrl={[Function]}
|
||||
goInApp={[Function]}
|
||||
isSearching={false}
|
||||
itemId="id"
|
||||
items={
|
||||
Array [
|
||||
Object {
|
||||
"icon": "indexPatternApp",
|
||||
"id": "1",
|
||||
"title": "MyIndexPattern*",
|
||||
"type": "index-pattern",
|
||||
},
|
||||
Object {
|
||||
"icon": "search",
|
||||
"id": "2",
|
||||
"title": "MySearch",
|
||||
"type": "search",
|
||||
},
|
||||
Object {
|
||||
"icon": "dashboardApp",
|
||||
"id": "3",
|
||||
"title": "MyDashboard",
|
||||
"type": "dashboard",
|
||||
},
|
||||
Object {
|
||||
"icon": "visualizeApp",
|
||||
"id": "4",
|
||||
"title": "MyViz",
|
||||
"type": "visualization",
|
||||
},
|
||||
]
|
||||
}
|
||||
onDelete={[Function]}
|
||||
onExport={[Function]}
|
||||
onQueryChange={[Function]}
|
||||
onShowRelationships={[Function]}
|
||||
onTableChange={[Function]}
|
||||
pageIndex={0}
|
||||
pageSize={15}
|
||||
selectedSavedObjects={Array []}
|
||||
selectionConfig={
|
||||
Object {
|
||||
"onSelectionChange": [Function],
|
||||
}
|
||||
}
|
||||
totalItemCount={4}
|
||||
/>
|
||||
</EuiPageContent>
|
||||
</EuiPageBody>
|
||||
</EuiPage>
|
||||
}
|
||||
totalItemCount={4}
|
||||
/>
|
||||
</EuiPageContent>
|
||||
`;
|
||||
|
|
|
@ -37,9 +37,7 @@ import {
|
|||
EUI_MODAL_CONFIRM_BUTTON,
|
||||
EuiCheckboxGroup,
|
||||
EuiToolTip,
|
||||
EuiPage,
|
||||
EuiPageContent,
|
||||
EuiPageBody,
|
||||
} from '@elastic/eui';
|
||||
import {
|
||||
retrieveAndExportDocs,
|
||||
|
@ -593,47 +591,43 @@ class ObjectsTableUI extends Component {
|
|||
}));
|
||||
|
||||
return (
|
||||
<EuiPage>
|
||||
<EuiPageBody>
|
||||
<EuiPageContent
|
||||
verticalPosition="center"
|
||||
horizontalPosition="center"
|
||||
style={{ maxWidth: 1000, marginTop: 16, marginBottom: 16 }}
|
||||
>
|
||||
{this.renderFlyout()}
|
||||
{this.renderRelationships()}
|
||||
{this.renderDeleteConfirmModal()}
|
||||
{this.renderExportAllOptionsModal()}
|
||||
<Header
|
||||
onExportAll={() =>
|
||||
this.setState({ isShowingExportAllOptionsModal: true })
|
||||
}
|
||||
onImport={this.showImportFlyout}
|
||||
onRefresh={this.refreshData}
|
||||
filteredCount={filteredItemCount}
|
||||
/>
|
||||
<EuiSpacer size="xs" />
|
||||
<Table
|
||||
itemId={'id'}
|
||||
selectionConfig={selectionConfig}
|
||||
selectedSavedObjects={selectedSavedObjects}
|
||||
onQueryChange={this.onQueryChange}
|
||||
onTableChange={this.onTableChange}
|
||||
filterOptions={filterOptions}
|
||||
onExport={this.onExport}
|
||||
onDelete={this.onDelete}
|
||||
getEditUrl={this.props.getEditUrl}
|
||||
goInApp={this.props.goInApp}
|
||||
pageIndex={page}
|
||||
pageSize={perPage}
|
||||
items={savedObjects}
|
||||
totalItemCount={filteredItemCount}
|
||||
isSearching={isSearching}
|
||||
onShowRelationships={this.onShowRelationships}
|
||||
/>
|
||||
</EuiPageContent>
|
||||
</EuiPageBody>
|
||||
</EuiPage>
|
||||
<EuiPageContent
|
||||
verticalPosition="center"
|
||||
horizontalPosition="center"
|
||||
style={{ maxWidth: 1000 }}
|
||||
>
|
||||
{this.renderFlyout()}
|
||||
{this.renderRelationships()}
|
||||
{this.renderDeleteConfirmModal()}
|
||||
{this.renderExportAllOptionsModal()}
|
||||
<Header
|
||||
onExportAll={() =>
|
||||
this.setState({ isShowingExportAllOptionsModal: true })
|
||||
}
|
||||
onImport={this.showImportFlyout}
|
||||
onRefresh={this.refreshData}
|
||||
filteredCount={filteredItemCount}
|
||||
/>
|
||||
<EuiSpacer size="xs" />
|
||||
<Table
|
||||
itemId={'id'}
|
||||
selectionConfig={selectionConfig}
|
||||
selectedSavedObjects={selectedSavedObjects}
|
||||
onQueryChange={this.onQueryChange}
|
||||
onTableChange={this.onTableChange}
|
||||
filterOptions={filterOptions}
|
||||
onExport={this.onExport}
|
||||
onDelete={this.onDelete}
|
||||
getEditUrl={this.props.getEditUrl}
|
||||
goInApp={this.props.goInApp}
|
||||
pageIndex={page}
|
||||
pageSize={perPage}
|
||||
items={savedObjects}
|
||||
totalItemCount={filteredItemCount}
|
||||
isSearching={isSearching}
|
||||
onShowRelationships={this.onShowRelationships}
|
||||
/>
|
||||
</EuiPageContent>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,498 +2,486 @@
|
|||
|
||||
exports[`AdvancedSettings should render normally 1`] = `
|
||||
<I18nProvider>
|
||||
<EuiPage
|
||||
restrictWidth={true}
|
||||
>
|
||||
<div
|
||||
className="mgtAdvancedSettings"
|
||||
<div>
|
||||
<EuiFlexGroup
|
||||
alignItems="stretch"
|
||||
component="div"
|
||||
direction="row"
|
||||
gutterSize="none"
|
||||
justifyContent="flexStart"
|
||||
responsive={true}
|
||||
wrap={false}
|
||||
>
|
||||
<EuiFlexGroup
|
||||
alignItems="stretch"
|
||||
<EuiFlexItem
|
||||
component="div"
|
||||
direction="row"
|
||||
gutterSize="none"
|
||||
justifyContent="flexStart"
|
||||
responsive={true}
|
||||
wrap={false}
|
||||
grow={true}
|
||||
>
|
||||
<EuiFlexItem
|
||||
component="div"
|
||||
grow={true}
|
||||
>
|
||||
<advanced_settings_page_title />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
component="div"
|
||||
grow={true}
|
||||
>
|
||||
<Search
|
||||
categories={
|
||||
Array [
|
||||
"general",
|
||||
"elasticsearch",
|
||||
]
|
||||
}
|
||||
onQueryChange={[Function]}
|
||||
query={
|
||||
Query {
|
||||
"ast": _AST {
|
||||
"_clauses": Array [],
|
||||
"_indexedClauses": Object {
|
||||
"field": Object {},
|
||||
"is": Object {},
|
||||
"term": Array [],
|
||||
},
|
||||
},
|
||||
"syntax": Object {
|
||||
"parse": [Function],
|
||||
"print": [Function],
|
||||
},
|
||||
"text": "",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<advanced_settings_page_subtitle />
|
||||
<EuiSpacer
|
||||
size="m"
|
||||
/>
|
||||
<CallOuts />
|
||||
<EuiSpacer
|
||||
size="m"
|
||||
/>
|
||||
<InjectIntl(FormUI)
|
||||
categories={
|
||||
Array [
|
||||
"general",
|
||||
"elasticsearch",
|
||||
]
|
||||
}
|
||||
categoryCounts={
|
||||
Object {
|
||||
"elasticsearch": 2,
|
||||
"general": 11,
|
||||
<advanced_settings_page_title />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
component="div"
|
||||
grow={true}
|
||||
>
|
||||
<Search
|
||||
categories={
|
||||
Array [
|
||||
"general",
|
||||
"elasticsearch",
|
||||
]
|
||||
}
|
||||
onQueryChange={[Function]}
|
||||
query={
|
||||
Query {
|
||||
"ast": _AST {
|
||||
"_clauses": Array [],
|
||||
"_indexedClauses": Object {
|
||||
"field": Object {},
|
||||
"is": Object {},
|
||||
"term": Array [],
|
||||
},
|
||||
},
|
||||
"syntax": Object {
|
||||
"parse": [Function],
|
||||
"print": [Function],
|
||||
},
|
||||
"text": "",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<advanced_settings_page_subtitle />
|
||||
<EuiSpacer
|
||||
size="m"
|
||||
/>
|
||||
<CallOuts />
|
||||
<EuiSpacer
|
||||
size="m"
|
||||
/>
|
||||
<InjectIntl(FormUI)
|
||||
categories={
|
||||
Array [
|
||||
"general",
|
||||
"elasticsearch",
|
||||
]
|
||||
}
|
||||
categoryCounts={
|
||||
Object {
|
||||
"elasticsearch": 2,
|
||||
"general": 11,
|
||||
}
|
||||
clear={[Function]}
|
||||
clearQuery={[Function]}
|
||||
save={[Function]}
|
||||
settings={
|
||||
Object {
|
||||
"elasticsearch": Array [
|
||||
Object {
|
||||
"ariaName": "test array setting",
|
||||
"category": Array [
|
||||
"elasticsearch",
|
||||
],
|
||||
"defVal": Array [
|
||||
"default_value",
|
||||
],
|
||||
"description": "Description for Test array setting",
|
||||
"displayName": "Test array setting",
|
||||
"isCustom": undefined,
|
||||
"isOverridden": false,
|
||||
"name": "test:array:setting",
|
||||
"options": undefined,
|
||||
"readonly": false,
|
||||
"type": "array",
|
||||
"value": undefined,
|
||||
},
|
||||
Object {
|
||||
"ariaName": "test boolean setting",
|
||||
"category": Array [
|
||||
"elasticsearch",
|
||||
],
|
||||
"defVal": true,
|
||||
"description": "Description for Test boolean setting",
|
||||
"displayName": "Test boolean setting",
|
||||
"isCustom": undefined,
|
||||
"isOverridden": false,
|
||||
"name": "test:boolean:setting",
|
||||
"options": undefined,
|
||||
"readonly": false,
|
||||
"type": "boolean",
|
||||
"value": undefined,
|
||||
},
|
||||
],
|
||||
"general": Array [
|
||||
Object {
|
||||
"ariaName": "test customstring setting",
|
||||
"category": Array [
|
||||
"general",
|
||||
],
|
||||
"defVal": null,
|
||||
"description": "Description for Test custom string setting",
|
||||
"displayName": "Test custom string setting",
|
||||
"isCustom": undefined,
|
||||
"isOverridden": false,
|
||||
"name": "test:customstring:setting",
|
||||
"options": undefined,
|
||||
"readonly": false,
|
||||
"type": "string",
|
||||
"value": undefined,
|
||||
},
|
||||
Object {
|
||||
"ariaName": "test image setting",
|
||||
"category": Array [
|
||||
"general",
|
||||
],
|
||||
"defVal": null,
|
||||
"description": "Description for Test image setting",
|
||||
"displayName": "Test image setting",
|
||||
"isCustom": undefined,
|
||||
"isOverridden": false,
|
||||
"name": "test:image:setting",
|
||||
"options": undefined,
|
||||
"readonly": false,
|
||||
"type": "image",
|
||||
"value": undefined,
|
||||
},
|
||||
Object {
|
||||
"ariaName": "test is overridden json",
|
||||
"category": Array [
|
||||
"general",
|
||||
],
|
||||
"defVal": "{
|
||||
}
|
||||
clear={[Function]}
|
||||
clearQuery={[Function]}
|
||||
save={[Function]}
|
||||
settings={
|
||||
Object {
|
||||
"elasticsearch": Array [
|
||||
Object {
|
||||
"ariaName": "test array setting",
|
||||
"category": Array [
|
||||
"elasticsearch",
|
||||
],
|
||||
"defVal": Array [
|
||||
"default_value",
|
||||
],
|
||||
"description": "Description for Test array setting",
|
||||
"displayName": "Test array setting",
|
||||
"isCustom": undefined,
|
||||
"isOverridden": false,
|
||||
"name": "test:array:setting",
|
||||
"options": undefined,
|
||||
"readonly": false,
|
||||
"type": "array",
|
||||
"value": undefined,
|
||||
},
|
||||
Object {
|
||||
"ariaName": "test boolean setting",
|
||||
"category": Array [
|
||||
"elasticsearch",
|
||||
],
|
||||
"defVal": true,
|
||||
"description": "Description for Test boolean setting",
|
||||
"displayName": "Test boolean setting",
|
||||
"isCustom": undefined,
|
||||
"isOverridden": false,
|
||||
"name": "test:boolean:setting",
|
||||
"options": undefined,
|
||||
"readonly": false,
|
||||
"type": "boolean",
|
||||
"value": undefined,
|
||||
},
|
||||
],
|
||||
"general": Array [
|
||||
Object {
|
||||
"ariaName": "test customstring setting",
|
||||
"category": Array [
|
||||
"general",
|
||||
],
|
||||
"defVal": null,
|
||||
"description": "Description for Test custom string setting",
|
||||
"displayName": "Test custom string setting",
|
||||
"isCustom": undefined,
|
||||
"isOverridden": false,
|
||||
"name": "test:customstring:setting",
|
||||
"options": undefined,
|
||||
"readonly": false,
|
||||
"type": "string",
|
||||
"value": undefined,
|
||||
},
|
||||
Object {
|
||||
"ariaName": "test image setting",
|
||||
"category": Array [
|
||||
"general",
|
||||
],
|
||||
"defVal": null,
|
||||
"description": "Description for Test image setting",
|
||||
"displayName": "Test image setting",
|
||||
"isCustom": undefined,
|
||||
"isOverridden": false,
|
||||
"name": "test:image:setting",
|
||||
"options": undefined,
|
||||
"readonly": false,
|
||||
"type": "image",
|
||||
"value": undefined,
|
||||
},
|
||||
Object {
|
||||
"ariaName": "test is overridden json",
|
||||
"category": Array [
|
||||
"general",
|
||||
],
|
||||
"defVal": "{
|
||||
\\"foo\\": \\"bar\\"
|
||||
}",
|
||||
"description": "Description for overridden json",
|
||||
"displayName": "An overridden json",
|
||||
"isCustom": undefined,
|
||||
"isOverridden": true,
|
||||
"name": "test:isOverridden:json",
|
||||
"options": undefined,
|
||||
"readonly": false,
|
||||
"type": "json",
|
||||
"value": undefined,
|
||||
},
|
||||
Object {
|
||||
"ariaName": "test is overridden number",
|
||||
"category": Array [
|
||||
"general",
|
||||
],
|
||||
"defVal": 1234,
|
||||
"description": "Description for overridden number",
|
||||
"displayName": "An overridden number",
|
||||
"isCustom": undefined,
|
||||
"isOverridden": true,
|
||||
"name": "test:isOverridden:number",
|
||||
"options": undefined,
|
||||
"readonly": false,
|
||||
"type": "number",
|
||||
"value": undefined,
|
||||
},
|
||||
Object {
|
||||
"ariaName": "test is overridden select",
|
||||
"category": Array [
|
||||
"general",
|
||||
],
|
||||
"defVal": "orange",
|
||||
"description": "Description for overridden select setting",
|
||||
"displayName": "Test overridden select setting",
|
||||
"isCustom": undefined,
|
||||
"isOverridden": true,
|
||||
"name": "test:isOverridden:select",
|
||||
"options": Array [
|
||||
"apple",
|
||||
"orange",
|
||||
"banana",
|
||||
],
|
||||
"readonly": false,
|
||||
"type": "select",
|
||||
"value": undefined,
|
||||
},
|
||||
Object {
|
||||
"ariaName": "test is overridden string",
|
||||
"category": Array [
|
||||
"general",
|
||||
],
|
||||
"defVal": "foo",
|
||||
"description": "Description for overridden string",
|
||||
"displayName": "An overridden string",
|
||||
"isCustom": undefined,
|
||||
"isOverridden": true,
|
||||
"name": "test:isOverridden:string",
|
||||
"options": undefined,
|
||||
"readonly": false,
|
||||
"type": "string",
|
||||
"value": undefined,
|
||||
},
|
||||
Object {
|
||||
"ariaName": "test json setting",
|
||||
"category": Array [
|
||||
"general",
|
||||
],
|
||||
"defVal": "{\\"foo\\": \\"bar\\"}",
|
||||
"description": "Description for Test json setting",
|
||||
"displayName": "Test json setting",
|
||||
"isCustom": undefined,
|
||||
"isOverridden": false,
|
||||
"name": "test:json:setting",
|
||||
"options": undefined,
|
||||
"readonly": false,
|
||||
"type": "json",
|
||||
"value": undefined,
|
||||
},
|
||||
Object {
|
||||
"ariaName": "test markdown setting",
|
||||
"category": Array [
|
||||
"general",
|
||||
],
|
||||
"defVal": "",
|
||||
"description": "Description for Test markdown setting",
|
||||
"displayName": "Test markdown setting",
|
||||
"isCustom": undefined,
|
||||
"isOverridden": false,
|
||||
"name": "test:markdown:setting",
|
||||
"options": undefined,
|
||||
"readonly": false,
|
||||
"type": "markdown",
|
||||
"value": undefined,
|
||||
},
|
||||
Object {
|
||||
"ariaName": "test number setting",
|
||||
"category": Array [
|
||||
"general",
|
||||
],
|
||||
"defVal": 5,
|
||||
"description": "Description for Test number setting",
|
||||
"displayName": "Test number setting",
|
||||
"isCustom": undefined,
|
||||
"isOverridden": false,
|
||||
"name": "test:number:setting",
|
||||
"options": undefined,
|
||||
"readonly": false,
|
||||
"type": "number",
|
||||
"value": undefined,
|
||||
},
|
||||
Object {
|
||||
"ariaName": "test select setting",
|
||||
"category": Array [
|
||||
"general",
|
||||
],
|
||||
"defVal": "orange",
|
||||
"description": "Description for Test select setting",
|
||||
"displayName": "Test select setting",
|
||||
"isCustom": undefined,
|
||||
"isOverridden": false,
|
||||
"name": "test:select:setting",
|
||||
"options": Array [
|
||||
"apple",
|
||||
"orange",
|
||||
"banana",
|
||||
],
|
||||
"readonly": false,
|
||||
"type": "select",
|
||||
"value": undefined,
|
||||
},
|
||||
Object {
|
||||
"ariaName": "test string setting",
|
||||
"category": Array [
|
||||
"general",
|
||||
],
|
||||
"defVal": null,
|
||||
"description": "Description for Test string setting",
|
||||
"displayName": "Test string setting",
|
||||
"isCustom": undefined,
|
||||
"isOverridden": false,
|
||||
"name": "test:string:setting",
|
||||
"options": undefined,
|
||||
"readonly": false,
|
||||
"type": "string",
|
||||
"value": undefined,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
showNoResultsMessage={true}
|
||||
/>
|
||||
<advanced_settings_page_footer
|
||||
onQueryMatchChange={[Function]}
|
||||
query={
|
||||
Query {
|
||||
"ast": _AST {
|
||||
"_clauses": Array [],
|
||||
"_indexedClauses": Object {
|
||||
"field": Object {},
|
||||
"is": Object {},
|
||||
"term": Array [],
|
||||
},
|
||||
"description": "Description for overridden json",
|
||||
"displayName": "An overridden json",
|
||||
"isCustom": undefined,
|
||||
"isOverridden": true,
|
||||
"name": "test:isOverridden:json",
|
||||
"options": undefined,
|
||||
"readonly": false,
|
||||
"type": "json",
|
||||
"value": undefined,
|
||||
},
|
||||
"syntax": Object {
|
||||
"parse": [Function],
|
||||
"print": [Function],
|
||||
Object {
|
||||
"ariaName": "test is overridden number",
|
||||
"category": Array [
|
||||
"general",
|
||||
],
|
||||
"defVal": 1234,
|
||||
"description": "Description for overridden number",
|
||||
"displayName": "An overridden number",
|
||||
"isCustom": undefined,
|
||||
"isOverridden": true,
|
||||
"name": "test:isOverridden:number",
|
||||
"options": undefined,
|
||||
"readonly": false,
|
||||
"type": "number",
|
||||
"value": undefined,
|
||||
},
|
||||
"text": "",
|
||||
}
|
||||
Object {
|
||||
"ariaName": "test is overridden select",
|
||||
"category": Array [
|
||||
"general",
|
||||
],
|
||||
"defVal": "orange",
|
||||
"description": "Description for overridden select setting",
|
||||
"displayName": "Test overridden select setting",
|
||||
"isCustom": undefined,
|
||||
"isOverridden": true,
|
||||
"name": "test:isOverridden:select",
|
||||
"options": Array [
|
||||
"apple",
|
||||
"orange",
|
||||
"banana",
|
||||
],
|
||||
"readonly": false,
|
||||
"type": "select",
|
||||
"value": undefined,
|
||||
},
|
||||
Object {
|
||||
"ariaName": "test is overridden string",
|
||||
"category": Array [
|
||||
"general",
|
||||
],
|
||||
"defVal": "foo",
|
||||
"description": "Description for overridden string",
|
||||
"displayName": "An overridden string",
|
||||
"isCustom": undefined,
|
||||
"isOverridden": true,
|
||||
"name": "test:isOverridden:string",
|
||||
"options": undefined,
|
||||
"readonly": false,
|
||||
"type": "string",
|
||||
"value": undefined,
|
||||
},
|
||||
Object {
|
||||
"ariaName": "test json setting",
|
||||
"category": Array [
|
||||
"general",
|
||||
],
|
||||
"defVal": "{\\"foo\\": \\"bar\\"}",
|
||||
"description": "Description for Test json setting",
|
||||
"displayName": "Test json setting",
|
||||
"isCustom": undefined,
|
||||
"isOverridden": false,
|
||||
"name": "test:json:setting",
|
||||
"options": undefined,
|
||||
"readonly": false,
|
||||
"type": "json",
|
||||
"value": undefined,
|
||||
},
|
||||
Object {
|
||||
"ariaName": "test markdown setting",
|
||||
"category": Array [
|
||||
"general",
|
||||
],
|
||||
"defVal": "",
|
||||
"description": "Description for Test markdown setting",
|
||||
"displayName": "Test markdown setting",
|
||||
"isCustom": undefined,
|
||||
"isOverridden": false,
|
||||
"name": "test:markdown:setting",
|
||||
"options": undefined,
|
||||
"readonly": false,
|
||||
"type": "markdown",
|
||||
"value": undefined,
|
||||
},
|
||||
Object {
|
||||
"ariaName": "test number setting",
|
||||
"category": Array [
|
||||
"general",
|
||||
],
|
||||
"defVal": 5,
|
||||
"description": "Description for Test number setting",
|
||||
"displayName": "Test number setting",
|
||||
"isCustom": undefined,
|
||||
"isOverridden": false,
|
||||
"name": "test:number:setting",
|
||||
"options": undefined,
|
||||
"readonly": false,
|
||||
"type": "number",
|
||||
"value": undefined,
|
||||
},
|
||||
Object {
|
||||
"ariaName": "test select setting",
|
||||
"category": Array [
|
||||
"general",
|
||||
],
|
||||
"defVal": "orange",
|
||||
"description": "Description for Test select setting",
|
||||
"displayName": "Test select setting",
|
||||
"isCustom": undefined,
|
||||
"isOverridden": false,
|
||||
"name": "test:select:setting",
|
||||
"options": Array [
|
||||
"apple",
|
||||
"orange",
|
||||
"banana",
|
||||
],
|
||||
"readonly": false,
|
||||
"type": "select",
|
||||
"value": undefined,
|
||||
},
|
||||
Object {
|
||||
"ariaName": "test string setting",
|
||||
"category": Array [
|
||||
"general",
|
||||
],
|
||||
"defVal": null,
|
||||
"description": "Description for Test string setting",
|
||||
"displayName": "Test string setting",
|
||||
"isCustom": undefined,
|
||||
"isOverridden": false,
|
||||
"name": "test:string:setting",
|
||||
"options": undefined,
|
||||
"readonly": false,
|
||||
"type": "string",
|
||||
"value": undefined,
|
||||
},
|
||||
],
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</EuiPage>
|
||||
}
|
||||
showNoResultsMessage={true}
|
||||
/>
|
||||
<advanced_settings_page_footer
|
||||
onQueryMatchChange={[Function]}
|
||||
query={
|
||||
Query {
|
||||
"ast": _AST {
|
||||
"_clauses": Array [],
|
||||
"_indexedClauses": Object {
|
||||
"field": Object {},
|
||||
"is": Object {},
|
||||
"term": Array [],
|
||||
},
|
||||
},
|
||||
"syntax": Object {
|
||||
"parse": [Function],
|
||||
"print": [Function],
|
||||
},
|
||||
"text": "",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</I18nProvider>
|
||||
`;
|
||||
|
||||
exports[`AdvancedSettings should render specific setting if given setting key 1`] = `
|
||||
<I18nProvider>
|
||||
<EuiPage
|
||||
restrictWidth={true}
|
||||
>
|
||||
<div
|
||||
className="mgtAdvancedSettings"
|
||||
<div>
|
||||
<EuiFlexGroup
|
||||
alignItems="stretch"
|
||||
component="div"
|
||||
direction="row"
|
||||
gutterSize="none"
|
||||
justifyContent="flexStart"
|
||||
responsive={true}
|
||||
wrap={false}
|
||||
>
|
||||
<EuiFlexGroup
|
||||
alignItems="stretch"
|
||||
<EuiFlexItem
|
||||
component="div"
|
||||
direction="row"
|
||||
gutterSize="none"
|
||||
justifyContent="flexStart"
|
||||
responsive={true}
|
||||
wrap={false}
|
||||
grow={true}
|
||||
>
|
||||
<EuiFlexItem
|
||||
component="div"
|
||||
grow={true}
|
||||
>
|
||||
<advanced_settings_page_title />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
component="div"
|
||||
grow={true}
|
||||
>
|
||||
<Search
|
||||
categories={
|
||||
Array [
|
||||
"general",
|
||||
"elasticsearch",
|
||||
]
|
||||
}
|
||||
onQueryChange={[Function]}
|
||||
query={
|
||||
Query {
|
||||
"ast": _AST {
|
||||
"_clauses": Array [
|
||||
Object {
|
||||
"field": "ariaName",
|
||||
"match": "must",
|
||||
"operator": "eq",
|
||||
"type": "field",
|
||||
"value": "test string setting",
|
||||
},
|
||||
],
|
||||
"_indexedClauses": Object {
|
||||
"field": Object {
|
||||
"ariaName": Array [
|
||||
Object {
|
||||
"field": "ariaName",
|
||||
"match": "must",
|
||||
"operator": "eq",
|
||||
"type": "field",
|
||||
"value": "test string setting",
|
||||
},
|
||||
],
|
||||
},
|
||||
"is": Object {},
|
||||
"term": Array [],
|
||||
},
|
||||
},
|
||||
"syntax": Object {
|
||||
"parse": [Function],
|
||||
"print": [Function],
|
||||
},
|
||||
"text": "ariaName:\\"test string setting\\"",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<advanced_settings_page_subtitle />
|
||||
<EuiSpacer
|
||||
size="m"
|
||||
/>
|
||||
<CallOuts />
|
||||
<EuiSpacer
|
||||
size="m"
|
||||
/>
|
||||
<InjectIntl(FormUI)
|
||||
categories={
|
||||
Array [
|
||||
"general",
|
||||
"elasticsearch",
|
||||
]
|
||||
}
|
||||
categoryCounts={
|
||||
Object {
|
||||
"elasticsearch": 2,
|
||||
"general": 11,
|
||||
<advanced_settings_page_title />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem
|
||||
component="div"
|
||||
grow={true}
|
||||
>
|
||||
<Search
|
||||
categories={
|
||||
Array [
|
||||
"general",
|
||||
"elasticsearch",
|
||||
]
|
||||
}
|
||||
}
|
||||
clear={[Function]}
|
||||
clearQuery={[Function]}
|
||||
save={[Function]}
|
||||
settings={
|
||||
Object {
|
||||
"general": Array [
|
||||
Object {
|
||||
"ariaName": "test string setting",
|
||||
"category": Array [
|
||||
"general",
|
||||
onQueryChange={[Function]}
|
||||
query={
|
||||
Query {
|
||||
"ast": _AST {
|
||||
"_clauses": Array [
|
||||
Object {
|
||||
"field": "ariaName",
|
||||
"match": "must",
|
||||
"operator": "eq",
|
||||
"type": "field",
|
||||
"value": "test string setting",
|
||||
},
|
||||
],
|
||||
"defVal": null,
|
||||
"description": "Description for Test string setting",
|
||||
"displayName": "Test string setting",
|
||||
"isCustom": undefined,
|
||||
"isOverridden": false,
|
||||
"name": "test:string:setting",
|
||||
"options": undefined,
|
||||
"readonly": false,
|
||||
"type": "string",
|
||||
"value": undefined,
|
||||
"_indexedClauses": Object {
|
||||
"field": Object {
|
||||
"ariaName": Array [
|
||||
Object {
|
||||
"field": "ariaName",
|
||||
"match": "must",
|
||||
"operator": "eq",
|
||||
"type": "field",
|
||||
"value": "test string setting",
|
||||
},
|
||||
],
|
||||
},
|
||||
"is": Object {},
|
||||
"term": Array [],
|
||||
},
|
||||
},
|
||||
"syntax": Object {
|
||||
"parse": [Function],
|
||||
"print": [Function],
|
||||
},
|
||||
"text": "ariaName:\\"test string setting\\"",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<advanced_settings_page_subtitle />
|
||||
<EuiSpacer
|
||||
size="m"
|
||||
/>
|
||||
<CallOuts />
|
||||
<EuiSpacer
|
||||
size="m"
|
||||
/>
|
||||
<InjectIntl(FormUI)
|
||||
categories={
|
||||
Array [
|
||||
"general",
|
||||
"elasticsearch",
|
||||
]
|
||||
}
|
||||
categoryCounts={
|
||||
Object {
|
||||
"elasticsearch": 2,
|
||||
"general": 11,
|
||||
}
|
||||
}
|
||||
clear={[Function]}
|
||||
clearQuery={[Function]}
|
||||
save={[Function]}
|
||||
settings={
|
||||
Object {
|
||||
"general": Array [
|
||||
Object {
|
||||
"ariaName": "test string setting",
|
||||
"category": Array [
|
||||
"general",
|
||||
],
|
||||
"defVal": null,
|
||||
"description": "Description for Test string setting",
|
||||
"displayName": "Test string setting",
|
||||
"isCustom": undefined,
|
||||
"isOverridden": false,
|
||||
"name": "test:string:setting",
|
||||
"options": undefined,
|
||||
"readonly": false,
|
||||
"type": "string",
|
||||
"value": undefined,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
showNoResultsMessage={true}
|
||||
/>
|
||||
<advanced_settings_page_footer
|
||||
onQueryMatchChange={[Function]}
|
||||
query={
|
||||
Query {
|
||||
"ast": _AST {
|
||||
"_clauses": Array [
|
||||
Object {
|
||||
"field": "ariaName",
|
||||
"match": "must",
|
||||
"operator": "eq",
|
||||
"type": "field",
|
||||
"value": "test string setting",
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
showNoResultsMessage={true}
|
||||
/>
|
||||
<advanced_settings_page_footer
|
||||
onQueryMatchChange={[Function]}
|
||||
query={
|
||||
Query {
|
||||
"ast": _AST {
|
||||
"_clauses": Array [
|
||||
Object {
|
||||
"field": "ariaName",
|
||||
"match": "must",
|
||||
"operator": "eq",
|
||||
"type": "field",
|
||||
"value": "test string setting",
|
||||
},
|
||||
],
|
||||
"_indexedClauses": Object {
|
||||
"field": Object {
|
||||
"ariaName": Array [
|
||||
Object {
|
||||
"field": "ariaName",
|
||||
"match": "must",
|
||||
"operator": "eq",
|
||||
"type": "field",
|
||||
"value": "test string setting",
|
||||
},
|
||||
],
|
||||
},
|
||||
"is": Object {},
|
||||
"term": Array [],
|
||||
"_indexedClauses": Object {
|
||||
"field": Object {
|
||||
"ariaName": Array [
|
||||
Object {
|
||||
"field": "ariaName",
|
||||
"match": "must",
|
||||
"operator": "eq",
|
||||
"type": "field",
|
||||
"value": "test string setting",
|
||||
},
|
||||
],
|
||||
},
|
||||
"is": Object {},
|
||||
"term": Array [],
|
||||
},
|
||||
"syntax": Object {
|
||||
"parse": [Function],
|
||||
"print": [Function],
|
||||
},
|
||||
"text": "ariaName:\\"test string setting\\"",
|
||||
}
|
||||
},
|
||||
"syntax": Object {
|
||||
"parse": [Function],
|
||||
"print": [Function],
|
||||
},
|
||||
"text": "ariaName:\\"test string setting\\"",
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</EuiPage>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</I18nProvider>
|
||||
`;
|
||||
|
|
|
@ -25,7 +25,6 @@ import {
|
|||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiSpacer,
|
||||
EuiPage,
|
||||
Query,
|
||||
} from '@elastic/eui';
|
||||
|
||||
|
@ -157,36 +156,34 @@ export class AdvancedSettings extends Component {
|
|||
|
||||
return (
|
||||
<I18nProvider>
|
||||
<EuiPage restrictWidth>
|
||||
<div className="mgtAdvancedSettings">
|
||||
<EuiFlexGroup gutterSize="none">
|
||||
<EuiFlexItem>
|
||||
<PageTitle />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<Search
|
||||
query={query}
|
||||
categories={this.categories}
|
||||
onQueryChange={this.onQueryChange}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<PageSubtitle />
|
||||
<EuiSpacer size="m" />
|
||||
<CallOuts />
|
||||
<EuiSpacer size="m" />
|
||||
<Form
|
||||
settings={filteredSettings}
|
||||
categories={this.categories}
|
||||
categoryCounts={this.categoryCounts}
|
||||
clearQuery={this.clearQuery}
|
||||
save={this.saveConfig}
|
||||
clear={this.clearConfig}
|
||||
showNoResultsMessage={!footerQueryMatched}
|
||||
/>
|
||||
<PageFooter query={query} onQueryMatchChange={this.onFooterQueryMatchChange} />
|
||||
</div>
|
||||
</EuiPage>
|
||||
<div>
|
||||
<EuiFlexGroup gutterSize="none">
|
||||
<EuiFlexItem>
|
||||
<PageTitle />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<Search
|
||||
query={query}
|
||||
categories={this.categories}
|
||||
onQueryChange={this.onQueryChange}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<PageSubtitle />
|
||||
<EuiSpacer size="m" />
|
||||
<CallOuts />
|
||||
<EuiSpacer size="m" />
|
||||
<Form
|
||||
settings={filteredSettings}
|
||||
categories={this.categories}
|
||||
categoryCounts={this.categoryCounts}
|
||||
clearQuery={this.clearQuery}
|
||||
save={this.saveConfig}
|
||||
clear={this.clearConfig}
|
||||
showNoResultsMessage={!footerQueryMatched}
|
||||
/>
|
||||
<PageFooter query={query} onQueryMatchChange={this.onFooterQueryMatchChange} />
|
||||
</div>
|
||||
</I18nProvider>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,19 +1,13 @@
|
|||
.mgtAdvancedSettings {
|
||||
padding: $euiSizeL;
|
||||
background: $euiColorLightestShade;
|
||||
min-height: calc(100vh - 70px);
|
||||
.mgtAdvancedSettings__field {
|
||||
+ * {
|
||||
margin-top: $euiSize;
|
||||
}
|
||||
|
||||
.mgtAdvancedSettings__field {
|
||||
+ * {
|
||||
margin-top: $euiSize;
|
||||
}
|
||||
&Wrapper {
|
||||
width: 640px;
|
||||
}
|
||||
|
||||
&Wrapper {
|
||||
width: 640px;
|
||||
}
|
||||
|
||||
&Actions {
|
||||
padding-top: $euiSizeM;
|
||||
}
|
||||
&Actions {
|
||||
padding-top: $euiSizeM;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<kbn-management-app section="kibana">
|
||||
<kbn-management-app section="kibana/settings">
|
||||
<kbn-management-advanced>
|
||||
<div id="reactAdvancedSettings"></div>
|
||||
</kbn-management-advanced>
|
||||
|
|
|
@ -92,8 +92,10 @@ const unescapeTemplateVars = url => {
|
|||
const DEFAULT_LANGUAGE = 'en';
|
||||
|
||||
|
||||
|
||||
export class EMSClientV66 {
|
||||
|
||||
EMS_LOAD_TIMEOUT = 32000;
|
||||
|
||||
constructor({ kbnVersion, manifestServiceUrl, htmlSanitizer, language, landingPageUrl }) {
|
||||
|
||||
|
@ -103,7 +105,6 @@ export class EMSClientV66 {
|
|||
|
||||
this._sanitizer = htmlSanitizer ? htmlSanitizer : x => x;
|
||||
this._manifestServiceUrl = manifestServiceUrl;
|
||||
this._loadCatalogue = null;
|
||||
this._loadFileLayers = null;
|
||||
this._loadTMSServices = null;
|
||||
this._emsLandingPageUrl = landingPageUrl;
|
||||
|
@ -125,11 +126,39 @@ export class EMSClientV66 {
|
|||
* this internal method is overridden by the tests to simulate custom manifest.
|
||||
*/
|
||||
async _getManifest(manifestUrl) {
|
||||
const url = extendUrl(manifestUrl, { query: this._queryParams });
|
||||
const result = await fetch(url);
|
||||
return await result.json();
|
||||
let result;
|
||||
try {
|
||||
const url = extendUrl(manifestUrl, { query: this._queryParams });
|
||||
result = await this._fetchWithTimeout(url);
|
||||
} catch (e) {
|
||||
if (!e) {
|
||||
e = new Error('Unknown error');
|
||||
}
|
||||
if (!(e instanceof Error)) {
|
||||
e = new Error(e.data || `status ${e.statusText || e.status}`);
|
||||
}
|
||||
throw new Error(`Unable to retrieve manifest from ${manifestUrl}: ${e.message}`);
|
||||
} finally {
|
||||
return result
|
||||
? await result.json()
|
||||
: null;
|
||||
}
|
||||
}
|
||||
|
||||
_fetchWithTimeout(url) {
|
||||
return new Promise(
|
||||
(resolve, reject) => {
|
||||
const timer = setTimeout(
|
||||
() => reject(new Error(`Request to ${url} timed out`)),
|
||||
this.EMS_LOAD_TIMEOUT
|
||||
);
|
||||
fetch(url)
|
||||
.then(
|
||||
response => resolve(response),
|
||||
err => reject(err)
|
||||
).finally(() => clearTimeout(timer));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add optional query-parameters to all requests
|
||||
|
@ -151,54 +180,42 @@ export class EMSClientV66 {
|
|||
|
||||
_invalidateSettings() {
|
||||
|
||||
this._loadCatalogue = _.once(async () => {
|
||||
this._getManifestWithParams = _.once(
|
||||
async url => this._getManifest(this.extendUrlWithParams(url)));
|
||||
|
||||
try {
|
||||
const url = this.extendUrlWithParams(this._manifestServiceUrl);
|
||||
return await this._getManifest(url);
|
||||
} catch (e) {
|
||||
if (!e) {
|
||||
e = new Error('Unknown error');
|
||||
}
|
||||
if (!(e instanceof Error)) {
|
||||
e = new Error(e.data || `status ${e.statusText || e.status}`);
|
||||
}
|
||||
throw new Error(`Could not retrieve manifest from the tile service: ${e.message}`);
|
||||
this._getCatalogueService = async serviceType => {
|
||||
const catalogueManifest = await this._getManifestWithParams(this._manifestServiceUrl);
|
||||
let service;
|
||||
if(_.has(catalogueManifest, 'services')) {
|
||||
service = catalogueManifest.services
|
||||
.find(s => s.type === serviceType);
|
||||
}
|
||||
});
|
||||
return service || {};
|
||||
};
|
||||
|
||||
this._wrapServiceAttribute = async (manifestUrl, attr, WrapperClass) => {
|
||||
const manifest = await this._getManifest(manifestUrl);
|
||||
if (_.has(manifest, attr)) {
|
||||
return manifest[attr].map(config => {
|
||||
return new WrapperClass(config, this);
|
||||
});
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
this._loadFileLayers = _.once(async () => {
|
||||
|
||||
const catalogue = await this._loadCatalogue();
|
||||
const fileService = catalogue.services.find(service => service.type === 'file');
|
||||
if (!fileService) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const manifest = await this._getManifest(fileService.manifest, this._queryParams);
|
||||
|
||||
return manifest.layers.map(layerConfig => {
|
||||
return new FileLayer(layerConfig, this);
|
||||
});
|
||||
const fileService = await this._getCatalogueService('file');
|
||||
return _.isEmpty(fileService)
|
||||
? []
|
||||
: this._wrapServiceAttribute(fileService.manifest, 'layers', FileLayer);
|
||||
});
|
||||
|
||||
this._loadTMSServices = _.once(async () => {
|
||||
|
||||
const catalogue = await this._loadCatalogue();
|
||||
const tmsService = catalogue.services.find((service) => service.type === 'tms');
|
||||
if (!tmsService) {
|
||||
return [];
|
||||
}
|
||||
const tmsManifest = await this._getManifest(tmsService.manifest, this._queryParams);
|
||||
|
||||
|
||||
return tmsManifest.services.map(serviceConfig => {
|
||||
return new TMSService(serviceConfig, this);
|
||||
});
|
||||
|
||||
const tmsService = await this._getCatalogueService('tms');
|
||||
return _.isEmpty(tmsService)
|
||||
? []
|
||||
: await this._wrapServiceAttribute(tmsService.manifest, 'services', TMSService);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
getLandingPageUrl() {
|
||||
|
|
|
@ -143,6 +143,8 @@ export function BaseMapsVisualizationProvider(serviceSettings, i18n) {
|
|||
|
||||
async _updateBaseLayer() {
|
||||
|
||||
const DEFAULT_EMS_BASEMAP = 'road_map';
|
||||
|
||||
if (!this._kibanaMap) {
|
||||
return;
|
||||
}
|
||||
|
@ -151,13 +153,11 @@ export function BaseMapsVisualizationProvider(serviceSettings, i18n) {
|
|||
if (!this._tmsConfigured()) {
|
||||
try {
|
||||
const tmsServices = await serviceSettings.getTMSServices();
|
||||
const firstRoadMapLayer = tmsServices.find((s) => {
|
||||
return s.id === 'road_map';//first road map layer
|
||||
});
|
||||
const fallback = firstRoadMapLayer ? firstRoadMapLayer : tmsServices[0];
|
||||
if (fallback) {
|
||||
this._setTmsLayer(firstRoadMapLayer);
|
||||
}
|
||||
const userConfiguredTmsLayer = tmsServices[0];
|
||||
const initBasemapLayer = userConfiguredTmsLayer
|
||||
? userConfiguredTmsLayer
|
||||
: tmsServices.find(s => s.id === DEFAULT_EMS_BASEMAP);
|
||||
if (initBasemapLayer) { this._setTmsLayer(initBasemapLayer); }
|
||||
} catch (e) {
|
||||
toastNotifications.addWarning(e.message);
|
||||
return;
|
||||
|
|
|
@ -23,7 +23,7 @@ module.exports = {
|
|||
browsers: [
|
||||
'last 2 versions',
|
||||
'> 5%',
|
||||
'Safari 7' // for PhantomJS support
|
||||
'Safari 7' // for PhantomJS support: https://github.com/elastic/kibana/issues/27136
|
||||
]
|
||||
})
|
||||
]
|
||||
|
|
|
@ -240,9 +240,8 @@ export default () => Joi.object({
|
|||
includeElasticMapsService: Joi.boolean().default(true),
|
||||
tilemap: tilemapSchema,
|
||||
regionmap: regionmapSchema,
|
||||
// manifestServiceUrl: Joi.string().default(' https://catalogue.maps.elastic.co/v2/manifest'),
|
||||
manifestServiceUrl: Joi.string().default('https://catalogue-staging.maps.elastic.co/v6.6/manifest'),
|
||||
emsLandingPageUrl: Joi.string().default('https://maps.elastic.co/v2'),
|
||||
manifestServiceUrl: Joi.string().default('https://catalogue.maps.elastic.co/v6.6/manifest'),
|
||||
emsLandingPageUrl: Joi.string().default('https://maps.elastic.co/v6.6'),
|
||||
}).default(),
|
||||
tilemap: tilemapSchema.notes('Deprecated'),
|
||||
regionmap: regionmapSchema.notes('Deprecated'),
|
||||
|
|
|
@ -21,7 +21,6 @@ import path from 'path';
|
|||
import { promisify } from 'util';
|
||||
import fs from 'fs';
|
||||
import sass from 'node-sass';
|
||||
import sassLint from 'sass-lint';
|
||||
import autoprefixer from 'autoprefixer';
|
||||
import postcss from 'postcss';
|
||||
|
||||
|
@ -64,15 +63,6 @@ export class Build {
|
|||
async build() {
|
||||
const outFile = this.outputPath();
|
||||
|
||||
const lintResults = sassLint.lintFiles(this.getGlob(), {}, path.resolve(__dirname, '..', '..', '..', '.sass-lint.yml'));
|
||||
|
||||
lintResults.forEach(result => {
|
||||
if (result.messages.length > 0) {
|
||||
this.log.info(`lint errors in ${result.filePath}`);
|
||||
this.log.info(JSON.stringify(result.messages, null, 2));
|
||||
}
|
||||
});
|
||||
|
||||
const rendered = await renderSass({
|
||||
file: this.source,
|
||||
outFile,
|
||||
|
|
|
@ -101,6 +101,15 @@ describe('ManagementSection', () => {
|
|||
|
||||
expect(threwException).to.be(true);
|
||||
});
|
||||
|
||||
it('calls listener when item added', () => {
|
||||
let listerCalled = false;
|
||||
const listenerFn = () => { listerCalled = true; };
|
||||
|
||||
section.addListener(listenerFn);
|
||||
section.register('about');
|
||||
expect(listerCalled).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('deregister', () => {
|
||||
|
@ -122,6 +131,14 @@ describe('ManagementSection', () => {
|
|||
expect(section.items).to.have.length(0);
|
||||
});
|
||||
|
||||
it('calls listener when item added', () => {
|
||||
let listerCalled = false;
|
||||
const listenerFn = () => { listerCalled = true; };
|
||||
|
||||
section.addListener(listenerFn);
|
||||
section.deregister('about');
|
||||
expect(listerCalled).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getSection', () => {
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Management filters and filters and maps section objects into SidebarNav items 1`] = `
|
||||
Array [
|
||||
Object {
|
||||
"data-test-subj": "activeSection",
|
||||
"href": undefined,
|
||||
"icon": null,
|
||||
"id": "activeSection",
|
||||
"isSelected": false,
|
||||
"items": Array [
|
||||
Object {
|
||||
"data-test-subj": "item",
|
||||
"href": undefined,
|
||||
"icon": null,
|
||||
"id": "item",
|
||||
"isSelected": false,
|
||||
"name": "item",
|
||||
},
|
||||
],
|
||||
"name": "activeSection",
|
||||
},
|
||||
]
|
||||
`;
|
20
src/ui/public/management/components/index.ts
Normal file
20
src/ui/public/management/components/index.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
export { SidebarNav } from './sidebar_nav';
|
82
src/ui/public/management/components/sidebar_nav.test.ts
Normal file
82
src/ui/public/management/components/sidebar_nav.test.ts
Normal file
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { IndexedArray } from '../../indexed_array';
|
||||
import { sideNavItems } from '../components/sidebar_nav';
|
||||
|
||||
const toIndexedArray = (initialSet: any[]) =>
|
||||
new IndexedArray({
|
||||
index: ['id'],
|
||||
order: ['order'],
|
||||
initialSet,
|
||||
});
|
||||
|
||||
const activeProps = { visible: true, disabled: false };
|
||||
const disabledProps = { visible: true, disabled: true };
|
||||
const notVisibleProps = { visible: false, disabled: false };
|
||||
|
||||
const visibleItem = { display: 'item', id: 'item', ...activeProps };
|
||||
|
||||
const notVisibleSection = {
|
||||
display: 'Not visible',
|
||||
id: 'not-visible',
|
||||
items: toIndexedArray([visibleItem]),
|
||||
...notVisibleProps,
|
||||
};
|
||||
const disabledSection = {
|
||||
display: 'Disabled',
|
||||
id: 'disabled',
|
||||
items: toIndexedArray([visibleItem]),
|
||||
...disabledProps,
|
||||
};
|
||||
const noItemsSection = {
|
||||
display: 'No items',
|
||||
id: 'no-items',
|
||||
items: toIndexedArray([]),
|
||||
...activeProps,
|
||||
};
|
||||
const noActiveItemsSection = {
|
||||
display: 'No active items',
|
||||
id: 'no-active-items',
|
||||
items: toIndexedArray([
|
||||
{ display: 'disabled', id: 'disabled', ...disabledProps },
|
||||
{ display: 'notVisible', id: 'notVisible', ...notVisibleProps },
|
||||
]),
|
||||
...activeProps,
|
||||
};
|
||||
const activeSection = {
|
||||
display: 'activeSection',
|
||||
id: 'activeSection',
|
||||
items: toIndexedArray([visibleItem]),
|
||||
...activeProps,
|
||||
};
|
||||
|
||||
const managementSections = [
|
||||
notVisibleSection,
|
||||
disabledSection,
|
||||
noItemsSection,
|
||||
noActiveItemsSection,
|
||||
activeSection,
|
||||
];
|
||||
|
||||
describe('Management', () => {
|
||||
it('filters and filters and maps section objects into SidebarNav items', () => {
|
||||
expect(sideNavItems(managementSections, 'active-item-id')).toMatchSnapshot();
|
||||
});
|
||||
});
|
94
src/ui/public/management/components/sidebar_nav.tsx
Normal file
94
src/ui/public/management/components/sidebar_nav.tsx
Normal file
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { EuiIcon, EuiSideNav, IconType } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import React from 'react';
|
||||
import { IndexedArray } from 'ui/indexed_array';
|
||||
|
||||
interface Subsection {
|
||||
disabled: boolean;
|
||||
visible: boolean;
|
||||
id: string;
|
||||
display: string;
|
||||
url?: string;
|
||||
icon?: IconType;
|
||||
}
|
||||
interface Section extends Subsection {
|
||||
items: IndexedArray<Subsection>;
|
||||
}
|
||||
|
||||
const sectionVisible = (section: Subsection) => !section.disabled && section.visible;
|
||||
const sectionToNav = (selectedId: string) => ({ display, id, url, icon }: Subsection) => ({
|
||||
id,
|
||||
name: display,
|
||||
icon: icon ? <EuiIcon type={icon} /> : null,
|
||||
isSelected: selectedId === id,
|
||||
href: url,
|
||||
'data-test-subj': id,
|
||||
});
|
||||
|
||||
export const sideNavItems = (sections: Section[], selectedId: string) =>
|
||||
sections
|
||||
.filter(sectionVisible)
|
||||
.filter(section => section.items.filter(sectionVisible).length)
|
||||
.map(section => ({
|
||||
items: section.items.inOrder.filter(sectionVisible).map(sectionToNav(selectedId)),
|
||||
...sectionToNav(selectedId)(section),
|
||||
}));
|
||||
|
||||
interface SidebarNavProps {
|
||||
sections: Section[];
|
||||
selectedId: string;
|
||||
}
|
||||
|
||||
interface SidebarNavState {
|
||||
isSideNavOpenOnMobile: boolean;
|
||||
}
|
||||
|
||||
export class SidebarNav extends React.Component<SidebarNavProps, SidebarNavState> {
|
||||
constructor(props: SidebarNavProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isSideNavOpenOnMobile: false,
|
||||
};
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<EuiSideNav
|
||||
mobileTitle={this.renderMobileTitle()}
|
||||
isOpenOnMobile={this.state.isSideNavOpenOnMobile}
|
||||
toggleOpenOnMobile={this.toggleOpenOnMobile}
|
||||
items={sideNavItems(this.props.sections, this.props.selectedId)}
|
||||
style={{ width: 192, paddingBottom: '16px' }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
private renderMobileTitle() {
|
||||
return <FormattedMessage id="common.ui.management.nav.menu" defaultMessage="Management menu" />;
|
||||
}
|
||||
|
||||
private toggleOpenOnMobile = () => {
|
||||
this.setState({
|
||||
isSideNavOpenOnMobile: !this.state.isSideNavOpenOnMobile,
|
||||
});
|
||||
};
|
||||
}
|
|
@ -17,29 +17,15 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { callWithRequestFactory } from './call_with_request_factory';
|
||||
import handleEsError from '../../../lib/handle_es_error';
|
||||
|
||||
async function fetchRemoteClusters(callWithRequest) {
|
||||
const options = {
|
||||
method: 'GET',
|
||||
path: '_remote/info'
|
||||
};
|
||||
const remoteInfo = await callWithRequest('transport.request', options);
|
||||
return Object.keys(remoteInfo);
|
||||
}
|
||||
|
||||
export function registerClustersRoute(server) {
|
||||
server.route({
|
||||
path: '/api/kibana/clusters',
|
||||
method: 'GET',
|
||||
handler: async request => {
|
||||
const callWithRequest = callWithRequestFactory(server, request);
|
||||
try {
|
||||
return await fetchRemoteClusters(callWithRequest);
|
||||
} catch (error) {
|
||||
throw handleEsError(error);
|
||||
}
|
||||
}
|
||||
});
|
||||
declare module 'ui/management' {
|
||||
export const PAGE_TITLE_COMPONENT: string;
|
||||
export const PAGE_SUBTITLE_COMPONENT: string;
|
||||
export const PAGE_FOOTER_COMPONENT: string;
|
||||
export const SidebarNav: React.SFC<any>;
|
||||
export function registerSettingsComponent(
|
||||
id: string,
|
||||
component: string | React.SFC<any>,
|
||||
allowOverride: boolean
|
||||
): void;
|
||||
export const management: any; // TODO - properly provide types
|
||||
}
|
|
@ -27,4 +27,5 @@ export {
|
|||
} from '../../../legacy/core_plugins/kibana/public/management/sections/settings/components/component_registry';
|
||||
export { Field } from '../../../legacy/core_plugins/kibana/public/management/sections/settings/components/field/field';
|
||||
export { management } from './sections_register';
|
||||
export { SidebarNav } from './components';
|
||||
export { MANAGEMENT_BREADCRUMB } from './breadcrumbs';
|
||||
|
|
|
@ -20,8 +20,9 @@
|
|||
import { assign } from 'lodash';
|
||||
import { IndexedArray } from '../indexed_array';
|
||||
|
||||
export class ManagementSection {
|
||||
const listeners = [];
|
||||
|
||||
export class ManagementSection {
|
||||
/**
|
||||
* @param {string} id
|
||||
* @param {object} options
|
||||
|
@ -55,6 +56,16 @@ export class ManagementSection {
|
|||
return this.items.inOrder.filter(item => item.visible);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a callback that will be executed when management sections are updated
|
||||
* Globally bound to solve for sidebar nav needs
|
||||
*
|
||||
* @param {function} fn
|
||||
*/
|
||||
addListener(fn) {
|
||||
listeners.push(fn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a sub-section
|
||||
*
|
||||
|
@ -71,6 +82,7 @@ export class ManagementSection {
|
|||
}
|
||||
|
||||
this.items.push(item);
|
||||
listeners.forEach(fn => fn());
|
||||
|
||||
return item;
|
||||
}
|
||||
|
@ -82,6 +94,7 @@ export class ManagementSection {
|
|||
*/
|
||||
deregister(id) {
|
||||
this.items.remove(item => item.id === id);
|
||||
listeners.forEach(fn => fn(this.items));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -93,6 +93,7 @@ ObjDefine.prototype.create = function () {
|
|||
// clone the object on serialization and choose which properties
|
||||
// to include or trim manually. This is currently only in use in PhantomJS
|
||||
// due to https://github.com/ariya/phantomjs/issues/11856
|
||||
// TODO: remove this: https://github.com/elastic/kibana/issues/27136
|
||||
self.obj.toJSON = function () {
|
||||
return _.transform(self.obj, function (json, val, key) {
|
||||
const desc = self.descs[key];
|
||||
|
|
|
@ -29,6 +29,10 @@ export {
|
|||
validations,
|
||||
} from './saved_object';
|
||||
|
||||
export {
|
||||
taskDefinitions
|
||||
} from './task_definitions';
|
||||
|
||||
export {
|
||||
app,
|
||||
apps,
|
||||
|
|
|
@ -17,15 +17,12 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { once } from 'lodash';
|
||||
import { mergeAtType } from './reduce';
|
||||
import { alias, wrap, uniqueKeys } from './modify_reduce';
|
||||
|
||||
const callWithRequest = once(server => {
|
||||
const cluster = server.plugins.elasticsearch.getCluster('data');
|
||||
return cluster.callWithRequest;
|
||||
});
|
||||
|
||||
export const callWithRequestFactory = (server, request) => {
|
||||
return (...args) => {
|
||||
return callWithRequest(server)(request, ...args);
|
||||
};
|
||||
};
|
||||
// How plugins define tasks that the task manager can run.
|
||||
export const taskDefinitions = wrap(
|
||||
alias('taskDefinitions'),
|
||||
uniqueKeys(),
|
||||
mergeAtType,
|
||||
);
|
|
@ -1,58 +0,0 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { bgRed, white } from 'chalk';
|
||||
import { execSync } from 'child_process';
|
||||
import { createInterface } from 'readline';
|
||||
|
||||
export default function (grunt) {
|
||||
|
||||
grunt.registerTask('sterilize', function () {
|
||||
|
||||
const cmd = 'git clean -fdx';
|
||||
const ignores = [
|
||||
'.aws-config.json',
|
||||
'config/kibana.dev.yml'
|
||||
]
|
||||
.concat(String(grunt.option('ignore') || '').split(','))
|
||||
.map(f => `-e "${f.split('"').join('\\"')}"`)
|
||||
.reduce((all, arg) => `${all} ${arg}`, '');
|
||||
|
||||
const stdio = 'inherit';
|
||||
execSync(`${cmd} -n ${ignores}`, { stdio });
|
||||
|
||||
const rl = createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout
|
||||
});
|
||||
const danger = bgRed(white('DANGER'));
|
||||
|
||||
rl.on('close', this.async());
|
||||
rl.question(`\n${danger} Do you really want to delete all of the above files?, [N/y] `, function (resp) {
|
||||
const yes = resp.toLowerCase().trim()[0] === 'y';
|
||||
rl.close();
|
||||
|
||||
if (yes) {
|
||||
execSync(`${cmd} ${ignores}`, { stdio });
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
}
|
|
@ -38,19 +38,18 @@ export function SettingsPageProvider({ getService, getPageObjects }) {
|
|||
await find.clickByDisplayedLinkText(text);
|
||||
}
|
||||
async clickKibanaSettings() {
|
||||
await find.clickByDisplayedLinkText('Advanced Settings');
|
||||
await testSubjects.click('settings');
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
// Verify navigation is successful.
|
||||
await testSubjects.existOrFail('managementSettingsTitle');
|
||||
}
|
||||
|
||||
async clickKibanaSavedObjects() {
|
||||
await find.clickByDisplayedLinkText('Saved Objects');
|
||||
await testSubjects.click('objects');
|
||||
}
|
||||
|
||||
async clickKibanaIndices() {
|
||||
log.debug('clickKibanaIndices link');
|
||||
await find.clickByDisplayedLinkText('Index Patterns');
|
||||
await testSubjects.click('indices');
|
||||
}
|
||||
|
||||
async getAdvancedSettings(propertyName) {
|
||||
|
|
|
@ -64,7 +64,7 @@ export function FindProvider({ getService }) {
|
|||
return await retry.try(async () => {
|
||||
const element = await getElementFunction();
|
||||
// Calling any method forces a staleness check
|
||||
element.isEnabled();
|
||||
await element.isEnabled();
|
||||
return element;
|
||||
});
|
||||
}
|
||||
|
@ -75,7 +75,7 @@ export function FindProvider({ getService }) {
|
|||
return await retry.try(async () => {
|
||||
const element = await getElementFunction(leadfootWithTimeout);
|
||||
// Calling any method forces a staleness check
|
||||
element.isEnabled();
|
||||
await element.isEnabled();
|
||||
return element;
|
||||
});
|
||||
} finally {
|
||||
|
|
1
typings/@elastic/eui/index.d.ts
vendored
1
typings/@elastic/eui/index.d.ts
vendored
|
@ -23,4 +23,5 @@ declare module '@elastic/eui' {
|
|||
export const EuiWrappingPopover: React.SFC<any>;
|
||||
export const EuiCopy: React.SFC<any>;
|
||||
export const EuiOutsideClickDetector: React.SFC<any>;
|
||||
export const EuiSideNav: React.SFC<any>;
|
||||
}
|
||||
|
|
|
@ -127,18 +127,3 @@ For both of the above commands, it's crucial that you pass in `--config` to spec
|
|||
Read more about how the scripts work [here](scripts/README.md).
|
||||
|
||||
For a deeper dive, read more about the way functional tests and servers work [here](packages/kbn-test/README.md).
|
||||
|
||||
### Issues starting dev more of creating builds
|
||||
|
||||
You may see an error like this when you are getting started:
|
||||
|
||||
```
|
||||
[14:08:15] Error: Linux x86 checksum failed
|
||||
at download_phantom.js:42:15
|
||||
at process._tickDomainCallback (node.js:407:9)
|
||||
```
|
||||
|
||||
That's thanks to the binary Phantom downloads that have to happen, and Bitbucket being annoying with throttling and redirecting or... something. The real issue eludes me, but you have 2 options to resolve it.
|
||||
|
||||
1. Just keep re-running the command until it passes. Eventually the downloads will work, and since they are cached, it won't ever be an issue again.
|
||||
1. Download them by hand [from Bitbucket](https://bitbucket.org/ariya/phantomjs/downloads) and copy them into the `.phantom` path. We're currently using 1.9.8, and you'll need the Window, Mac, and Linux builds.
|
||||
|
|
|
@ -30,10 +30,12 @@ import { notifications } from './plugins/notifications';
|
|||
import { kueryAutocomplete } from './plugins/kuery_autocomplete';
|
||||
import { canvas } from './plugins/canvas';
|
||||
import { infra } from './plugins/infra';
|
||||
import { taskManager } from './plugins/task_manager';
|
||||
import { rollup } from './plugins/rollup';
|
||||
import { remoteClusters } from './plugins/remote_clusters';
|
||||
import { crossClusterReplication } from './plugins/cross_cluster_replication';
|
||||
import { upgradeAssistant } from './plugins/upgrade_assistant';
|
||||
import { uptime } from './plugins/uptime';
|
||||
|
||||
module.exports = function (kibana) {
|
||||
return [
|
||||
|
@ -63,9 +65,11 @@ module.exports = function (kibana) {
|
|||
indexLifecycleManagement(kibana),
|
||||
kueryAutocomplete(kibana),
|
||||
infra(kibana),
|
||||
taskManager(kibana),
|
||||
rollup(kibana),
|
||||
remoteClusters(kibana),
|
||||
crossClusterReplication(kibana),
|
||||
upgradeAssistant(kibana),
|
||||
uptime(kibana),
|
||||
];
|
||||
};
|
||||
|
|
|
@ -143,7 +143,6 @@
|
|||
"@elastic/javascript-typescript-langserver": "^0.1.7",
|
||||
"@elastic/lsp-extension": "^0.1.1",
|
||||
"@elastic/node-crypto": "0.1.2",
|
||||
"@elastic/node-phantom-simple": "2.2.4",
|
||||
"@elastic/numeral": "2.3.2",
|
||||
"@kbn/babel-preset": "1.0.0",
|
||||
"@kbn/es-query": "1.0.0",
|
||||
|
@ -153,10 +152,11 @@
|
|||
"@samverschueren/stream-to-observable": "^0.3.0",
|
||||
"@scant/router": "^0.1.0",
|
||||
"@slack/client": "^4.8.0",
|
||||
"@turf/boolean-contains": "6.0.1",
|
||||
"angular-resource": "1.4.9",
|
||||
"angular-sanitize": "1.6.5",
|
||||
"angular-ui-ace": "0.2.3",
|
||||
"apollo-cache-inmemory": "^1.2.7",
|
||||
"apollo-cache-inmemory": "1.2.7",
|
||||
"apollo-client": "^2.3.8",
|
||||
"apollo-link": "^1.2.3",
|
||||
"apollo-link-http": "^1.5.4",
|
||||
|
@ -236,6 +236,7 @@
|
|||
"nodegit": "git+https://github.com/elastic/nodegit.git#v0.24.0-alpha.6",
|
||||
"nodemailer": "^4.6.4",
|
||||
"node-fetch": "^2.1.2",
|
||||
"nodemailer": "^4.6.4",
|
||||
"object-path-immutable": "^0.5.3",
|
||||
"oppsy": "^2.0.0",
|
||||
"papaparse": "^4.6.0",
|
||||
|
@ -288,22 +289,21 @@
|
|||
"socket.io-client": "^2.1.1",
|
||||
"squel": "^5.12.2",
|
||||
"stats-lite": "^2.2.0",
|
||||
"style-it": "^1.6.12",
|
||||
"style-it": "2.1.2",
|
||||
"styled-components": "3.3.3",
|
||||
"tar-fs": "1.13.0",
|
||||
"tinycolor2": "1.3.0",
|
||||
"tinymath": "1.1.1",
|
||||
"tslib": "^1.9.3",
|
||||
"topojson-client": "3.0.0",
|
||||
"tslib": "^1.9.3",
|
||||
"turf": "3.0.14",
|
||||
"@turf/boolean-contains": "6.0.1",
|
||||
"typescript-fsa": "^2.5.0",
|
||||
"typescript-fsa-reducers": "^0.4.5",
|
||||
"ui-select": "0.19.4",
|
||||
"unbzip2-stream": "1.0.9",
|
||||
"unstated": "^2.1.1",
|
||||
"uuid": "3.0.1",
|
||||
"url-parse": "1.3.0",
|
||||
"uuid": "3.0.1",
|
||||
"venn.js": "0.2.9",
|
||||
"vscode-jsonrpc": "^3.6.2",
|
||||
"vscode-languageserver": "^4.2.1",
|
||||
|
|
|
@ -144,6 +144,7 @@ export class KibanaFrameworkAdapter implements FrameworkAdapter {
|
|||
toController === 'self'
|
||||
? `<${DOM_ELEMENT_NAME}><div id="${DOM_ELEMENT_NAME}ReactRoot"></div></${DOM_ELEMENT_NAME}>`
|
||||
: `<kbn-management-app section="${this.PLUGIN_ID.replace('_', '-')}">
|
||||
<div id="management-sidenav" class="euiPageSideBar" style="position: static;"></div>
|
||||
<div id="${DOM_ELEMENT_NAME}ReactRoot" />
|
||||
</kbn-management-app>`,
|
||||
// tslint:disable-next-line: max-classes-per-file
|
||||
|
|
52
x-pack/plugins/beats_management/public/pages/index.ts
Normal file
52
x-pack/plugins/beats_management/public/pages/index.ts
Normal file
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { BeatDetailPage } from './beat/details';
|
||||
import { BeatDetailsPage } from './beat/index';
|
||||
import { BeatTagsPage } from './beat/tags';
|
||||
import { EnforceSecurityPage } from './error/enforce_security';
|
||||
import { InvalidLicensePage } from './error/invalid_license';
|
||||
import { NoAccessPage } from './error/no_access';
|
||||
import { TagsPage } from './overview/configuration_tags';
|
||||
import { BeatsPage } from './overview/enrolled_beats';
|
||||
import { MainPage } from './overview/index';
|
||||
import { TagPage } from './tag';
|
||||
import { BeatsInitialEnrollmentPage } from './walkthrough/initial/beat';
|
||||
import { FinishWalkthroughPage } from './walkthrough/initial/finish';
|
||||
import { InitialWalkthroughPage } from './walkthrough/initial/index';
|
||||
import { InitialTagPage } from './walkthrough/initial/tag';
|
||||
|
||||
export const routeMap = [
|
||||
{ path: '/tag/:action/:tagid?', component: TagPage },
|
||||
{
|
||||
path: '/beat/:beatId',
|
||||
component: BeatDetailsPage,
|
||||
routes: [
|
||||
{ path: '/beat/:beatId/details', component: BeatDetailPage },
|
||||
{ path: '/beat/:beatId/tags', component: BeatTagsPage },
|
||||
],
|
||||
},
|
||||
{ path: '/error/enforce_security', component: EnforceSecurityPage },
|
||||
{ path: '/error/invalid_license', component: InvalidLicensePage },
|
||||
{ path: '/error/no_access', component: NoAccessPage },
|
||||
{
|
||||
path: '/overview',
|
||||
component: MainPage,
|
||||
routes: [
|
||||
{ path: '/overview/configuration_tags', component: TagsPage },
|
||||
{ path: '/overview/enrolled_beats', component: BeatsPage },
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/walkthrough/initial',
|
||||
component: InitialWalkthroughPage,
|
||||
routes: [
|
||||
{ path: '/walkthrough/initial/beat', component: BeatsInitialEnrollmentPage },
|
||||
{ path: '/walkthrough/initial/finish', component: FinishWalkthroughPage },
|
||||
{ path: '/walkthrough/initial/tag', component: InitialTagPage },
|
||||
],
|
||||
},
|
||||
];
|
|
@ -13,18 +13,7 @@ import { BeatsContainer } from './containers/beats';
|
|||
import { TagsContainer } from './containers/tags';
|
||||
import { URLStateProps, WithURLState } from './containers/with_url_state';
|
||||
import { FrontendLibs } from './lib/types';
|
||||
import { RouteTreeBuilder } from './utils/page_loader/page_loader';
|
||||
|
||||
// See ./utils/page_loader/readme.md for details on how this works
|
||||
// suffice to to say it dynamicly creates routes and pages based on the filesystem
|
||||
// This is to ensure that the patterns are followed and types assured
|
||||
// @ts-ignore
|
||||
const requirePages = require.context('./pages', true, /\.tsx$/);
|
||||
const routeTreeBuilder = new RouteTreeBuilder(requirePages);
|
||||
const routesFromFilesystem = routeTreeBuilder.routeTreeFromPaths(requirePages.keys(), {
|
||||
'/tag': ['action', 'tagid?'],
|
||||
'/beat': ['beatId'],
|
||||
});
|
||||
import { routeMap } from './pages/index';
|
||||
|
||||
interface RouterProps {
|
||||
libs: FrontendLibs;
|
||||
|
@ -124,7 +113,7 @@ export class AppRouter extends Component<RouterProps, RouterState> {
|
|||
<WithURLState>
|
||||
{(URLProps: URLStateProps) => (
|
||||
<ChildRoutes
|
||||
routes={routesFromFilesystem}
|
||||
routes={routeMap}
|
||||
{...URLProps}
|
||||
{...{
|
||||
libs: this.props.libs,
|
||||
|
|
|
@ -1,143 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`RouteTreeBuilder routeTreeFromPaths Should create a route tree 1`] = `
|
||||
Array [
|
||||
Object {
|
||||
"component": null,
|
||||
"path": "/tag",
|
||||
},
|
||||
Object {
|
||||
"component": null,
|
||||
"path": "/beat",
|
||||
"routes": Array [
|
||||
Object {
|
||||
"component": null,
|
||||
"path": "/beat/detail",
|
||||
},
|
||||
Object {
|
||||
"component": null,
|
||||
"path": "/beat/tags",
|
||||
},
|
||||
],
|
||||
},
|
||||
Object {
|
||||
"component": null,
|
||||
"path": "/error/enforce_security",
|
||||
},
|
||||
Object {
|
||||
"component": null,
|
||||
"path": "/error/invalid_license",
|
||||
},
|
||||
Object {
|
||||
"component": null,
|
||||
"path": "/error/no_access",
|
||||
},
|
||||
Object {
|
||||
"component": null,
|
||||
"path": "/overview",
|
||||
"routes": Array [
|
||||
Object {
|
||||
"component": null,
|
||||
"path": "/overview/enrolled_beats",
|
||||
},
|
||||
Object {
|
||||
"component": null,
|
||||
"path": "/overview/tag_configurations",
|
||||
},
|
||||
],
|
||||
},
|
||||
Object {
|
||||
"component": null,
|
||||
"path": "/walkthrough/initial",
|
||||
"routes": Array [
|
||||
Object {
|
||||
"component": null,
|
||||
"path": "/walkthrough/initial/beat",
|
||||
},
|
||||
Object {
|
||||
"component": null,
|
||||
"path": "/walkthrough/initial/finish",
|
||||
},
|
||||
Object {
|
||||
"component": null,
|
||||
"path": "/walkthrough/initial/tag",
|
||||
},
|
||||
],
|
||||
},
|
||||
Object {
|
||||
"component": null,
|
||||
"path": "*",
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`RouteTreeBuilder routeTreeFromPaths Should create a route tree, with top level route having params 1`] = `
|
||||
Array [
|
||||
Object {
|
||||
"component": null,
|
||||
"path": "/tag/:action/:tagid?",
|
||||
},
|
||||
Object {
|
||||
"component": null,
|
||||
"path": "/beat",
|
||||
"routes": Array [
|
||||
Object {
|
||||
"component": null,
|
||||
"path": "/beat/detail",
|
||||
},
|
||||
Object {
|
||||
"component": null,
|
||||
"path": "/beat/tags",
|
||||
},
|
||||
],
|
||||
},
|
||||
Object {
|
||||
"component": null,
|
||||
"path": "/error/enforce_security",
|
||||
},
|
||||
Object {
|
||||
"component": null,
|
||||
"path": "/error/invalid_license",
|
||||
},
|
||||
Object {
|
||||
"component": null,
|
||||
"path": "/error/no_access",
|
||||
},
|
||||
Object {
|
||||
"component": null,
|
||||
"path": "/overview",
|
||||
"routes": Array [
|
||||
Object {
|
||||
"component": null,
|
||||
"path": "/overview/enrolled_beats",
|
||||
},
|
||||
Object {
|
||||
"component": null,
|
||||
"path": "/overview/tag_configurations",
|
||||
},
|
||||
],
|
||||
},
|
||||
Object {
|
||||
"component": null,
|
||||
"path": "/walkthrough/initial",
|
||||
"routes": Array [
|
||||
Object {
|
||||
"component": null,
|
||||
"path": "/walkthrough/initial/beat",
|
||||
},
|
||||
Object {
|
||||
"component": null,
|
||||
"path": "/walkthrough/initial/finish",
|
||||
},
|
||||
Object {
|
||||
"component": null,
|
||||
"path": "/walkthrough/initial/tag",
|
||||
},
|
||||
],
|
||||
},
|
||||
Object {
|
||||
"component": null,
|
||||
"path": "*",
|
||||
},
|
||||
]
|
||||
`;
|
|
@ -1,138 +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;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { RouteTreeBuilder } from './page_loader';
|
||||
|
||||
const pages = [
|
||||
'./_404.tsx',
|
||||
'./beat/detail.tsx',
|
||||
'./beat/index.tsx',
|
||||
'./beat/tags.tsx',
|
||||
'./error/enforce_security.tsx',
|
||||
'./error/invalid_license.tsx',
|
||||
'./error/no_access.tsx',
|
||||
'./overview/enrolled_beats.tsx',
|
||||
'./overview/index.tsx',
|
||||
'./overview/tag_configurations.tsx',
|
||||
'./tag.tsx',
|
||||
'./walkthrough/initial/beat.tsx',
|
||||
'./walkthrough/initial/finish.tsx',
|
||||
'./walkthrough/initial/index.tsx',
|
||||
'./walkthrough/initial/tag.tsx',
|
||||
];
|
||||
|
||||
describe('RouteTreeBuilder', () => {
|
||||
describe('routeTreeFromPaths', () => {
|
||||
it('Should fail to create a route tree due to no exported *Page component', () => {
|
||||
const mockRequire = jest.fn(path => ({
|
||||
path,
|
||||
testComponent: null,
|
||||
}));
|
||||
|
||||
const treeBuilder = new RouteTreeBuilder(mockRequire);
|
||||
|
||||
expect(() => {
|
||||
treeBuilder.routeTreeFromPaths(pages);
|
||||
}).toThrowError(/in the pages folder does not include an exported/);
|
||||
});
|
||||
|
||||
it('Should create a route tree', () => {
|
||||
const mockRequire = jest.fn(path => ({
|
||||
path,
|
||||
testPage: null,
|
||||
}));
|
||||
|
||||
const treeBuilder = new RouteTreeBuilder(mockRequire);
|
||||
|
||||
let tree;
|
||||
expect(() => {
|
||||
tree = treeBuilder.routeTreeFromPaths(pages);
|
||||
}).not.toThrow();
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('Should fail to create a route tree due to no exported custom *Component component', () => {
|
||||
const mockRequire = jest.fn(path => ({
|
||||
path,
|
||||
testComponent: null,
|
||||
}));
|
||||
|
||||
const treeBuilder = new RouteTreeBuilder(mockRequire, /Component$/);
|
||||
|
||||
expect(() => {
|
||||
treeBuilder.routeTreeFromPaths(pages);
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
it('Should create a route tree, with top level route having params', () => {
|
||||
const mockRequire = jest.fn(path => ({
|
||||
path,
|
||||
testPage: null,
|
||||
}));
|
||||
|
||||
const treeBuilder = new RouteTreeBuilder(mockRequire);
|
||||
const tree = treeBuilder.routeTreeFromPaths(pages, {
|
||||
'/tag': ['action', 'tagid?'],
|
||||
});
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('Should create a route tree, with a nested route having params', () => {
|
||||
const mockRequire = jest.fn(path => ({
|
||||
path,
|
||||
testPage: null,
|
||||
}));
|
||||
|
||||
const treeBuilder = new RouteTreeBuilder(mockRequire);
|
||||
const tree = treeBuilder.routeTreeFromPaths(pages, {
|
||||
'/beat': ['beatId'],
|
||||
});
|
||||
expect(tree[1].path).toEqual('/beat/:beatId');
|
||||
});
|
||||
});
|
||||
it('Should create a route tree, with a deep nested route having params', () => {
|
||||
const mockRequire = jest.fn(path => ({
|
||||
path,
|
||||
testPage: null,
|
||||
}));
|
||||
|
||||
const treeBuilder = new RouteTreeBuilder(mockRequire);
|
||||
const tree = treeBuilder.routeTreeFromPaths(pages, {
|
||||
'/beat': ['beatId'],
|
||||
'/beat/detail': ['other'],
|
||||
});
|
||||
expect(tree[1].path).toEqual('/beat/:beatId');
|
||||
expect(tree[1].routes![0].path).toEqual('/beat/:beatId/detail/:other');
|
||||
expect(tree[1].routes![1].path).toEqual('/beat/:beatId/tags');
|
||||
});
|
||||
it('Should throw an error on invalid mapped path', () => {
|
||||
const mockRequire = jest.fn(path => ({
|
||||
path,
|
||||
testPage: null,
|
||||
}));
|
||||
|
||||
const treeBuilder = new RouteTreeBuilder(mockRequire);
|
||||
expect(() => {
|
||||
treeBuilder.routeTreeFromPaths(pages, {
|
||||
'/non-existant-path': ['beatId'],
|
||||
});
|
||||
}).toThrowError(/Invalid overrideMap provided to 'routeTreeFromPaths', \/non-existant-path /);
|
||||
});
|
||||
it('Should rended 404.tsx as a 404 route not /404', () => {
|
||||
const mockRequire = jest.fn(path => ({
|
||||
path,
|
||||
testPage: null,
|
||||
}));
|
||||
|
||||
const treeBuilder = new RouteTreeBuilder(mockRequire);
|
||||
const tree = treeBuilder.routeTreeFromPaths(pages);
|
||||
const firstPath = tree[0].path;
|
||||
const lastPath = tree[tree.length - 1].path;
|
||||
|
||||
expect(firstPath).not.toBe('/_404');
|
||||
expect(lastPath).toBe('*');
|
||||
});
|
||||
});
|
|
@ -1,170 +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;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { difference, flatten, last } from 'lodash';
|
||||
|
||||
interface PathTree {
|
||||
[path: string]: string[];
|
||||
}
|
||||
export interface RouteConfig {
|
||||
path: string;
|
||||
component: React.ComponentType<any>;
|
||||
routes?: RouteConfig[];
|
||||
}
|
||||
|
||||
interface RouteParamsMap {
|
||||
[path: string]: string[];
|
||||
}
|
||||
|
||||
export class RouteTreeBuilder {
|
||||
constructor(
|
||||
private readonly requireWithContext: any,
|
||||
private readonly pageComponentPattern: RegExp = /Page$/
|
||||
) {}
|
||||
|
||||
public routeTreeFromPaths(paths: string[], mapParams: RouteParamsMap = {}): RouteConfig[] {
|
||||
const pathTree = this.buildTree('./', paths);
|
||||
const allRoutes = Object.keys(pathTree).reduce((routes: any[], filePath) => {
|
||||
if (pathTree[filePath].includes('index.tsx')) {
|
||||
routes.push(this.buildRouteWithChildren(filePath, pathTree[filePath], mapParams));
|
||||
} else {
|
||||
routes.concat(
|
||||
pathTree[filePath].map(file => routes.push(this.buildRoute(filePath, file, mapParams)))
|
||||
);
|
||||
}
|
||||
|
||||
return routes;
|
||||
}, []);
|
||||
// Check that no overide maps are ignored due to being invalid
|
||||
const flatRoutes = this.flatpackRoutes(allRoutes);
|
||||
const mappedPaths = Object.keys(mapParams);
|
||||
const invalidOverrides = difference(mappedPaths, flatRoutes);
|
||||
if (invalidOverrides.length > 0 && flatRoutes.length > 0) {
|
||||
throw new Error(
|
||||
`Invalid overrideMap provided to 'routeTreeFromPaths', ${
|
||||
invalidOverrides[0]
|
||||
} is not a valid route. Only the following are: ${flatRoutes.join(', ')}`
|
||||
);
|
||||
}
|
||||
|
||||
// 404 route MUST be last or it gets used first in a switch
|
||||
return allRoutes.sort((a: RouteConfig) => {
|
||||
return a.path === '*' ? 1 : 0;
|
||||
});
|
||||
}
|
||||
|
||||
private flatpackRoutes(arr: RouteConfig[], pre: string = ''): string[] {
|
||||
return flatten(
|
||||
[].concat.apply(
|
||||
[],
|
||||
arr.map(item => {
|
||||
const path = (pre + item.path).trim();
|
||||
|
||||
// The flattened route based on files without params added
|
||||
const route = item.path.includes('/:')
|
||||
? item.path
|
||||
.split('/')
|
||||
.filter(s => s.charAt(0) !== ':')
|
||||
.join('/')
|
||||
: item.path;
|
||||
return item.routes ? [route, this.flatpackRoutes(item.routes, path)] : route;
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private buildRouteWithChildren(dir: string, files: string[], mapParams: RouteParamsMap) {
|
||||
const childFiles = files.filter(f => f !== 'index.tsx');
|
||||
const parentConfig = this.buildRoute(dir, 'index.tsx', mapParams);
|
||||
parentConfig.routes = childFiles.map(cf => this.buildRoute(dir, cf, mapParams));
|
||||
return parentConfig;
|
||||
}
|
||||
|
||||
private buildRoute(dir: string, file: string, mapParams: RouteParamsMap): RouteConfig {
|
||||
// Remove the file extension as we dont want that in the URL... also index resolves to parent route
|
||||
// so remove that... e.g. /beats/index is not the url we want, /beats should resolve to /beats/index
|
||||
// just like how files resolve in node
|
||||
const filePath = `${mapParams[dir] || dir}${file.replace('.tsx', '')}`.replace('/index', '');
|
||||
const page = this.requireWithContext(`.${dir}${file}`);
|
||||
const cleanDir = dir.replace(/\/$/, '');
|
||||
|
||||
// Make sure the expored variable name matches a pattern. By default it will choose the first
|
||||
// exported variable that matches *Page
|
||||
const componentExportName = Object.keys(page).find(varName =>
|
||||
this.pageComponentPattern.test(varName)
|
||||
);
|
||||
|
||||
if (!componentExportName) {
|
||||
throw new Error(
|
||||
`${dir}${file} in the pages folder does not include an exported \`${this.pageComponentPattern.toString()}\` component`
|
||||
);
|
||||
}
|
||||
|
||||
// _404 route is special and maps to a 404 page
|
||||
if (filePath === '/_404') {
|
||||
return {
|
||||
path: '*',
|
||||
component: page[componentExportName],
|
||||
};
|
||||
}
|
||||
|
||||
// mapped route has a parent with mapped params, so we map it here too
|
||||
// e.g. /beat has a beatid param, so /beat/detail, a child of /beat
|
||||
// should also have that param resulting in /beat/:beatid/detail/:other
|
||||
if (mapParams[cleanDir] && filePath !== cleanDir) {
|
||||
const dirWithParams = `${cleanDir}/:${mapParams[cleanDir].join('/:')}`;
|
||||
const path = `${dirWithParams}/${file.replace('.tsx', '')}${
|
||||
mapParams[filePath] ? '/:' : ''
|
||||
}${(mapParams[filePath] || []).join('/:')}`;
|
||||
return {
|
||||
path,
|
||||
component: page[componentExportName],
|
||||
};
|
||||
}
|
||||
|
||||
// route matches a mapped param exactly
|
||||
// e.g. /beat has a beatid param, so it becomes /beat/:beatid
|
||||
if (mapParams[filePath]) {
|
||||
return {
|
||||
path: `${filePath}/:${mapParams[filePath].join('/:')}`,
|
||||
component: page[componentExportName],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
path: filePath,
|
||||
component: page[componentExportName],
|
||||
};
|
||||
}
|
||||
|
||||
// Build tree recursively
|
||||
private buildTree(basePath: string, paths: string[]): PathTree {
|
||||
return paths.reduce(
|
||||
(dir: any, p) => {
|
||||
const path = {
|
||||
dir:
|
||||
p
|
||||
.replace(basePath, '/') // make path absolute
|
||||
.split('/')
|
||||
.slice(0, -1) // remove file from path
|
||||
.join('/')
|
||||
.replace(/^\/\//, '') + '/', // should end in a slash but not be only //
|
||||
file: last(p.split('/')),
|
||||
};
|
||||
// take each, remove the file name
|
||||
|
||||
if (dir[path.dir]) {
|
||||
dir[path.dir].push(path.file);
|
||||
} else {
|
||||
dir[path.dir] = [path.file];
|
||||
}
|
||||
return dir;
|
||||
},
|
||||
|
||||
{}
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
# Page loader
|
||||
|
||||
Routing in React is not easy, nether is ensuring a clean and simple api within pages.
|
||||
This solves for both without massive config files. It also ensure URL paths match our files to make things easier to find
|
||||
|
||||
It works like this...
|
||||
|
||||
```ts
|
||||
// Create a webpack context, ensureing all pages in the pages dir are included in the build
|
||||
const requirePages = require.context('./pages', true, /\.tsx$/);
|
||||
// Pass the context based require into the RouteTreeBuilder for require the files as-needed
|
||||
const routeTreeBuilder = new RouteTreeBuilder(requirePages);
|
||||
// turn the array of file paths from the require context into a nested tree of routes based on folder structure
|
||||
const routesFromFilesystem = routeTreeBuilder.routeTreeFromPaths(requirePages.keys(), {
|
||||
'/tag': ['action', 'tagid?'], // add params to a page. In this case /tag turns into /tag/:action/:tagid?
|
||||
'/beat': ['beatId'],
|
||||
'/beat/detail': ['action'], // it nests too, in this case, because of the above line, this is /beat/:beatId/detail/:action
|
||||
});
|
||||
```
|
||||
|
||||
In the above example to allow for flexability, the `./pages/beat.tsx` page would receve a prop of `routes` that is an array of sub-pages
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue