├── .eslintrc.js
├── .github
└── FUNDING.yml
├── .gitignore
├── LICENSE
├── README.md
├── common
└── index.ts
├── docs
├── GETTING_STARTED.md
├── sample_compare_results.jpg
├── sample_custom_analyzer.jpg
├── sample_image.jpg
├── sample_show_token_attrs.jpg
└── sample_specify_field.jpg
├── kibana.json
├── package.json
├── public
├── application.tsx
├── common
│ └── constants
│ │ ├── tab_names.tsx
│ │ └── token_properties.tsx
├── components
│ ├── analyze_ui
│ │ ├── analyze_ui.tsx
│ │ └── index.ts
│ ├── analyzer_form
│ │ ├── analyzer_form.tsx
│ │ ├── compare_analyzers.tsx
│ │ ├── custom.tsx
│ │ ├── field.tsx
│ │ ├── index.ts
│ │ └── simple.tsx
│ ├── app.tsx
│ ├── common_form
│ │ ├── common_form.tsx
│ │ └── index.ts
│ ├── form_control
│ │ ├── form_control.tsx
│ │ └── index.ts
│ └── result
│ │ ├── analyzer_result
│ │ ├── analyzer_result.tsx
│ │ ├── charfilters
│ │ │ ├── chafilters.tsx
│ │ │ └── index.ts
│ │ ├── index.ts
│ │ └── tokenizer_and_filters
│ │ │ ├── filters.tsx
│ │ │ ├── index.ts
│ │ │ ├── token.tsx
│ │ │ ├── tokenizer.tsx
│ │ │ └── tokenizer_and_filters.tsx
│ │ ├── compare_result
│ │ ├── compare_result.tsx
│ │ └── index.ts
│ │ ├── index.ts
│ │ └── result.tsx
├── index.scss
├── index.ts
├── plugin.ts
├── services
│ ├── api.ts
│ ├── pagestate.ts
│ ├── params.ts
│ ├── state_handler.ts
│ ├── storage.ts
│ ├── token_utils.ts
│ └── validator.ts
└── types.ts
├── server
├── index.ts
├── plugin.ts
├── routes
│ ├── handle_es_error.ts
│ └── index.ts
└── types.ts
└── tsconfig.json
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | extends: ['@elastic/eslint-config-kibana', 'plugin:@elastic/eui/recommended'],
4 | rules: {
5 | '@kbn/eslint/require-license-header': 'off',
6 | },
7 | };
8 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: johtani
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | npm-debug.log*
2 | node_modules
3 | /build
4 | /target
5 | .idea
6 | *.iml
7 | .DS_store
8 | /yarn.lock
9 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # analyze-api-ui-plugin
2 |
3 | > UI for elasticsearch analyze API
4 |
5 | Analyze text with Analyzer
6 | 
7 |
8 | This branch is for Kibana 7.x.
9 |
10 | ## How to use?
11 |
12 | See [Getting Started](docs/GETTING_STARTED.md)
13 |
14 | ## Installation
15 | The latest stable version is for kibana 7.17.0.
16 |
17 | | Kibana version | Command |
18 | |----------------|---------------------------------------------------------------------------------------------------------------------------------|
19 | | 7.17.0 | `./bin/kibana-plugin install https://github.com/johtani/analyze-api-ui-plugin/releases/download/7.17.0/analyzeApiUi-7.17.0.zip` |
20 | | 7.16.2 | `./bin/kibana-plugin install https://github.com/johtani/analyze-api-ui-plugin/releases/download/7.16.2/analyzeApiUi-7.16.2.zip` |
21 | | 7.13.4 | `./bin/kibana-plugin install https://github.com/johtani/analyze-api-ui-plugin/releases/download/7.13.4/analyzeApiUi-7.13.4.zip` |
22 | | 7.6.2 | `./bin/kibana-plugin install https://github.com/johtani/analyze-api-ui-plugin/releases/download/7.6.2/analyze_api_ui-7.6.2.zip` |
23 | | 7.6.2 | `./bin/kibana-plugin install https://github.com/johtani/analyze-api-ui-plugin/releases/download/7.6.2/analyze_api_ui-7.6.2.zip` |
24 | | 7.6.1 | `./bin/kibana-plugin install https://github.com/johtani/analyze-api-ui-plugin/releases/download/7.6.1/analyze_api_ui-7.6.1.zip` |
25 | | 7.6.0 | `./bin/kibana-plugin install https://github.com/johtani/analyze-api-ui-plugin/releases/download/7.6.0/analyze_api_ui-7.6.0.zip` |
26 | | 7.5.2 | `./bin/kibana-plugin install https://github.com/johtani/analyze-api-ui-plugin/releases/download/7.5.2/analyze_api_ui-7.5.2.zip` |
27 | | 7.5.1 | `./bin/kibana-plugin install https://github.com/johtani/analyze-api-ui-plugin/releases/download/7.5.1/analyze_api_ui-7.5.1.zip` |
28 | | 7.5.0 | `./bin/kibana-plugin install https://github.com/johtani/analyze-api-ui-plugin/releases/download/7.5.0/analyze_api_ui-7.5.0.zip` |
29 | | 7.4.2 | `./bin/kibana-plugin install https://github.com/johtani/analyze-api-ui-plugin/releases/download/7.4.2/analyze_api_ui-7.4.2.zip` |
30 | | 7.4.1 | `./bin/kibana-plugin install https://github.com/johtani/analyze-api-ui-plugin/releases/download/7.4.1/analyze_api_ui-7.4.1.zip` |
31 | | 7.4.0 | `./bin/kibana-plugin install https://github.com/johtani/analyze-api-ui-plugin/releases/download/7.4.0/analyze_api_ui-7.4.0.zip` |
32 | | 7.3.2 | `./bin/kibana-plugin install https://github.com/johtani/analyze-api-ui-plugin/releases/download/7.3.2/analyze_api_ui-7.3.2.zip` |
33 | | 7.3.1 | `./bin/kibana-plugin install https://github.com/johtani/analyze-api-ui-plugin/releases/download/7.3.1/analyze_api_ui-7.3.1.zip` |
34 | | 7.3.0 | `./bin/kibana-plugin install https://github.com/johtani/analyze-api-ui-plugin/releases/download/7.3.0/analyze_api_ui-7.3.0.zip` |
35 | | 7.2.0 | `./bin/kibana-plugin install https://github.com/johtani/analyze-api-ui-plugin/releases/download/7.2.0/analyze_api_ui-7.2.0.zip` |
36 | | 7.1.1 | `./bin/kibana-plugin install https://github.com/johtani/analyze-api-ui-plugin/releases/download/7.1.1/analyze_api_ui-7.1.1.zip` |
37 | | 7.1.0 | `./bin/kibana-plugin install https://github.com/johtani/analyze-api-ui-plugin/releases/download/7.1.0/analyze_api_ui-7.1.0.zip` |
38 | | 7.0.1 | `./bin/kibana-plugin install https://github.com/johtani/analyze-api-ui-plugin/releases/download/7.0.1/analyze_api_ui-7.0.1.zip` |
39 | | 7.0.0 | `./bin/kibana-plugin install https://github.com/johtani/analyze-api-ui-plugin/releases/download/7.0.0/analyze_api_ui-7.0.0.zip` |
40 |
41 |
42 | Older versions (6.7.1 - 6.1.2)
43 |
44 | | Kibana version | Command |
45 | |----------------|----------------------------------------------------------------------------------------------------------------------------------------|
46 | | 6.7.1 | `./bin/kibana-plugin install https://github.com/johtani/analyze-api-ui-plugin/releases/download/6.7.1/analyze-api-ui-plugin-6.7.1.zip` |
47 | | 6.7.0 | `./bin/kibana-plugin install https://github.com/johtani/analyze-api-ui-plugin/releases/download/6.7.0/analyze-api-ui-plugin-6.7.0.zip` |
48 | | 6.6.2 | `./bin/kibana-plugin install https://github.com/johtani/analyze-api-ui-plugin/releases/download/6.6.2/analyze-api-ui-plugin-6.6.2.zip` |
49 | | 6.6.1 | `./bin/kibana-plugin install https://github.com/johtani/analyze-api-ui-plugin/releases/download/6.6.1/analyze-api-ui-plugin-6.6.1.zip` |
50 | | 6.6.0 | `./bin/kibana-plugin install https://github.com/johtani/analyze-api-ui-plugin/releases/download/6.6.0/analyze-api-ui-plugin-6.6.0.zip` |
51 | | 6.5.4 | `./bin/kibana-plugin install https://github.com/johtani/analyze-api-ui-plugin/releases/download/6.5.4/analyze-api-ui-plugin-6.5.4.zip` |
52 | | 6.5.3 | `./bin/kibana-plugin install https://github.com/johtani/analyze-api-ui-plugin/releases/download/6.5.3/analyze-api-ui-plugin-6.5.3.zip` |
53 | | 6.5.2 | `./bin/kibana-plugin install https://github.com/johtani/analyze-api-ui-plugin/releases/download/6.5.2/analyze-api-ui-plugin-6.5.2.zip` |
54 | | 6.5.1 | `./bin/kibana-plugin install https://github.com/johtani/analyze-api-ui-plugin/releases/download/6.5.1/analyze-api-ui-plugin-6.5.1.zip` |
55 | | 6.5.0 | `./bin/kibana-plugin install https://github.com/johtani/analyze-api-ui-plugin/releases/download/6.5.0/analyze-api-ui-plugin-6.5.0.zip` |
56 | | 6.4.3 | `./bin/kibana-plugin install https://github.com/johtani/analyze-api-ui-plugin/releases/download/6.4.3/analyze-api-ui-plugin-6.4.3.zip` |
57 | | 6.4.2 | `./bin/kibana-plugin install https://github.com/johtani/analyze-api-ui-plugin/releases/download/6.4.2/analyze-api-ui-plugin-6.4.2.zip` |
58 | | 6.4.1 | `./bin/kibana-plugin install https://github.com/johtani/analyze-api-ui-plugin/releases/download/6.4.1/analyze-api-ui-plugin-6.4.1.zip` |
59 | | 6.4.0 | `./bin/kibana-plugin install https://github.com/johtani/analyze-api-ui-plugin/releases/download/6.4.0/analyze-api-ui-plugin-6.4.0.zip` |
60 | | 6.3.2 | `./bin/kibana-plugin install https://github.com/johtani/analyze-api-ui-plugin/releases/download/6.3.2/analyze-api-ui-plugin-6.3.2.zip` |
61 | | 6.3.0 | `./bin/kibana-plugin install https://github.com/johtani/analyze-api-ui-plugin/releases/download/6.3.0/analyze-api-ui-plugin-6.3.0.zip` |
62 | | 6.2.4 | `./bin/kibana-plugin install https://github.com/johtani/analyze-api-ui-plugin/releases/download/6.2.4/analyze-api-ui-plugin-6.2.4.zip` |
63 | | 6.2.3 | `./bin/kibana-plugin install https://github.com/johtani/analyze-api-ui-plugin/releases/download/6.2.3/analyze-api-ui-plugin-6.2.3.zip` |
64 | | 6.2.2 | `./bin/kibana-plugin install https://github.com/johtani/analyze-api-ui-plugin/releases/download/6.2.2/analyze-api-ui-plugin-6.2.2.zip` |
65 | | 6.2.1 | `./bin/kibana-plugin install https://github.com/johtani/analyze-api-ui-plugin/releases/download/6.2.1/analyze-api-ui-plugin-6.2.1.zip` |
66 | | 6.2.0 | `./bin/kibana-plugin install https://github.com/johtani/analyze-api-ui-plugin/releases/download/6.2.0/analyze-api-ui-plugin-6.2.0.zip` |
67 | | 6.1.3 | `./bin/kibana-plugin install https://github.com/johtani/analyze-api-ui-plugin/releases/download/6.1.3/analyze-api-ui-plugin-6.1.3.zip` |
68 | | 6.1.2 | `./bin/kibana-plugin install https://github.com/johtani/analyze-api-ui-plugin/releases/download/6.1.2/analyze-api-ui-plugin-6.1.2.zip` |
69 |
70 |
71 |
72 | ## Known issues
73 |
74 | * not restore filters/char_filters after moving another tabs [#42](https://github.com/johtani/analyze-api-ui-plugin/issues/42)
75 |
76 | ## TODO
77 |
78 | * change index name to select
79 | * preload index_name from _cat/indices
80 | * error handling
81 |
82 | ---
83 |
84 | ## Development
85 |
86 | See the [kibana contributing guide](https://github.com/elastic/kibana/blob/main/CONTRIBUTING.md) for instructions setting up your development environment.
87 |
88 | ## Scripts
89 |
90 |
91 | yarn kbn bootstrap
92 | - Execute this to install node_modules and set up the dependencies in your plugin and in Kibana
93 |
94 | yarn plugin-helpers build
95 | - Execute this to create a distributable version of this plugin that can be installed in Kibana
96 |
97 |
98 | ## Thanks
99 |
100 | Thank [@risdenk](https://github.com/risdenk) for your great help to upgrade 7.10+!
101 |
--------------------------------------------------------------------------------
/common/index.ts:
--------------------------------------------------------------------------------
1 | export const PLUGIN_ID = 'analyzeApiUi';
2 | export const PLUGIN_NAME = 'analyze-api-ui';
3 |
--------------------------------------------------------------------------------
/docs/GETTING_STARTED.md:
--------------------------------------------------------------------------------
1 | # Getting Started Analuze UI Plugin
2 |
3 | ## What is this?
4 |
5 | This Kibana plugin is the UI for [Elasticsearch _analyze API](https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-analyze.html).
6 | The _analyze API is great for understanding how to work analysis process in Elasticsearch,
7 | but JSON response isn't human-readable if text is long or `explain=true` parameter is used.
8 |
9 | This plugin will show analyzed result more readable :)
10 | The plugin have some features, let's show how it works.
11 | And it also show the request syntax for Console under the table.
12 |
13 | ## 1. Show analyzed result of built-in analyzer
14 |
15 | This is simplest use-case for this plugin.
16 | Choose `analyzer` tab and fill two text inputs.
17 |
18 | 1. Text - Text what you want to analyze
19 | 2. Analyzer - Built-in analyzer name, e.g. `standard`
20 |
21 | Then click `Analyze!` button, it will show a list of `tokens`.
22 |
23 | 
24 |
25 | ## 2. Show analyzed result of defined analyzer in index
26 |
27 | If you want to see analyzed result of the analyzer that you define in index,
28 | you can specify your index name and analyzer name.
29 | Choose `Analyzer` tab and fill three text inputs.
30 |
31 | 1. Index name - Index name what define an analyzer you want to use
32 | 2. Text - Text what you want to analyze
33 | 3. Analyzer - your analyzer name
34 |
35 | Then click `Analyze!` button, it will show a list of `tokens`.
36 |
37 | ## 3. Show analyzed result of custom analyzer
38 |
39 | If you want to test a combination of char_filter/tokenizer/filter before defining your custom analyzer,
40 | you can use `custom_analyzer` tab.
41 | Choose `custom_analyzer` tab and fill char_filter/tokenizer/filter text inputs.
42 |
43 | 1. Text - Text what you want to analyzer
44 | 2. char_filter - char_filter name/custom char_filter if you need
45 | 3. tokenizer - tokenizer name
46 | 4. filter - filter name/custom filter if you need
47 |
48 | Then click `Analyze!` button, it will show lists of text and `tokens`.
49 |
50 | 
51 |
52 | If you want to use multiple char_filters/filters, click `plus` icon.
53 | The plugin will show new text input.
54 |
55 | You can also specify custom char_filter/tokenizer/filter as follow:
56 |
57 | ```JSON
58 | {
59 | "type": "mapping",
60 | "mappings": [
61 | ":) => _happy_",
62 | ":( => _sad_"
63 | ]
64 | }
65 | ```
66 |
67 | The example is char_filter with custom mapping.
68 |
69 |
70 | ## 4. Show more token attributes in the result
71 |
72 | Lucene analysis module provides many token attributes for each tokens.
73 | e.g. Kuromoji tokenizer outputs token attributes like `partOfSpeech`, `reading`, 'baseForm', etc.
74 | However, this plugin shows only `token` and `position` by default.
75 | If turn on `Show all Token Attrs?` switch that next to `Analyze!` button,
76 | the plugin shows all token attributes in the result table.
77 | It also works after clicking `Analyze!` button.
78 |
79 | 
80 |
81 |
82 | ## 4. Show analyzed result of a field in the index mapping
83 |
84 | If you want to analyze text with existing field,
85 | you can specify your index name and "field name".
86 |
87 | 1. Index name - Index name what define an analyzer you want to use
88 | 2. Text - Text what you want to analyze
89 | 3. Field - your field name
90 |
91 | Then the plugin shows lists of tokens that are provided by the analyzer that is defined to the field.
92 |
93 | 
94 |
95 | ## 5. Compare analyzed results between some analyzers
96 |
97 | The plugin also shows a comparison of list of tokens that are provided by each analyzers you put in form.
98 |
99 | 
100 |
--------------------------------------------------------------------------------
/docs/sample_compare_results.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johtani/analyze-api-ui-plugin/aa3d2ebcdc71a1485897fa741c91ff610586ded1/docs/sample_compare_results.jpg
--------------------------------------------------------------------------------
/docs/sample_custom_analyzer.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johtani/analyze-api-ui-plugin/aa3d2ebcdc71a1485897fa741c91ff610586ded1/docs/sample_custom_analyzer.jpg
--------------------------------------------------------------------------------
/docs/sample_image.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johtani/analyze-api-ui-plugin/aa3d2ebcdc71a1485897fa741c91ff610586ded1/docs/sample_image.jpg
--------------------------------------------------------------------------------
/docs/sample_show_token_attrs.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johtani/analyze-api-ui-plugin/aa3d2ebcdc71a1485897fa741c91ff610586ded1/docs/sample_show_token_attrs.jpg
--------------------------------------------------------------------------------
/docs/sample_specify_field.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/johtani/analyze-api-ui-plugin/aa3d2ebcdc71a1485897fa741c91ff610586ded1/docs/sample_specify_field.jpg
--------------------------------------------------------------------------------
/kibana.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "analyzeApiUi",
3 | "version": "7.17.0",
4 | "kibanaVersion": "kibana",
5 | "owner": {
6 | "name": "johtani"
7 | },
8 | "description": "UI for elasticsearch analyze API",
9 | "server": true,
10 | "ui": true,
11 | "requiredPlugins": ["navigation"],
12 | "optionalPlugins": []
13 | }
14 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "analyze_api_ui",
3 | "version": "7.17.0",
4 | "private": true,
5 | "scripts": {
6 | "build": "yarn plugin-helpers build",
7 | "plugin-helpers": "node ../../scripts/plugin_helpers",
8 | "kbn": "node ../../scripts/kbn"
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/public/application.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { AppMountParameters, CoreStart } from 'kibana/public';
4 | import { AppPluginStartDependencies } from './types';
5 | import { AnalyzeApiUiApp } from './components/app';
6 |
7 | export const renderApp = (
8 | { notifications, http }: CoreStart,
9 | { navigation }: AppPluginStartDependencies,
10 | { appBasePath, element }: AppMountParameters
11 | ) => {
12 | ReactDOM.render(
13 | ,
19 | element
20 | );
21 |
22 | return () => ReactDOM.unmountComponentAtNode(element);
23 | };
24 |
--------------------------------------------------------------------------------
/public/common/constants/tab_names.tsx:
--------------------------------------------------------------------------------
1 | export const TAB_NAME = {
2 | ANALYZER: 'Analyzer',
3 | CUSTOM_ANALYZER: 'Custom Analyzer',
4 | FIELD: 'Field',
5 | COMPARE_ANALYZERS: 'Compare Analyzers'
6 | };
7 |
8 | export const RESULT_TYPE = {
9 | SINGLE: 'single',
10 | MULTI: 'multi'
11 | };
12 |
--------------------------------------------------------------------------------
/public/common/constants/token_properties.tsx:
--------------------------------------------------------------------------------
1 | export const DISP_TOKEN_PROPS = {
2 | alwaysShowTokenProperties: [
3 | "token",
4 | "position"
5 | ],
6 | hiddenTokenProperties: [
7 | "bytes",
8 | "pronunciation (en)",
9 | "reading (en)",
10 | "partOfSpeech (en)",
11 | "inflectionType (en)",
12 | "inflectionForm (en)"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/public/components/analyze_ui/analyze_ui.tsx:
--------------------------------------------------------------------------------
1 | import React, {
2 | Component,
3 | Fragment,
4 | } from 'react';
5 | import {
6 | EuiCallOut,
7 | EuiSpacer,
8 | } from '@elastic/eui'
9 |
10 | import { CommonForm } from "../common_form";
11 | import { AnalyzerForm } from "../analyzer_form";
12 | import { FormControl } from "../form_control";
13 | import { Result } from "../result";
14 | import {
15 | updateParamsWithEvent,
16 | updateParamsWithEventAndIndex,
17 | selectTab
18 | } from "../../services/params";
19 | import {
20 | setHttpClient,
21 | analyze,
22 | multiAnalyze
23 | } from "../../services/api";
24 | import {validateAnalyzeRequestValues} from "../../services/validator";
25 | import {TAB_NAME} from "../../common/constants/tab_names";
26 | import {loadSavedState, saveState} from "../../services/state_handler";
27 |
28 | export class AnalyzeUi extends Component {
29 | constructor(props) {
30 | super(props);
31 | const params = loadSavedState();
32 | this.state = {
33 | params: params,
34 | errors: {}
35 | };
36 | setHttpClient(this.props.httpClient);
37 |
38 | this.selectTab = this.selectTab.bind(this);
39 | this.updateParamsWithEvent = this.updateParamsWithEvent.bind(this);
40 | this.updateParamsWithEventAndIndex = this.updateParamsWithEventAndIndex.bind(this);
41 | this.displayResult = this.displayResult.bind(this);
42 | }
43 |
44 | clearErrors() {
45 | this.setState({
46 | errors: {}
47 | });
48 | }
49 |
50 | updateParamsWithEvent(e) {
51 | const params = updateParamsWithEvent(e, this.state.params);
52 | this.updateParams(params);
53 | };
54 |
55 | updateParamsWithEventAndIndex(e) {
56 | const params = updateParamsWithEventAndIndex(e, this.state.params);
57 | this.updateParams(params);
58 | };
59 |
60 | selectTab(tab) {
61 | const params = selectTab(tab, this.state.params);
62 | this.updateParams(params);
63 | this.clearResults();
64 | this.clearErrors();
65 | };
66 |
67 | updateParams(params) {
68 | this.setState({
69 | params: params
70 | });
71 | saveState(this.state);
72 | };
73 |
74 | clearResults() {
75 | if (this.state.detail) {
76 | this.setState({
77 | "detail": {},
78 | "showResult": false,
79 | "esRequest": "",
80 | "resultAnalyzers": []
81 | });
82 | }
83 | }
84 |
85 | callMultiAnalyzeApi (params) {
86 | const result = multiAnalyze(params);
87 | result.then(
88 | (response) => {
89 | this.setState({
90 | showResult: true,
91 | resultAnalyzers: response.resultAnalyzers,
92 | resultType: "multi"
93 | });
94 | }
95 | ).catch(
96 | error => {
97 | if (error.body) {
98 | if (error.body.statusCode == 404) {
99 | this.setState({
100 | errors: {
101 | indexNameError: error.body.message
102 | }
103 | })
104 | } else if (error.body.statusCode == 400) {
105 | this.setState({
106 | errors: {
107 | analyzerError: error.body.message
108 | }
109 | });
110 | } else {
111 | //TODO Notification
112 | console.error(error);
113 | }
114 | } else {
115 | //TODO Notification
116 | console.error(error);
117 | }
118 | }
119 | );
120 | }
121 |
122 | callAnalyzeApi (params) {
123 | const result = analyze(params);
124 | result.then(
125 | (response) => {
126 | this.setState({
127 | showResult: true,
128 | detail: response.detail,
129 | esRequest: response.request,
130 | resultType: "single"
131 | });
132 | }
133 | );
134 | result.catch(
135 | error => {
136 | if (error.body) {
137 | if (error.body.statusCode == 404) {
138 | this.setState({
139 | errors: {
140 | indexNameError: error.body.message
141 | }
142 | })
143 | } else if (error.body.statusCode == 400) {
144 | this.setState({
145 | errors: {
146 | analyzerError: error.body.message
147 | }
148 | });
149 | } else {
150 | //TODO
151 | console.error(error);
152 | }
153 | }
154 | console.error(error);
155 | //TODO Notification!
156 | }
157 | );
158 | }
159 |
160 | // render results
161 | displayResult(e) {
162 | this.clearErrors();
163 | this.clearResults();
164 | const {params} = this.state;
165 | const {tab} = this.state.params;
166 | const validatedParams = validateAnalyzeRequestValues(params);
167 | if (Object.keys(validatedParams.errors).length) {
168 | this.setState({
169 | errors: validatedParams.errors
170 | });
171 | } else {
172 | //collect current values from common form and analyzer form
173 | if (tab != TAB_NAME.COMPARE_ANALYZERS) {
174 | this.callAnalyzeApi(validatedParams.requestParams);
175 | } else {
176 | this.callMultiAnalyzeApi(validatedParams.requestParams);
177 | }
178 | }
179 | };
180 |
181 | renderCommonFormErrors() {
182 | const {errors} = this.state;
183 | if (!Object.keys(errors).includes("indexNameError") &&
184 | !Object.keys(errors).includes("textError")) {
185 | return;
186 | } else {
187 | return (
188 |
189 |
194 | Please check every inputs.
195 |
196 |
197 |
198 | );
199 | }
200 | }
201 |
202 | renderAnalyzerErrors() {
203 | const {errors} = this.state;
204 | if (!Object.keys(errors).includes("analyzerError") &&
205 | !Object.keys(errors).includes("fieldError")) {
206 | return;
207 | } else {
208 | let target = "analyzer";
209 | const {tab} = this.state.params;
210 | if (tab == TAB_NAME.CUSTOM_ANALYZER) {
211 | target = "chafilter/tokenizer/filter";
212 | } else if (tab == TAB_NAME.FIELD) {
213 | target = "field";
214 | }
215 | return (
216 |
217 |
222 | {this.state.errors.analyzerError}
223 | {this.state.errors.fieldError}
224 |
225 |
226 |
227 | );
228 | }
229 | }
230 |
231 | render() {
232 | const {indexName} = this.state.params;
233 | return (
234 |
235 | {this.renderCommonFormErrors()}
236 |
239 |
240 | {this.renderAnalyzerErrors()}
241 |
246 |
247 |
252 |
253 | {this.state.showResult && (
254 |
262 | )}
263 |
264 | );
265 | }
266 | }
267 |
--------------------------------------------------------------------------------
/public/components/analyze_ui/index.ts:
--------------------------------------------------------------------------------
1 | export { AnalyzeUi } from './analyze_ui';
--------------------------------------------------------------------------------
/public/components/analyzer_form/analyzer_form.tsx:
--------------------------------------------------------------------------------
1 | import React, {
2 | Component,
3 | Fragment
4 | } from 'react';
5 |
6 | import {
7 | EuiButtonIcon,
8 | EuiTabbedContent,
9 | EuiTableRow,
10 | EuiTableRowCell,
11 | EuiToolTip,
12 | EuiSpacer,
13 | } from '@elastic/eui';
14 |
15 | import { SimpleAnalyzer } from './simple';
16 | import { CustomAnalyzer } from './custom';
17 | import { FieldAnalyzer } from './field';
18 | import { CompareAnalyzers } from './compare_analyzers';
19 | import { TAB_NAME } from "../../common/constants/tab_names";
20 |
21 | export class AnalyzerForm extends Component {
22 | tabs: { id: string; name: string; content: JSX.Element; }[];
23 | constructor(props: Readonly<{}>) {
24 | super(props);
25 | this.tabs = [{
26 | id: 'analyzer',
27 | name: TAB_NAME.ANALYZER,
28 | content: (
29 |
30 |
31 |
35 |
36 | )
37 | }, {
38 | id: 'custom_analyzer',
39 | name: TAB_NAME.CUSTOM_ANALYZER,
40 | content: (
41 |
42 |
43 |
48 |
49 | )
50 | }, {
51 | id: 'field',
52 | name: TAB_NAME.FIELD,
53 | content: (
54 |
55 |
56 |
60 |
61 | )
62 | }, {
63 | id: 'compare_analyzers',
64 | name: TAB_NAME.COMPARE_ANALYZERS,
65 | content: (
66 |
67 |
68 |
73 |
74 | )
75 | }];
76 | this.props.selectTab(this.tabs[this.tabIndex()]);
77 | }
78 |
79 | tabIndex() {
80 | const {tab} = this.props.params;
81 | let tabIdx = 0;
82 | if (tab) {
83 | this.tabs.forEach((tmpTab, idx) => {
84 | if (tmpTab.name == tab) {
85 | tabIdx = idx;
86 | }
87 | }
88 | );
89 | }
90 | return tabIdx;
91 | }
92 |
93 | render() {
94 | return (
95 |
100 | )
101 | }
102 | }
103 |
104 | export function displayRowsComponent(WrappedComponent): typeof WrappedComponent {
105 | return class extends WrappedComponent {
106 |
107 | appendRow(type, index) {
108 | super.appendRow(type, index);
109 | }
110 |
111 | removeRow(type, index) {
112 | super.removeRow(type, index);
113 | }
114 |
115 | renderAdjustButton (type, cell, index, length) {
116 | if (cell.button) {
117 | return (
118 |
119 | {index == 0 ? (
120 |
121 | this.appendRow(type, index)}
125 | />
126 |
127 | ) : null }
128 | {length > 1 ? (
129 |
130 | this.removeRow(type, index)}
134 | />
135 |
136 | ) : null }
137 |
138 | );
139 | } else {
140 | return (
141 |
142 | );
143 | }
144 | }
145 |
146 | renderRow (type, cells) {
147 | return cells.map((cell, index) => {
148 | return (
149 |
150 |
151 | {type}
152 |
153 |
154 | {cell.form.renderTag(index)}
155 |
156 | {this.renderAdjustButton(type, cell, index, cells.length)}
157 |
158 | );
159 | });
160 | }
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/public/components/analyzer_form/compare_analyzers.tsx:
--------------------------------------------------------------------------------
1 | import React, {
2 | Component,
3 | } from 'react';
4 | import {
5 | EuiFieldText,
6 | EuiTable,
7 | EuiTableBody
8 | } from '@elastic/eui';
9 | import { displayRowsComponent } from './analyzer_form';
10 |
11 | class CompareAnalyzersComponent extends Component {
12 | constructor(props) {
13 | super(props);
14 | this.state = {
15 | rows: {
16 | "analyzersForCompare": []
17 | }
18 | };
19 | //Need two boxes because of "Compare..."
20 | this.appendAnalyzer();
21 | this.appendAnalyzer();
22 | }
23 |
24 | appendAnalyzer(type, index) {
25 | this.state.rows.analyzersForCompare.push(
26 | {
27 | type: "analyzersForCompare",
28 | label: {
29 | value: "analyzer",
30 | style: {width: 100},
31 | },
32 | form: {
33 | renderTag: (index) => {
34 | return (
35 |
40 | );
41 | },
42 | style: {width: 300},
43 | },
44 | button: true
45 | }
46 | );
47 | this.setState({rows: this.state.rows});
48 | }
49 |
50 | appendRow(type, index) {
51 | this.appendAnalyzer();
52 | }
53 |
54 | removeRow(type, index) {
55 | this.state.rows.analyzersForCompare.splice(index, 1);
56 | this.setState({rows: this.state.rows});
57 | }
58 |
59 | render() {
60 | return (
61 |
62 |
63 | {this.renderRow("analyzer", this.state.rows.analyzersForCompare)}
64 |
65 |
66 | )
67 | }
68 | }
69 | const CompareAnalyzers = displayRowsComponent(CompareAnalyzersComponent);
70 | export { CompareAnalyzers };
71 |
--------------------------------------------------------------------------------
/public/components/analyzer_form/custom.tsx:
--------------------------------------------------------------------------------
1 | import React, {
2 | Component
3 | } from 'react';
4 | import {
5 | EuiTextArea,
6 | EuiTable,
7 | EuiTableBody
8 | } from '@elastic/eui';
9 | import { displayRowsComponent } from './analyzer_form';
10 |
11 | class CustomAnalyzerComponent extends Component {
12 | constructor(props) {
13 | super(props);
14 | this.state = {
15 | rows: {
16 | "char_filter": [],
17 | "tokenizer": [
18 | {
19 | type: "tokenizer",
20 | label: {
21 | value: "tokenizer",
22 | style: {width: 100},
23 | },
24 | form: {
25 | renderTag: (index) => {
26 | return (
27 |
33 | );
34 | },
35 | style: {width: 300},
36 | }
37 | }
38 | ],
39 | "filter": []
40 | }
41 | }
42 | // initialize charfilter and filter
43 | this.appendCharFilter();
44 | this.appendFilter();
45 | }
46 |
47 | appendCharFilter() {
48 | this.state.rows.char_filter.push(
49 | {
50 | type: "char_filter",
51 | label: {
52 | value: "char_filter",
53 | style: {width: 100},
54 | },
55 | form: {
56 | renderTag: (index) => {
57 | return (
58 |
64 | );
65 | },
66 | style: {width: 300},
67 | },
68 | button: true
69 | }
70 | );
71 | this.setState(
72 | {
73 | rows: this.state.rows
74 | }
75 | );
76 | }
77 |
78 | appendFilter() {
79 | this.state.rows.filter.push(
80 | {
81 | type: "filter",
82 | label: {
83 | value: "filter",
84 | style: {width: 100},
85 | },
86 | form: {
87 | renderTag: (index) => {
88 | return (
89 |
95 | );
96 | },
97 | style: {width: 300},
98 | },
99 | button: true
100 | }
101 | );
102 | this.setState(
103 | {
104 | rows: this.state.rows
105 | }
106 | );
107 | }
108 |
109 | appendRow(type, index) {
110 | if (type == "char_filter") {
111 | this.appendCharFilter();
112 | } else if (type == "filter") {
113 | this.appendFilter();
114 | }
115 | }
116 |
117 | removeRow(type, index) {
118 | if (type == "char_filter") {
119 | this.state.rows.char_filter.splice(index, 1);
120 | this.setState({rows: this.state.rows});
121 | } else if (type == "filter") {
122 | this.state.rows.filter.splice(index, 1);
123 | this.setState({rows: this.state.rows});
124 | }
125 | }
126 |
127 | render () {
128 | return (
129 |
130 |
131 | {this.renderRow("char_filter", this.state.rows.char_filter)}
132 | {this.renderRow("tokenizer", this.state.rows.tokenizer)}
133 | {this.renderRow("filter", this.state.rows.filter)}
134 |
135 |
136 | )
137 | }
138 | }
139 | const CustomAnalyzer = displayRowsComponent(CustomAnalyzerComponent);
140 | export { CustomAnalyzer };
141 |
--------------------------------------------------------------------------------
/public/components/analyzer_form/field.tsx:
--------------------------------------------------------------------------------
1 | import React, {
2 | Component
3 | } from 'react';
4 | import {
5 | EuiFieldText,
6 | EuiTable,
7 | EuiTableBody
8 | } from '@elastic/eui';
9 | import { displayRowsComponent } from './analyzer_form';
10 |
11 | class FieldAnalyzerComponent extends Component {
12 |
13 | constructor(props) {
14 | super(props);
15 | this.state = {
16 | rows: {
17 | "field": [{
18 | type: "field",
19 | label: {
20 | value: "field",
21 | style: {width: 100},
22 | },
23 | form: {
24 | renderTag: (index) => {
25 | return (
26 |
33 | );
34 | },
35 | style: {width: 300},
36 | }
37 | }]
38 | }
39 | }
40 | }
41 |
42 | render () {
43 | return (
44 |
45 |
46 | {this.renderRow("field", this.state.rows.field)}
47 |
48 |
49 | )
50 | }
51 | }
52 | const FieldAnalyzer = displayRowsComponent(FieldAnalyzerComponent);
53 | export { FieldAnalyzer };
--------------------------------------------------------------------------------
/public/components/analyzer_form/index.ts:
--------------------------------------------------------------------------------
1 | export { AnalyzerForm } from './analyzer_form';
--------------------------------------------------------------------------------
/public/components/analyzer_form/simple.tsx:
--------------------------------------------------------------------------------
1 | import React, {
2 | Component
3 | } from 'react';
4 | import {
5 | EuiFieldText,
6 | EuiTable,
7 | EuiTableBody
8 | } from '@elastic/eui';
9 | import { displayRowsComponent } from './analyzer_form';
10 |
11 | class SimpleAnalyzerComponent extends Component {
12 | constructor(props: Readonly<{}>) {
13 | super(props);
14 | this.state = {
15 | rows: {
16 | "analyzer": [
17 | {
18 | label: {
19 | style: { width: 100},
20 | },
21 | form: {
22 | renderTag: (index) => {
23 | return (
24 |
30 | );
31 | },
32 | style: { width: 300 },
33 | }
34 | }
35 | ]
36 | }
37 | }
38 | }
39 |
40 | render () {
41 | return (
42 |
43 |
44 | {this.renderRow("analyzer", this.state.rows.analyzer)}
45 |
46 |
47 | )
48 | }
49 | }
50 | const SimpleAnalyzer = displayRowsComponent(SimpleAnalyzerComponent);
51 | export { SimpleAnalyzer };
52 |
--------------------------------------------------------------------------------
/public/components/app.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { I18nProvider } from '@kbn/i18n/react';
3 | import { BrowserRouter as Router } from 'react-router-dom';
4 |
5 | import {
6 | EuiPage,
7 | EuiPageBody,
8 | EuiPageContent,
9 | EuiPageHeader,
10 | EuiPageHeaderSection,
11 | EuiSpacer,
12 | EuiTitle,
13 | } from '@elastic/eui';
14 |
15 | import { CoreStart } from '../../../../src/core/public';
16 | import { NavigationPublicPluginStart } from '../../../../src/plugins/navigation/public';
17 |
18 | import {AnalyzeUi} from "./analyze_ui";
19 |
20 | import { PLUGIN_ID } from '../../common';
21 |
22 | interface AnalyzeApiUiAppDeps {
23 | basename: string;
24 | notifications: CoreStart['notifications'];
25 | http: CoreStart['http'];
26 | navigation: NavigationPublicPluginStart;
27 | }
28 |
29 | export const AnalyzeApiUiApp = ({
30 | basename,
31 | http,
32 | navigation,
33 | }: AnalyzeApiUiAppDeps) => {
34 | // Render the application DOM.
35 | // Note that `navigation.ui.TopNavMenu` is a stateful component exported on the `navigation` plugin's start contract.
36 | return (
37 |
38 |
39 | <>
40 |
45 |
46 |
47 |
48 |
49 |
50 | Perform analyze via _analyze API
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 | >
61 |
62 |
63 | );
64 | };
65 |
--------------------------------------------------------------------------------
/public/components/common_form/common_form.tsx:
--------------------------------------------------------------------------------
1 | import React, {
2 | Component
3 | } from 'react';
4 | import {
5 | EuiFlexGroup,
6 | EuiFlexItem,
7 | EuiFormRow,
8 | EuiFieldText,
9 | EuiTextColor
10 | } from '@elastic/eui';
11 |
12 | export class CommonForm extends Component {
13 |
14 | constructor(props) {
15 | super(props);
16 | }
17 |
18 | render () {
19 | const {params} = this.props;
20 | const {errors} = this.props;
21 | const showIndexNameError = Object.keys(errors).includes("indexNameError");
22 | const indexNameError = showIndexNameError ? errors.indexNameError : undefined;
23 | const showTextError = Object.keys(errors).includes("textError");
24 | const textError = showTextError ? errors.textError : undefined;
25 |
26 | return (
27 |
28 |
29 |
33 | Index name - select index name if you use the analyzer to configure an index
34 |
35 | }
36 | fullWidth
37 | isInvalid={showIndexNameError}
38 | error={indexNameError}
39 | onChange={this.props.updateParamsWithEvent}
40 | >
41 |
47 |
48 |
52 | Text - Text what you want to analyze
53 |
54 | }
55 | fullWidth
56 | isInvalid={showTextError}
57 | error={textError}
58 | onChange={this.props.updateParamsWithEvent}
59 | >
60 |
66 |
67 |
68 |
69 |
70 | );
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/public/components/common_form/index.ts:
--------------------------------------------------------------------------------
1 | export { CommonForm } from './common_form';
--------------------------------------------------------------------------------
/public/components/form_control/form_control.tsx:
--------------------------------------------------------------------------------
1 | import React, {
2 | Component
3 | } from 'react';
4 | import {
5 | EuiFlexGroup,
6 | EuiFlexItem,
7 | EuiButton,
8 | EuiSwitch,
9 | } from '@elastic/eui'
10 |
11 | export class FormControl extends Component {
12 | constructor(props) {
13 | super(props);
14 | }
15 |
16 | render () {
17 | return (
18 |
19 |
20 |
27 | Analyze!
28 |
29 |
30 |
31 |
37 |
38 |
39 | );
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/public/components/form_control/index.ts:
--------------------------------------------------------------------------------
1 | export { FormControl } from './form_control';
--------------------------------------------------------------------------------
/public/components/result/analyzer_result/analyzer_result.tsx:
--------------------------------------------------------------------------------
1 | import React, {
2 | Component,
3 | } from 'react';
4 | import {
5 | EuiCodeBlock,
6 | EuiPanel,
7 | EuiSpacer
8 | } from '@elastic/eui';
9 | import {Charfilters} from "./charfilters";
10 | import {TokenizerAndFilters} from "./tokenizer_and_filters";
11 | import {createTokenIndices, getLength} from "../../../services/token_utils";
12 |
13 | export class AnalyzerResult extends Component {
14 | constructor(props) {
15 | super(props);
16 | }
17 |
18 | countTokenSteamLength(detail) {
19 | // FIXME tokens length is not fit if it has synonym token/compound token...
20 | let tokenStreamLength = 0;
21 |
22 | if (detail.tokenizer) {
23 | tokenStreamLength = getLength(tokenStreamLength, detail.tokenizer.tokens);
24 | } else if (detail.analyzer) {
25 | tokenStreamLength = getLength(tokenStreamLength, detail.analyzer.tokens);
26 | }
27 | if (detail.tokenfilters) {
28 | detail.tokenfilters.forEach( (filter) => {
29 | tokenStreamLength = getLength(tokenStreamLength, filter.tokens);
30 | });
31 | }
32 | return createTokenIndices(tokenStreamLength);
33 | }
34 |
35 | render() {
36 | const {
37 | detail,
38 | esRequest,
39 | indexName
40 | } = this.props;
41 | const tokenIndices = this.countTokenSteamLength(detail);
42 | return (
43 |
44 |
45 |
46 |
47 |
54 |
55 |
56 |
57 | Request for Elasticsearch - you can copy and paste to Console
58 |
59 | GET {indexName}/_analyze
60 | {JSON.stringify(esRequest, null, 2)}
61 |
62 |
63 |
64 | );
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/public/components/result/analyzer_result/charfilters/chafilters.tsx:
--------------------------------------------------------------------------------
1 | import React, {
2 | Component
3 | } from 'react';
4 | import {
5 | EuiTable,
6 | EuiTableBody,
7 | EuiTableHeader,
8 | EuiTableHeaderCell,
9 | EuiTableRow,
10 | EuiTableRowCell
11 | } from '@elastic/eui';
12 |
13 | export class Charfilters extends Component {
14 | constructor(props) {
15 | super(props);
16 | }
17 |
18 | renderRow(target) {
19 | return (
20 |
21 |
22 | char_filter
23 | {target.name}
24 |
25 |
26 |
27 | {target.filtered_text[0]}
28 |
29 |
30 | );
31 | }
32 |
33 | renderRows(charfilters) {
34 | if (charfilters && charfilters.length > 0) {
35 | const rows = charfilters.map((target) => {
36 | return this.renderRow(target);
37 | });
38 | return rows;
39 | } else {
40 | return null;
41 | }
42 | }
43 |
44 | render() {
45 | const {charfilters} = this.props;
46 | if (charfilters && charfilters.length > 0) {
47 | return (
48 |
49 |
50 |
51 |
55 | type
name
56 |
57 |
60 | filtered text
61 |
62 |
63 |
64 | {this.renderRows(charfilters)}
65 |
66 |
67 |
68 | );
69 | } else {
70 | return null;
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/public/components/result/analyzer_result/charfilters/index.ts:
--------------------------------------------------------------------------------
1 | export { Charfilters } from './chafilters';
--------------------------------------------------------------------------------
/public/components/result/analyzer_result/index.ts:
--------------------------------------------------------------------------------
1 | export { AnalyzerResult } from './analyzer_result';
--------------------------------------------------------------------------------
/public/components/result/analyzer_result/tokenizer_and_filters/filters.tsx:
--------------------------------------------------------------------------------
1 | import React, {
2 | Component
3 | } from 'react';
4 |
5 | import {displayRowsComponent} from "./tokenizer_and_filters";
6 |
7 | class FiltersComponent extends Component {
8 | constructor(props) {
9 | super(props);
10 | }
11 |
12 | render () {
13 | const {
14 | filters,
15 | tokenIndices
16 | } = this.props;
17 | if (filters && filters.length > 0) {
18 | const rows = filters.map((target) => {
19 | return this.renderRow(target, "filter", tokenIndices);
20 | }
21 | );
22 | return rows;
23 | } else {
24 | return null;
25 | }
26 | }
27 | }
28 | const Filters = displayRowsComponent(FiltersComponent);
29 | export { Filters };
30 |
--------------------------------------------------------------------------------
/public/components/result/analyzer_result/tokenizer_and_filters/index.ts:
--------------------------------------------------------------------------------
1 | export { TokenizerAndFilters } from './tokenizer_and_filters';
--------------------------------------------------------------------------------
/public/components/result/analyzer_result/tokenizer_and_filters/token.tsx:
--------------------------------------------------------------------------------
1 | import React, {
2 | Component
3 | } from 'react';
4 |
5 | import { DISP_TOKEN_PROPS } from '../../../../common/constants/token_properties';
6 |
7 | export class Token extends Component {
8 |
9 | constructor(props) {
10 | super(props);
11 | }
12 |
13 | makeTokenListFromTokenStream(index, target) {
14 | const currentLevelTokenList = [];
15 | for (let token of target.tokens) {
16 | if (token.position > index) {
17 | break;
18 | }
19 | if (token.position == index) {
20 | currentLevelTokenList.push(token);
21 | }
22 | }
23 | return currentLevelTokenList;
24 | }
25 |
26 | hideTokenProperty(propertyName) {
27 | if (DISP_TOKEN_PROPS.alwaysShowTokenProperties.includes(propertyName)) {
28 | return true;
29 | } else {
30 | // TODO should we handle each attribute to show/hide?
31 | return this.props.showAllTokenAttr;
32 | }
33 | };
34 |
35 | // filter token properties
36 | filteredCurrentTokenInfo(token) {
37 | if (token != null) {
38 | const result = {};
39 | Object.keys(token).forEach((key) => {
40 | if (!DISP_TOKEN_PROPS.hiddenTokenProperties.includes(key)) {
41 | result[key] = token[key];
42 | }
43 | });
44 | return result;
45 | } else {
46 | return null;
47 | }
48 | }
49 |
50 | renderTokenValue(key, value) {
51 | if (key=='token') {
52 | return (
53 | {value}
54 | );
55 | } else {
56 | return (
57 | {value}
58 | );
59 | }
60 | }
61 |
62 | renderTokenKey(key) {
63 | if (this.props.index == 0) {
64 | return (
65 |
66 | {key}
67 | |
68 | );
69 | } else {
70 | return null;
71 | }
72 | }
73 |
74 | renderRow(key, value) {
75 | if (this.hideTokenProperty(key)) {
76 | return (
77 |
78 | {this.renderTokenKey(key)}
79 |
80 | {this.renderTokenValue(key, value)}
81 |
82 | |
83 |
84 | );
85 | } else {
86 | return null;
87 | }
88 | }
89 |
90 | renderTokenProperties(token) {
91 | const currentTokenInfo = this.filteredCurrentTokenInfo(token);
92 | if (currentTokenInfo) {
93 | const propRows = [];
94 | Object.keys(token).forEach(
95 | (key) => {
96 | propRows.push(this.renderRow(key, token[key]));
97 | }
98 | );
99 | return propRows;
100 | } else {
101 | return null;
102 | }
103 | }
104 |
105 | renderTokenTable(currentLevelTokenList) {
106 | const tokenTables = currentLevelTokenList.map(
107 | (token) =>
108 |
109 |
110 | {this.renderTokenProperties(token)}
111 |
112 |
113 | );
114 | if (tokenTables.length > 0) {
115 | return tokenTables;
116 | } else {
117 | return (
118 | no token
119 | );
120 | }
121 | }
122 |
123 | render () {
124 | const {
125 | index,
126 | target,
127 | } = this.props;
128 | const currentLevelTokenList = this.makeTokenListFromTokenStream(index, target);
129 | if (currentLevelTokenList.length > 0){
130 | return (
131 |
134 | {this.renderTokenTable(currentLevelTokenList)}
135 |
136 | );
137 | } else {
138 | return null;
139 | }
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/public/components/result/analyzer_result/tokenizer_and_filters/tokenizer.tsx:
--------------------------------------------------------------------------------
1 | import React, {
2 | Component
3 | } from 'react';
4 |
5 | import {displayRowsComponent} from "./tokenizer_and_filters";
6 |
7 | class TokenizerComponent extends Component {
8 | constructor(props) {
9 | super(props);
10 | }
11 |
12 | render () {
13 | const {
14 | analyzer,
15 | tokenizer,
16 | tokenIndices
17 | } = this.props;
18 |
19 | const target = analyzer? analyzer : tokenizer;
20 | const name = analyzer? "analyzer" : "tokenizer";
21 | return this.renderRow(target, name, tokenIndices);
22 | }
23 | }
24 | const Tokenizer = displayRowsComponent(TokenizerComponent);
25 | export { Tokenizer };
26 |
--------------------------------------------------------------------------------
/public/components/result/analyzer_result/tokenizer_and_filters/tokenizer_and_filters.tsx:
--------------------------------------------------------------------------------
1 | import React, {
2 | Component
3 | } from 'react';
4 |
5 | import {
6 | EuiTable,
7 | EuiTableBody,
8 | EuiTableHeader,
9 | EuiTableHeaderCell,
10 | EuiTableRow,
11 | EuiTableRowCell,
12 | } from '@elastic/eui';
13 | import {Tokenizer} from "./tokenizer";
14 | import {Filters} from "./filters";
15 | import {Token} from "./token";
16 |
17 | export class TokenizerAndFilters extends Component {
18 | constructor(props) {
19 | super(props);
20 | }
21 |
22 | renderHeaderTokensCells(tokenIndices) {
23 | const headerCells = tokenIndices.map(
24 | (index) =>
25 |
29 | tokens[{index}]
30 |
31 | );
32 | return headerCells;
33 | }
34 |
35 | render() {
36 | const {tokenizer} = this.props;
37 | const {analyzer} = this.props;
38 | const {filters} = this.props;
39 | const {tokenIndices} = this.props;
40 | const {showAllTokenAttr} = this.props;
41 | if (tokenizer || analyzer || filters) {
42 | return (
43 |
44 |
45 |
46 |
50 | type
name
51 |
52 | {this.renderHeaderTokensCells(tokenIndices)}
53 |
54 |
55 |
61 |
66 |
67 |
68 |
69 | );
70 | }
71 | }
72 | }
73 |
74 | export function displayRowsComponent(WrappedComponent) {
75 | return class extends WrappedComponent {
76 |
77 | shortenName(name) {
78 | if (name && name.indexOf('.') > 0) {
79 | return name.substring(name.lastIndexOf('.')+1);
80 | }
81 | return name;
82 | }
83 |
84 | renderTokenCells (tokenIndices, target) {
85 | const tokenCells = tokenIndices.map(
86 | (index) =>
87 |
88 |
93 |
94 | );
95 | return tokenCells;
96 | }
97 |
98 | renderRow(target, name, tokenIndices) {
99 | return (
100 |
103 |
104 | {name}
105 | {this.shortenName(target.name)}
106 |
107 |
108 | {this.renderTokenCells(tokenIndices, target)}
109 |
110 | );
111 | }
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/public/components/result/compare_result/compare_result.tsx:
--------------------------------------------------------------------------------
1 | import React, {
2 | Component,
3 | } from 'react';
4 | import {
5 | EuiPanel,
6 | EuiTable,
7 | EuiTableHeader,
8 | EuiTableHeaderCell,
9 | EuiTableBody,
10 | EuiTableRow,
11 | EuiTableRowCell,
12 | } from '@elastic/eui';
13 | import {createTokenIndices, getLength} from "../../../services/token_utils";
14 | import {Token} from "../analyzer_result/tokenizer_and_filters/token";
15 |
16 |
17 | export class CompareResult extends Component {
18 |
19 | constructor(props) {
20 | super(props);
21 | }
22 |
23 | countTokenStreamLength(resultAnalyzers) {
24 | let tokenStreamLength = 0;
25 | resultAnalyzers.forEach((detail) => {
26 | tokenStreamLength = getLength(tokenStreamLength, detail.tokens);
27 | });
28 | return createTokenIndices(tokenStreamLength);
29 | }
30 |
31 | renderHeaderTokensCells(tokenIndices) {
32 | const headerCells = tokenIndices.map(
33 | (index) =>
34 |
38 | type
tokens[{index}]
40 |
41 | );
42 | return headerCells;
43 | }
44 |
45 | renderTokenCells (tokenIndices, target) {
46 | const tokenCells = tokenIndices.map(
47 | (index) =>
48 |
49 |
54 |
55 | );
56 | return tokenCells;
57 | }
58 |
59 | renderRows(tokenIndices, resultAnalyzers) {
60 | const rows = resultAnalyzers.map(
61 | (result) =>
62 |
65 |
66 | analyzer
67 | {result.analyzer}
68 |
69 |
70 | {this.renderTokenCells(tokenIndices, result)}
71 |
72 | );
73 | return rows;
74 | }
75 |
76 | render() {
77 | const {
78 | resultAnalyzers
79 | } = this.props;
80 | const tokenIndices = this.countTokenStreamLength(resultAnalyzers);
81 |
82 | return (
83 |
87 | There are only tokens and positions. If you know analyzer details, use analyzer/custom_analyzer tab.
88 |
89 |
90 |
91 |
92 |
96 | type
name
97 |
98 | {this.renderHeaderTokensCells(tokenIndices)}
99 |
100 |
101 | {this.renderRows(tokenIndices, resultAnalyzers)}
102 |
103 |
104 |
105 |
106 | );
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/public/components/result/compare_result/index.ts:
--------------------------------------------------------------------------------
1 | export { CompareResult } from './compare_result';
--------------------------------------------------------------------------------
/public/components/result/index.ts:
--------------------------------------------------------------------------------
1 | export { Result } from './result';
--------------------------------------------------------------------------------
/public/components/result/result.tsx:
--------------------------------------------------------------------------------
1 | import React, {
2 | Component
3 | } from 'react';
4 | import {AnalyzerResult} from "./analyzer_result";
5 | import {CompareResult} from "./compare_result";
6 | import {RESULT_TYPE} from "../../common/constants/tab_names";
7 |
8 | export class Result extends Component {
9 |
10 | constructor(props) {
11 | super(props);
12 | }
13 |
14 | render() {
15 | const {type} = this.props;
16 | const {detail} = this.props;
17 | const {esRequest} = this.props;
18 | const {indexName} = this.props;
19 | const {showAllTokenAttr} = this.props;
20 | const {resultAnalyzers} = this.props;
21 | if (type === RESULT_TYPE.SINGLE) {
22 | return (
23 |
29 | );
30 | } else {
31 | return (
32 |
35 | );
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/public/index.scss:
--------------------------------------------------------------------------------
1 | .NoMaxWidth {
2 | max-width: none;
3 | }
4 |
5 | .analyzeApiEUIPage {
6 | flex-direction: column;
7 | }
8 |
9 | .analyzeApiTableScroll {
10 | overflow-x: scroll;
11 | border: 1px solid #E4E4E4;
12 | }
13 |
14 | .analyzeApiTableScroll::-webkit-scrollbar{
15 | height: 0.5em;
16 | background-color: #F1F1F1;
17 | }
18 | .analyzeApiTableScroll::-webkit-scrollbar-thumb{
19 | background-color: #BCBCBC;
20 | border-radius: 12px;
21 | border-radius: 1.2rem;
22 | }
23 | .analyzeApiTableScroll::-webkit-scrollbar-thumb:hover{
24 | background-color: #A9A9AA;
25 | }
26 |
27 | .analyzeApiTableCell{
28 | max-width: none;
29 | padding: 7px 8px 8px;
30 | overflow: unset;
31 | }
32 |
33 | .analyzeApiTableTokenCell{
34 | margin-left: auto;
35 | }
36 |
37 | .analyzeRequest {
38 | margin: 2px;
39 | border: 1px solid #E4E4E4;
40 | background-color: #F1F1F1;
41 | padding: 7px 8px 8px;
42 | }
43 |
44 | .analyzeApiFontBold {
45 | font-weight: bold;
46 | }
47 |
48 | .analyzeApiTableLayout {
49 | table-layout: auto
50 | }
51 |
52 | .analyzeApiTableRowTop {
53 | vertical-align: top
54 | }
55 |
56 | .analyzeApiTableRowTokenCell {
57 | overflow: hidden;
58 | white-space: nowrap;
59 | display: grid;
60 | }
61 |
62 | .analyzeApiEmptyToken {
63 | color: #a7a7a7;
64 | }
65 |
66 | .analyzeApiTextAlignRight {
67 | text-align: right;
68 | }
69 |
70 | .analyzeApiFormWidthSize {
71 | width: 300px;
72 | }
73 |
74 | .analyzeApiFormHeightSize {
75 | height: 100px;
76 | }
77 |
--------------------------------------------------------------------------------
/public/index.ts:
--------------------------------------------------------------------------------
1 | import './index.scss';
2 |
3 | import { AnalyzeApiUiPlugin } from './plugin';
4 |
5 | // This exports static code and TypeScript types,
6 | // as well as, Kibana Platform `plugin()` initializer.
7 | export function plugin() {
8 | return new AnalyzeApiUiPlugin();
9 | }
10 | export { AnalyzeApiUiPluginSetup, AnalyzeApiUiPluginStart } from './types';
11 |
--------------------------------------------------------------------------------
/public/plugin.ts:
--------------------------------------------------------------------------------
1 | import { AppMountParameters, CoreSetup, CoreStart, Plugin } from 'kibana/public';
2 | import {
3 | AnalyzeApiUiPluginSetup,
4 | AnalyzeApiUiPluginStart,
5 | AppPluginStartDependencies,
6 | } from './types';
7 | import { PLUGIN_NAME } from '../common';
8 |
9 | export class AnalyzeApiUiPlugin
10 | implements Plugin
11 | {
12 | public setup(core: CoreSetup): AnalyzeApiUiPluginSetup {
13 | // Register an application into the side navigation menu
14 | core.application.register({
15 | id: 'analyzeApiUi',
16 | title: PLUGIN_NAME,
17 | async mount(params: AppMountParameters) {
18 | // Load application bundle
19 | const { renderApp } = await import('./application');
20 | // Get start services as specified in kibana.json
21 | const [coreStart, depsStart] = await core.getStartServices();
22 | // Render the application
23 | return renderApp(coreStart, depsStart as AppPluginStartDependencies, params);
24 | },
25 | });
26 | return {};
27 | }
28 |
29 | public start(core: CoreStart): AnalyzeApiUiPluginStart {
30 | return {};
31 | }
32 |
33 | public stop() {}
34 | }
35 |
--------------------------------------------------------------------------------
/public/services/api.ts:
--------------------------------------------------------------------------------
1 | let httpClient;
2 | export const setHttpClient = (client) => {
3 | httpClient = client;
4 | };
5 | export const getHttpClient = () => {
6 | return httpClient;
7 | };
8 |
9 | export async function analyze(params) {
10 | return await httpClient.post('/api/analyze_api_ui/analyze', {body: JSON.stringify(params)});
11 | }
12 |
13 | export async function multiAnalyze(params) {
14 | return await httpClient.post('/api/analyze_api_ui/multi_analyze', {body: JSON.stringify(params)});
15 | }
16 |
--------------------------------------------------------------------------------
/public/services/pagestate.ts:
--------------------------------------------------------------------------------
1 | import instance from "./storage";
2 |
3 | const pagestate = {
4 |
5 | updateCurrentState(content: { params: any; }) {
6 | const timestamp = new Date().getTime();
7 | instance.set("page_state", {
8 | time: timestamp,
9 | content: content
10 | });
11 | },
12 |
13 | getSavedEditorState() {
14 | const saved = instance.get('page_state', null);
15 | if (!saved) return;
16 | const { time, content } = saved;
17 | return { time, content };
18 | }
19 |
20 | };
21 |
22 | export default pagestate;
23 |
--------------------------------------------------------------------------------
/public/services/params.ts:
--------------------------------------------------------------------------------
1 | export function updateParamsWithEvent(event: any, params: any) {
2 | const target = event.target;
3 | const value = target.type === 'checkbox' || target.name === 'showAllTokenAttr' ? target.checked : target.value;
4 | const name = target.name;
5 | params[name] = value;
6 | return params;
7 | };
8 |
9 | export function updateParamsWithEventAndIndex(event: any, params: any) {
10 | const index = event.target.getAttribute('data-index');
11 | const target = event.target;
12 | const value = target.type === 'checkbox' ? target.checked : target.value;
13 | const name = target.name;
14 |
15 | if (params[name] == null) {
16 | params[name] = [];
17 | }
18 | params[name][index] = value;
19 | return params;
20 | };
21 |
22 | export function selectTab(tab: any, params: any) {
23 | params["tab"] = tab.name;
24 | return params;
25 | }
26 |
--------------------------------------------------------------------------------
/public/services/state_handler.ts:
--------------------------------------------------------------------------------
1 | import pagestate from './pagestate';
2 |
3 | export function initializeParams() {
4 | const params = {
5 | indexName: '',
6 | text: '',
7 | analyzer: '',
8 | tokenizer: '',
9 | charfilters: [],
10 | filters: [],
11 | field: '',
12 | analyzersForCompare: [],
13 | showAllTokenAttr: false,
14 | };
15 | return params;
16 | }
17 |
18 | // load saved state from storage
19 | export function loadSavedState() {
20 | const preState = pagestate.getSavedEditorState();
21 | let params;
22 | if (preState) {
23 | params = preState.content.params;
24 | } else {
25 | params = initializeParams();
26 | }
27 | return params;
28 | }
29 |
30 | // save state
31 | export function saveState(state) {
32 | try {
33 | pagestate.updateCurrentState({
34 | params: state.params
35 | });
36 | } catch (e) {
37 | console.log("Ignoring saving error: " + e);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/public/services/storage.ts:
--------------------------------------------------------------------------------
1 | const { transform, keys, startsWith } = require('lodash');
2 |
3 | class Storage {
4 | engine: globalThis.Storage;
5 | prefix: string;
6 |
7 | constructor(engine: globalThis.Storage, prefix: string) {
8 | this.engine = engine;
9 | this.prefix = prefix;
10 | }
11 |
12 | encode(val: any) {
13 | return JSON.stringify(val);
14 | }
15 |
16 | decode(val: string | null) {
17 | if (typeof val === 'string') {
18 | return JSON.parse(val);
19 | }
20 | }
21 |
22 | encodeKey(key: any) {
23 | return `${this.prefix}${key}`;
24 | }
25 |
26 | decodeKey(key: string | any[]) {
27 | if (startsWith(key, this.prefix)) {
28 | return `${key.slice(this.prefix.length)}`;
29 | }
30 | }
31 |
32 | set(key: any, val: any) {
33 | this.engine.setItem(this.encodeKey(key), this.encode(val));
34 | return val;
35 | }
36 |
37 | has(key: any) {
38 | return this.engine.getItem(this.encodeKey(key)) != null;
39 | }
40 |
41 | get(key: any, _default: any) {
42 | if (this.has(key)) {
43 | return this.decode(this.engine.getItem(this.encodeKey(key)));
44 | } else {
45 | return _default;
46 | }
47 | }
48 |
49 | delete(key: any) {
50 | return this.engine.removeItem(this.encodeKey(key));
51 | }
52 |
53 | keys() {
54 | return transform(keys(this.engine), (ours: string[], key: string | any[]) => {
55 | const ourKey = this.decodeKey(key);
56 | if (ourKey != null) ours.push(ourKey);
57 | });
58 | }
59 | }
60 |
61 | const instance = new Storage(sessionStorage, 'analyzeui:');
62 |
63 | export default instance;
64 |
--------------------------------------------------------------------------------
/public/services/token_utils.ts:
--------------------------------------------------------------------------------
1 | // compare and swap tokenStreamLength
2 | export function getLength(current: number, tokenArray: string | any[] | null) {
3 | // FIXME check if there is synonyms or compound
4 | let length = current;
5 | if (tokenArray != null) {
6 | // FIXME must consider the situation if positionIncrements != 1
7 | if (tokenArray[tokenArray.length -1].position >= current) {
8 | length = tokenArray[tokenArray.length -1].position + 1;
9 | }
10 | }
11 | return length;
12 | };
13 |
14 | export function createTokenIndices(tokenStreamLength: number) {
15 | const tokenIndices = [];
16 | for (let i = 0; i < tokenStreamLength; i++) {
17 | tokenIndices.push(i);
18 | }
19 | return tokenIndices;
20 | }
21 |
--------------------------------------------------------------------------------
/public/services/validator.ts:
--------------------------------------------------------------------------------
1 | import {TAB_NAME} from "../common/constants/tab_names";
2 |
3 | export function validateAnalyzeRequestValues(params) {
4 | let validatedParams = {
5 | requestParams: {
6 | text: params.text
7 | },
8 | errors: {}
9 | };
10 | // console.log("before validation");
11 | // console.log(params);
12 | // console.log("--------------");
13 | if (validatedParams.requestParams.text.trim().length === 0) {
14 | validatedParams.errors.textError= 'text should be not null!'
15 |
16 | }
17 | if (params.indexName.length > 0) {
18 | validatedParams.requestParams.indexName = params.indexName.trim();
19 | }
20 |
21 | if (params.tab == TAB_NAME.ANALYZER) {
22 | if (params.analyzer.length > 0) {
23 | validatedParams.requestParams.analyzer = params.analyzer.trim();
24 | }
25 | } else if (params.tab == TAB_NAME.FIELD) {
26 | if (params.field.trim().length === 0)
27 | validatedParams.errors.fieldError = 'field is required. ';
28 | if (params.indexName.trim().length === 0)
29 | validatedParams.errors.indexNameError = 'index name is required for "field". ';
30 |
31 | validatedParams.requestParams.field = params.field.trim();
32 | } else if (params.tab == TAB_NAME.CUSTOM_ANALYZER) {
33 | if (params.tokenizer) {
34 | let tmpObj = parseCustom(params.tokenizer.trim(), "tokenizer", validatedParams);
35 | if (tmpObj != -1) {
36 | validatedParams.requestParams.tokenizer = tmpObj;
37 | tmpObj = null;
38 | }
39 | }
40 |
41 | if (params.charfilters.length > 0) {
42 | params.charfilters.forEach( (charfilter) => {
43 | if (charfilter && charfilter.trim().length > 0 ) {
44 | if(validatedParams.requestParams.charfilters == null) validatedParams.requestParams.charfilters = [];
45 | let tmpCharfilter = parseCustom(charfilter.trim(), "charfilter", validatedParams);
46 | if (tmpCharfilter != -1) {
47 | validatedParams.requestParams.charfilters.push(tmpCharfilter);
48 | }
49 | }
50 | });
51 | }
52 | if (params.filters.length > 0) {
53 | params.filters.forEach( (filter) => {
54 | if (filter && filter.trim().length > 0 ) {
55 | if(validatedParams.requestParams.filters == null) validatedParams.requestParams.filters = [];
56 | let tmpFilter = parseCustom(filter.trim(), "filter", validatedParams);
57 | if (tmpFilter != -1) {
58 | validatedParams.requestParams.filters.push(tmpFilter);
59 | }
60 | }
61 | });
62 | }
63 | } else if (params.tab == TAB_NAME.COMPARE_ANALYZERS) {
64 | params.analyzersForCompare.forEach( (analyzer) => {
65 | if (analyzer && analyzer.trim().length > 0) {
66 | if (validatedParams.requestParams.analyzers == null) validatedParams.requestParams.analyzers = [];
67 | validatedParams.requestParams.analyzers.push(analyzer);
68 | }
69 | });
70 | }
71 |
72 | //console.log("after validation");
73 | //console.log(validatedParams);
74 | //console.log("--------------");
75 | return validatedParams;
76 | };
77 |
78 | function parseCustom(target, label, validatedParams) {
79 | if (typeof target === 'string' && target.startsWith('{')) {
80 | try {
81 | let tmpJson = JSON.parse(target);
82 | if (tmpJson !== null && typeof tmpJson === 'object') {
83 | return tmpJson;
84 | } else {
85 | validatedParams.errors.analyzerError = label+' has wrong custom '+label;
86 | return -1;
87 | }
88 | } catch (e) {
89 | validatedParams.errors.analyzerError = e.message;
90 | return -1;
91 | }
92 | } else {
93 | return target;
94 | }
95 | };
96 |
--------------------------------------------------------------------------------
/public/types.ts:
--------------------------------------------------------------------------------
1 | import { NavigationPublicPluginStart } from '../../../src/plugins/navigation/public';
2 |
3 | export interface AnalyzeApiUiPluginSetup {}
4 | // eslint-disable-next-line @typescript-eslint/no-empty-interface
5 | export interface AnalyzeApiUiPluginStart {}
6 |
7 | export interface AppPluginStartDependencies {
8 | navigation: NavigationPublicPluginStart;
9 | }
10 |
--------------------------------------------------------------------------------
/server/index.ts:
--------------------------------------------------------------------------------
1 | import { PluginInitializerContext } from 'kibana/server';
2 | import { AnalyzeApiUiPlugin } from './plugin';
3 |
4 | // This exports static code and TypeScript types,
5 | // as well as, Kibana Platform `plugin()` initializer.
6 |
7 | export function plugin(initializerContext: PluginInitializerContext) {
8 | return new AnalyzeApiUiPlugin(initializerContext);
9 | }
10 |
11 | export { AnalyzeApiUiPluginSetup, AnalyzeApiUiPluginStart } from './types';
12 |
--------------------------------------------------------------------------------
/server/plugin.ts:
--------------------------------------------------------------------------------
1 | import {
2 | PluginInitializerContext,
3 | CoreSetup,
4 | CoreStart,
5 | Plugin,
6 | Logger,
7 | } from 'kibana/server';
8 |
9 | import { AnalyzeApiUiPluginSetup, AnalyzeApiUiPluginStart } from './types';
10 | import { defineRoutes } from './routes';
11 |
12 | export class AnalyzeApiUiPlugin
13 | implements Plugin
14 | {
15 | private readonly logger: Logger;
16 |
17 | constructor(initializerContext: PluginInitializerContext) {
18 | this.logger = initializerContext.logger.get();
19 | }
20 |
21 | public setup(core: CoreSetup) {
22 | this.logger.debug('analyze-api-ui-plugin: Setup');
23 | const router = core.http.createRouter();
24 |
25 | // Register server side APIs
26 | defineRoutes(router, this.logger);
27 |
28 | return {};
29 | }
30 |
31 | public start(core: CoreStart) {
32 | this.logger.debug('analyze-api-ui-plugin: Started');
33 | return {};
34 | }
35 |
36 | public stop() {}
37 | }
38 |
--------------------------------------------------------------------------------
/server/routes/handle_es_error.ts:
--------------------------------------------------------------------------------
1 | import Boom from '@hapi/boom';
2 | import { get } from 'lodash';
3 |
4 | const ERR_ES_INDEX_NOT_FOUND = 'index_not_found_exception';
5 | const ERR_ES_ILLEGAL_ARG = 'illegal_argument_exception';
6 |
7 | export function isEsIndexNotFoundError(err: any) {
8 | return get(err, ['body', 'error', 'type']) === ERR_ES_INDEX_NOT_FOUND;
9 | }
10 |
11 | export function isEsIllegalArgumentError(err: any) {
12 | return get(err, ['body', 'error', 'type']) === ERR_ES_ILLEGAL_ARG;
13 | }
14 |
15 | export function convertEsError(response: any, error: any) {
16 | const message = error.body ? error.body.error.reason : undefined;
17 | if (isEsIndexNotFoundError(error)) {
18 | error = Boom.notFound(message, error);
19 | } else if (isEsIllegalArgumentError(error)) {
20 | error = Boom.badRequest(message, error);
21 | }
22 |
23 | if (!Boom.isBoom(error)) {
24 | const statusCode = error.statusCode;
25 | if (!error.message) {
26 | error.message = message;
27 | }
28 | error = Boom.boomify(error, {statusCode: statusCode, message: message});
29 | }
30 | return response.customError({
31 | body: error.output.payload,
32 | statusCode: error.output.statusCode,
33 | headers: error.output.headers as { [key: string]: string },
34 | });
35 | }
36 |
--------------------------------------------------------------------------------
/server/routes/index.ts:
--------------------------------------------------------------------------------
1 | import { IRouter, Logger } from 'kibana/server';
2 | import { schema } from '@kbn/config-schema';
3 | import { convertEsError } from "./handle_es_error";
4 |
5 | export function defineRoutes(router: IRouter, logger: logger) {
6 | router.post(
7 | {
8 | path: '/api/analyze_api_ui/analyze',
9 | validate: {
10 | body: schema.object({
11 | text: schema.string(),
12 | indexName: schema.maybe(schema.nullable(schema.string())),
13 | analyzer: schema.maybe(schema.nullable(schema.string())),
14 | tokenizer: schema.maybe(schema.oneOf([schema.nullable(schema.string()), schema.object({}, { unknowns: 'allow' })])),
15 | field: schema.maybe(schema.nullable(schema.string())),
16 | filters: schema.maybe(schema.arrayOf(schema.nullable(schema.oneOf([schema.string(), schema.object({}, { unknowns: 'allow' })])))),
17 | charfilters: schema.maybe(schema.arrayOf(schema.nullable(schema.oneOf([schema.string(), schema.object({}, { unknowns: 'allow' })]))))
18 | })
19 | }
20 | },
21 | async (context, request, response) => {
22 | let param = {
23 | body: {
24 | explain: true,
25 | text: request.body.text
26 | }
27 | };
28 | if (request.body.indexName) param.index = request.body.indexName;
29 | if (request.body.analyzer) param.body.analyzer = request.body.analyzer;
30 | if (request.body.tokenizer) param.body.tokenizer = request.body.tokenizer;
31 | if (request.body.charfilters) param.body.char_filter = request.body.charfilters;
32 | if (request.body.field) param.body.field = request.body.field;
33 | if (request.body.filters) param.body.filter = request.body.filters;
34 | try {
35 | const results = await context.core.elasticsearch.client.asCurrentUser.indices.analyze(param);
36 | return response.ok({
37 | body: {
38 | detail: results.body.detail,
39 | request: param.body
40 | }
41 | });
42 | } catch (error) {
43 | return convertEsError(response, error);
44 | }
45 | }
46 | );
47 |
48 | router.post(
49 | {
50 | path: '/api/analyze_api_ui/multi_analyze',
51 | validate: {
52 | body: schema.object({
53 | text: schema.string(),
54 | indexName: schema.maybe(schema.nullable(schema.string())),
55 | analyzers: schema.nullable(schema.arrayOf(schema.string(), {minSize: 2}))
56 | })
57 | }
58 | },
59 | async (context, request, response) => {
60 | let param = {
61 | body: {
62 | explain: false,
63 | text: request.body.text
64 | }
65 | };
66 | if (request.body.indexName) param.index = request.body.indexName;
67 | const res = {
68 | body: {
69 | resultAnalyzers: []
70 | }
71 | };
72 |
73 | function getAnalyzerResult(analyzer, id) {
74 | return new Promise(function (resolve, reject) {
75 | param.body.analyzer = analyzer;
76 | const results = context.core.elasticsearch.client.asCurrentUser.indices.analyze(param)
77 | results.then(
78 | (x) => {
79 | res.body.resultAnalyzers.push({analyzer: analyzer, id: id, tokens: x.body.tokens});
80 | resolve(res);
81 | }
82 | );
83 | results.catch(error => {
84 | reject(convertEsError(response, error));
85 | });
86 | });
87 | };
88 |
89 | if (Array.isArray(request.body.analyzers) && request.body.analyzers.length >= 1) {
90 | try {
91 | const res_promise = await Promise.all(request.body.analyzers.map(getAnalyzerResult));
92 | res.body.resultAnalyzers.sort(
93 | function (a, b) {
94 | if (a.id < b.id) return -1;
95 | if (a.id > b.id) return 1;
96 | return 0;
97 | }
98 | );
99 | return response.ok(res);
100 | } catch (error) {
101 | return convertEsError(response, error);
102 | }
103 | } else {
104 | return response.ok(res);
105 | }
106 | }
107 | );
108 | }
109 |
--------------------------------------------------------------------------------
/server/types.ts:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line @typescript-eslint/no-empty-interface
2 | export interface AnalyzeApiUiPluginSetup {}
3 | // eslint-disable-next-line @typescript-eslint/no-empty-interface
4 | export interface AnalyzeApiUiPluginStart {}
5 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "./target/types"
5 | },
6 | "include": [
7 | "index.ts",
8 | "common/**/*.ts",
9 | "public/**/*.ts",
10 | "public/**/*.tsx",
11 | "server/**/*.ts",
12 | "../../typings/**/*"
13 | ],
14 | "exclude": []
15 | }
16 |
--------------------------------------------------------------------------------