mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
Merge branch 'main' into task-manager/mget-default-claim-strategy-2
This commit is contained in:
commit
77492e8a7b
100 changed files with 5398 additions and 3897 deletions
|
@ -207,3 +207,6 @@ xpack.ml.compatibleModuleType: 'observability'
|
|||
|
||||
# Disable the embedded Dev Console
|
||||
console.ui.embeddedEnabled: false
|
||||
|
||||
# Disable role management (custom roles)
|
||||
xpack.security.roleManagementEnabled: false
|
||||
|
|
|
@ -155,7 +155,7 @@ server.versioned.versionResolution: newest
|
|||
server.versioned.strictClientVersionCheck: false
|
||||
|
||||
# Enforce single "default" space and disable feature visibility controls
|
||||
xpack.spaces.maxSpaces: 1
|
||||
xpack.spaces.maxSpaces: 100
|
||||
xpack.spaces.allowFeatureVisibility: false
|
||||
xpack.spaces.allowSolutionVisibility: false
|
||||
|
||||
|
|
|
@ -40993,6 +40993,697 @@
|
|||
]
|
||||
}
|
||||
},
|
||||
"/api/security/role": {
|
||||
"get": {
|
||||
"operationId": "%2Fapi%2Fsecurity%2Frole#0",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "The version of the API to use",
|
||||
"in": "header",
|
||||
"name": "elastic-api-version",
|
||||
"schema": {
|
||||
"default": "2023-10-31",
|
||||
"enum": [
|
||||
"2023-10-31"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"in": "query",
|
||||
"name": "replaceDeprecatedPrivileges",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {},
|
||||
"summary": "Get all roles",
|
||||
"tags": [
|
||||
"roles"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/security/role/{name}": {
|
||||
"delete": {
|
||||
"operationId": "%2Fapi%2Fsecurity%2Frole%2F%7Bname%7D#1",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "The version of the API to use",
|
||||
"in": "header",
|
||||
"name": "elastic-api-version",
|
||||
"schema": {
|
||||
"default": "2023-10-31",
|
||||
"enum": [
|
||||
"2023-10-31"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "A required header to protect against CSRF attacks",
|
||||
"in": "header",
|
||||
"name": "kbn-xsrf",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"example": "true",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"in": "path",
|
||||
"name": "name",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"minLength": 1,
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {},
|
||||
"summary": "Delete a role",
|
||||
"tags": [
|
||||
"roles"
|
||||
]
|
||||
},
|
||||
"get": {
|
||||
"operationId": "%2Fapi%2Fsecurity%2Frole%2F%7Bname%7D#0",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "The version of the API to use",
|
||||
"in": "header",
|
||||
"name": "elastic-api-version",
|
||||
"schema": {
|
||||
"default": "2023-10-31",
|
||||
"enum": [
|
||||
"2023-10-31"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"in": "path",
|
||||
"name": "name",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"minLength": 1,
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"in": "query",
|
||||
"name": "replaceDeprecatedPrivileges",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {},
|
||||
"summary": "Get a role",
|
||||
"tags": [
|
||||
"roles"
|
||||
]
|
||||
},
|
||||
"put": {
|
||||
"operationId": "%2Fapi%2Fsecurity%2Frole%2F%7Bname%7D#2",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "The version of the API to use",
|
||||
"in": "header",
|
||||
"name": "elastic-api-version",
|
||||
"schema": {
|
||||
"default": "2023-10-31",
|
||||
"enum": [
|
||||
"2023-10-31"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "A required header to protect against CSRF attacks",
|
||||
"in": "header",
|
||||
"name": "kbn-xsrf",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"example": "true",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"in": "path",
|
||||
"name": "name",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"maxLength": 1024,
|
||||
"minLength": 1,
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"in": "query",
|
||||
"name": "createOnly",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"default": false,
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json; Elastic-Api-Version=2023-10-31": {
|
||||
"schema": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"description": {
|
||||
"maxLength": 2048,
|
||||
"type": "string"
|
||||
},
|
||||
"elasticsearch": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"cluster": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"indices": {
|
||||
"items": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"allow_restricted_indices": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"field_security": {
|
||||
"additionalProperties": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"names": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"minItems": 1,
|
||||
"type": "array"
|
||||
},
|
||||
"privileges": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"minItems": 1,
|
||||
"type": "array"
|
||||
},
|
||||
"query": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"names",
|
||||
"privileges"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"remote_cluster": {
|
||||
"items": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"clusters": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"minItems": 1,
|
||||
"type": "array"
|
||||
},
|
||||
"privileges": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"minItems": 1,
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"privileges",
|
||||
"clusters"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"remote_indices": {
|
||||
"items": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"allow_restricted_indices": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"clusters": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"minItems": 1,
|
||||
"type": "array"
|
||||
},
|
||||
"field_security": {
|
||||
"additionalProperties": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"names": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"minItems": 1,
|
||||
"type": "array"
|
||||
},
|
||||
"privileges": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"minItems": 1,
|
||||
"type": "array"
|
||||
},
|
||||
"query": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"clusters",
|
||||
"names",
|
||||
"privileges"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"run_as": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"kibana": {
|
||||
"items": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"base": {
|
||||
"anyOf": [
|
||||
{
|
||||
"items": {},
|
||||
"type": "array"
|
||||
},
|
||||
{
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"nullable": true,
|
||||
"oneOf": [
|
||||
{
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
{
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
]
|
||||
},
|
||||
"feature": {
|
||||
"additionalProperties": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"spaces": {
|
||||
"anyOf": [
|
||||
{
|
||||
"items": {
|
||||
"enum": [
|
||||
"*"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"maxItems": 1,
|
||||
"minItems": 1,
|
||||
"type": "array"
|
||||
},
|
||||
{
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
],
|
||||
"default": [
|
||||
"*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"base"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"metadata": {
|
||||
"additionalProperties": {},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"elasticsearch"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {},
|
||||
"summary": "Create or update a role",
|
||||
"tags": [
|
||||
"roles"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/security/roles": {
|
||||
"post": {
|
||||
"operationId": "%2Fapi%2Fsecurity%2Froles#0",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "The version of the API to use",
|
||||
"in": "header",
|
||||
"name": "elastic-api-version",
|
||||
"schema": {
|
||||
"default": "2023-10-31",
|
||||
"enum": [
|
||||
"2023-10-31"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "A required header to protect against CSRF attacks",
|
||||
"in": "header",
|
||||
"name": "kbn-xsrf",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"example": "true",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json; Elastic-Api-Version=2023-10-31": {
|
||||
"schema": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"roles": {
|
||||
"additionalProperties": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"description": {
|
||||
"maxLength": 2048,
|
||||
"type": "string"
|
||||
},
|
||||
"elasticsearch": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"cluster": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"indices": {
|
||||
"items": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"allow_restricted_indices": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"field_security": {
|
||||
"additionalProperties": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"names": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"minItems": 1,
|
||||
"type": "array"
|
||||
},
|
||||
"privileges": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"minItems": 1,
|
||||
"type": "array"
|
||||
},
|
||||
"query": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"names",
|
||||
"privileges"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"remote_cluster": {
|
||||
"items": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"clusters": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"minItems": 1,
|
||||
"type": "array"
|
||||
},
|
||||
"privileges": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"minItems": 1,
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"privileges",
|
||||
"clusters"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"remote_indices": {
|
||||
"items": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"allow_restricted_indices": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"clusters": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"minItems": 1,
|
||||
"type": "array"
|
||||
},
|
||||
"field_security": {
|
||||
"additionalProperties": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"names": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"minItems": 1,
|
||||
"type": "array"
|
||||
},
|
||||
"privileges": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"minItems": 1,
|
||||
"type": "array"
|
||||
},
|
||||
"query": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"clusters",
|
||||
"names",
|
||||
"privileges"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"run_as": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"kibana": {
|
||||
"items": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"base": {
|
||||
"anyOf": [
|
||||
{
|
||||
"items": {},
|
||||
"type": "array"
|
||||
},
|
||||
{
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"nullable": true,
|
||||
"oneOf": [
|
||||
{
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
{
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
]
|
||||
},
|
||||
"feature": {
|
||||
"additionalProperties": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"spaces": {
|
||||
"anyOf": [
|
||||
{
|
||||
"items": {
|
||||
"enum": [
|
||||
"*"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"maxItems": 1,
|
||||
"minItems": 1,
|
||||
"type": "array"
|
||||
},
|
||||
{
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
],
|
||||
"default": [
|
||||
"*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"base"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"metadata": {
|
||||
"additionalProperties": {},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"elasticsearch"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"roles"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {},
|
||||
"summary": "Create or update roles",
|
||||
"tags": [
|
||||
"roles"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/spaces/space": {
|
||||
"get": {
|
||||
"operationId": "%2Fapi%2Fspaces%2Fspace#0",
|
||||
|
@ -41493,6 +42184,9 @@
|
|||
{
|
||||
"name": "Message Signing Service"
|
||||
},
|
||||
{
|
||||
"name": "roles"
|
||||
},
|
||||
{
|
||||
"name": "spaces"
|
||||
},
|
||||
|
|
|
@ -36556,6 +36556,456 @@ paths:
|
|||
tags:
|
||||
- Security AI Assistant API
|
||||
- Prompts API
|
||||
/api/security/role:
|
||||
get:
|
||||
operationId: '%2Fapi%2Fsecurity%2Frole#0'
|
||||
parameters:
|
||||
- description: The version of the API to use
|
||||
in: header
|
||||
name: elastic-api-version
|
||||
schema:
|
||||
default: '2023-10-31'
|
||||
enum:
|
||||
- '2023-10-31'
|
||||
type: string
|
||||
- in: query
|
||||
name: replaceDeprecatedPrivileges
|
||||
required: false
|
||||
schema:
|
||||
type: boolean
|
||||
responses: {}
|
||||
summary: Get all roles
|
||||
tags:
|
||||
- roles
|
||||
/api/security/role/{name}:
|
||||
delete:
|
||||
operationId: '%2Fapi%2Fsecurity%2Frole%2F%7Bname%7D#1'
|
||||
parameters:
|
||||
- description: The version of the API to use
|
||||
in: header
|
||||
name: elastic-api-version
|
||||
schema:
|
||||
default: '2023-10-31'
|
||||
enum:
|
||||
- '2023-10-31'
|
||||
type: string
|
||||
- description: A required header to protect against CSRF attacks
|
||||
in: header
|
||||
name: kbn-xsrf
|
||||
required: true
|
||||
schema:
|
||||
example: 'true'
|
||||
type: string
|
||||
- in: path
|
||||
name: name
|
||||
required: true
|
||||
schema:
|
||||
minLength: 1
|
||||
type: string
|
||||
responses: {}
|
||||
summary: Delete a role
|
||||
tags:
|
||||
- roles
|
||||
get:
|
||||
operationId: '%2Fapi%2Fsecurity%2Frole%2F%7Bname%7D#0'
|
||||
parameters:
|
||||
- description: The version of the API to use
|
||||
in: header
|
||||
name: elastic-api-version
|
||||
schema:
|
||||
default: '2023-10-31'
|
||||
enum:
|
||||
- '2023-10-31'
|
||||
type: string
|
||||
- in: path
|
||||
name: name
|
||||
required: true
|
||||
schema:
|
||||
minLength: 1
|
||||
type: string
|
||||
- in: query
|
||||
name: replaceDeprecatedPrivileges
|
||||
required: false
|
||||
schema:
|
||||
type: boolean
|
||||
responses: {}
|
||||
summary: Get a role
|
||||
tags:
|
||||
- roles
|
||||
put:
|
||||
operationId: '%2Fapi%2Fsecurity%2Frole%2F%7Bname%7D#2'
|
||||
parameters:
|
||||
- description: The version of the API to use
|
||||
in: header
|
||||
name: elastic-api-version
|
||||
schema:
|
||||
default: '2023-10-31'
|
||||
enum:
|
||||
- '2023-10-31'
|
||||
type: string
|
||||
- description: A required header to protect against CSRF attacks
|
||||
in: header
|
||||
name: kbn-xsrf
|
||||
required: true
|
||||
schema:
|
||||
example: 'true'
|
||||
type: string
|
||||
- in: path
|
||||
name: name
|
||||
required: true
|
||||
schema:
|
||||
maxLength: 1024
|
||||
minLength: 1
|
||||
type: string
|
||||
- in: query
|
||||
name: createOnly
|
||||
required: false
|
||||
schema:
|
||||
default: false
|
||||
type: boolean
|
||||
requestBody:
|
||||
content:
|
||||
application/json; Elastic-Api-Version=2023-10-31:
|
||||
schema:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
description:
|
||||
maxLength: 2048
|
||||
type: string
|
||||
elasticsearch:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
cluster:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
indices:
|
||||
items:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
allow_restricted_indices:
|
||||
type: boolean
|
||||
field_security:
|
||||
additionalProperties:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
type: object
|
||||
names:
|
||||
items:
|
||||
type: string
|
||||
minItems: 1
|
||||
type: array
|
||||
privileges:
|
||||
items:
|
||||
type: string
|
||||
minItems: 1
|
||||
type: array
|
||||
query:
|
||||
type: string
|
||||
required:
|
||||
- names
|
||||
- privileges
|
||||
type: array
|
||||
remote_cluster:
|
||||
items:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
clusters:
|
||||
items:
|
||||
type: string
|
||||
minItems: 1
|
||||
type: array
|
||||
privileges:
|
||||
items:
|
||||
type: string
|
||||
minItems: 1
|
||||
type: array
|
||||
required:
|
||||
- privileges
|
||||
- clusters
|
||||
type: array
|
||||
remote_indices:
|
||||
items:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
allow_restricted_indices:
|
||||
type: boolean
|
||||
clusters:
|
||||
items:
|
||||
type: string
|
||||
minItems: 1
|
||||
type: array
|
||||
field_security:
|
||||
additionalProperties:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
type: object
|
||||
names:
|
||||
items:
|
||||
type: string
|
||||
minItems: 1
|
||||
type: array
|
||||
privileges:
|
||||
items:
|
||||
type: string
|
||||
minItems: 1
|
||||
type: array
|
||||
query:
|
||||
type: string
|
||||
required:
|
||||
- clusters
|
||||
- names
|
||||
- privileges
|
||||
type: array
|
||||
run_as:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
kibana:
|
||||
items:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
base:
|
||||
anyOf:
|
||||
- items: {}
|
||||
type: array
|
||||
- type: boolean
|
||||
- type: number
|
||||
- type: object
|
||||
- type: string
|
||||
nullable: true
|
||||
oneOf:
|
||||
- items:
|
||||
type: string
|
||||
type: array
|
||||
- items:
|
||||
type: string
|
||||
type: array
|
||||
feature:
|
||||
additionalProperties:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
type: object
|
||||
spaces:
|
||||
anyOf:
|
||||
- items:
|
||||
enum:
|
||||
- '*'
|
||||
type: string
|
||||
maxItems: 1
|
||||
minItems: 1
|
||||
type: array
|
||||
- items:
|
||||
type: string
|
||||
type: array
|
||||
default:
|
||||
- '*'
|
||||
required:
|
||||
- base
|
||||
type: array
|
||||
metadata:
|
||||
additionalProperties: {}
|
||||
type: object
|
||||
required:
|
||||
- elasticsearch
|
||||
responses: {}
|
||||
summary: Create or update a role
|
||||
tags:
|
||||
- roles
|
||||
/api/security/roles:
|
||||
post:
|
||||
operationId: '%2Fapi%2Fsecurity%2Froles#0'
|
||||
parameters:
|
||||
- description: The version of the API to use
|
||||
in: header
|
||||
name: elastic-api-version
|
||||
schema:
|
||||
default: '2023-10-31'
|
||||
enum:
|
||||
- '2023-10-31'
|
||||
type: string
|
||||
- description: A required header to protect against CSRF attacks
|
||||
in: header
|
||||
name: kbn-xsrf
|
||||
required: true
|
||||
schema:
|
||||
example: 'true'
|
||||
type: string
|
||||
requestBody:
|
||||
content:
|
||||
application/json; Elastic-Api-Version=2023-10-31:
|
||||
schema:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
roles:
|
||||
additionalProperties:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
description:
|
||||
maxLength: 2048
|
||||
type: string
|
||||
elasticsearch:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
cluster:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
indices:
|
||||
items:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
allow_restricted_indices:
|
||||
type: boolean
|
||||
field_security:
|
||||
additionalProperties:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
type: object
|
||||
names:
|
||||
items:
|
||||
type: string
|
||||
minItems: 1
|
||||
type: array
|
||||
privileges:
|
||||
items:
|
||||
type: string
|
||||
minItems: 1
|
||||
type: array
|
||||
query:
|
||||
type: string
|
||||
required:
|
||||
- names
|
||||
- privileges
|
||||
type: array
|
||||
remote_cluster:
|
||||
items:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
clusters:
|
||||
items:
|
||||
type: string
|
||||
minItems: 1
|
||||
type: array
|
||||
privileges:
|
||||
items:
|
||||
type: string
|
||||
minItems: 1
|
||||
type: array
|
||||
required:
|
||||
- privileges
|
||||
- clusters
|
||||
type: array
|
||||
remote_indices:
|
||||
items:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
allow_restricted_indices:
|
||||
type: boolean
|
||||
clusters:
|
||||
items:
|
||||
type: string
|
||||
minItems: 1
|
||||
type: array
|
||||
field_security:
|
||||
additionalProperties:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
type: object
|
||||
names:
|
||||
items:
|
||||
type: string
|
||||
minItems: 1
|
||||
type: array
|
||||
privileges:
|
||||
items:
|
||||
type: string
|
||||
minItems: 1
|
||||
type: array
|
||||
query:
|
||||
type: string
|
||||
required:
|
||||
- clusters
|
||||
- names
|
||||
- privileges
|
||||
type: array
|
||||
run_as:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
kibana:
|
||||
items:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
base:
|
||||
anyOf:
|
||||
- items: {}
|
||||
type: array
|
||||
- type: boolean
|
||||
- type: number
|
||||
- type: object
|
||||
- type: string
|
||||
nullable: true
|
||||
oneOf:
|
||||
- items:
|
||||
type: string
|
||||
type: array
|
||||
- items:
|
||||
type: string
|
||||
type: array
|
||||
feature:
|
||||
additionalProperties:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
type: object
|
||||
spaces:
|
||||
anyOf:
|
||||
- items:
|
||||
enum:
|
||||
- '*'
|
||||
type: string
|
||||
maxItems: 1
|
||||
minItems: 1
|
||||
type: array
|
||||
- items:
|
||||
type: string
|
||||
type: array
|
||||
default:
|
||||
- '*'
|
||||
required:
|
||||
- base
|
||||
type: array
|
||||
metadata:
|
||||
additionalProperties: {}
|
||||
type: object
|
||||
required:
|
||||
- elasticsearch
|
||||
type: object
|
||||
required:
|
||||
- roles
|
||||
responses: {}
|
||||
summary: Create or update roles
|
||||
tags:
|
||||
- roles
|
||||
/api/spaces/space:
|
||||
get:
|
||||
operationId: '%2Fapi%2Fspaces%2Fspace#0'
|
||||
|
@ -51504,6 +51954,7 @@ tags:
|
|||
- name: Message Signing Service
|
||||
- description: Machine learning
|
||||
name: ml
|
||||
- name: roles
|
||||
- description: >
|
||||
Export sets of saved objects that you want to import into {kib}, resolve
|
||||
import errors, and rotate an encryption key for encrypted saved objects
|
||||
|
|
|
@ -36556,6 +36556,456 @@ paths:
|
|||
tags:
|
||||
- Security AI Assistant API
|
||||
- Prompts API
|
||||
/api/security/role:
|
||||
get:
|
||||
operationId: '%2Fapi%2Fsecurity%2Frole#0'
|
||||
parameters:
|
||||
- description: The version of the API to use
|
||||
in: header
|
||||
name: elastic-api-version
|
||||
schema:
|
||||
default: '2023-10-31'
|
||||
enum:
|
||||
- '2023-10-31'
|
||||
type: string
|
||||
- in: query
|
||||
name: replaceDeprecatedPrivileges
|
||||
required: false
|
||||
schema:
|
||||
type: boolean
|
||||
responses: {}
|
||||
summary: Get all roles
|
||||
tags:
|
||||
- roles
|
||||
/api/security/role/{name}:
|
||||
delete:
|
||||
operationId: '%2Fapi%2Fsecurity%2Frole%2F%7Bname%7D#1'
|
||||
parameters:
|
||||
- description: The version of the API to use
|
||||
in: header
|
||||
name: elastic-api-version
|
||||
schema:
|
||||
default: '2023-10-31'
|
||||
enum:
|
||||
- '2023-10-31'
|
||||
type: string
|
||||
- description: A required header to protect against CSRF attacks
|
||||
in: header
|
||||
name: kbn-xsrf
|
||||
required: true
|
||||
schema:
|
||||
example: 'true'
|
||||
type: string
|
||||
- in: path
|
||||
name: name
|
||||
required: true
|
||||
schema:
|
||||
minLength: 1
|
||||
type: string
|
||||
responses: {}
|
||||
summary: Delete a role
|
||||
tags:
|
||||
- roles
|
||||
get:
|
||||
operationId: '%2Fapi%2Fsecurity%2Frole%2F%7Bname%7D#0'
|
||||
parameters:
|
||||
- description: The version of the API to use
|
||||
in: header
|
||||
name: elastic-api-version
|
||||
schema:
|
||||
default: '2023-10-31'
|
||||
enum:
|
||||
- '2023-10-31'
|
||||
type: string
|
||||
- in: path
|
||||
name: name
|
||||
required: true
|
||||
schema:
|
||||
minLength: 1
|
||||
type: string
|
||||
- in: query
|
||||
name: replaceDeprecatedPrivileges
|
||||
required: false
|
||||
schema:
|
||||
type: boolean
|
||||
responses: {}
|
||||
summary: Get a role
|
||||
tags:
|
||||
- roles
|
||||
put:
|
||||
operationId: '%2Fapi%2Fsecurity%2Frole%2F%7Bname%7D#2'
|
||||
parameters:
|
||||
- description: The version of the API to use
|
||||
in: header
|
||||
name: elastic-api-version
|
||||
schema:
|
||||
default: '2023-10-31'
|
||||
enum:
|
||||
- '2023-10-31'
|
||||
type: string
|
||||
- description: A required header to protect against CSRF attacks
|
||||
in: header
|
||||
name: kbn-xsrf
|
||||
required: true
|
||||
schema:
|
||||
example: 'true'
|
||||
type: string
|
||||
- in: path
|
||||
name: name
|
||||
required: true
|
||||
schema:
|
||||
maxLength: 1024
|
||||
minLength: 1
|
||||
type: string
|
||||
- in: query
|
||||
name: createOnly
|
||||
required: false
|
||||
schema:
|
||||
default: false
|
||||
type: boolean
|
||||
requestBody:
|
||||
content:
|
||||
application/json; Elastic-Api-Version=2023-10-31:
|
||||
schema:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
description:
|
||||
maxLength: 2048
|
||||
type: string
|
||||
elasticsearch:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
cluster:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
indices:
|
||||
items:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
allow_restricted_indices:
|
||||
type: boolean
|
||||
field_security:
|
||||
additionalProperties:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
type: object
|
||||
names:
|
||||
items:
|
||||
type: string
|
||||
minItems: 1
|
||||
type: array
|
||||
privileges:
|
||||
items:
|
||||
type: string
|
||||
minItems: 1
|
||||
type: array
|
||||
query:
|
||||
type: string
|
||||
required:
|
||||
- names
|
||||
- privileges
|
||||
type: array
|
||||
remote_cluster:
|
||||
items:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
clusters:
|
||||
items:
|
||||
type: string
|
||||
minItems: 1
|
||||
type: array
|
||||
privileges:
|
||||
items:
|
||||
type: string
|
||||
minItems: 1
|
||||
type: array
|
||||
required:
|
||||
- privileges
|
||||
- clusters
|
||||
type: array
|
||||
remote_indices:
|
||||
items:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
allow_restricted_indices:
|
||||
type: boolean
|
||||
clusters:
|
||||
items:
|
||||
type: string
|
||||
minItems: 1
|
||||
type: array
|
||||
field_security:
|
||||
additionalProperties:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
type: object
|
||||
names:
|
||||
items:
|
||||
type: string
|
||||
minItems: 1
|
||||
type: array
|
||||
privileges:
|
||||
items:
|
||||
type: string
|
||||
minItems: 1
|
||||
type: array
|
||||
query:
|
||||
type: string
|
||||
required:
|
||||
- clusters
|
||||
- names
|
||||
- privileges
|
||||
type: array
|
||||
run_as:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
kibana:
|
||||
items:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
base:
|
||||
anyOf:
|
||||
- items: {}
|
||||
type: array
|
||||
- type: boolean
|
||||
- type: number
|
||||
- type: object
|
||||
- type: string
|
||||
nullable: true
|
||||
oneOf:
|
||||
- items:
|
||||
type: string
|
||||
type: array
|
||||
- items:
|
||||
type: string
|
||||
type: array
|
||||
feature:
|
||||
additionalProperties:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
type: object
|
||||
spaces:
|
||||
anyOf:
|
||||
- items:
|
||||
enum:
|
||||
- '*'
|
||||
type: string
|
||||
maxItems: 1
|
||||
minItems: 1
|
||||
type: array
|
||||
- items:
|
||||
type: string
|
||||
type: array
|
||||
default:
|
||||
- '*'
|
||||
required:
|
||||
- base
|
||||
type: array
|
||||
metadata:
|
||||
additionalProperties: {}
|
||||
type: object
|
||||
required:
|
||||
- elasticsearch
|
||||
responses: {}
|
||||
summary: Create or update a role
|
||||
tags:
|
||||
- roles
|
||||
/api/security/roles:
|
||||
post:
|
||||
operationId: '%2Fapi%2Fsecurity%2Froles#0'
|
||||
parameters:
|
||||
- description: The version of the API to use
|
||||
in: header
|
||||
name: elastic-api-version
|
||||
schema:
|
||||
default: '2023-10-31'
|
||||
enum:
|
||||
- '2023-10-31'
|
||||
type: string
|
||||
- description: A required header to protect against CSRF attacks
|
||||
in: header
|
||||
name: kbn-xsrf
|
||||
required: true
|
||||
schema:
|
||||
example: 'true'
|
||||
type: string
|
||||
requestBody:
|
||||
content:
|
||||
application/json; Elastic-Api-Version=2023-10-31:
|
||||
schema:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
roles:
|
||||
additionalProperties:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
description:
|
||||
maxLength: 2048
|
||||
type: string
|
||||
elasticsearch:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
cluster:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
indices:
|
||||
items:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
allow_restricted_indices:
|
||||
type: boolean
|
||||
field_security:
|
||||
additionalProperties:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
type: object
|
||||
names:
|
||||
items:
|
||||
type: string
|
||||
minItems: 1
|
||||
type: array
|
||||
privileges:
|
||||
items:
|
||||
type: string
|
||||
minItems: 1
|
||||
type: array
|
||||
query:
|
||||
type: string
|
||||
required:
|
||||
- names
|
||||
- privileges
|
||||
type: array
|
||||
remote_cluster:
|
||||
items:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
clusters:
|
||||
items:
|
||||
type: string
|
||||
minItems: 1
|
||||
type: array
|
||||
privileges:
|
||||
items:
|
||||
type: string
|
||||
minItems: 1
|
||||
type: array
|
||||
required:
|
||||
- privileges
|
||||
- clusters
|
||||
type: array
|
||||
remote_indices:
|
||||
items:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
allow_restricted_indices:
|
||||
type: boolean
|
||||
clusters:
|
||||
items:
|
||||
type: string
|
||||
minItems: 1
|
||||
type: array
|
||||
field_security:
|
||||
additionalProperties:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
type: object
|
||||
names:
|
||||
items:
|
||||
type: string
|
||||
minItems: 1
|
||||
type: array
|
||||
privileges:
|
||||
items:
|
||||
type: string
|
||||
minItems: 1
|
||||
type: array
|
||||
query:
|
||||
type: string
|
||||
required:
|
||||
- clusters
|
||||
- names
|
||||
- privileges
|
||||
type: array
|
||||
run_as:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
kibana:
|
||||
items:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
base:
|
||||
anyOf:
|
||||
- items: {}
|
||||
type: array
|
||||
- type: boolean
|
||||
- type: number
|
||||
- type: object
|
||||
- type: string
|
||||
nullable: true
|
||||
oneOf:
|
||||
- items:
|
||||
type: string
|
||||
type: array
|
||||
- items:
|
||||
type: string
|
||||
type: array
|
||||
feature:
|
||||
additionalProperties:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
type: object
|
||||
spaces:
|
||||
anyOf:
|
||||
- items:
|
||||
enum:
|
||||
- '*'
|
||||
type: string
|
||||
maxItems: 1
|
||||
minItems: 1
|
||||
type: array
|
||||
- items:
|
||||
type: string
|
||||
type: array
|
||||
default:
|
||||
- '*'
|
||||
required:
|
||||
- base
|
||||
type: array
|
||||
metadata:
|
||||
additionalProperties: {}
|
||||
type: object
|
||||
required:
|
||||
- elasticsearch
|
||||
type: object
|
||||
required:
|
||||
- roles
|
||||
responses: {}
|
||||
summary: Create or update roles
|
||||
tags:
|
||||
- roles
|
||||
/api/spaces/space:
|
||||
get:
|
||||
operationId: '%2Fapi%2Fspaces%2Fspace#0'
|
||||
|
@ -51504,6 +51954,7 @@ tags:
|
|||
- name: Message Signing Service
|
||||
- description: Machine learning
|
||||
name: ml
|
||||
- name: roles
|
||||
- description: >
|
||||
Export sets of saved objects that you want to import into {kib}, resolve
|
||||
import errors, and rotate an encryption key for encrypted saved objects
|
||||
|
|
|
@ -248,6 +248,7 @@ export class CoreAppsService {
|
|||
if (latestOverrideVersion !== persistedOverrides.version) {
|
||||
this.configService.setDynamicConfigOverrides(persistedOverrides.attributes);
|
||||
latestOverrideVersion = persistedOverrides.version;
|
||||
this.logger.info('Succeeded in applying persisted dynamic config overrides');
|
||||
}
|
||||
} catch (err) {
|
||||
// Potential failures:
|
||||
|
|
|
@ -88,7 +88,6 @@ WHERE : 'where' -> pushMode(EXPRESSION_MODE);
|
|||
// MYCOMMAND : 'mycommand' -> ...
|
||||
DEV_INLINESTATS : {this.isDevVersion()}? 'inlinestats' -> pushMode(EXPRESSION_MODE);
|
||||
DEV_LOOKUP : {this.isDevVersion()}? 'lookup' -> pushMode(LOOKUP_MODE);
|
||||
DEV_MATCH : {this.isDevVersion()}? 'match' -> pushMode(EXPRESSION_MODE);
|
||||
DEV_METRICS : {this.isDevVersion()}? 'metrics' -> pushMode(METRICS_MODE);
|
||||
|
||||
//
|
||||
|
@ -211,8 +210,8 @@ ASTERISK : '*';
|
|||
SLASH : '/';
|
||||
PERCENT : '%';
|
||||
|
||||
// move it in the main section if the feature gets promoted
|
||||
DEV_MATCH_OP : {this.isDevVersion()}? DEV_MATCH -> type(DEV_MATCH);
|
||||
MATCH : 'match';
|
||||
NESTED_WHERE : WHERE -> type(WHERE);
|
||||
|
||||
NAMED_OR_POSITIONAL_PARAM
|
||||
: PARAM (LETTER | UNDERSCORE) UNQUOTED_ID_BODY*
|
||||
|
@ -308,8 +307,8 @@ mode PROJECT_MODE;
|
|||
PROJECT_PIPE : PIPE -> type(PIPE), popMode;
|
||||
PROJECT_DOT: DOT -> type(DOT);
|
||||
PROJECT_COMMA : COMMA -> type(COMMA);
|
||||
PROJECT_PARAM : PARAM -> type(PARAM);
|
||||
PROJECT_NAMED_OR_POSITIONAL_PARAM : NAMED_OR_POSITIONAL_PARAM -> type(NAMED_OR_POSITIONAL_PARAM);
|
||||
PROJECT_PARAM : {this.isDevVersion()}? PARAM -> type(PARAM);
|
||||
PROJECT_NAMED_OR_POSITIONAL_PARAM : {this.isDevVersion()}? NAMED_OR_POSITIONAL_PARAM -> type(NAMED_OR_POSITIONAL_PARAM);
|
||||
|
||||
fragment UNQUOTED_ID_BODY_WITH_PATTERN
|
||||
: (LETTER | DIGIT | UNDERSCORE | ASTERISK)
|
||||
|
@ -343,8 +342,8 @@ RENAME_PIPE : PIPE -> type(PIPE), popMode;
|
|||
RENAME_ASSIGN : ASSIGN -> type(ASSIGN);
|
||||
RENAME_COMMA : COMMA -> type(COMMA);
|
||||
RENAME_DOT: DOT -> type(DOT);
|
||||
RENAME_PARAM : PARAM -> type(PARAM);
|
||||
RENAME_NAMED_OR_POSITIONAL_PARAM : NAMED_OR_POSITIONAL_PARAM -> type(NAMED_OR_POSITIONAL_PARAM);
|
||||
RENAME_PARAM : {this.isDevVersion()}? PARAM -> type(PARAM);
|
||||
RENAME_NAMED_OR_POSITIONAL_PARAM : {this.isDevVersion()}? NAMED_OR_POSITIONAL_PARAM -> type(NAMED_OR_POSITIONAL_PARAM);
|
||||
|
||||
AS : 'as';
|
||||
|
||||
|
@ -416,8 +415,8 @@ ENRICH_FIELD_QUOTED_IDENTIFIER
|
|||
: QUOTED_IDENTIFIER -> type(QUOTED_IDENTIFIER)
|
||||
;
|
||||
|
||||
ENRICH_FIELD_PARAM : PARAM -> type(PARAM);
|
||||
ENRICH_FIELD_NAMED_OR_POSITIONAL_PARAM : NAMED_OR_POSITIONAL_PARAM -> type(NAMED_OR_POSITIONAL_PARAM);
|
||||
ENRICH_FIELD_PARAM : {this.isDevVersion()}? PARAM -> type(PARAM);
|
||||
ENRICH_FIELD_NAMED_OR_POSITIONAL_PARAM : {this.isDevVersion()}? NAMED_OR_POSITIONAL_PARAM -> type(NAMED_OR_POSITIONAL_PARAM);
|
||||
|
||||
ENRICH_FIELD_LINE_COMMENT
|
||||
: LINE_COMMENT -> channel(HIDDEN)
|
||||
|
@ -434,8 +433,8 @@ ENRICH_FIELD_WS
|
|||
mode MVEXPAND_MODE;
|
||||
MVEXPAND_PIPE : PIPE -> type(PIPE), popMode;
|
||||
MVEXPAND_DOT: DOT -> type(DOT);
|
||||
MVEXPAND_PARAM : PARAM -> type(PARAM);
|
||||
MVEXPAND_NAMED_OR_POSITIONAL_PARAM : NAMED_OR_POSITIONAL_PARAM -> type(NAMED_OR_POSITIONAL_PARAM);
|
||||
MVEXPAND_PARAM : {this.isDevVersion()}? PARAM -> type(PARAM);
|
||||
MVEXPAND_NAMED_OR_POSITIONAL_PARAM : {this.isDevVersion()}? NAMED_OR_POSITIONAL_PARAM -> type(NAMED_OR_POSITIONAL_PARAM);
|
||||
|
||||
MVEXPAND_QUOTED_IDENTIFIER
|
||||
: QUOTED_IDENTIFIER -> type(QUOTED_IDENTIFIER)
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -16,51 +16,51 @@ STATS=15
|
|||
WHERE=16
|
||||
DEV_INLINESTATS=17
|
||||
DEV_LOOKUP=18
|
||||
DEV_MATCH=19
|
||||
DEV_METRICS=20
|
||||
UNKNOWN_CMD=21
|
||||
LINE_COMMENT=22
|
||||
MULTILINE_COMMENT=23
|
||||
WS=24
|
||||
PIPE=25
|
||||
QUOTED_STRING=26
|
||||
INTEGER_LITERAL=27
|
||||
DECIMAL_LITERAL=28
|
||||
BY=29
|
||||
AND=30
|
||||
ASC=31
|
||||
ASSIGN=32
|
||||
CAST_OP=33
|
||||
COMMA=34
|
||||
DESC=35
|
||||
DOT=36
|
||||
FALSE=37
|
||||
FIRST=38
|
||||
IN=39
|
||||
IS=40
|
||||
LAST=41
|
||||
LIKE=42
|
||||
LP=43
|
||||
NOT=44
|
||||
NULL=45
|
||||
NULLS=46
|
||||
OR=47
|
||||
PARAM=48
|
||||
RLIKE=49
|
||||
RP=50
|
||||
TRUE=51
|
||||
EQ=52
|
||||
CIEQ=53
|
||||
NEQ=54
|
||||
LT=55
|
||||
LTE=56
|
||||
GT=57
|
||||
GTE=58
|
||||
PLUS=59
|
||||
MINUS=60
|
||||
ASTERISK=61
|
||||
SLASH=62
|
||||
PERCENT=63
|
||||
DEV_METRICS=19
|
||||
UNKNOWN_CMD=20
|
||||
LINE_COMMENT=21
|
||||
MULTILINE_COMMENT=22
|
||||
WS=23
|
||||
PIPE=24
|
||||
QUOTED_STRING=25
|
||||
INTEGER_LITERAL=26
|
||||
DECIMAL_LITERAL=27
|
||||
BY=28
|
||||
AND=29
|
||||
ASC=30
|
||||
ASSIGN=31
|
||||
CAST_OP=32
|
||||
COMMA=33
|
||||
DESC=34
|
||||
DOT=35
|
||||
FALSE=36
|
||||
FIRST=37
|
||||
IN=38
|
||||
IS=39
|
||||
LAST=40
|
||||
LIKE=41
|
||||
LP=42
|
||||
NOT=43
|
||||
NULL=44
|
||||
NULLS=45
|
||||
OR=46
|
||||
PARAM=47
|
||||
RLIKE=48
|
||||
RP=49
|
||||
TRUE=50
|
||||
EQ=51
|
||||
CIEQ=52
|
||||
NEQ=53
|
||||
LT=54
|
||||
LTE=55
|
||||
GT=56
|
||||
GTE=57
|
||||
PLUS=58
|
||||
MINUS=59
|
||||
ASTERISK=60
|
||||
SLASH=61
|
||||
PERCENT=62
|
||||
MATCH=63
|
||||
NAMED_OR_POSITIONAL_PARAM=64
|
||||
OPENING_BRACKET=65
|
||||
CLOSING_BRACKET=66
|
||||
|
@ -134,42 +134,43 @@ CLOSING_METRICS_WS=120
|
|||
'sort'=14
|
||||
'stats'=15
|
||||
'where'=16
|
||||
'|'=25
|
||||
'by'=29
|
||||
'and'=30
|
||||
'asc'=31
|
||||
'='=32
|
||||
'::'=33
|
||||
','=34
|
||||
'desc'=35
|
||||
'.'=36
|
||||
'false'=37
|
||||
'first'=38
|
||||
'in'=39
|
||||
'is'=40
|
||||
'last'=41
|
||||
'like'=42
|
||||
'('=43
|
||||
'not'=44
|
||||
'null'=45
|
||||
'nulls'=46
|
||||
'or'=47
|
||||
'?'=48
|
||||
'rlike'=49
|
||||
')'=50
|
||||
'true'=51
|
||||
'=='=52
|
||||
'=~'=53
|
||||
'!='=54
|
||||
'<'=55
|
||||
'<='=56
|
||||
'>'=57
|
||||
'>='=58
|
||||
'+'=59
|
||||
'-'=60
|
||||
'*'=61
|
||||
'/'=62
|
||||
'%'=63
|
||||
'|'=24
|
||||
'by'=28
|
||||
'and'=29
|
||||
'asc'=30
|
||||
'='=31
|
||||
'::'=32
|
||||
','=33
|
||||
'desc'=34
|
||||
'.'=35
|
||||
'false'=36
|
||||
'first'=37
|
||||
'in'=38
|
||||
'is'=39
|
||||
'last'=40
|
||||
'like'=41
|
||||
'('=42
|
||||
'not'=43
|
||||
'null'=44
|
||||
'nulls'=45
|
||||
'or'=46
|
||||
'?'=47
|
||||
'rlike'=48
|
||||
')'=49
|
||||
'true'=50
|
||||
'=='=51
|
||||
'=~'=52
|
||||
'!='=53
|
||||
'<'=54
|
||||
'<='=55
|
||||
'>'=56
|
||||
'>='=57
|
||||
'+'=58
|
||||
'-'=59
|
||||
'*'=60
|
||||
'/'=61
|
||||
'%'=62
|
||||
'match'=63
|
||||
']'=66
|
||||
'metadata'=75
|
||||
'as'=84
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -79,7 +79,7 @@ regexBooleanExpression
|
|||
;
|
||||
|
||||
matchBooleanExpression
|
||||
: valueExpression DEV_MATCH queryString=string
|
||||
: valueExpression MATCH queryString=string
|
||||
;
|
||||
|
||||
valueExpression
|
||||
|
@ -103,7 +103,13 @@ primaryExpression
|
|||
;
|
||||
|
||||
functionExpression
|
||||
: identifierOrParameter LP (ASTERISK | (booleanExpression (COMMA booleanExpression)*))? RP
|
||||
: functionName LP (ASTERISK | (booleanExpression (COMMA booleanExpression)*))? RP
|
||||
;
|
||||
|
||||
functionName
|
||||
// Additional function identifiers that are already a reserved word in the language
|
||||
: MATCH
|
||||
| identifierOrParameter
|
||||
;
|
||||
|
||||
dataType
|
||||
|
@ -119,8 +125,7 @@ fields
|
|||
;
|
||||
|
||||
field
|
||||
: booleanExpression
|
||||
| qualifiedName ASSIGN booleanExpression
|
||||
: (qualifiedName ASSIGN)? booleanExpression
|
||||
;
|
||||
|
||||
fromCommand
|
||||
|
@ -128,8 +133,7 @@ fromCommand
|
|||
;
|
||||
|
||||
indexPattern
|
||||
: clusterString COLON indexString
|
||||
| indexString
|
||||
: (clusterString COLON)? indexString
|
||||
;
|
||||
|
||||
clusterString
|
||||
|
@ -155,7 +159,7 @@ deprecated_metadata
|
|||
;
|
||||
|
||||
metricsCommand
|
||||
: DEV_METRICS indexPattern (COMMA indexPattern)* aggregates=fields? (BY grouping=fields)?
|
||||
: DEV_METRICS indexPattern (COMMA indexPattern)* aggregates=aggFields? (BY grouping=fields)?
|
||||
;
|
||||
|
||||
evalCommand
|
||||
|
@ -163,7 +167,15 @@ evalCommand
|
|||
;
|
||||
|
||||
statsCommand
|
||||
: STATS stats=fields? (BY grouping=fields)?
|
||||
: STATS stats=aggFields? (BY grouping=fields)?
|
||||
;
|
||||
|
||||
aggFields
|
||||
: aggField (COMMA aggField)*
|
||||
;
|
||||
|
||||
aggField
|
||||
: field (WHERE booleanExpression)?
|
||||
;
|
||||
|
||||
qualifiedName
|
||||
|
@ -185,7 +197,7 @@ identifier
|
|||
|
||||
identifierPattern
|
||||
: ID_PATTERN
|
||||
| parameter
|
||||
| {this.isDevVersion()}? parameter
|
||||
;
|
||||
|
||||
constant
|
||||
|
@ -208,7 +220,7 @@ parameter
|
|||
|
||||
identifierOrParameter
|
||||
: identifier
|
||||
| parameter
|
||||
| {this.isDevVersion()}? parameter
|
||||
;
|
||||
|
||||
limitCommand
|
||||
|
@ -312,5 +324,5 @@ lookupCommand
|
|||
;
|
||||
|
||||
inlinestatsCommand
|
||||
: DEV_INLINESTATS stats=fields (BY grouping=fields)?
|
||||
: DEV_INLINESTATS stats=aggFields (BY grouping=fields)?
|
||||
;
|
File diff suppressed because one or more lines are too long
|
@ -16,51 +16,51 @@ STATS=15
|
|||
WHERE=16
|
||||
DEV_INLINESTATS=17
|
||||
DEV_LOOKUP=18
|
||||
DEV_MATCH=19
|
||||
DEV_METRICS=20
|
||||
UNKNOWN_CMD=21
|
||||
LINE_COMMENT=22
|
||||
MULTILINE_COMMENT=23
|
||||
WS=24
|
||||
PIPE=25
|
||||
QUOTED_STRING=26
|
||||
INTEGER_LITERAL=27
|
||||
DECIMAL_LITERAL=28
|
||||
BY=29
|
||||
AND=30
|
||||
ASC=31
|
||||
ASSIGN=32
|
||||
CAST_OP=33
|
||||
COMMA=34
|
||||
DESC=35
|
||||
DOT=36
|
||||
FALSE=37
|
||||
FIRST=38
|
||||
IN=39
|
||||
IS=40
|
||||
LAST=41
|
||||
LIKE=42
|
||||
LP=43
|
||||
NOT=44
|
||||
NULL=45
|
||||
NULLS=46
|
||||
OR=47
|
||||
PARAM=48
|
||||
RLIKE=49
|
||||
RP=50
|
||||
TRUE=51
|
||||
EQ=52
|
||||
CIEQ=53
|
||||
NEQ=54
|
||||
LT=55
|
||||
LTE=56
|
||||
GT=57
|
||||
GTE=58
|
||||
PLUS=59
|
||||
MINUS=60
|
||||
ASTERISK=61
|
||||
SLASH=62
|
||||
PERCENT=63
|
||||
DEV_METRICS=19
|
||||
UNKNOWN_CMD=20
|
||||
LINE_COMMENT=21
|
||||
MULTILINE_COMMENT=22
|
||||
WS=23
|
||||
PIPE=24
|
||||
QUOTED_STRING=25
|
||||
INTEGER_LITERAL=26
|
||||
DECIMAL_LITERAL=27
|
||||
BY=28
|
||||
AND=29
|
||||
ASC=30
|
||||
ASSIGN=31
|
||||
CAST_OP=32
|
||||
COMMA=33
|
||||
DESC=34
|
||||
DOT=35
|
||||
FALSE=36
|
||||
FIRST=37
|
||||
IN=38
|
||||
IS=39
|
||||
LAST=40
|
||||
LIKE=41
|
||||
LP=42
|
||||
NOT=43
|
||||
NULL=44
|
||||
NULLS=45
|
||||
OR=46
|
||||
PARAM=47
|
||||
RLIKE=48
|
||||
RP=49
|
||||
TRUE=50
|
||||
EQ=51
|
||||
CIEQ=52
|
||||
NEQ=53
|
||||
LT=54
|
||||
LTE=55
|
||||
GT=56
|
||||
GTE=57
|
||||
PLUS=58
|
||||
MINUS=59
|
||||
ASTERISK=60
|
||||
SLASH=61
|
||||
PERCENT=62
|
||||
MATCH=63
|
||||
NAMED_OR_POSITIONAL_PARAM=64
|
||||
OPENING_BRACKET=65
|
||||
CLOSING_BRACKET=66
|
||||
|
@ -134,42 +134,43 @@ CLOSING_METRICS_WS=120
|
|||
'sort'=14
|
||||
'stats'=15
|
||||
'where'=16
|
||||
'|'=25
|
||||
'by'=29
|
||||
'and'=30
|
||||
'asc'=31
|
||||
'='=32
|
||||
'::'=33
|
||||
','=34
|
||||
'desc'=35
|
||||
'.'=36
|
||||
'false'=37
|
||||
'first'=38
|
||||
'in'=39
|
||||
'is'=40
|
||||
'last'=41
|
||||
'like'=42
|
||||
'('=43
|
||||
'not'=44
|
||||
'null'=45
|
||||
'nulls'=46
|
||||
'or'=47
|
||||
'?'=48
|
||||
'rlike'=49
|
||||
')'=50
|
||||
'true'=51
|
||||
'=='=52
|
||||
'=~'=53
|
||||
'!='=54
|
||||
'<'=55
|
||||
'<='=56
|
||||
'>'=57
|
||||
'>='=58
|
||||
'+'=59
|
||||
'-'=60
|
||||
'*'=61
|
||||
'/'=62
|
||||
'%'=63
|
||||
'|'=24
|
||||
'by'=28
|
||||
'and'=29
|
||||
'asc'=30
|
||||
'='=31
|
||||
'::'=32
|
||||
','=33
|
||||
'desc'=34
|
||||
'.'=35
|
||||
'false'=36
|
||||
'first'=37
|
||||
'in'=38
|
||||
'is'=39
|
||||
'last'=40
|
||||
'like'=41
|
||||
'('=42
|
||||
'not'=43
|
||||
'null'=44
|
||||
'nulls'=45
|
||||
'or'=46
|
||||
'?'=47
|
||||
'rlike'=48
|
||||
')'=49
|
||||
'true'=50
|
||||
'=='=51
|
||||
'=~'=52
|
||||
'!='=53
|
||||
'<'=54
|
||||
'<='=55
|
||||
'>'=56
|
||||
'>='=57
|
||||
'+'=58
|
||||
'-'=59
|
||||
'*'=60
|
||||
'/'=61
|
||||
'%'=62
|
||||
'match'=63
|
||||
']'=66
|
||||
'metadata'=75
|
||||
'as'=84
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -38,6 +38,7 @@ import { ConstantDefaultContext } from "./esql_parser.js";
|
|||
import { ParenthesizedExpressionContext } from "./esql_parser.js";
|
||||
import { FunctionContext } from "./esql_parser.js";
|
||||
import { FunctionExpressionContext } from "./esql_parser.js";
|
||||
import { FunctionNameContext } from "./esql_parser.js";
|
||||
import { ToDataTypeContext } from "./esql_parser.js";
|
||||
import { RowCommandContext } from "./esql_parser.js";
|
||||
import { FieldsContext } from "./esql_parser.js";
|
||||
|
@ -52,6 +53,8 @@ import { Deprecated_metadataContext } from "./esql_parser.js";
|
|||
import { MetricsCommandContext } from "./esql_parser.js";
|
||||
import { EvalCommandContext } from "./esql_parser.js";
|
||||
import { StatsCommandContext } from "./esql_parser.js";
|
||||
import { AggFieldsContext } from "./esql_parser.js";
|
||||
import { AggFieldContext } from "./esql_parser.js";
|
||||
import { QualifiedNameContext } from "./esql_parser.js";
|
||||
import { QualifiedNamePatternContext } from "./esql_parser.js";
|
||||
import { QualifiedNamePatternsContext } from "./esql_parser.js";
|
||||
|
@ -400,6 +403,16 @@ export default class esql_parserListener extends ParseTreeListener {
|
|||
* @param ctx the parse tree
|
||||
*/
|
||||
exitFunctionExpression?: (ctx: FunctionExpressionContext) => void;
|
||||
/**
|
||||
* Enter a parse tree produced by `esql_parser.functionName`.
|
||||
* @param ctx the parse tree
|
||||
*/
|
||||
enterFunctionName?: (ctx: FunctionNameContext) => void;
|
||||
/**
|
||||
* Exit a parse tree produced by `esql_parser.functionName`.
|
||||
* @param ctx the parse tree
|
||||
*/
|
||||
exitFunctionName?: (ctx: FunctionNameContext) => void;
|
||||
/**
|
||||
* Enter a parse tree produced by the `toDataType`
|
||||
* labeled alternative in `esql_parser.dataType`.
|
||||
|
@ -542,6 +555,26 @@ export default class esql_parserListener extends ParseTreeListener {
|
|||
* @param ctx the parse tree
|
||||
*/
|
||||
exitStatsCommand?: (ctx: StatsCommandContext) => void;
|
||||
/**
|
||||
* Enter a parse tree produced by `esql_parser.aggFields`.
|
||||
* @param ctx the parse tree
|
||||
*/
|
||||
enterAggFields?: (ctx: AggFieldsContext) => void;
|
||||
/**
|
||||
* Exit a parse tree produced by `esql_parser.aggFields`.
|
||||
* @param ctx the parse tree
|
||||
*/
|
||||
exitAggFields?: (ctx: AggFieldsContext) => void;
|
||||
/**
|
||||
* Enter a parse tree produced by `esql_parser.aggField`.
|
||||
* @param ctx the parse tree
|
||||
*/
|
||||
enterAggField?: (ctx: AggFieldContext) => void;
|
||||
/**
|
||||
* Exit a parse tree produced by `esql_parser.aggField`.
|
||||
* @param ctx the parse tree
|
||||
*/
|
||||
exitAggField?: (ctx: AggFieldContext) => void;
|
||||
/**
|
||||
* Enter a parse tree produced by `esql_parser.qualifiedName`.
|
||||
* @param ctx the parse tree
|
||||
|
|
|
@ -45,6 +45,7 @@ import { getPosition } from './helpers';
|
|||
import {
|
||||
collectAllSourceIdentifiers,
|
||||
collectAllFields,
|
||||
collectAllAggFields,
|
||||
visitByOption,
|
||||
collectAllColumnIdentifiers,
|
||||
visitRenameClauses,
|
||||
|
@ -144,8 +145,8 @@ export class ESQLAstBuilderListener implements ESQLParserListener {
|
|||
.map((sourceCtx) => createSource(sourceCtx)),
|
||||
};
|
||||
this.ast.push(node);
|
||||
const aggregates = collectAllFields(ctx.fields(0));
|
||||
const grouping = collectAllFields(ctx.fields(1));
|
||||
const aggregates = collectAllAggFields(ctx.aggFields());
|
||||
const grouping = collectAllFields(ctx.fields());
|
||||
if (aggregates && aggregates.length) {
|
||||
node.aggregates = aggregates;
|
||||
}
|
||||
|
@ -175,10 +176,10 @@ export class ESQLAstBuilderListener implements ESQLParserListener {
|
|||
|
||||
// STATS expression is optional
|
||||
if (ctx._stats) {
|
||||
command.args.push(...collectAllFields(ctx.fields(0)));
|
||||
command.args.push(...collectAllAggFields(ctx.aggFields()));
|
||||
}
|
||||
if (ctx._grouping) {
|
||||
command.args.push(...visitByOption(ctx, ctx._stats ? ctx.fields(1) : ctx.fields(0)));
|
||||
command.args.push(...visitByOption(ctx, ctx.fields()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -192,10 +193,10 @@ export class ESQLAstBuilderListener implements ESQLParserListener {
|
|||
|
||||
// STATS expression is optional
|
||||
if (ctx._stats) {
|
||||
command.args.push(...collectAllFields(ctx.fields(0)));
|
||||
command.args.push(...collectAllAggFields(ctx.aggFields()));
|
||||
}
|
||||
if (ctx._grouping) {
|
||||
command.args.push(...visitByOption(ctx, ctx._stats ? ctx.fields(1) : ctx.fields(0)));
|
||||
command.args.push(...visitByOption(ctx, ctx.fields()));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ import {
|
|||
type EnrichCommandContext,
|
||||
type FieldContext,
|
||||
type FieldsContext,
|
||||
type AggFieldsContext,
|
||||
type FromCommandContext,
|
||||
FunctionContext,
|
||||
type GrokCommandContext,
|
||||
|
@ -477,8 +478,11 @@ export function visitPrimaryExpression(ctx: PrimaryExpressionContext): ESQLAstIt
|
|||
}
|
||||
if (ctx instanceof FunctionContext) {
|
||||
const functionExpressionCtx = ctx.functionExpression();
|
||||
const functionNameContext = functionExpressionCtx.functionName().MATCH()
|
||||
? functionExpressionCtx.functionName().MATCH()
|
||||
: functionExpressionCtx.functionName().identifierOrParameter();
|
||||
const fn = createFunction(
|
||||
functionExpressionCtx.identifierOrParameter().getText().toLowerCase(),
|
||||
functionNameContext.getText().toLowerCase(),
|
||||
ctx,
|
||||
undefined,
|
||||
'variadic-call'
|
||||
|
@ -596,6 +600,21 @@ export function visitField(ctx: FieldContext) {
|
|||
return collectBooleanExpression(ctx.booleanExpression());
|
||||
}
|
||||
|
||||
export function collectAllAggFields(ctx: AggFieldsContext | undefined): ESQLAstField[] {
|
||||
const ast: ESQLAstField[] = [];
|
||||
if (!ctx) {
|
||||
return ast;
|
||||
}
|
||||
try {
|
||||
for (const aggField of ctx.aggField_list()) {
|
||||
ast.push(...(visitField(aggField.field()) as ESQLAstField[]));
|
||||
}
|
||||
} catch (e) {
|
||||
// do nothing
|
||||
}
|
||||
return ast;
|
||||
}
|
||||
|
||||
export function collectAllFields(ctx: FieldsContext | undefined): ESQLAstField[] {
|
||||
const ast: ESQLAstField[] = [];
|
||||
if (!ctx) {
|
||||
|
|
|
@ -36,10 +36,10 @@ test('can walk all functions', () => {
|
|||
|
||||
test('can find assignment expression', () => {
|
||||
const query = 'METRICS source var0 = bucket(bytes, 1 hour)';
|
||||
const { ast } = parse(query);
|
||||
const { root } = parse(query);
|
||||
const functions: ESQLFunction[] = [];
|
||||
|
||||
Walker.walk(ast, {
|
||||
Walker.walk(root, {
|
||||
visitFunction: (fn) => {
|
||||
if (fn.name === '=') {
|
||||
functions.push(fn);
|
||||
|
|
|
@ -126,10 +126,10 @@ export const validationStatsCommandTestSuite = (setup: helpers.Setup) => {
|
|||
const { expectErrors } = await setup();
|
||||
|
||||
await expectErrors('from a_index | INLINESTATS doubleField=', [
|
||||
"SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}",
|
||||
"SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}",
|
||||
]);
|
||||
await expectErrors('from a_index | INLINESTATS doubleField=5 by ', [
|
||||
"SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}",
|
||||
"SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}",
|
||||
]);
|
||||
await expectErrors('from a_index | INLINESTATS avg(doubleField) by wrongField', [
|
||||
'Unknown column [wrongField]',
|
||||
|
@ -186,7 +186,7 @@ export const validationStatsCommandTestSuite = (setup: helpers.Setup) => {
|
|||
const { expectErrors } = await setup();
|
||||
|
||||
await expectErrors('from a_index | INLINESTATS by ', [
|
||||
"SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}",
|
||||
"SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}",
|
||||
]);
|
||||
});
|
||||
|
||||
|
|
|
@ -117,11 +117,11 @@ export const validationMetricsCommandTestSuite = (setup: helpers.Setup) => {
|
|||
|
||||
await expectErrors('metrics a_index doubleField=', [
|
||||
expect.any(String),
|
||||
"SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}",
|
||||
"SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}",
|
||||
]);
|
||||
await expectErrors('metrics a_index doubleField=5 by ', [
|
||||
expect.any(String),
|
||||
"SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}",
|
||||
"SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}",
|
||||
]);
|
||||
});
|
||||
|
||||
|
|
|
@ -117,10 +117,10 @@ export const validationStatsCommandTestSuite = (setup: helpers.Setup) => {
|
|||
const { expectErrors } = await setup();
|
||||
|
||||
await expectErrors('from a_index | stats doubleField=', [
|
||||
"SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}",
|
||||
"SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}",
|
||||
]);
|
||||
await expectErrors('from a_index | stats doubleField=5 by ', [
|
||||
"SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}",
|
||||
"SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}",
|
||||
]);
|
||||
await expectErrors('from a_index | stats avg(doubleField) by wrongField', [
|
||||
'Unknown column [wrongField]',
|
||||
|
@ -176,7 +176,7 @@ export const validationStatsCommandTestSuite = (setup: helpers.Setup) => {
|
|||
const { expectErrors } = await setup();
|
||||
|
||||
await expectErrors('from a_index | stats by ', [
|
||||
"SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}",
|
||||
"SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}",
|
||||
]);
|
||||
});
|
||||
|
||||
|
|
|
@ -223,7 +223,7 @@
|
|||
{
|
||||
"query": "row",
|
||||
"error": [
|
||||
"SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}"
|
||||
"SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}"
|
||||
],
|
||||
"warning": []
|
||||
},
|
||||
|
@ -331,7 +331,7 @@
|
|||
{
|
||||
"query": "row var = 1 in (",
|
||||
"error": [
|
||||
"SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}",
|
||||
"SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}",
|
||||
"Error: [in] function expects exactly 2 arguments, got 1."
|
||||
],
|
||||
"warning": []
|
||||
|
@ -2645,7 +2645,7 @@
|
|||
{
|
||||
"query": "from a_index | dissect",
|
||||
"error": [
|
||||
"SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}"
|
||||
"SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}"
|
||||
],
|
||||
"warning": []
|
||||
},
|
||||
|
@ -2740,7 +2740,7 @@
|
|||
{
|
||||
"query": "from a_index | grok",
|
||||
"error": [
|
||||
"SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}"
|
||||
"SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}"
|
||||
],
|
||||
"warning": []
|
||||
},
|
||||
|
@ -3542,21 +3542,21 @@
|
|||
{
|
||||
"query": "from a_index | where *+ doubleField",
|
||||
"error": [
|
||||
"SyntaxError: extraneous input '*' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}"
|
||||
"SyntaxError: extraneous input '*' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}"
|
||||
],
|
||||
"warning": []
|
||||
},
|
||||
{
|
||||
"query": "from a_index | where /+ doubleField",
|
||||
"error": [
|
||||
"SyntaxError: extraneous input '/' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}"
|
||||
"SyntaxError: extraneous input '/' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}"
|
||||
],
|
||||
"warning": []
|
||||
},
|
||||
{
|
||||
"query": "from a_index | where %+ doubleField",
|
||||
"error": [
|
||||
"SyntaxError: extraneous input '%' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}"
|
||||
"SyntaxError: extraneous input '%' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}"
|
||||
],
|
||||
"warning": []
|
||||
},
|
||||
|
@ -4443,7 +4443,7 @@
|
|||
{
|
||||
"query": "from a_index | eval ",
|
||||
"error": [
|
||||
"SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}"
|
||||
"SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}"
|
||||
],
|
||||
"warning": []
|
||||
},
|
||||
|
@ -4486,7 +4486,7 @@
|
|||
{
|
||||
"query": "from a_index | eval a=b, ",
|
||||
"error": [
|
||||
"SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}",
|
||||
"SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}",
|
||||
"Unknown column [b]"
|
||||
],
|
||||
"warning": []
|
||||
|
@ -4513,7 +4513,7 @@
|
|||
{
|
||||
"query": "from a_index | eval a=round(doubleField), ",
|
||||
"error": [
|
||||
"SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}"
|
||||
"SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}"
|
||||
],
|
||||
"warning": []
|
||||
},
|
||||
|
@ -5619,21 +5619,21 @@
|
|||
{
|
||||
"query": "from a_index | eval *+ doubleField",
|
||||
"error": [
|
||||
"SyntaxError: extraneous input '*' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}"
|
||||
"SyntaxError: extraneous input '*' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}"
|
||||
],
|
||||
"warning": []
|
||||
},
|
||||
{
|
||||
"query": "from a_index | eval /+ doubleField",
|
||||
"error": [
|
||||
"SyntaxError: extraneous input '/' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}"
|
||||
"SyntaxError: extraneous input '/' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}"
|
||||
],
|
||||
"warning": []
|
||||
},
|
||||
{
|
||||
"query": "from a_index | eval %+ doubleField",
|
||||
"error": [
|
||||
"SyntaxError: extraneous input '%' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}"
|
||||
"SyntaxError: extraneous input '%' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}"
|
||||
],
|
||||
"warning": []
|
||||
},
|
||||
|
@ -6957,7 +6957,7 @@
|
|||
{
|
||||
"query": "from a_index | eval not",
|
||||
"error": [
|
||||
"SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}",
|
||||
"SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}",
|
||||
"Error: [not] function expects exactly one argument, got 0."
|
||||
],
|
||||
"warning": []
|
||||
|
@ -6965,7 +6965,7 @@
|
|||
{
|
||||
"query": "from a_index | eval in",
|
||||
"error": [
|
||||
"SyntaxError: mismatched input 'in' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}"
|
||||
"SyntaxError: mismatched input 'in' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}"
|
||||
],
|
||||
"warning": []
|
||||
},
|
||||
|
@ -9014,7 +9014,7 @@
|
|||
{
|
||||
"query": "from a_index | sort ",
|
||||
"error": [
|
||||
"SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}"
|
||||
"SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}"
|
||||
],
|
||||
"warning": []
|
||||
},
|
||||
|
@ -9033,7 +9033,7 @@
|
|||
{
|
||||
"query": "from a_index | sort doubleField, ",
|
||||
"error": [
|
||||
"SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}"
|
||||
"SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}"
|
||||
],
|
||||
"warning": []
|
||||
},
|
||||
|
|
|
@ -306,7 +306,7 @@ describe('validation logic', () => {
|
|||
|
||||
describe('row', () => {
|
||||
testErrorsAndWarnings('row', [
|
||||
"SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}",
|
||||
"SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}",
|
||||
]);
|
||||
testErrorsAndWarnings('row missing_column', ['Unknown column [missing_column]']);
|
||||
testErrorsAndWarnings('row fn()', ['Unknown function [fn]']);
|
||||
|
@ -335,7 +335,7 @@ describe('validation logic', () => {
|
|||
"SyntaxError: mismatched input '<EOF>' expecting '('",
|
||||
]);
|
||||
testErrorsAndWarnings('row var = 1 in (', [
|
||||
"SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}",
|
||||
"SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}",
|
||||
'Error: [in] function expects exactly 2 arguments, got 1.',
|
||||
]);
|
||||
testErrorsAndWarnings('row var = 1 not in ', [
|
||||
|
@ -690,7 +690,7 @@ describe('validation logic', () => {
|
|||
|
||||
describe('dissect', () => {
|
||||
testErrorsAndWarnings('from a_index | dissect', [
|
||||
"SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}",
|
||||
"SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}",
|
||||
]);
|
||||
testErrorsAndWarnings('from a_index | dissect textField', [
|
||||
"SyntaxError: missing QUOTED_STRING at '<EOF>'",
|
||||
|
@ -741,7 +741,7 @@ describe('validation logic', () => {
|
|||
|
||||
describe('grok', () => {
|
||||
testErrorsAndWarnings('from a_index | grok', [
|
||||
"SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}",
|
||||
"SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}",
|
||||
]);
|
||||
testErrorsAndWarnings('from a_index | grok textField', [
|
||||
"SyntaxError: missing QUOTED_STRING at '<EOF>'",
|
||||
|
@ -826,7 +826,7 @@ describe('validation logic', () => {
|
|||
}
|
||||
for (const wrongOp of ['*', '/', '%']) {
|
||||
testErrorsAndWarnings(`from a_index | where ${wrongOp}+ doubleField`, [
|
||||
`SyntaxError: extraneous input '${wrongOp}' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}`,
|
||||
`SyntaxError: extraneous input '${wrongOp}' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}`,
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -899,7 +899,7 @@ describe('validation logic', () => {
|
|||
|
||||
describe('eval', () => {
|
||||
testErrorsAndWarnings('from a_index | eval ', [
|
||||
"SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}",
|
||||
"SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}",
|
||||
]);
|
||||
testErrorsAndWarnings('from a_index | eval textField ', []);
|
||||
testErrorsAndWarnings('from a_index | eval b = textField', []);
|
||||
|
@ -912,7 +912,7 @@ describe('validation logic', () => {
|
|||
]);
|
||||
testErrorsAndWarnings('from a_index | eval a=b', ['Unknown column [b]']);
|
||||
testErrorsAndWarnings('from a_index | eval a=b, ', [
|
||||
"SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}",
|
||||
"SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}",
|
||||
'Unknown column [b]',
|
||||
]);
|
||||
testErrorsAndWarnings('from a_index | eval a=round', ['Unknown column [round]']);
|
||||
|
@ -921,7 +921,7 @@ describe('validation logic', () => {
|
|||
]);
|
||||
testErrorsAndWarnings('from a_index | eval a=round(doubleField) ', []);
|
||||
testErrorsAndWarnings('from a_index | eval a=round(doubleField), ', [
|
||||
"SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}",
|
||||
"SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}",
|
||||
]);
|
||||
testErrorsAndWarnings('from a_index | eval a=round(doubleField) + round(doubleField) ', []);
|
||||
testErrorsAndWarnings('from a_index | eval a=round(doubleField) + round(textField) ', [
|
||||
|
@ -984,7 +984,7 @@ describe('validation logic', () => {
|
|||
|
||||
for (const wrongOp of ['*', '/', '%']) {
|
||||
testErrorsAndWarnings(`from a_index | eval ${wrongOp}+ doubleField`, [
|
||||
`SyntaxError: extraneous input '${wrongOp}' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}`,
|
||||
`SyntaxError: extraneous input '${wrongOp}' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}`,
|
||||
]);
|
||||
}
|
||||
testErrorsAndWarnings(
|
||||
|
@ -1203,11 +1203,11 @@ describe('validation logic', () => {
|
|||
[]
|
||||
);
|
||||
testErrorsAndWarnings('from a_index | eval not', [
|
||||
"SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}",
|
||||
"SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}",
|
||||
'Error: [not] function expects exactly one argument, got 0.',
|
||||
]);
|
||||
testErrorsAndWarnings('from a_index | eval in', [
|
||||
"SyntaxError: mismatched input 'in' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}",
|
||||
"SyntaxError: mismatched input 'in' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}",
|
||||
]);
|
||||
|
||||
testErrorsAndWarnings('from a_index | eval textField in textField', [
|
||||
|
@ -1289,12 +1289,12 @@ describe('validation logic', () => {
|
|||
|
||||
describe('sort', () => {
|
||||
testErrorsAndWarnings('from a_index | sort ', [
|
||||
"SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}",
|
||||
"SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}",
|
||||
]);
|
||||
testErrorsAndWarnings('from a_index | sort "field" ', []);
|
||||
testErrorsAndWarnings('from a_index | sort wrongField ', ['Unknown column [wrongField]']);
|
||||
testErrorsAndWarnings('from a_index | sort doubleField, ', [
|
||||
"SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}",
|
||||
"SyntaxError: mismatched input '<EOF>' expecting {QUOTED_STRING, INTEGER_LITERAL, DECIMAL_LITERAL, 'false', '(', 'not', 'null', '?', 'true', '+', '-', 'match', NAMED_OR_POSITIONAL_PARAM, OPENING_BRACKET, UNQUOTED_IDENTIFIER, QUOTED_IDENTIFIER}",
|
||||
]);
|
||||
testErrorsAndWarnings('from a_index | sort doubleField, textField', []);
|
||||
for (const dir of ['desc', 'asc']) {
|
||||
|
|
|
@ -36,7 +36,7 @@ const getDisplayName = (name, imported) => {
|
|||
displayName = name.split('.').pop() || name;
|
||||
}
|
||||
|
||||
return displayName.replace('$', '.');
|
||||
return displayName.replace(/^\$/g, '.');
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -47,7 +47,6 @@ export const buildESQlTheme = (): monaco.editor.IStandaloneThemeData => ({
|
|||
[
|
||||
'dev_metrics',
|
||||
'metadata',
|
||||
'dev_match',
|
||||
'mv_expand',
|
||||
'stats',
|
||||
'dev_inlinestats',
|
||||
|
@ -75,6 +74,7 @@ export const buildESQlTheme = (): monaco.editor.IStandaloneThemeData => ({
|
|||
'asc',
|
||||
'desc',
|
||||
'nulls_order',
|
||||
'match',
|
||||
],
|
||||
euiThemeVars.euiColorAccentText,
|
||||
true // isBold
|
||||
|
|
|
@ -44,7 +44,7 @@ export class PainlessWorker implements BaseWorkerDefinition {
|
|||
fields?: PainlessAutocompleteField[]
|
||||
): PainlessCompletionResult {
|
||||
// Array of the active line words, e.g., [boolean, isTrue, =, true]
|
||||
const words = currentLineChars.replace('\t', '').split(' ');
|
||||
const words = currentLineChars.replace(/\t/g, '').split(/\s/);
|
||||
|
||||
const autocompleteSuggestions: PainlessCompletionResult = getAutocompleteSuggestions(
|
||||
context,
|
||||
|
|
|
@ -23,10 +23,10 @@ export const markdownVisDefinition: VisTypeDefinition<MarkdownVisParams> = {
|
|||
icon: 'visText',
|
||||
group: VisGroups.TOOLS,
|
||||
titleInWizard: i18n.translate('visTypeMarkdown.markdownTitleInWizard', {
|
||||
defaultMessage: 'Text',
|
||||
defaultMessage: 'Markdown text',
|
||||
}),
|
||||
description: i18n.translate('visTypeMarkdown.markdownDescription', {
|
||||
defaultMessage: 'Add text and images to your dashboard.',
|
||||
defaultMessage: 'Add custom text or images to dashboards.',
|
||||
}),
|
||||
order: 30,
|
||||
toExpressionAst,
|
||||
|
|
|
@ -102,7 +102,7 @@ export const metricsVisDefinition: VisTypeDefinition<
|
|||
name: VIS_TYPE,
|
||||
title: i18n.translate('visTypeTimeseries.kbnVisTypes.metricsTitle', { defaultMessage: 'TSVB' }),
|
||||
description: i18n.translate('visTypeTimeseries.kbnVisTypes.metricsDescription', {
|
||||
defaultMessage: 'Perform advanced analysis of your time series data.',
|
||||
defaultMessage: 'Create visualizations using time series data.',
|
||||
}),
|
||||
icon: 'visVisualBuilder',
|
||||
group: VisGroups.LEGACY,
|
||||
|
|
|
@ -32,12 +32,9 @@ export const createVegaTypeDefinition = (): VisTypeDefinition<VisParams> => {
|
|||
title: 'Vega',
|
||||
getInfoMessage,
|
||||
description: i18n.translate('visTypeVega.type.vegaDescription', {
|
||||
defaultMessage: 'Use Vega to create new types of visualizations.',
|
||||
defaultMessage: 'Use the Vega syntax to create new types of visualizations.',
|
||||
description: 'Vega and Vega-Lite are product names and should not be translated',
|
||||
}),
|
||||
note: i18n.translate('visTypeVega.type.vegaNote', {
|
||||
defaultMessage: 'Requires knowledge of Vega syntax.',
|
||||
}),
|
||||
icon: 'visVega',
|
||||
group: VisGroups.PROMOTED,
|
||||
titleInWizard: i18n.translate('visTypeVega.type.vegaTitleInWizard', {
|
||||
|
|
|
@ -70,18 +70,18 @@ describe('AggBasedSelection', () => {
|
|||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should call the toggleGroups if the user clicks the goBack link', () => {
|
||||
const toggleGroups = jest.fn();
|
||||
it('should call the showMainDialog if the user clicks the goBack link', () => {
|
||||
const showMainDialog = jest.fn();
|
||||
const wrapper = mountWithIntl(
|
||||
<AggBasedSelection
|
||||
visTypesRegistry={visTypes}
|
||||
toggleGroups={toggleGroups}
|
||||
showMainDialog={showMainDialog}
|
||||
onVisTypeSelected={jest.fn()}
|
||||
/>
|
||||
);
|
||||
const aggBasedGroupCard = wrapper.find('[data-test-subj="goBackLink"]').last();
|
||||
aggBasedGroupCard.simulate('click');
|
||||
expect(toggleGroups).toHaveBeenCalled();
|
||||
expect(showMainDialog).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('filter for visualization types', () => {
|
||||
|
@ -89,7 +89,7 @@ describe('AggBasedSelection', () => {
|
|||
const wrapper = mountWithIntl(
|
||||
<AggBasedSelection
|
||||
visTypesRegistry={visTypes}
|
||||
toggleGroups={jest.fn()}
|
||||
showMainDialog={jest.fn()}
|
||||
onVisTypeSelected={jest.fn()}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -39,7 +39,7 @@ interface AggBasedSelectionProps {
|
|||
openedAsRoot?: boolean;
|
||||
onVisTypeSelected: (visType: BaseVisType) => void;
|
||||
visTypesRegistry: TypesStart;
|
||||
toggleGroups: (flag: boolean) => void;
|
||||
showMainDialog: (flag: boolean) => void;
|
||||
}
|
||||
interface AggBasedSelectionState {
|
||||
query: string;
|
||||
|
@ -67,7 +67,7 @@ class AggBasedSelection extends React.Component<AggBasedSelectionProps, AggBased
|
|||
</EuiModalHeader>
|
||||
<EuiModalBody>
|
||||
{this.props.openedAsRoot ? null : (
|
||||
<DialogNavigation goBack={() => this.props.toggleGroups(true)} />
|
||||
<DialogNavigation goBack={() => this.props.showMainDialog(true)} />
|
||||
)}
|
||||
<EuiFieldSearch
|
||||
placeholder="Filter"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
$modalWidth: $euiSizeL * 34;
|
||||
$modalHeight: $euiSizeL * 30;
|
||||
$modalWidth: $euiSizeS * 100;
|
||||
$modalHeight: $euiSizeS * 90;
|
||||
|
||||
.visNewVisDialog {
|
||||
max-width: $modalWidth;
|
||||
|
|
|
@ -8,11 +8,13 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { mountWithIntl } from '@kbn/test-jest-helpers';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { TypesStart, BaseVisType, VisGroups } from '../../vis_types';
|
||||
import { GroupSelection } from './group_selection';
|
||||
import { GroupSelection, GroupSelectionProps } from './group_selection';
|
||||
import { DocLinksStart } from '@kbn/core/public';
|
||||
import { VisParams } from '../../../common';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { I18nProvider } from '@kbn/i18n-react';
|
||||
|
||||
describe('GroupSelection', () => {
|
||||
const defaultVisTypeParams = {
|
||||
|
@ -99,35 +101,34 @@ describe('GroupSelection', () => {
|
|||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
const renderGroupSelectionComponent = (overrideProps?: Partial<GroupSelectionProps>) => {
|
||||
return render(
|
||||
<I18nProvider>
|
||||
<GroupSelection
|
||||
tab="recommended"
|
||||
setTab={jest.fn()}
|
||||
visTypesRegistry={visTypesRegistry(_visTypes)}
|
||||
docLinks={docLinks as DocLinksStart}
|
||||
showMainDialog={jest.fn()}
|
||||
onVisTypeSelected={jest.fn()}
|
||||
{...overrideProps}
|
||||
/>
|
||||
</I18nProvider>
|
||||
);
|
||||
};
|
||||
|
||||
it('should render the header title', () => {
|
||||
const wrapper = mountWithIntl(
|
||||
<GroupSelection
|
||||
visTypesRegistry={visTypesRegistry(_visTypes)}
|
||||
docLinks={docLinks as DocLinksStart}
|
||||
toggleGroups={jest.fn()}
|
||||
onVisTypeSelected={jest.fn()}
|
||||
showExperimental={true}
|
||||
/>
|
||||
);
|
||||
expect(wrapper.find('[data-test-subj="groupModalHeader"]').at(0).text()).toBe(
|
||||
'New visualization'
|
||||
);
|
||||
renderGroupSelectionComponent();
|
||||
expect(screen.getByTestId('groupModalHeader')).toHaveTextContent('Create visualization');
|
||||
});
|
||||
|
||||
it('should not render the aggBased group card if no aggBased visualization is registered', () => {
|
||||
const wrapper = mountWithIntl(
|
||||
<GroupSelection
|
||||
visTypesRegistry={visTypesRegistry(_visTypes)}
|
||||
docLinks={docLinks as DocLinksStart}
|
||||
toggleGroups={jest.fn()}
|
||||
onVisTypeSelected={jest.fn()}
|
||||
showExperimental={true}
|
||||
/>
|
||||
);
|
||||
expect(wrapper.find('[data-test-subj="visGroup-aggbased"]').exists()).toBe(false);
|
||||
it('should not render tabs if no legacy, tools or tsvb visualizations are registered', async () => {
|
||||
renderGroupSelectionComponent();
|
||||
expect(screen.queryByRole('tab', { name: /legacy/i })).toBeNull();
|
||||
expect(screen.queryByRole('tab', { name: /recommended/i })).toBeNull();
|
||||
});
|
||||
|
||||
it('should render the aggBased group card if an aggBased group vis is registered', () => {
|
||||
it('should render tabs and the aggBased group card if an aggBased group vis is registered', async () => {
|
||||
const aggBasedVisType = {
|
||||
name: 'visWithSearch',
|
||||
title: 'Vis with search',
|
||||
|
@ -135,53 +136,18 @@ describe('GroupSelection', () => {
|
|||
stage: 'production',
|
||||
...defaultVisTypeParams,
|
||||
};
|
||||
const wrapper = mountWithIntl(
|
||||
<GroupSelection
|
||||
visTypesRegistry={visTypesRegistry([..._visTypes, aggBasedVisType] as BaseVisType[])}
|
||||
docLinks={docLinks as DocLinksStart}
|
||||
toggleGroups={jest.fn()}
|
||||
onVisTypeSelected={jest.fn()}
|
||||
showExperimental={true}
|
||||
/>
|
||||
);
|
||||
expect(wrapper.find('[data-test-subj="visGroup-aggbased"]').exists()).toBe(true);
|
||||
renderGroupSelectionComponent({
|
||||
visTypesRegistry: visTypesRegistry([..._visTypes, aggBasedVisType] as BaseVisType[]),
|
||||
tab: 'legacy',
|
||||
});
|
||||
|
||||
expect(screen.queryByRole('tab', { name: /legacy/i })).toBeInTheDocument();
|
||||
expect(screen.queryByRole('tab', { name: /recommended/i })).toBeInTheDocument();
|
||||
expect(screen.getByTestId('visType-aggbased')).toHaveTextContent('Aggregation-based');
|
||||
});
|
||||
|
||||
it('should not render the tools group card if no tools visualization is registered', () => {
|
||||
const wrapper = mountWithIntl(
|
||||
<GroupSelection
|
||||
visTypesRegistry={visTypesRegistry(_visTypes)}
|
||||
docLinks={docLinks as DocLinksStart}
|
||||
toggleGroups={jest.fn()}
|
||||
onVisTypeSelected={jest.fn()}
|
||||
showExperimental={true}
|
||||
/>
|
||||
);
|
||||
expect(wrapper.find('[data-test-subj="visGroup-tools"]').exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('should render the tools group card if a tools group vis is registered', () => {
|
||||
const toolsVisType = {
|
||||
name: 'vis3',
|
||||
title: 'Vis3',
|
||||
stage: 'production',
|
||||
group: VisGroups.TOOLS,
|
||||
...defaultVisTypeParams,
|
||||
};
|
||||
const wrapper = mountWithIntl(
|
||||
<GroupSelection
|
||||
visTypesRegistry={visTypesRegistry([..._visTypes, toolsVisType] as BaseVisType[])}
|
||||
docLinks={docLinks as DocLinksStart}
|
||||
toggleGroups={jest.fn()}
|
||||
onVisTypeSelected={jest.fn()}
|
||||
showExperimental={true}
|
||||
/>
|
||||
);
|
||||
expect(wrapper.find('[data-test-subj="visGroup-tools"]').exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('should call the toggleGroups if the aggBased group card is clicked', () => {
|
||||
const toggleGroups = jest.fn();
|
||||
it('should call the showMainDialog if the aggBased group card is clicked', async () => {
|
||||
const showMainDialog = jest.fn();
|
||||
const aggBasedVisType = {
|
||||
name: 'visWithSearch',
|
||||
title: 'Vis with search',
|
||||
|
@ -189,82 +155,26 @@ describe('GroupSelection', () => {
|
|||
stage: 'production',
|
||||
...defaultVisTypeParams,
|
||||
};
|
||||
const wrapper = mountWithIntl(
|
||||
<GroupSelection
|
||||
visTypesRegistry={visTypesRegistry([..._visTypes, aggBasedVisType] as BaseVisType[])}
|
||||
docLinks={docLinks as DocLinksStart}
|
||||
toggleGroups={toggleGroups}
|
||||
onVisTypeSelected={jest.fn()}
|
||||
showExperimental={true}
|
||||
/>
|
||||
);
|
||||
const aggBasedGroupCard = wrapper.find('[data-test-subj="visGroupAggBasedExploreLink"]').last();
|
||||
aggBasedGroupCard.simulate('click');
|
||||
expect(toggleGroups).toHaveBeenCalled();
|
||||
renderGroupSelectionComponent({
|
||||
showMainDialog,
|
||||
visTypesRegistry: visTypesRegistry([..._visTypes, aggBasedVisType] as BaseVisType[]),
|
||||
tab: 'legacy',
|
||||
});
|
||||
|
||||
await userEvent.click(screen.getByRole('button', { name: /Aggregation-based/i }));
|
||||
expect(showMainDialog).toHaveBeenCalledWith(false);
|
||||
});
|
||||
|
||||
it('should sort promoted visualizations first', () => {
|
||||
const wrapper = mountWithIntl(
|
||||
<GroupSelection
|
||||
visTypesRegistry={visTypesRegistry(_visTypes as BaseVisType[])}
|
||||
docLinks={docLinks as DocLinksStart}
|
||||
toggleGroups={jest.fn()}
|
||||
onVisTypeSelected={jest.fn()}
|
||||
showExperimental={true}
|
||||
/>
|
||||
);
|
||||
it('should only show promoted visualizations in recommended tab', () => {
|
||||
renderGroupSelectionComponent();
|
||||
|
||||
const cards = [
|
||||
...new Set(
|
||||
wrapper.find('[data-test-subj^="visType-"]').map((card) => card.prop('data-test-subj'))
|
||||
),
|
||||
];
|
||||
const cards = screen.getAllByRole('button').map((el) => el.textContent);
|
||||
|
||||
expect(cards).toEqual([
|
||||
'visType-visAliasWithPromotion',
|
||||
'visType-vis1',
|
||||
'visType-vis2',
|
||||
'visType-visWithAliasUrl',
|
||||
'Vis alias with promotion',
|
||||
'Vis Type 1',
|
||||
'Vis Type 2',
|
||||
'Vis with alias Url',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should not show tools experimental visualizations if showExperimentalis false', () => {
|
||||
const expVis = {
|
||||
name: 'visExp',
|
||||
title: 'Experimental Vis',
|
||||
group: VisGroups.TOOLS,
|
||||
stage: 'experimental',
|
||||
...defaultVisTypeParams,
|
||||
};
|
||||
const wrapper = mountWithIntl(
|
||||
<GroupSelection
|
||||
visTypesRegistry={visTypesRegistry([..._visTypes, expVis] as BaseVisType[])}
|
||||
docLinks={docLinks as DocLinksStart}
|
||||
toggleGroups={jest.fn()}
|
||||
onVisTypeSelected={jest.fn()}
|
||||
showExperimental={false}
|
||||
/>
|
||||
);
|
||||
expect(wrapper.find('[data-test-subj="visType-visExp"]').exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('should show tools experimental visualizations if showExperimental is true', () => {
|
||||
const expVis = {
|
||||
name: 'visExp',
|
||||
title: 'Experimental Vis',
|
||||
group: VisGroups.TOOLS,
|
||||
stage: 'experimental',
|
||||
...defaultVisTypeParams,
|
||||
};
|
||||
const wrapper = mountWithIntl(
|
||||
<GroupSelection
|
||||
visTypesRegistry={visTypesRegistry([..._visTypes, expVis] as BaseVisType[])}
|
||||
docLinks={docLinks as DocLinksStart}
|
||||
toggleGroups={jest.fn()}
|
||||
onVisTypeSelected={jest.fn()}
|
||||
showExperimental={true}
|
||||
/>
|
||||
);
|
||||
expect(wrapper.find('[data-test-subj="visType-visExp"]').exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
*/
|
||||
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import React, { ReactNode, useCallback, useMemo } from 'react';
|
||||
import { orderBy } from 'lodash';
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
|
@ -20,14 +20,13 @@ import {
|
|||
EuiModalBody,
|
||||
EuiModalHeaderTitle,
|
||||
EuiLink,
|
||||
EuiText,
|
||||
EuiSpacer,
|
||||
EuiBetaBadge,
|
||||
EuiTitle,
|
||||
EuiDescriptionListTitle,
|
||||
EuiDescriptionListDescription,
|
||||
EuiDescriptionList,
|
||||
EuiBadge,
|
||||
EuiTabs,
|
||||
EuiTab,
|
||||
EuiIconTip,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { DocLinksStart } from '@kbn/core/public';
|
||||
|
@ -36,162 +35,210 @@ import { VisGroups } from '../../vis_types/vis_groups_enum';
|
|||
import type { VisTypeAlias } from '../../vis_types/vis_type_alias_registry';
|
||||
import './group_selection.scss';
|
||||
|
||||
interface GroupSelectionProps {
|
||||
export interface GroupSelectionProps {
|
||||
onVisTypeSelected: (visType: BaseVisType | VisTypeAlias) => void;
|
||||
visTypesRegistry: TypesStart;
|
||||
docLinks: DocLinksStart;
|
||||
toggleGroups: (flag: boolean) => void;
|
||||
showExperimental: boolean;
|
||||
showMainDialog: (flag: boolean) => void;
|
||||
tab: 'recommended' | 'legacy';
|
||||
setTab: (tab: 'recommended' | 'legacy') => void;
|
||||
}
|
||||
|
||||
interface VisCardProps {
|
||||
onVisTypeSelected: (visType: BaseVisType | VisTypeAlias) => void;
|
||||
visType: BaseVisType | VisTypeAlias;
|
||||
showExperimental?: boolean | undefined;
|
||||
shouldStretch?: boolean;
|
||||
}
|
||||
|
||||
function GroupSelection(props: GroupSelectionProps) {
|
||||
const tabs: Array<{ id: 'recommended' | 'legacy'; label: ReactNode; dataTestSubj: string }> = [
|
||||
{
|
||||
id: 'recommended',
|
||||
label: i18n.translate('visualizations.newVisWizard.recommendedTab', {
|
||||
defaultMessage: 'Recommended',
|
||||
}),
|
||||
dataTestSubj: 'groupModalRecommendedTab',
|
||||
},
|
||||
{
|
||||
id: 'legacy',
|
||||
dataTestSubj: 'groupModalLegacyTab',
|
||||
label: (
|
||||
<EuiFlexGroup alignItems="center" gutterSize="s" responsive={false}>
|
||||
<EuiFlexItem grow={false}>
|
||||
{i18n.translate('visualizations.newVisWizard.legacyTab', {
|
||||
defaultMessage: 'Legacy',
|
||||
})}
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIconTip
|
||||
content={i18n.translate('visualizations.newVisWizard.legacyTabDescription', {
|
||||
defaultMessage: 'Legacy visualizations are scheduled for deprecation in the future.',
|
||||
})}
|
||||
position="top"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const getVisTypesFromGroup = (
|
||||
visTypesRegistry: TypesStart,
|
||||
group: VisGroups
|
||||
): Array<BaseVisType | VisTypeAlias> => {
|
||||
return visTypesRegistry.getByGroup(group).filter(({ disableCreate }) => !disableCreate);
|
||||
};
|
||||
|
||||
function GroupSelection({
|
||||
tab = 'recommended',
|
||||
setTab,
|
||||
visTypesRegistry,
|
||||
...props
|
||||
}: GroupSelectionProps) {
|
||||
const visualizeGuideLink = props.docLinks.links.dashboard.guide;
|
||||
const promotedVisGroups = useMemo(
|
||||
() =>
|
||||
orderBy(
|
||||
[
|
||||
...props.visTypesRegistry.getAliases(),
|
||||
...props.visTypesRegistry.getByGroup(VisGroups.PROMOTED),
|
||||
// Include so TSVB still gets displayed
|
||||
...props.visTypesRegistry.getByGroup(VisGroups.LEGACY),
|
||||
...visTypesRegistry.getAliases(),
|
||||
...visTypesRegistry.getByGroup(VisGroups.PROMOTED),
|
||||
].filter((visDefinition) => {
|
||||
return !visDefinition.disableCreate;
|
||||
}),
|
||||
['promotion', 'title'],
|
||||
['asc', 'asc']
|
||||
),
|
||||
[props.visTypesRegistry]
|
||||
[visTypesRegistry]
|
||||
);
|
||||
|
||||
const aggBasedTypes = getVisTypesFromGroup(visTypesRegistry, VisGroups.AGGBASED);
|
||||
const legacyTypes = getVisTypesFromGroup(visTypesRegistry, VisGroups.LEGACY);
|
||||
|
||||
const shouldDisplayLegacyTab = legacyTypes.length + aggBasedTypes.length;
|
||||
|
||||
const [tsvbProps] = legacyTypes.map((visType) => ({
|
||||
visType: {
|
||||
...visType,
|
||||
icon: visType.name === 'metrics' ? 'visualizeApp' : (visType.icon as string),
|
||||
},
|
||||
onVisTypeSelected: props.onVisTypeSelected,
|
||||
key: visType.name,
|
||||
}));
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiModalHeader>
|
||||
<EuiModalHeaderTitle data-test-subj="groupModalHeader">
|
||||
<FormattedMessage
|
||||
id="visualizations.newVisWizard.title"
|
||||
defaultMessage="New visualization"
|
||||
defaultMessage="Create visualization"
|
||||
/>
|
||||
</EuiModalHeaderTitle>
|
||||
</EuiModalHeader>
|
||||
<EuiModalBody className="visNewVisDialogGroupSelection__body">
|
||||
{shouldDisplayLegacyTab && (
|
||||
<div className="visNewVisDialogGroupSelection__visGroups">
|
||||
<EuiTabs>
|
||||
{tabs.map((t) => (
|
||||
<EuiTab
|
||||
data-test-subj={t.dataTestSubj}
|
||||
isSelected={tab === t.id}
|
||||
onClick={() => setTab(t.id)}
|
||||
key={t.id}
|
||||
>
|
||||
{t.label}
|
||||
</EuiTab>
|
||||
))}
|
||||
</EuiTabs>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="visNewVisDialogGroupSelection__visGroups">
|
||||
<EuiSpacer size="s" />
|
||||
<EuiFlexGrid columns={2} data-test-subj="visNewDialogGroups">
|
||||
{promotedVisGroups.map((visType) => (
|
||||
<VisGroup
|
||||
visType={visType}
|
||||
key={visType.name}
|
||||
onVisTypeSelected={props.onVisTypeSelected}
|
||||
/>
|
||||
))}
|
||||
</EuiFlexGrid>
|
||||
<EuiSpacer size="l" />
|
||||
</div>
|
||||
<div className="visNewVisDialogGroupSelection__footer">
|
||||
<EuiSpacer size="l" />
|
||||
<EuiFlexGrid columns={2}>
|
||||
{props.visTypesRegistry.getByGroup(VisGroups.AGGBASED).filter((visDefinition) => {
|
||||
return !visDefinition.disableCreate;
|
||||
}).length > 0 && (
|
||||
<EuiFlexItem>
|
||||
<EuiCard
|
||||
titleSize="xs"
|
||||
layout="horizontal"
|
||||
display="transparent"
|
||||
onClick={() => props.toggleGroups(false)}
|
||||
title={
|
||||
<span data-test-subj="visGroupAggBasedTitle">
|
||||
{i18n.translate('visualizations.newVisWizard.aggBasedGroupTitle', {
|
||||
defaultMessage: 'Aggregation based',
|
||||
})}
|
||||
</span>
|
||||
}
|
||||
data-test-subj="visGroup-aggbased"
|
||||
description={i18n.translate(
|
||||
'visualizations.newVisWizard.aggBasedGroupDescription',
|
||||
{
|
||||
defaultMessage:
|
||||
'Use our classic visualize library to create charts based on aggregations.',
|
||||
}
|
||||
)}
|
||||
icon={<EuiIcon type="heatmap" size="xl" color="success" />}
|
||||
>
|
||||
<EuiLink
|
||||
data-test-subj="visGroupAggBasedExploreLink"
|
||||
onClick={() => props.toggleGroups(false)}
|
||||
>
|
||||
<EuiText size="s">
|
||||
{i18n.translate('visualizations.newVisWizard.exploreOptionLinkText', {
|
||||
defaultMessage: 'Explore options',
|
||||
})}{' '}
|
||||
<EuiIcon type="sortRight" />
|
||||
</EuiText>
|
||||
</EuiLink>
|
||||
</EuiCard>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
{props.visTypesRegistry.getByGroup(VisGroups.TOOLS).length > 0 && (
|
||||
<EuiFlexItem className="visNewVisDialog__toolsCard">
|
||||
<EuiSpacer size="m" />
|
||||
<EuiTitle size="xs">
|
||||
<span data-test-subj="visGroup-tools">
|
||||
{i18n.translate('visualizations.newVisWizard.toolsGroupTitle', {
|
||||
defaultMessage: 'Tools',
|
||||
})}
|
||||
</span>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="s" />
|
||||
<div className="visNewVisDialog__toolsCardGroupContainer">
|
||||
{props.visTypesRegistry.getByGroup(VisGroups.TOOLS).map((visType) => (
|
||||
<ToolsGroup
|
||||
visType={visType}
|
||||
key={visType.name}
|
||||
onVisTypeSelected={props.onVisTypeSelected}
|
||||
showExperimental={props.showExperimental}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGrid>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiDescriptionList
|
||||
className="visNewVisDialogGroupSelection__footerDescriptionList"
|
||||
type="responsiveColumn"
|
||||
>
|
||||
<EuiDescriptionListTitle className="visNewVisDialogGroupSelection__footerDescriptionListTitle">
|
||||
<FormattedMessage
|
||||
id="visualizations.newVisWizard.learnMoreText"
|
||||
defaultMessage="Want to learn more?"
|
||||
/>
|
||||
</EuiDescriptionListTitle>
|
||||
<EuiDescriptionListDescription>
|
||||
<EuiLink href={visualizeGuideLink} target="_blank" external>
|
||||
<FormattedMessage
|
||||
id="visualizations.newVisWizard.readDocumentationLink"
|
||||
defaultMessage="Read documentation"
|
||||
{tab === 'recommended' ? (
|
||||
<EuiFlexGrid columns={2} data-test-subj="visNewDialogGroups">
|
||||
{promotedVisGroups.map((visType) => (
|
||||
<VisGroup
|
||||
visType={visType}
|
||||
key={visType.name}
|
||||
onVisTypeSelected={props.onVisTypeSelected}
|
||||
shouldStretch={visType.name === 'lens'}
|
||||
/>
|
||||
</EuiLink>
|
||||
</EuiDescriptionListDescription>
|
||||
</EuiDescriptionList>
|
||||
))}
|
||||
</EuiFlexGrid>
|
||||
) : (
|
||||
<EuiFlexGrid columns={2} data-test-subj="visNewDialogGroups">
|
||||
{tsvbProps ? <VisGroup {...tsvbProps} /> : null}
|
||||
{
|
||||
<VisGroup
|
||||
visType={{
|
||||
stage: 'production',
|
||||
name: 'aggbased',
|
||||
description: i18n.translate(
|
||||
'visualizations.newVisWizard.aggBasedGroupDescription',
|
||||
{
|
||||
defaultMessage: 'Craft charts using basic aggregations.',
|
||||
}
|
||||
),
|
||||
icon: 'indexPatternApp',
|
||||
title: i18n.translate('visualizations.newVisWizard.aggBasedGroupTitle', {
|
||||
defaultMessage: 'Aggregation-based',
|
||||
}),
|
||||
}}
|
||||
onVisTypeSelected={() => {
|
||||
props.showMainDialog(false);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
</EuiFlexGrid>
|
||||
)}
|
||||
|
||||
<EuiSpacer size="l" />
|
||||
</div>
|
||||
|
||||
<ModalFooter visualizeGuideLink={visualizeGuideLink} />
|
||||
</EuiModalBody>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const VisGroup = ({ visType, onVisTypeSelected }: VisCardProps) => {
|
||||
const ModalFooter = ({ visualizeGuideLink }: { visualizeGuideLink: string }) => {
|
||||
return (
|
||||
<div className="visNewVisDialogGroupSelection__footer">
|
||||
<EuiSpacer size="l" />
|
||||
<EuiDescriptionList
|
||||
className="visNewVisDialogGroupSelection__footerDescriptionList"
|
||||
type="responsiveColumn"
|
||||
compressed
|
||||
>
|
||||
<EuiDescriptionListTitle className="visNewVisDialogGroupSelection__footerDescriptionListTitle">
|
||||
<FormattedMessage
|
||||
id="visualizations.newVisWizard.learnMoreText"
|
||||
defaultMessage="Want to learn more?"
|
||||
/>
|
||||
</EuiDescriptionListTitle>
|
||||
<EuiDescriptionListDescription>
|
||||
<EuiLink href={visualizeGuideLink} target="_blank" external>
|
||||
<FormattedMessage
|
||||
id="visualizations.newVisWizard.viewDocumentationLink"
|
||||
defaultMessage="View documentation"
|
||||
/>
|
||||
</EuiLink>
|
||||
</EuiDescriptionListDescription>
|
||||
</EuiDescriptionList>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const VisGroup = ({ visType, onVisTypeSelected, shouldStretch = false }: VisCardProps) => {
|
||||
const onClick = useCallback(() => {
|
||||
onVisTypeSelected(visType);
|
||||
}, [onVisTypeSelected, visType]);
|
||||
return (
|
||||
<EuiFlexItem className="visNewVisDialog__groupsCardWrapper">
|
||||
<EuiFlexItem
|
||||
className="visNewVisDialog__groupsCardWrapper"
|
||||
css={shouldStretch ? { gridColumn: '1 / -1' } : null}
|
||||
>
|
||||
<EuiCard
|
||||
titleSize="xs"
|
||||
hasBorder={true}
|
||||
|
@ -213,68 +260,10 @@ const VisGroup = ({ visType, onVisTypeSelected }: VisCardProps) => {
|
|||
</>
|
||||
}
|
||||
layout="horizontal"
|
||||
icon={<EuiIcon type={visType.icon || 'empty'} size="xl" color="success" />}
|
||||
icon={<EuiIcon type={visType.icon || 'empty'} size="xl" />}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
};
|
||||
|
||||
const ToolsGroup = ({ visType, onVisTypeSelected, showExperimental }: VisCardProps) => {
|
||||
const onClick = useCallback(() => {
|
||||
onVisTypeSelected(visType);
|
||||
}, [onVisTypeSelected, visType]);
|
||||
// hide both the hidden visualizations and, if lab mode is not enabled, the experimental visualizations
|
||||
// TODO: Remove the showExperimental logic as part of https://github.com/elastic/kibana/issues/152833
|
||||
if (visType.disableCreate || (!showExperimental && visType.stage === 'experimental')) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<EuiFlexGroup alignItems="center" gutterSize="m" responsive={false}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIcon type={visType.icon || 'empty'} size="l" />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup gutterSize="s" alignItems="center" responsive={false}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiLink
|
||||
data-test-subj={`visType-${visType.name}`}
|
||||
data-vis-stage={visType.stage}
|
||||
onClick={onClick}
|
||||
>
|
||||
{'titleInWizard' in visType && visType.titleInWizard
|
||||
? visType.titleInWizard
|
||||
: visType.title}
|
||||
</EuiLink>
|
||||
</EuiFlexItem>
|
||||
{visType.stage === 'experimental' && !visType.isDeprecated ? (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiBetaBadge
|
||||
iconType="beaker"
|
||||
tooltipContent={i18n.translate('visualizations.newVisWizard.experimentalTooltip', {
|
||||
defaultMessage:
|
||||
'This functionality is in technical preview and may be changed or removed completely in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.',
|
||||
})}
|
||||
label={i18n.translate('visualizations.newVisWizard.experimentalTitle', {
|
||||
defaultMessage: 'Technical preview',
|
||||
})}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
) : (
|
||||
visType.isDeprecated && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiBadge color="warning">
|
||||
<FormattedMessage id="visualizations.deprecatedTag" defaultMessage="Deprecated" />
|
||||
</EuiBadge>
|
||||
</EuiFlexItem>
|
||||
)
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
<EuiText color="subdued" size="s">
|
||||
{visType.description}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
||||
|
||||
export { GroupSelection };
|
||||
|
|
|
@ -8,13 +8,15 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { mountWithIntl } from '@kbn/test-jest-helpers';
|
||||
import { TypesStart, VisGroups, BaseVisType } from '../vis_types';
|
||||
import NewVisModal from './new_vis_modal';
|
||||
import NewVisModal, { TypeSelectionProps } from './new_vis_modal';
|
||||
import { ApplicationStart, DocLinksStart } from '@kbn/core/public';
|
||||
import { embeddablePluginMock } from '@kbn/embeddable-plugin/public/mocks';
|
||||
import { contentManagementMock } from '@kbn/content-management-plugin/public/mocks';
|
||||
import { VisParams } from '../../common';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { I18nProvider } from '@kbn/i18n-react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
describe('NewVisModal', () => {
|
||||
const defaultVisTypeParams = {
|
||||
|
@ -96,116 +98,67 @@ describe('NewVisModal', () => {
|
|||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should show the aggbased group but not the visualization assigned to this group', () => {
|
||||
const wrapper = mountWithIntl(
|
||||
<NewVisModal
|
||||
isOpen={true}
|
||||
onClose={() => null}
|
||||
visTypesRegistry={visTypes}
|
||||
addBasePath={addBasePath}
|
||||
uiSettings={uiSettings}
|
||||
application={{} as ApplicationStart}
|
||||
docLinks={docLinks as DocLinksStart}
|
||||
contentClient={contentManagement.client}
|
||||
/>
|
||||
const renderNewVisModal = (propsOverrides?: Partial<TypeSelectionProps>) => {
|
||||
return render(
|
||||
<I18nProvider>
|
||||
<NewVisModal
|
||||
isOpen={true}
|
||||
onClose={() => null}
|
||||
visTypesRegistry={visTypes}
|
||||
addBasePath={addBasePath}
|
||||
uiSettings={uiSettings}
|
||||
application={{} as ApplicationStart}
|
||||
docLinks={docLinks as DocLinksStart}
|
||||
contentClient={contentManagement.client}
|
||||
{...propsOverrides}
|
||||
/>
|
||||
</I18nProvider>
|
||||
);
|
||||
expect(wrapper.find('[data-test-subj="visGroup-aggbased"]').exists()).toBe(true);
|
||||
expect(wrapper.find('[data-test-subj="visType-visWithSearch"]').exists()).toBe(false);
|
||||
});
|
||||
};
|
||||
|
||||
it('should show the tools group', () => {
|
||||
const wrapper = mountWithIntl(
|
||||
<NewVisModal
|
||||
isOpen={true}
|
||||
onClose={() => null}
|
||||
visTypesRegistry={visTypes}
|
||||
addBasePath={addBasePath}
|
||||
uiSettings={uiSettings}
|
||||
application={{} as ApplicationStart}
|
||||
docLinks={docLinks as DocLinksStart}
|
||||
contentClient={contentManagement.client}
|
||||
/>
|
||||
);
|
||||
expect(wrapper.find('[data-test-subj="visGroup-tools"]').exists()).toBe(true);
|
||||
it('should show the aggbased group but not the visualization assigned to this group', async () => {
|
||||
renderNewVisModal();
|
||||
expect(screen.queryByText('Aggregation-based')).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('Vis with search')).not.toBeInTheDocument();
|
||||
await userEvent.click(screen.getByRole('tab', { name: /Legacy/i }));
|
||||
expect(screen.queryByText('Aggregation-based')).toBeInTheDocument();
|
||||
expect(screen.queryByText('Vis with search')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should display the visualizations of the other group', () => {
|
||||
const wrapper = mountWithIntl(
|
||||
<NewVisModal
|
||||
isOpen={true}
|
||||
onClose={() => null}
|
||||
visTypesRegistry={visTypes}
|
||||
addBasePath={addBasePath}
|
||||
uiSettings={uiSettings}
|
||||
application={{} as ApplicationStart}
|
||||
docLinks={docLinks as DocLinksStart}
|
||||
contentClient={contentManagement.client}
|
||||
/>
|
||||
);
|
||||
expect(wrapper.find('[data-test-subj="visType-vis2"]').exists()).toBe(true);
|
||||
renderNewVisModal();
|
||||
expect(screen.queryByText('Vis Type 2')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe('open editor', () => {
|
||||
it('should open the editor for visualizations without search', () => {
|
||||
const wrapper = mountWithIntl(
|
||||
<NewVisModal
|
||||
isOpen={true}
|
||||
onClose={() => null}
|
||||
visTypesRegistry={visTypes}
|
||||
addBasePath={addBasePath}
|
||||
uiSettings={uiSettings}
|
||||
application={{} as ApplicationStart}
|
||||
docLinks={docLinks as DocLinksStart}
|
||||
contentClient={contentManagement.client}
|
||||
/>
|
||||
);
|
||||
const visCard = wrapper.find('[data-test-subj="visType-vis"]').last();
|
||||
visCard.simulate('click');
|
||||
it('should open the editor for visualizations without search', async () => {
|
||||
renderNewVisModal();
|
||||
await userEvent.click(screen.getByText('Vis Type 1'));
|
||||
expect(window.location.assign).toBeCalledWith('testbasepath/app/visualize#/create?type=vis');
|
||||
});
|
||||
|
||||
it('passes through editor params to the editor URL', () => {
|
||||
const wrapper = mountWithIntl(
|
||||
<NewVisModal
|
||||
isOpen={true}
|
||||
onClose={() => null}
|
||||
visTypesRegistry={visTypes}
|
||||
editorParams={['foo=true', 'bar=42']}
|
||||
addBasePath={addBasePath}
|
||||
uiSettings={uiSettings}
|
||||
application={{} as ApplicationStart}
|
||||
docLinks={docLinks as DocLinksStart}
|
||||
contentClient={contentManagement.client}
|
||||
/>
|
||||
);
|
||||
const visCard = wrapper.find('[data-test-subj="visType-vis"]').last();
|
||||
visCard.simulate('click');
|
||||
it('passes through editor params to the editor URL', async () => {
|
||||
renderNewVisModal({
|
||||
editorParams: ['foo=true', 'bar=42'],
|
||||
});
|
||||
await userEvent.click(screen.getByText('Vis Type 1'));
|
||||
expect(window.location.assign).toBeCalledWith(
|
||||
'testbasepath/app/visualize#/create?type=vis&foo=true&bar=42'
|
||||
);
|
||||
});
|
||||
|
||||
it('closes and redirects properly if visualization with alias.path and originatingApp in props', () => {
|
||||
it('closes and redirects properly if visualization with alias.path and originatingApp in props', async () => {
|
||||
const onClose = jest.fn();
|
||||
const navigateToApp = jest.fn();
|
||||
const stateTransfer = embeddablePluginMock.createStartContract().getStateTransfer();
|
||||
const wrapper = mountWithIntl(
|
||||
<NewVisModal
|
||||
isOpen={true}
|
||||
onClose={onClose}
|
||||
visTypesRegistry={visTypes}
|
||||
editorParams={['foo=true', 'bar=42']}
|
||||
originatingApp={'coolJestTestApp'}
|
||||
addBasePath={addBasePath}
|
||||
uiSettings={uiSettings}
|
||||
application={{ navigateToApp } as unknown as ApplicationStart}
|
||||
docLinks={docLinks as DocLinksStart}
|
||||
stateTransfer={stateTransfer}
|
||||
contentClient={contentManagement.client}
|
||||
/>
|
||||
);
|
||||
const visCard = wrapper.find('[data-test-subj="visType-visWithAliasUrl"]').last();
|
||||
visCard.simulate('click');
|
||||
renderNewVisModal({
|
||||
editorParams: ['foo=true', 'bar=42'],
|
||||
onClose,
|
||||
application: { navigateToApp } as unknown as ApplicationStart,
|
||||
originatingApp: 'coolJestTestApp',
|
||||
stateTransfer,
|
||||
});
|
||||
await userEvent.click(screen.getByText('Vis with alias Url'));
|
||||
expect(stateTransfer.navigateToEditor).toBeCalledWith('otherApp', {
|
||||
path: '#/aliasUrl',
|
||||
state: { originatingApp: 'coolJestTestApp' },
|
||||
|
@ -213,48 +166,28 @@ describe('NewVisModal', () => {
|
|||
expect(onClose).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('closes and redirects properly if visualization with aliasApp and without originatingApp in props', () => {
|
||||
it('closes and redirects properly if visualization with aliasApp and without originatingApp in props', async () => {
|
||||
const onClose = jest.fn();
|
||||
const navigateToApp = jest.fn();
|
||||
const wrapper = mountWithIntl(
|
||||
<NewVisModal
|
||||
isOpen={true}
|
||||
onClose={onClose}
|
||||
visTypesRegistry={visTypes}
|
||||
editorParams={['foo=true', 'bar=42']}
|
||||
addBasePath={addBasePath}
|
||||
uiSettings={uiSettings}
|
||||
application={{ navigateToApp } as unknown as ApplicationStart}
|
||||
docLinks={docLinks as DocLinksStart}
|
||||
contentClient={contentManagement.client}
|
||||
/>
|
||||
);
|
||||
const visCard = wrapper.find('[data-test-subj="visType-visWithAliasUrl"]').last();
|
||||
visCard.simulate('click');
|
||||
|
||||
renderNewVisModal({
|
||||
editorParams: ['foo=true', 'bar=42'],
|
||||
onClose,
|
||||
application: { navigateToApp } as unknown as ApplicationStart,
|
||||
});
|
||||
await userEvent.click(screen.getByText('Vis with alias Url'));
|
||||
expect(navigateToApp).toBeCalledWith('otherApp', { path: '#/aliasUrl' });
|
||||
expect(onClose).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('aggBased visualizations', () => {
|
||||
it('should render as expected', () => {
|
||||
const wrapper = mountWithIntl(
|
||||
<NewVisModal
|
||||
isOpen={true}
|
||||
onClose={() => null}
|
||||
visTypesRegistry={visTypes}
|
||||
addBasePath={addBasePath}
|
||||
uiSettings={uiSettings}
|
||||
application={{} as ApplicationStart}
|
||||
docLinks={docLinks as DocLinksStart}
|
||||
contentClient={contentManagement.client}
|
||||
/>
|
||||
);
|
||||
const aggBasedGroupCard = wrapper
|
||||
.find('[data-test-subj="visGroupAggBasedExploreLink"]')
|
||||
.last();
|
||||
aggBasedGroupCard.simulate('click');
|
||||
expect(wrapper.find('[data-test-subj="visType-visWithSearch"]').exists()).toBe(true);
|
||||
it('should render as expected', async () => {
|
||||
renderNewVisModal();
|
||||
await userEvent.click(screen.getByRole('tab', { name: /Legacy/i }));
|
||||
expect(screen.queryByText('Aggregation-based')).toBeInTheDocument();
|
||||
await userEvent.click(screen.getByText('Aggregation-based'));
|
||||
expect(screen.queryByText('Vis with search')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -22,7 +22,7 @@ import { AggBasedSelection } from './agg_based_selection';
|
|||
import type { TypesStart, BaseVisType, VisTypeAlias } from '../vis_types';
|
||||
import './dialog.scss';
|
||||
|
||||
interface TypeSelectionProps {
|
||||
export interface TypeSelectionProps {
|
||||
contentClient: ContentClient;
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
|
@ -41,8 +41,9 @@ interface TypeSelectionProps {
|
|||
|
||||
interface TypeSelectionState {
|
||||
showSearchVisModal: boolean;
|
||||
showGroups: boolean;
|
||||
isMainDialogShown: boolean;
|
||||
visType?: BaseVisType;
|
||||
tab: 'recommended' | 'legacy';
|
||||
}
|
||||
|
||||
// TODO: redirect logic is specific to visualise & dashboard
|
||||
|
@ -64,11 +65,16 @@ class NewVisModal extends React.Component<TypeSelectionProps, TypeSelectionState
|
|||
|
||||
this.state = {
|
||||
showSearchVisModal: Boolean(this.props.selectedVisType),
|
||||
showGroups: !this.props.showAggsSelection,
|
||||
isMainDialogShown: !this.props.showAggsSelection,
|
||||
visType: this.props.selectedVisType,
|
||||
tab: 'recommended',
|
||||
};
|
||||
}
|
||||
|
||||
public setTab = (tab: 'recommended' | 'legacy') => {
|
||||
this.setState({ tab });
|
||||
};
|
||||
|
||||
public render() {
|
||||
if (!this.props.isOpen) {
|
||||
return null;
|
||||
|
@ -82,7 +88,7 @@ class NewVisModal extends React.Component<TypeSelectionProps, TypeSelectionState
|
|||
}
|
||||
);
|
||||
|
||||
const WizardComponent = this.state.showGroups ? GroupSelection : AggBasedSelection;
|
||||
const WizardComponent = this.state.isMainDialogShown ? GroupSelection : AggBasedSelection;
|
||||
|
||||
const selectionModal =
|
||||
this.state.showSearchVisModal && this.state.visType ? (
|
||||
|
@ -98,15 +104,21 @@ class NewVisModal extends React.Component<TypeSelectionProps, TypeSelectionState
|
|||
) : (
|
||||
<EuiModal
|
||||
onClose={this.onCloseModal}
|
||||
className={this.state.showGroups ? 'visNewVisDialog' : 'visNewVisDialog--aggbased'}
|
||||
className={this.state.isMainDialogShown ? 'visNewVisDialog' : 'visNewVisDialog--aggbased'}
|
||||
aria-label={visNewVisDialogAriaLabel}
|
||||
>
|
||||
<WizardComponent
|
||||
showExperimental={true}
|
||||
onVisTypeSelected={this.onVisTypeSelected}
|
||||
visTypesRegistry={this.props.visTypesRegistry}
|
||||
docLinks={this.props.docLinks}
|
||||
toggleGroups={(flag: boolean) => this.setState({ showGroups: flag })}
|
||||
setTab={this.setTab}
|
||||
tab={this.state.tab}
|
||||
showMainDialog={(shouldMainBeShown: boolean) => {
|
||||
this.setState({ isMainDialogShown: shouldMainBeShown });
|
||||
if (shouldMainBeShown) {
|
||||
this.setTab('legacy');
|
||||
}
|
||||
}}
|
||||
openedAsRoot={this.props.showAggsSelection && !this.props.selectedVisType}
|
||||
/>
|
||||
</EuiModal>
|
||||
|
|
|
@ -191,7 +191,7 @@ export default function ({ getService, getPageObjects }) {
|
|||
it('should lose its connection to the dashboard when creating new visualization', async () => {
|
||||
await visualize.gotoVisualizationLandingPage();
|
||||
await visualize.clickNewVisualization();
|
||||
await visualize.clickMarkdownWidget();
|
||||
await visualize.clickVisualBuilder();
|
||||
await visualize.notLinkedToOriginatingApp();
|
||||
|
||||
// return to origin should not be present in save modal
|
||||
|
|
|
@ -86,13 +86,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
});
|
||||
|
||||
it('resolves markdown link', async () => {
|
||||
await visualize.navigateToNewVisualization();
|
||||
await visualize.clickMarkdownWidget();
|
||||
await dashboard.gotoDashboardLandingPage();
|
||||
await dashboard.clickNewDashboard();
|
||||
await dashboardAddPanel.clickMarkdownQuickButton();
|
||||
await visEditor.setMarkdownTxt(`[abc](#/dashboard/${testDashboardId})`);
|
||||
await visEditor.clickGo();
|
||||
|
||||
await visualize.saveVisualizationExpectSuccess('legacy url markdown');
|
||||
|
||||
await visualize.saveVisualization('legacy url markdown', { redirectToOrigin: true });
|
||||
await (await find.byLinkText('abc')).click();
|
||||
|
||||
await header.waitUntilLoadingHasFinished();
|
||||
|
|
|
@ -12,25 +12,26 @@ import expect from '@kbn/expect';
|
|||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
|
||||
export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||
const { visualize, visEditor, visChart, header } = getPageObjects([
|
||||
'visualize',
|
||||
const { visEditor, visChart, header, dashboard } = getPageObjects([
|
||||
'dashboard',
|
||||
'visEditor',
|
||||
'visChart',
|
||||
'header',
|
||||
]);
|
||||
const find = getService('find');
|
||||
const inspector = getService('inspector');
|
||||
const dashboardAddPanel = getService('dashboardAddPanel');
|
||||
const markdown = `
|
||||
# Heading 1
|
||||
|
||||
<h3>Inline HTML that should not be rendered as html</h3>
|
||||
`;
|
||||
|
||||
describe('markdown app in visualize app', () => {
|
||||
describe('markdown app', () => {
|
||||
before(async function () {
|
||||
await visualize.initTests();
|
||||
await visualize.navigateToNewVisualization();
|
||||
await visualize.clickMarkdownWidget();
|
||||
await dashboard.initTests();
|
||||
await dashboard.clickNewDashboard();
|
||||
await dashboardAddPanel.clickMarkdownQuickButton();
|
||||
await visEditor.setMarkdownTxt(markdown);
|
||||
await visEditor.clickGo();
|
||||
});
|
||||
|
|
|
@ -22,14 +22,18 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
await visualize.navigateToNewVisualization();
|
||||
});
|
||||
|
||||
it('should show the promoted vis types for the first step', async function () {
|
||||
const expectedChartTypes = ['Custom visualization', 'Lens', 'Maps', 'TSVB'];
|
||||
it('should show the expected visualizations types for both recommended and legacy tabs', async function () {
|
||||
const expectedRecommendedChartTypes = ['Custom visualization', 'Lens', 'Maps'];
|
||||
const expectedLegacyChartTypes = ['Aggregation-based', 'TSVB'];
|
||||
|
||||
// find all the chart types and make sure there all there
|
||||
const chartTypes = (await visualize.getPromotedVisTypes()).sort();
|
||||
const chartTypes = await visualize.getVisibleVisTypes();
|
||||
log.debug('returned chart types = ' + chartTypes);
|
||||
log.debug('expected chart types = ' + expectedChartTypes);
|
||||
expect(chartTypes).to.eql(expectedChartTypes);
|
||||
log.debug('expected chart types = ' + expectedRecommendedChartTypes);
|
||||
expect(chartTypes).to.eql(expectedRecommendedChartTypes);
|
||||
await visualize.clickLegacyTab();
|
||||
const legacyChartTypes = await visualize.getVisibleVisTypes();
|
||||
expect(legacyChartTypes).to.eql(expectedLegacyChartTypes);
|
||||
});
|
||||
|
||||
it('should show the correct agg based chart types', async function () {
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
|
||||
export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||
const { visualize, visEditor } = getPageObjects(['visualize', 'visEditor']);
|
||||
const { visualize, visualBuilder } = getPageObjects(['visualize', 'visualBuilder']);
|
||||
const listingTable = getService('listingTable');
|
||||
|
||||
describe('visualize listing page', function describeIndexTests() {
|
||||
|
@ -24,18 +24,18 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
});
|
||||
|
||||
it('create new viz', async function () {
|
||||
// type markdown is used for simplicity
|
||||
await visualize.createSimpleMarkdownViz(vizName);
|
||||
// type tsvb is used for simplicity
|
||||
await visualize.createSimpleTSVBViz(vizName);
|
||||
await visualize.gotoVisualizationLandingPage();
|
||||
await listingTable.expectItemsCount('visualize', 1);
|
||||
});
|
||||
|
||||
it('delete all viz', async function () {
|
||||
await visualize.createSimpleMarkdownViz(vizName + '1');
|
||||
await visualize.createSimpleTSVBViz(vizName + '1');
|
||||
await visualize.gotoVisualizationLandingPage();
|
||||
await listingTable.expectItemsCount('visualize', 2);
|
||||
|
||||
await visualize.createSimpleMarkdownViz(vizName + '2');
|
||||
await visualize.createSimpleTSVBViz(vizName + '2');
|
||||
await visualize.gotoVisualizationLandingPage();
|
||||
await listingTable.expectItemsCount('visualize', 3);
|
||||
|
||||
|
@ -48,9 +48,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
|||
before(async function () {
|
||||
// create one new viz
|
||||
await visualize.navigateToNewVisualization();
|
||||
await visualize.clickMarkdownWidget();
|
||||
await visEditor.setMarkdownTxt('HELLO');
|
||||
await visEditor.clickGo();
|
||||
await visualize.clickVisualBuilder();
|
||||
await visualBuilder.checkVisualBuilderIsPresent();
|
||||
await visualize.saveVisualization('Hello World');
|
||||
await visualize.gotoVisualizationLandingPage();
|
||||
});
|
||||
|
|
|
@ -41,7 +41,6 @@ export class VisualizePageObject extends FtrService {
|
|||
private readonly elasticChart = this.ctx.getService('elasticChart');
|
||||
private readonly common = this.ctx.getPageObject('common');
|
||||
private readonly header = this.ctx.getPageObject('header');
|
||||
private readonly visEditor = this.ctx.getPageObject('visEditor');
|
||||
private readonly visChart = this.ctx.getPageObject('visChart');
|
||||
private readonly toasts = this.ctx.getService('toasts');
|
||||
|
||||
|
@ -106,7 +105,8 @@ export class VisualizePageObject extends FtrService {
|
|||
}
|
||||
|
||||
public async clickAggBasedVisualizations() {
|
||||
await this.testSubjects.click('visGroupAggBasedExploreLink');
|
||||
await this.clickLegacyTab();
|
||||
await this.testSubjects.click('visType-aggbased');
|
||||
}
|
||||
|
||||
public async goBackToGroups() {
|
||||
|
@ -125,7 +125,7 @@ export class VisualizePageObject extends FtrService {
|
|||
.map((chart) => $(chart).findTestSubject('visTypeTitle').text().trim());
|
||||
}
|
||||
|
||||
public async getPromotedVisTypes() {
|
||||
public async getVisibleVisTypes() {
|
||||
const chartTypeField = await this.testSubjects.find('visNewDialogGroups');
|
||||
const $ = await chartTypeField.parseDomContent();
|
||||
const promotedVisTypes: string[] = [];
|
||||
|
@ -137,7 +137,7 @@ export class VisualizePageObject extends FtrService {
|
|||
promotedVisTypes.push(title);
|
||||
}
|
||||
});
|
||||
return promotedVisTypes;
|
||||
return promotedVisTypes.sort();
|
||||
}
|
||||
|
||||
public async waitForVisualizationSelectPage() {
|
||||
|
@ -221,8 +221,8 @@ export class VisualizePageObject extends FtrService {
|
|||
await this.clickVisType('line');
|
||||
}
|
||||
|
||||
public async clickMarkdownWidget() {
|
||||
await this.clickVisType('markdown');
|
||||
public async clickLegacyTab() {
|
||||
await this.testSubjects.click('groupModalLegacyTab');
|
||||
}
|
||||
|
||||
public async clickMetric() {
|
||||
|
@ -254,6 +254,7 @@ export class VisualizePageObject extends FtrService {
|
|||
}
|
||||
|
||||
public async clickVisualBuilder() {
|
||||
await this.clickLegacyTab();
|
||||
await this.clickVisType('metrics');
|
||||
}
|
||||
|
||||
|
@ -281,12 +282,10 @@ export class VisualizePageObject extends FtrService {
|
|||
return await this.hasVisType('maps');
|
||||
}
|
||||
|
||||
public async createSimpleMarkdownViz(vizName: string) {
|
||||
public async createSimpleTSVBViz(vizName: string) {
|
||||
await this.gotoVisualizationLandingPage();
|
||||
await this.navigateToNewVisualization();
|
||||
await this.clickMarkdownWidget();
|
||||
await this.visEditor.setMarkdownTxt(vizName);
|
||||
await this.visEditor.clickGo();
|
||||
await this.clickVisualBuilder();
|
||||
await this.saveVisualization(vizName);
|
||||
}
|
||||
|
||||
|
|
|
@ -7,27 +7,20 @@
|
|||
|
||||
import React from 'react';
|
||||
|
||||
import { useActions, useValues } from 'kea';
|
||||
import { useValues } from 'kea';
|
||||
|
||||
import { EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { GenerateApiKeyModal } from './components/generate_api_key_modal/modal';
|
||||
|
||||
import { APIGettingStarted } from './components/getting_started/getting_started';
|
||||
import { IndexViewLogic } from './index_view_logic';
|
||||
import { OverviewLogic } from './overview.logic';
|
||||
|
||||
export const GenerateApiKeyPanel: React.FC = () => {
|
||||
const { isGenerateModalOpen } = useValues(OverviewLogic);
|
||||
const { indexName, isHiddenIndex } = useValues(IndexViewLogic);
|
||||
const { closeGenerateModal } = useActions(OverviewLogic);
|
||||
const { isHiddenIndex } = useValues(IndexViewLogic);
|
||||
|
||||
return (
|
||||
<>
|
||||
{isGenerateModalOpen && (
|
||||
<GenerateApiKeyModal indexName={indexName} onClose={closeGenerateModal} />
|
||||
)}
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<EuiPanel hasBorder paddingSize="xl">
|
||||
|
|
|
@ -22,10 +22,7 @@ export const getLensAliasConfig = (): VisTypeAlias => ({
|
|||
}),
|
||||
description: i18n.translate('xpack.lens.visTypeAlias.description', {
|
||||
defaultMessage:
|
||||
'Create visualizations with our drag and drop editor. Switch between visualization types at any time.',
|
||||
}),
|
||||
note: i18n.translate('xpack.lens.visTypeAlias.note', {
|
||||
defaultMessage: 'Recommended for most users.',
|
||||
'Create visualizations using an intuitive drag-and-drop interface. Smart suggestions help you follow best practices and find the chart types that best match your data.',
|
||||
}),
|
||||
order: 60,
|
||||
icon: 'lensApp',
|
||||
|
|
|
@ -11,6 +11,7 @@ import { act } from 'react-dom/test-utils';
|
|||
import { setupEnvironment, RemoteClustersActions } from '../helpers';
|
||||
import { setup } from './remote_clusters_add.helpers';
|
||||
import { NON_ALPHA_NUMERIC_CHARS, ACCENTED_CHARS } from './special_characters';
|
||||
import { MAX_NODE_CONNECTIONS } from '../../../common/constants';
|
||||
|
||||
const notInArray = (array: string[]) => (value: string) => array.indexOf(value) < 0;
|
||||
|
||||
|
@ -276,6 +277,17 @@ describe('Create Remote cluster', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('node connections', () => {
|
||||
test('should require a valid number of node connections', async () => {
|
||||
await actions.saveButton.click();
|
||||
|
||||
actions.nodeConnectionsInput.setValue(String(MAX_NODE_CONNECTIONS + 1));
|
||||
expect(actions.getErrorMessages()).toContain(
|
||||
`This number must be equal or less than ${MAX_NODE_CONNECTIONS}.`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('server name is optional (proxy connection)', () => {
|
||||
actions.connectionModeSwitch.toggle();
|
||||
actions.saveButton.click();
|
||||
|
|
|
@ -42,6 +42,9 @@ export interface RemoteClustersActions {
|
|||
setValue: (seed: string) => void;
|
||||
getValue: () => string;
|
||||
};
|
||||
nodeConnectionsInput: {
|
||||
setValue: (connections: string) => void;
|
||||
};
|
||||
proxyAddressInput: {
|
||||
setValue: (proxyAddress: string) => void;
|
||||
exists: () => boolean;
|
||||
|
@ -154,6 +157,16 @@ export const createRemoteClustersActions = (testBed: TestBed): RemoteClustersAct
|
|||
};
|
||||
};
|
||||
|
||||
const createNodeConnectionsInputActions = () => {
|
||||
const nodeConnectionsInputSelector = 'remoteClusterFormNodeConnectionsInput';
|
||||
return {
|
||||
nodeConnectionsInput: {
|
||||
setValue: (connections: string) =>
|
||||
form.setInputValue(nodeConnectionsInputSelector, connections),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const createProxyAddressActions = () => {
|
||||
const proxyAddressSelector = 'remoteClusterFormProxyAddressInput';
|
||||
return {
|
||||
|
@ -266,6 +279,7 @@ export const createRemoteClustersActions = (testBed: TestBed): RemoteClustersAct
|
|||
...createConnectionModeActions(),
|
||||
...createCloudAdvancedOptionsSwitchActions(),
|
||||
...createSeedsInputActions(),
|
||||
...createNodeConnectionsInputActions(),
|
||||
...createCloudRemoteAddressInputActions(),
|
||||
...createProxyAddressActions(),
|
||||
...createServerNameActions(),
|
||||
|
|
|
@ -42,3 +42,6 @@ export const getSecurityModel = (type: string) => {
|
|||
|
||||
return type;
|
||||
};
|
||||
|
||||
// Hardcoded limit of maximum node connections allowed
|
||||
export const MAX_NODE_CONNECTIONS = 2 ** 31 - 1; // 2147483647
|
||||
|
|
|
@ -28,14 +28,16 @@ export const SniffConnection: FunctionComponent<Props> = ({
|
|||
}) => {
|
||||
const [localSeedErrors, setLocalSeedErrors] = useState<JSX.Element[]>([]);
|
||||
const { seeds = [], nodeConnections } = fields;
|
||||
const { seeds: seedsError } = fieldsErrors;
|
||||
const { seeds: seedsError, nodeConnections: nodeError } = fieldsErrors;
|
||||
// Show errors if there is a general form error or local errors.
|
||||
const areFormErrorsVisible = Boolean(areErrorsVisible && seedsError);
|
||||
const showErrors = areFormErrorsVisible || localSeedErrors.length !== 0;
|
||||
const errors =
|
||||
const showLocalSeedErrors = areFormErrorsVisible || localSeedErrors.length !== 0;
|
||||
const errorsInLocalSeeds =
|
||||
areFormErrorsVisible && seedsError ? localSeedErrors.concat(seedsError) : localSeedErrors;
|
||||
const formattedSeeds: EuiComboBoxOptionOption[] = seeds.map((seed: string) => ({ label: seed }));
|
||||
|
||||
const showNodeConnectionErrors = Boolean(nodeError);
|
||||
|
||||
const onCreateSeed = (newSeed?: string) => {
|
||||
// If the user just hit enter without typing anything, treat it as a no-op.
|
||||
if (!newSeed) {
|
||||
|
@ -107,8 +109,8 @@ export const SniffConnection: FunctionComponent<Props> = ({
|
|||
}}
|
||||
/>
|
||||
}
|
||||
isInvalid={showErrors}
|
||||
error={errors}
|
||||
isInvalid={showLocalSeedErrors}
|
||||
error={errorsInLocalSeeds}
|
||||
fullWidth
|
||||
>
|
||||
<EuiComboBox
|
||||
|
@ -125,7 +127,7 @@ export const SniffConnection: FunctionComponent<Props> = ({
|
|||
onFieldsChange({ seeds: options.map(({ label }) => label) })
|
||||
}
|
||||
onSearchChange={onSeedsInputChange}
|
||||
isInvalid={showErrors}
|
||||
isInvalid={showLocalSeedErrors}
|
||||
fullWidth
|
||||
data-test-subj="remoteClusterFormSeedsInput"
|
||||
/>
|
||||
|
@ -146,11 +148,15 @@ export const SniffConnection: FunctionComponent<Props> = ({
|
|||
/>
|
||||
}
|
||||
fullWidth
|
||||
isInvalid={showNodeConnectionErrors}
|
||||
error={nodeError}
|
||||
>
|
||||
<EuiFieldNumber
|
||||
value={nodeConnections || ''}
|
||||
onChange={(e) => onFieldsChange({ nodeConnections: Number(e.target.value) })}
|
||||
isInvalid={showNodeConnectionErrors}
|
||||
fullWidth
|
||||
data-test-subj="remoteClusterFormNodeConnectionsInput"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</>
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`validateNodeConnections rejects numbers larger than MaxValue 1`] = `
|
||||
<Memo(MemoizedFormattedMessage)
|
||||
defaultMessage="This number must be equal or less than {maxValue}."
|
||||
id="xpack.remoteClusters.form.errors.maxValue"
|
||||
values={
|
||||
Object {
|
||||
"maxValue": 2147483647,
|
||||
}
|
||||
}
|
||||
/>
|
||||
`;
|
|
@ -16,3 +16,4 @@ export {
|
|||
validateCloudRemoteAddress,
|
||||
convertCloudRemoteAddressToProxyConnection,
|
||||
} from './validate_cloud_url';
|
||||
export { validateNodeConnections } from './validate_node_connections';
|
||||
|
|
|
@ -11,6 +11,7 @@ import { validateSeeds } from './validate_seeds';
|
|||
import { validateProxy } from './validate_proxy';
|
||||
import { validateCloudRemoteAddress } from './validate_cloud_url';
|
||||
import { FormFields } from '../remote_cluster_form';
|
||||
import { validateNodeConnections } from './validate_node_connections';
|
||||
|
||||
type ClusterError = JSX.Element | null;
|
||||
|
||||
|
@ -19,14 +20,16 @@ export interface ClusterErrors {
|
|||
seeds?: ClusterError;
|
||||
proxyAddress?: ClusterError;
|
||||
cloudRemoteAddress?: ClusterError;
|
||||
nodeConnections?: ClusterError;
|
||||
}
|
||||
export const validateCluster = (fields: FormFields, isCloudEnabled: boolean): ClusterErrors => {
|
||||
const { name, seeds = [], mode, proxyAddress, cloudRemoteAddress } = fields;
|
||||
const { name, seeds = [], mode, proxyAddress, cloudRemoteAddress, nodeConnections } = fields;
|
||||
|
||||
return {
|
||||
name: validateName(name),
|
||||
seeds: mode === SNIFF_MODE ? validateSeeds(seeds) : null,
|
||||
proxyAddress: mode === PROXY_MODE ? validateProxy(proxyAddress) : null,
|
||||
cloudRemoteAddress: isCloudEnabled ? validateCloudRemoteAddress(cloudRemoteAddress) : null,
|
||||
nodeConnections: mode === SNIFF_MODE ? validateNodeConnections(nodeConnections) : null,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { MAX_NODE_CONNECTIONS } from '../../../../../../common/constants';
|
||||
import { validateNodeConnections } from './validate_node_connections';
|
||||
|
||||
describe('validateNodeConnections', () => {
|
||||
test('rejects numbers larger than MaxValue', () => {
|
||||
expect(validateNodeConnections(MAX_NODE_CONNECTIONS + 1)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('accepts numbers equal than MaxValue', () => {
|
||||
expect(validateNodeConnections(MAX_NODE_CONNECTIONS)).toBe(null);
|
||||
});
|
||||
|
||||
test('accepts numbers smaller than MaxValue', () => {
|
||||
expect(validateNodeConnections(MAX_NODE_CONNECTIONS - 1)).toBe(null);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { MAX_NODE_CONNECTIONS } from '../../../../../../common/constants';
|
||||
|
||||
export function validateNodeConnections(connections?: number | null): null | JSX.Element {
|
||||
if (connections && connections > MAX_NODE_CONNECTIONS) {
|
||||
return (
|
||||
<FormattedMessage
|
||||
id="xpack.remoteClusters.form.errors.maxValue"
|
||||
defaultMessage="This number must be equal or less than {maxValue}."
|
||||
values={{
|
||||
maxValue: MAX_NODE_CONNECTIONS,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
|
@ -247,7 +247,7 @@ describe('config schema', () => {
|
|||
},
|
||||
"loginAssistanceMessage": "",
|
||||
"public": Object {},
|
||||
"roleManagementEnabled": false,
|
||||
"roleManagementEnabled": true,
|
||||
"secureCookies": false,
|
||||
"session": Object {
|
||||
"cleanupInterval": "PT1H",
|
||||
|
|
|
@ -303,8 +303,9 @@ export const ConfigSchema = schema.object({
|
|||
),
|
||||
}),
|
||||
|
||||
// config/serverless.oblt.yml contains an override to false for OBLT projects
|
||||
roleManagementEnabled: offeringBasedSchema({
|
||||
serverless: schema.boolean({ defaultValue: false }),
|
||||
serverless: schema.boolean({ defaultValue: true }),
|
||||
}),
|
||||
|
||||
// Setting only allowed in the Serverless offering
|
||||
|
|
|
@ -35,7 +35,6 @@ import { AllRules } from '../../components/rules_table';
|
|||
import { RulesTableContextProvider } from '../../components/rules_table/rules_table/rules_table_context';
|
||||
import { useInvalidateFetchCoverageOverviewQuery } from '../../../rule_management/api/hooks/use_fetch_coverage_overview_query';
|
||||
import { HeaderPage } from '../../../../common/components/header_page';
|
||||
import { RuleFeatureTour } from '../../components/rules_table/feature_tour/rules_feature_tour';
|
||||
|
||||
const RulesPageComponent: React.FC = () => {
|
||||
const [isImportModalVisible, showImportModal, hideImportModal] = useBoolState();
|
||||
|
@ -173,7 +172,6 @@ const RulesPageComponent: React.FC = () => {
|
|||
kibanaServices={kibanaServices}
|
||||
categories={[DEFAULT_APP_CATEGORIES.security.id]}
|
||||
/>
|
||||
<RuleFeatureTour />
|
||||
<AllRules data-test-subj="all-rules" />
|
||||
</SecuritySolutionPageWrapper>
|
||||
</RulesTableContextProvider>
|
||||
|
|
|
@ -125,7 +125,7 @@ export class ServerlessPlugin
|
|||
getNavigationCards: (roleManagementEnabled, extendCardNavDefinitions) => {
|
||||
if (!roleManagementEnabled) return extendCardNavDefinitions;
|
||||
|
||||
const manageOrgMembersNavCard = generateManageOrgMembersNavCard(cloud.organizationUrl);
|
||||
const manageOrgMembersNavCard = generateManageOrgMembersNavCard(cloud.usersAndRolesUrl);
|
||||
if (extendCardNavDefinitions) {
|
||||
extendCardNavDefinitions[manageOrgMembersNavCardName] = manageOrgMembersNavCard;
|
||||
return extendCardNavDefinitions;
|
||||
|
|
|
@ -8261,8 +8261,6 @@
|
|||
"visTypeMarkdown.function.help": "Visualisation Markdown",
|
||||
"visTypeMarkdown.function.markdown.help": "Markdown à rendre",
|
||||
"visTypeMarkdown.function.openLinksInNewTab.help": "Ouvre les liens dans un nouvel onglet",
|
||||
"visTypeMarkdown.markdownDescription": "Ajoutez du texte et des images à votre tableau de bord.",
|
||||
"visTypeMarkdown.markdownTitleInWizard": "Texte",
|
||||
"visTypeMarkdown.params.fontSizeLabel": "Taille de police de base en points",
|
||||
"visTypeMarkdown.params.helpLinkLabel": "Aide",
|
||||
"visTypeMarkdown.params.openLinksLabel": "Ouvrir les liens dans un nouvel onglet",
|
||||
|
@ -8591,7 +8589,6 @@
|
|||
"visTypeTimeseries.indexPatternSelect.switchModePopover.title": "Mode de vue de données",
|
||||
"visTypeTimeseries.indexPatternSelect.switchModePopover.useKibanaIndices": "Utiliser des vues de données Kibana",
|
||||
"visTypeTimeseries.indexPatternSelect.updateIndex": "Mettre à jour la visualisation avec la vue de données saisie",
|
||||
"visTypeTimeseries.kbnVisTypes.metricsDescription": "Réalisez des analyses avancées de vos données temporelles.",
|
||||
"visTypeTimeseries.kbnVisTypes.metricsTitle": "TSVB",
|
||||
"visTypeTimeseries.lastValueModeIndicator.lastBucketDate": "Compartiment : {lastBucketDate}",
|
||||
"visTypeTimeseries.lastValueModeIndicator.lastValue": "Dernière valeur",
|
||||
|
@ -8969,7 +8966,6 @@
|
|||
"visTypeVega.mapView.resettingPropertyToMaxValueWarningMessage": "Réinitialisation de {name} sur {max}",
|
||||
"visTypeVega.mapView.resettingPropertyToMinValueWarningMessage": "Réinitialisation de {name} sur {min}",
|
||||
"visTypeVega.type.vegaDescription": "Utilisez Vega pour créer de nouveaux types de visualisations.",
|
||||
"visTypeVega.type.vegaNote": "Requiert une connaissance de la syntaxe Vega.",
|
||||
"visTypeVega.type.vegaTitleInWizard": "Visualisation personnalisée",
|
||||
"visTypeVega.urlParser.dataUrlRequiresUrlParameterInFormErrorMessage": "{dataUrlParam} requiert un paramètre {urlParam} sous la forme \"{formLink}\"",
|
||||
"visTypeVega.urlParser.urlShouldHaveQuerySubObjectWarningMessage": "L'utilisation d'un {urlObject} requiert un sous-objet {subObjectName}",
|
||||
|
@ -9154,7 +9150,6 @@
|
|||
"visualizations.createVisualization.noIndexPatternOrSavedSearchIdErrorMessage": "Vous devez fournir un indexPattern ou un savedSearchId",
|
||||
"visualizations.createVisualization.noVisTypeErrorMessage": "Vous devez fournir un type de visualisation valide",
|
||||
"visualizations.dataView.label": "Vue de données",
|
||||
"visualizations.deprecatedTag": "Déclassé",
|
||||
"visualizations.displayName": "visualisation",
|
||||
"visualizations.editor.createBreadcrumb": "Créer",
|
||||
"visualizations.editor.defaultEditBreadcrumbText": "Modifier la visualisation",
|
||||
|
@ -9206,24 +9201,16 @@
|
|||
"visualizations.newChart.libraryMode.new": "nouveau",
|
||||
"visualizations.newChart.libraryMode.old": "âge",
|
||||
"visualizations.newGaugeChart.notificationMessage": "La nouvelle bibliothèque de graphiques de jauge ne prend pas encore en charge l'agrégation de graphiques fractionnés. {conditionalMessage}",
|
||||
"visualizations.newVisWizard.aggBasedGroupDescription": "Utilisez notre bibliothèque Visualize classique pour créer des graphiques basés sur des agrégations.",
|
||||
"visualizations.newVisWizard.aggBasedGroupTitle": "Basé sur une agrégation",
|
||||
"visualizations.newVisWizard.chooseSourceTitle": "Choisir une source",
|
||||
"visualizations.newVisWizard.experimentalTitle": "Version d'évaluation technique",
|
||||
"visualizations.newVisWizard.experimentalTooltip": "Cette fonctionnalité est en version d'évaluation technique et pourra être modifiée ou retirée complètement dans une future version. Elastic s'efforcera de corriger tout problème, mais les fonctionnalités des versions d'évaluation technique ne sont pas soumises aux SLA de support des fonctionnalités officielles en disponibilité générale.",
|
||||
"visualizations.newVisWizard.exploreOptionLinkText": "Explorer les options",
|
||||
"visualizations.newVisWizard.filterVisTypeAriaLabel": "Filtrer un type de visualisation",
|
||||
"visualizations.newVisWizard.goBackLink": "Sélectionner une visualisation différente",
|
||||
"visualizations.newVisWizard.helpTextAriaLabel": "Commencez à créer votre visualisation en sélectionnant un type pour cette visualisation. Appuyez sur Échap pour fermer ce mode. Appuyez sur Tab pour aller plus loin.",
|
||||
"visualizations.newVisWizard.learnMoreText": "Envie d'en savoir plus ?",
|
||||
"visualizations.newVisWizard.newVisTypeTitle": "Nouveau {visTypeName}",
|
||||
"visualizations.newVisWizard.readDocumentationLink": "Lire la documentation",
|
||||
"visualizations.newVisWizard.resultsFound": "{resultCount, plural, one {type trouvé} other {types trouvés}}",
|
||||
"visualizations.newVisWizard.searchSelection.notFoundLabel": "Aucun recherche enregistrée ni aucun index correspondants n'ont été trouvés.",
|
||||
"visualizations.newVisWizard.searchSelection.savedObjectType.dataView": "Vue de données",
|
||||
"visualizations.newVisWizard.searchSelection.savedObjectType.search": "Recherche enregistrée",
|
||||
"visualizations.newVisWizard.title": "Nouvelle visualisation",
|
||||
"visualizations.newVisWizard.toolsGroupTitle": "Outils",
|
||||
"visualizations.noDataView.label": "vue de données",
|
||||
"visualizations.noMatchRoute.bannerText": "L'application Visualize ne reconnaît pas cet itinéraire : {route}.",
|
||||
"visualizations.noMatchRoute.bannerTitleText": "Page introuvable",
|
||||
|
@ -25772,8 +25759,6 @@
|
|||
"xpack.lens.uniqueLabel": "{label} [{num}]",
|
||||
"xpack.lens.unknownDatasourceType.shortMessage": "Type de source de données inconnu",
|
||||
"xpack.lens.unknownVisType.shortMessage": "Type de visualisation inconnu",
|
||||
"xpack.lens.visTypeAlias.description": "Créez des visualisations avec notre éditeur de glisser-déposer. Basculez entre les différents types de visualisation à tout moment.",
|
||||
"xpack.lens.visTypeAlias.note": "Recommandé pour la plupart des utilisateurs.",
|
||||
"xpack.lens.visTypeAlias.title": "Lens",
|
||||
"xpack.lens.visTypeAlias.type": "Lens",
|
||||
"xpack.lens.visualizeAggBasedLegend": "Visualiser le graphique basé sur une agrégation",
|
||||
|
@ -47491,4 +47476,4 @@
|
|||
"xpack.watcher.watchEdit.thresholdWatchExpression.aggType.fieldIsRequiredValidationMessage": "Ce champ est requis.",
|
||||
"xpack.watcher.watcherDescription": "Détectez les modifications survenant dans vos données en créant, gérant et monitorant des alertes."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8015,8 +8015,6 @@
|
|||
"visTypeMarkdown.function.help": "マークダウンビジュアライゼーション",
|
||||
"visTypeMarkdown.function.markdown.help": "レンダリングするマークダウン",
|
||||
"visTypeMarkdown.function.openLinksInNewTab.help": "新規タブでリンクを開きます",
|
||||
"visTypeMarkdown.markdownDescription": "テキストと画像をダッシュボードに追加します。",
|
||||
"visTypeMarkdown.markdownTitleInWizard": "Text",
|
||||
"visTypeMarkdown.params.fontSizeLabel": "ポイント単位のベースフォントサイズです。",
|
||||
"visTypeMarkdown.params.helpLinkLabel": "ヘルプ",
|
||||
"visTypeMarkdown.params.openLinksLabel": "新規タブでリンクを開く",
|
||||
|
@ -8345,7 +8343,6 @@
|
|||
"visTypeTimeseries.indexPatternSelect.switchModePopover.title": "データビューモード",
|
||||
"visTypeTimeseries.indexPatternSelect.switchModePopover.useKibanaIndices": "Kibanaデータビューを使用",
|
||||
"visTypeTimeseries.indexPatternSelect.updateIndex": "入力したデータビューで可視化を更新",
|
||||
"visTypeTimeseries.kbnVisTypes.metricsDescription": "時系列データの高度な分析を実行します。",
|
||||
"visTypeTimeseries.kbnVisTypes.metricsTitle": "TSVB",
|
||||
"visTypeTimeseries.lastValueModeIndicator.lastBucketDate": "バケット:{lastBucketDate}",
|
||||
"visTypeTimeseries.lastValueModeIndicator.lastValue": "最終値",
|
||||
|
@ -8722,7 +8719,6 @@
|
|||
"visTypeVega.mapView.resettingPropertyToMaxValueWarningMessage": "{name} を {max} にリセットしています",
|
||||
"visTypeVega.mapView.resettingPropertyToMinValueWarningMessage": "{name} を {min} にリセットしています",
|
||||
"visTypeVega.type.vegaDescription": "Vega を使用して、新しいタイプのビジュアライゼーションを作成します。",
|
||||
"visTypeVega.type.vegaNote": "Vega 構文の知識が必要です。",
|
||||
"visTypeVega.type.vegaTitleInWizard": "カスタムビジュアライゼーション",
|
||||
"visTypeVega.urlParser.dataUrlRequiresUrlParameterInFormErrorMessage": "{dataUrlParam} には「{formLink}」の形で {urlParam} パラメーターが必要です",
|
||||
"visTypeVega.urlParser.urlShouldHaveQuerySubObjectWarningMessage": "{urlObject} を使用するには {subObjectName} サブオブジェクトが必要です",
|
||||
|
@ -8907,7 +8903,6 @@
|
|||
"visualizations.createVisualization.noIndexPatternOrSavedSearchIdErrorMessage": "indexPatternまたはsavedSearchIdが必要です",
|
||||
"visualizations.createVisualization.noVisTypeErrorMessage": "有効なビジュアライゼーションタイプを指定してください",
|
||||
"visualizations.dataView.label": "データビュー",
|
||||
"visualizations.deprecatedTag": "非推奨",
|
||||
"visualizations.displayName": "ビジュアライゼーション",
|
||||
"visualizations.editor.createBreadcrumb": "作成",
|
||||
"visualizations.editor.defaultEditBreadcrumbText": "ビジュアライゼーションを編集",
|
||||
|
@ -8959,24 +8954,16 @@
|
|||
"visualizations.newChart.libraryMode.new": "新規",
|
||||
"visualizations.newChart.libraryMode.old": "古",
|
||||
"visualizations.newGaugeChart.notificationMessage": "新しいゲージグラフライブラリはまだ分割グラフアグリゲーションをサポートしていません。{conditionalMessage}",
|
||||
"visualizations.newVisWizard.aggBasedGroupDescription": "クラシック Visualize ライブラリを使用して、アグリゲーションに基づいてグラフを作成します。",
|
||||
"visualizations.newVisWizard.aggBasedGroupTitle": "アグリゲーションに基づく",
|
||||
"visualizations.newVisWizard.chooseSourceTitle": "ソースの選択",
|
||||
"visualizations.newVisWizard.experimentalTitle": "テクニカルプレビュー",
|
||||
"visualizations.newVisWizard.experimentalTooltip": "この機能はテクニカルプレビュー中であり、将来のリリースでは変更されたり完全に削除されたりする場合があります。Elasticはすべての問題の修正に努めますが、テクニカルプレビュー中の機能には正式なGA機能のサポートSLAが適用されません。",
|
||||
"visualizations.newVisWizard.exploreOptionLinkText": "探索オプション",
|
||||
"visualizations.newVisWizard.filterVisTypeAriaLabel": "ビジュアライゼーションのタイプでフィルタリング",
|
||||
"visualizations.newVisWizard.goBackLink": "別のビジュアライゼーションを選択",
|
||||
"visualizations.newVisWizard.helpTextAriaLabel": "タイプを選択してビジュアライゼーションの作成を始めましょう。ESC を押してこのモーダルを閉じます。Tab キーを押して次に進みます。",
|
||||
"visualizations.newVisWizard.learnMoreText": "詳細について",
|
||||
"visualizations.newVisWizard.newVisTypeTitle": "新規 {visTypeName}",
|
||||
"visualizations.newVisWizard.readDocumentationLink": "ドキュメンテーションを表示",
|
||||
"visualizations.newVisWizard.resultsFound": "{resultCount, plural, other {個のタイプ}} が見つかりました",
|
||||
"visualizations.newVisWizard.searchSelection.notFoundLabel": "一致インデックスまたは保存した検索が見つかりません。",
|
||||
"visualizations.newVisWizard.searchSelection.savedObjectType.dataView": "データビュー",
|
||||
"visualizations.newVisWizard.searchSelection.savedObjectType.search": "保存検索",
|
||||
"visualizations.newVisWizard.title": "新規ビジュアライゼーション",
|
||||
"visualizations.newVisWizard.toolsGroupTitle": "ツール",
|
||||
"visualizations.noDataView.label": "データビュー",
|
||||
"visualizations.noMatchRoute.bannerText": "Visualizeアプリケーションはこのルートを認識できません。{route}",
|
||||
"visualizations.noMatchRoute.bannerTitleText": "ページが見つかりません",
|
||||
|
@ -25520,8 +25507,6 @@
|
|||
"xpack.lens.unknownDatasourceType.shortMessage": "不明なデータソースタイプ",
|
||||
"xpack.lens.unknownVisType.longMessage": "ビジュアライゼーションタイプ{visType}を解決できませんでした。",
|
||||
"xpack.lens.unknownVisType.shortMessage": "不明なビジュアライゼーションタイプ",
|
||||
"xpack.lens.visTypeAlias.description": "ドラッグアンドドロップエディターでビジュアライゼーションを作成します。いつでもビジュアライゼーションタイプを切り替えることができます。",
|
||||
"xpack.lens.visTypeAlias.note": "ほとんどのユーザーに推奨されます。",
|
||||
"xpack.lens.visTypeAlias.title": "Lens",
|
||||
"xpack.lens.visTypeAlias.type": "Lens",
|
||||
"xpack.lens.visualizeAggBasedLegend": "集約に基づくグラフを可視化",
|
||||
|
@ -47229,4 +47214,4 @@
|
|||
"xpack.watcher.watchEdit.thresholdWatchExpression.aggType.fieldIsRequiredValidationMessage": "フィールドを選択してください。",
|
||||
"xpack.watcher.watcherDescription": "アラートの作成、管理、監視によりデータへの変更を検知します。"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8031,8 +8031,6 @@
|
|||
"visTypeMarkdown.function.help": "Markdown 可视化",
|
||||
"visTypeMarkdown.function.markdown.help": "要渲染的 Markdown",
|
||||
"visTypeMarkdown.function.openLinksInNewTab.help": "在新标签页中打开链接",
|
||||
"visTypeMarkdown.markdownDescription": "将文本和图像添加到仪表板。",
|
||||
"visTypeMarkdown.markdownTitleInWizard": "文本",
|
||||
"visTypeMarkdown.params.fontSizeLabel": "基础字体大小(磅)",
|
||||
"visTypeMarkdown.params.helpLinkLabel": "帮助",
|
||||
"visTypeMarkdown.params.openLinksLabel": "在新标签页中打开链接",
|
||||
|
@ -8361,7 +8359,6 @@
|
|||
"visTypeTimeseries.indexPatternSelect.switchModePopover.title": "数据视图模式",
|
||||
"visTypeTimeseries.indexPatternSelect.switchModePopover.useKibanaIndices": "使用 Kibana 数据视图",
|
||||
"visTypeTimeseries.indexPatternSelect.updateIndex": "使用输入的数据视图更新可视化",
|
||||
"visTypeTimeseries.kbnVisTypes.metricsDescription": "对时间序列数据执行高级分析。",
|
||||
"visTypeTimeseries.kbnVisTypes.metricsTitle": "TSVB",
|
||||
"visTypeTimeseries.lastValueModeIndicator.lastBucketDate": "存储桶:{lastBucketDate}",
|
||||
"visTypeTimeseries.lastValueModeIndicator.lastValue": "最后值",
|
||||
|
@ -8739,7 +8736,6 @@
|
|||
"visTypeVega.mapView.resettingPropertyToMaxValueWarningMessage": "将 {name} 重置为 {max}",
|
||||
"visTypeVega.mapView.resettingPropertyToMinValueWarningMessage": "将 {name} 重置为 {min}",
|
||||
"visTypeVega.type.vegaDescription": "使用 Vega 创建新的可视化类型。",
|
||||
"visTypeVega.type.vegaNote": "需要有 Vega 语法知识。",
|
||||
"visTypeVega.type.vegaTitleInWizard": "定制可视化",
|
||||
"visTypeVega.urlParser.dataUrlRequiresUrlParameterInFormErrorMessage": "{dataUrlParam} 需要“{formLink}”形式的 {urlParam} 参数",
|
||||
"visTypeVega.urlParser.urlShouldHaveQuerySubObjectWarningMessage": "使用 {urlObject} 应具有 {subObjectName} 子对象",
|
||||
|
@ -8924,7 +8920,6 @@
|
|||
"visualizations.createVisualization.noIndexPatternOrSavedSearchIdErrorMessage": "必须提供 indexPattern 或 savedSearchId",
|
||||
"visualizations.createVisualization.noVisTypeErrorMessage": "必须提供有效的可视化类型",
|
||||
"visualizations.dataView.label": "数据视图",
|
||||
"visualizations.deprecatedTag": "(已过时)",
|
||||
"visualizations.displayName": "可视化",
|
||||
"visualizations.editor.createBreadcrumb": "创建",
|
||||
"visualizations.editor.defaultEditBreadcrumbText": "编辑可视化",
|
||||
|
@ -8976,24 +8971,16 @@
|
|||
"visualizations.newChart.libraryMode.new": "新",
|
||||
"visualizations.newChart.libraryMode.old": "以前",
|
||||
"visualizations.newGaugeChart.notificationMessage": "新的仪表盘图表库尚不支持拆分图表聚合。{conditionalMessage}",
|
||||
"visualizations.newVisWizard.aggBasedGroupDescription": "使用我们的经典可视化库,基于聚合创建图表。",
|
||||
"visualizations.newVisWizard.aggBasedGroupTitle": "基于聚合",
|
||||
"visualizations.newVisWizard.chooseSourceTitle": "选择源",
|
||||
"visualizations.newVisWizard.experimentalTitle": "技术预览",
|
||||
"visualizations.newVisWizard.experimentalTooltip": "此功能处于技术预览状态,在未来版本中可能会更改或完全移除。Elastic 将努力修复任何问题,但处于技术预览状态的功能不受正式 GA 功能支持 SLA 的约束。",
|
||||
"visualizations.newVisWizard.exploreOptionLinkText": "浏览选项",
|
||||
"visualizations.newVisWizard.filterVisTypeAriaLabel": "筛留可视化类型",
|
||||
"visualizations.newVisWizard.goBackLink": "选择不同的可视化",
|
||||
"visualizations.newVisWizard.helpTextAriaLabel": "通过为该可视化选择类型,开始创建您的可视化。按 Esc 键关闭此模式。按 Tab 键继续。",
|
||||
"visualizations.newVisWizard.learnMoreText": "希望了解详情?",
|
||||
"visualizations.newVisWizard.newVisTypeTitle": "新建{visTypeName}",
|
||||
"visualizations.newVisWizard.readDocumentationLink": "阅读文档",
|
||||
"visualizations.newVisWizard.resultsFound": "{resultCount, plural, other {类型}}已找到",
|
||||
"visualizations.newVisWizard.searchSelection.notFoundLabel": "未找到匹配的索引或已保存搜索。",
|
||||
"visualizations.newVisWizard.searchSelection.savedObjectType.dataView": "数据视图",
|
||||
"visualizations.newVisWizard.searchSelection.savedObjectType.search": "已保存搜索",
|
||||
"visualizations.newVisWizard.title": "新建可视化",
|
||||
"visualizations.newVisWizard.toolsGroupTitle": "工具",
|
||||
"visualizations.noDataView.label": "数据视图",
|
||||
"visualizations.noMatchRoute.bannerText": "Visualize 应用程序无法识别此路由:{route}。",
|
||||
"visualizations.noMatchRoute.bannerTitleText": "未找到页面",
|
||||
|
@ -25555,8 +25542,6 @@
|
|||
"xpack.lens.unknownDatasourceType.shortMessage": "数据源类型未知",
|
||||
"xpack.lens.unknownVisType.longMessage": "无法解析可视化类型 {visType}。",
|
||||
"xpack.lens.unknownVisType.shortMessage": "可视化类型未知",
|
||||
"xpack.lens.visTypeAlias.description": "使用拖放编辑器创建可视化。随时在可视化类型之间切换。",
|
||||
"xpack.lens.visTypeAlias.note": "适合绝大多数用户。",
|
||||
"xpack.lens.visTypeAlias.title": "Lens",
|
||||
"xpack.lens.visTypeAlias.type": "Lens",
|
||||
"xpack.lens.visualizeAggBasedLegend": "可视化基于聚合的图表",
|
||||
|
@ -47282,4 +47267,4 @@
|
|||
"xpack.watcher.watchEdit.thresholdWatchExpression.aggType.fieldIsRequiredValidationMessage": "此字段必填。",
|
||||
"xpack.watcher.watcherDescription": "通过创建、管理和监测警报来检测数据中的更改。"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,9 +13,24 @@ import { loadTestData } from './helper/load_test_data';
|
|||
import { SloEsClient } from './helper/es';
|
||||
import { sloData } from './fixtures/create_slo';
|
||||
|
||||
export const expectSummary = (summary: Record<string, any>) => {
|
||||
expect(summary).toEqual({
|
||||
sliValue: expect.any(Number),
|
||||
errorBudget: {
|
||||
initial: expect.any(Number),
|
||||
consumed: expect.any(Number),
|
||||
remaining: expect.any(Number),
|
||||
isEstimated: expect.any(Boolean),
|
||||
},
|
||||
status: expect.any(String),
|
||||
fiveMinuteBurnRate: expect.any(Number),
|
||||
oneDayBurnRate: expect.any(Number),
|
||||
oneHourBurnRate: expect.any(Number),
|
||||
});
|
||||
};
|
||||
|
||||
export default function ({ getService }: FtrProviderContext) {
|
||||
// FLAKY: https://github.com/elastic/kibana/issues/177806
|
||||
describe.skip('Get SLOs', function () {
|
||||
describe('GetSLOs', function () {
|
||||
this.tags('skipCloud');
|
||||
|
||||
const supertestAPI = getService('supertest');
|
||||
|
@ -23,8 +38,16 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
const logger = getService('log');
|
||||
const retry = getService('retry');
|
||||
const slo = getService('slo');
|
||||
// const transform = getService('transform');
|
||||
const sloEsClient = new SloEsClient(esClient);
|
||||
|
||||
// const onFailure = async () => {
|
||||
// const allTransforms = await transform.api.getTransformList();
|
||||
// for (const tf of allTransforms.transforms) {
|
||||
// await transform.api.scheduleTransform(tf.id);
|
||||
// }
|
||||
// };
|
||||
|
||||
let createSLOInput: CreateSLOInput;
|
||||
|
||||
const createSLO = async (requestOverrides?: Record<string, any>) => {
|
||||
|
@ -97,24 +120,13 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
version: 2,
|
||||
instanceId: '*',
|
||||
meta: {},
|
||||
summary: {
|
||||
sliValue: 0.5,
|
||||
errorBudget: {
|
||||
initial: 0.01,
|
||||
consumed: 50,
|
||||
remaining: -49,
|
||||
isEstimated: false,
|
||||
},
|
||||
fiveMinuteBurnRate: 40,
|
||||
oneDayBurnRate: 50,
|
||||
oneHourBurnRate: 50,
|
||||
status: 'VIOLATED',
|
||||
},
|
||||
summary: expect.any(Object),
|
||||
});
|
||||
expectSummary(getResponse.body.summary);
|
||||
});
|
||||
});
|
||||
|
||||
it('gets slo by id and calculates SLI - occurences calendarAligned', async () => {
|
||||
it('gets slo by id and calculates SLI - occurrences calendarAligned', async () => {
|
||||
const response = await createSLO({
|
||||
groupBy: '*',
|
||||
timeWindow: {
|
||||
|
@ -160,20 +172,9 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
version: 2,
|
||||
instanceId: '*',
|
||||
meta: {},
|
||||
summary: {
|
||||
sliValue: 0.5,
|
||||
errorBudget: {
|
||||
initial: 0.01,
|
||||
consumed: 50,
|
||||
remaining: -49,
|
||||
isEstimated: true,
|
||||
},
|
||||
fiveMinuteBurnRate: 40,
|
||||
oneDayBurnRate: 50,
|
||||
oneHourBurnRate: 50,
|
||||
status: 'VIOLATED',
|
||||
},
|
||||
summary: expect.any(Object),
|
||||
});
|
||||
expectSummary(getResponse.body.summary);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -233,17 +234,9 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
version: 2,
|
||||
instanceId: '*',
|
||||
meta: {},
|
||||
summary: expect.objectContaining({
|
||||
sliValue: 0.5,
|
||||
errorBudget: {
|
||||
initial: 0.01,
|
||||
consumed: 50,
|
||||
remaining: -49,
|
||||
isEstimated: false,
|
||||
},
|
||||
status: 'VIOLATED',
|
||||
}),
|
||||
summary: expect.any(Object),
|
||||
});
|
||||
expectSummary(getResponse.body.summary);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -302,20 +295,9 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
version: 2,
|
||||
instanceId: '*',
|
||||
meta: {},
|
||||
summary: {
|
||||
sliValue: 0,
|
||||
errorBudget: {
|
||||
initial: 0.01,
|
||||
consumed: 0.198413,
|
||||
remaining: 0.801587,
|
||||
isEstimated: false,
|
||||
},
|
||||
fiveMinuteBurnRate: 40,
|
||||
oneDayBurnRate: 50,
|
||||
oneHourBurnRate: 50,
|
||||
status: 'DEGRADING',
|
||||
},
|
||||
summary: expect.any(Object),
|
||||
});
|
||||
expectSummary(getResponse.body.summary);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -366,34 +348,39 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
});
|
||||
});
|
||||
|
||||
it('gets slos instances', async () => {
|
||||
const createResponse = await createSLO();
|
||||
const id = createResponse.body.id;
|
||||
|
||||
await retry.tryForTime(400 * 1000, async () => {
|
||||
const response = await supertestAPI
|
||||
.get(`/api/observability/slos`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send()
|
||||
.expect(200);
|
||||
|
||||
expect(response.body.results.length).toEqual(3);
|
||||
|
||||
response.body.results.forEach((result: Record<string, unknown>, i: number) => {
|
||||
expect(result.groupings).toEqual(expect.objectContaining({ tags: `${i + 1}` }));
|
||||
});
|
||||
|
||||
const instanceResponse = await supertestAPI
|
||||
.get(`/internal/observability/slos/${id}/_instances`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send()
|
||||
.expect(200);
|
||||
|
||||
// expect 3 instances to be created
|
||||
expect(instanceResponse.body.groupBy).toEqual('tags');
|
||||
expect(instanceResponse.body.instances.sort()).toEqual(['tags:1', 'tags:2', 'tags:3']);
|
||||
});
|
||||
});
|
||||
// not possible for now to reliably fix this
|
||||
// it.skip('gets slos instances', async () => {
|
||||
// const createResponse = await createSLO();
|
||||
// const id = createResponse.body.id;
|
||||
//
|
||||
// await retry.tryForTime(
|
||||
// 400 * 1000,
|
||||
// async () => {
|
||||
// const response = await supertestAPI
|
||||
// .get(`/api/observability/slos`)
|
||||
// .set('kbn-xsrf', 'true')
|
||||
// .send()
|
||||
// .expect(200);
|
||||
// const res = response.body.results;
|
||||
// expect(res.length).toEqual(3);
|
||||
// const groups = res.map((r: any) => r.groupings.tags);
|
||||
//
|
||||
// expect(groups.sort()).toEqual(['1', '2', '3']);
|
||||
//
|
||||
// const instanceResponse = await supertestAPI
|
||||
// .get(`/internal/observability/slos/${id}/_instances`)
|
||||
// .set('kbn-xsrf', 'true')
|
||||
// .send()
|
||||
// .expect(200);
|
||||
//
|
||||
// // expect 3 instances to be created
|
||||
// expect(instanceResponse.body.groupBy).toEqual('tags');
|
||||
// expect(instanceResponse.body.instances.sort()).toEqual(['1', '2', '3']);
|
||||
// },
|
||||
// onFailure,
|
||||
// 10 * 1000
|
||||
// );
|
||||
// });
|
||||
|
||||
it('gets slo definitions', async () => {
|
||||
const createResponse = await createSLO();
|
||||
|
|
|
@ -9,9 +9,9 @@ import { FtrProviderContext } from '../../ftr_provider_context';
|
|||
|
||||
export default function ({ loadTestFile }: FtrProviderContext) {
|
||||
describe('SLO API Tests', () => {
|
||||
loadTestFile(require.resolve('./get_slo'));
|
||||
loadTestFile(require.resolve('./create_slo'));
|
||||
loadTestFile(require.resolve('./delete_slo'));
|
||||
loadTestFile(require.resolve('./get_slo'));
|
||||
loadTestFile(require.resolve('./update_slo'));
|
||||
loadTestFile(require.resolve('./reset_slo'));
|
||||
loadTestFile(require.resolve('./fetch_historical_summary'));
|
||||
|
|
|
@ -113,10 +113,6 @@ export function createServerlessTestConfig<T extends DeploymentAgnosticCommonSer
|
|||
...svlSharedConfig.get('kbnTestServer.serverArgs'),
|
||||
...kbnServerArgsFromController[options.serverlessProject],
|
||||
`--serverless=${options.serverlessProject}`,
|
||||
// custom native roles are enabled only for search and security projects
|
||||
...(options.serverlessProject !== 'oblt'
|
||||
? ['--xpack.security.roleManagementEnabled=true']
|
||||
: []),
|
||||
],
|
||||
},
|
||||
testFiles: options.testFiles,
|
||||
|
|
|
@ -25,7 +25,8 @@ export default function (providerContext: FtrProviderContext) {
|
|||
const config = getService('config');
|
||||
const log = getService('log');
|
||||
|
||||
describe('inputs_with_standalone_docker_agent', () => {
|
||||
// Failing: See https://github.com/elastic/kibana/issues/193625
|
||||
describe.skip('inputs_with_standalone_docker_agent', () => {
|
||||
skipIfNoDockerRegistry(providerContext);
|
||||
let apiKey: string;
|
||||
let agent: AgentProcess;
|
||||
|
|
|
@ -29,13 +29,15 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
|
||||
await es
|
||||
.delete(
|
||||
{ id: `config-global:${version}`, index: '.kibana', refresh: true },
|
||||
{ id: `config-global:${version}`, index: '.kibana', refresh: 'wait_for' },
|
||||
{ headers: { 'kbn-xsrf': 'spaces' } }
|
||||
)
|
||||
.catch((error) => {
|
||||
if (error.statusCode === 404) return; // ignore 404 errors
|
||||
throw error;
|
||||
});
|
||||
|
||||
await PageObjects.common.sleep(500); // just to be on the safe side
|
||||
};
|
||||
|
||||
before(async () => {
|
||||
|
|
|
@ -285,6 +285,15 @@ export function TransformAPIProvider({ getService }: FtrProviderContext) {
|
|||
}
|
||||
},
|
||||
|
||||
async scheduleTransform(transformId: string, assertSuccess = true) {
|
||||
log.debug(`Scheduling now transform '${transformId}' ...`);
|
||||
const { body, status } = await esSupertest.post(`/_transform/${transformId}/_schedule_now`);
|
||||
|
||||
if (assertSuccess) {
|
||||
this.assertResponseStatusCode(200, status, body);
|
||||
}
|
||||
},
|
||||
|
||||
async stopTransform(transformId: string) {
|
||||
log.debug(`Stopping transform '${transformId}' ...`);
|
||||
const { body, status } = await esSupertest.post(`/_transform/${transformId}/_stop`);
|
||||
|
|
|
@ -32,14 +32,12 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
}
|
||||
await testSubjects.click('savedObjectTitle');
|
||||
};
|
||||
// creates a simple markdown vis with a tag provided.
|
||||
// creates a simple tsvb vis with a tag provided.
|
||||
const createSimpleMarkdownVis = async (opts: Record<string, string>) => {
|
||||
const { visName, visText, tagName } = opts;
|
||||
const { visName, tagName } = opts;
|
||||
await PageObjects.visualize.navigateToNewVisualization();
|
||||
|
||||
await PageObjects.visualize.clickMarkdownWidget();
|
||||
await PageObjects.visEditor.setMarkdownTxt(visText);
|
||||
await PageObjects.visEditor.clickGo();
|
||||
await PageObjects.visualize.clickVisualBuilder();
|
||||
|
||||
await PageObjects.visualize.ensureSavePanelOpen();
|
||||
await PageObjects.visualize.setSaveModalValues(visName, {
|
||||
|
@ -150,10 +148,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
|
||||
await PageObjects.visualize.navigateToNewVisualization();
|
||||
|
||||
await PageObjects.visualize.clickMarkdownWidget();
|
||||
await PageObjects.visEditor.setMarkdownTxt('Just some markdown');
|
||||
await PageObjects.visEditor.clickGo();
|
||||
|
||||
await PageObjects.visualize.clickVisualBuilder();
|
||||
await PageObjects.visualize.ensureSavePanelOpen();
|
||||
await PageObjects.visualize.setSaveModalValues('vis-with-new-tag', {
|
||||
saveAsNew: false,
|
||||
|
|
|
@ -58,7 +58,8 @@ describe(
|
|||
});
|
||||
|
||||
// https://github.com/elastic/kibana/issues/187621
|
||||
describe('without suppression', { tags: ['@skipInServerlessMKI'] }, () => {
|
||||
// FLAKY: https://github.com/elastic/kibana/issues/196711
|
||||
describe.skip('without suppression', { tags: ['@skipInServerlessMKI'] }, () => {
|
||||
beforeEach(() => {
|
||||
createRule(rule);
|
||||
});
|
||||
|
|
|
@ -21,7 +21,8 @@ import {
|
|||
} from '../../../../tasks/timeline';
|
||||
import { ALERTS_URL } from '../../../../urls/navigation';
|
||||
|
||||
describe(
|
||||
// FLAKY: https://github.com/elastic/kibana/issues/196851
|
||||
describe.skip(
|
||||
'Unified Timeline table Row Actions',
|
||||
{
|
||||
tags: ['@ess', '@serverless', '@skipInServerlessMKI'],
|
||||
|
|
|
@ -36,10 +36,6 @@ export function createTestConfig(options: CreateTestConfigOptions) {
|
|||
serverArgs: [
|
||||
...svlSharedConfig.get('kbnTestServer.serverArgs'),
|
||||
`--serverless=${options.serverlessProject}`,
|
||||
// custom native roles are enabled only for search and security projects
|
||||
...(options.serverlessProject !== 'oblt'
|
||||
? ['--xpack.security.roleManagementEnabled=true']
|
||||
: []),
|
||||
...(options.kbnServerArgs || []),
|
||||
],
|
||||
},
|
||||
|
|
|
@ -1,410 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import expect from 'expect';
|
||||
import { asyncForEach } from '@kbn/std';
|
||||
import { SupertestWithRoleScopeType } from '@kbn/test-suites-xpack/api_integration/deployment_agnostic/services';
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
|
||||
// Notes:
|
||||
// This suite is currently only called from the feature flags test configs, e.g.
|
||||
// x-pack/test_serverless/api_integration/test_suites/search/config.feature_flags.ts
|
||||
// Configuration toggle:
|
||||
// kbnServerArgs: ['--xpack.spaces.maxSpaces=100'],
|
||||
//
|
||||
// Initial test coverage limited to CRUD operations and ensuring disabling features/toggling feature visibility is not possible.
|
||||
// Full coverage of x-pack/test/api_integration/apis/spaces & x-pack/test/spaces_api_integration
|
||||
// should be converted into a deployment agnostic suite when spaces are
|
||||
// permanently enabled in serverless.
|
||||
//
|
||||
// The route access tests for the spaces APIs in ./spaces.ts should also get updated at that time.
|
||||
|
||||
export default function ({ getService }: FtrProviderContext) {
|
||||
const svlCommonApi = getService('svlCommonApi');
|
||||
const roleScopedSupertest = getService('roleScopedSupertest');
|
||||
const samlAuth = getService('samlAuth');
|
||||
|
||||
// CRUD operations to become public APIs: https://github.com/elastic/kibana/issues/192153
|
||||
let supertestAdminWithApiKey: SupertestWithRoleScopeType;
|
||||
let supertestAdminWithCookieCredentials: SupertestWithRoleScopeType;
|
||||
|
||||
async function createSpace(id: string) {
|
||||
await supertestAdminWithApiKey
|
||||
.post('/api/spaces/space')
|
||||
.send({
|
||||
id,
|
||||
name: id,
|
||||
disabledFeatures: [],
|
||||
})
|
||||
.expect(200);
|
||||
}
|
||||
|
||||
async function deleteSpace(id: string) {
|
||||
await supertestAdminWithApiKey.delete(`/api/spaces/space/${id}`).expect(204);
|
||||
}
|
||||
|
||||
describe('spaces', function () {
|
||||
before(async () => {
|
||||
supertestAdminWithApiKey = await roleScopedSupertest.getSupertestWithRoleScope('admin', {
|
||||
withCommonHeaders: true,
|
||||
});
|
||||
supertestAdminWithCookieCredentials = await roleScopedSupertest.getSupertestWithRoleScope(
|
||||
'admin',
|
||||
{
|
||||
useCookieHeader: true,
|
||||
withInternalHeaders: true,
|
||||
}
|
||||
);
|
||||
});
|
||||
after(async () => {
|
||||
// delete any lingering spaces
|
||||
const { body } = await supertestAdminWithApiKey.get('/api/spaces/space').send().expect(200);
|
||||
|
||||
const toDelete = (body as Array<{ id: string }>).filter((f) => f.id !== 'default');
|
||||
|
||||
await asyncForEach(toDelete, async (space) => {
|
||||
await deleteSpace(space.id);
|
||||
});
|
||||
|
||||
await supertestAdminWithApiKey.destroy();
|
||||
});
|
||||
|
||||
describe('Create (POST /api/spaces/space)', () => {
|
||||
it('should allow us to create a space', async () => {
|
||||
await supertestAdminWithApiKey
|
||||
.post('/api/spaces/space')
|
||||
.send({
|
||||
id: 'custom_space_1',
|
||||
name: 'custom_space_1',
|
||||
disabledFeatures: [],
|
||||
})
|
||||
.expect(200);
|
||||
});
|
||||
|
||||
it('should not allow us to create a space with disabled features', async () => {
|
||||
await supertestAdminWithApiKey
|
||||
.post('/api/spaces/space')
|
||||
.send({
|
||||
id: 'custom_space_2',
|
||||
name: 'custom_space_2',
|
||||
disabledFeatures: ['discover'],
|
||||
})
|
||||
.expect(400);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Read (GET /api/spaces/space)', () => {
|
||||
before(async () => {
|
||||
await createSpace('space_to_get_1');
|
||||
await createSpace('space_to_get_2');
|
||||
await createSpace('space_to_get_3');
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await deleteSpace('space_to_get_1');
|
||||
await deleteSpace('space_to_get_2');
|
||||
await deleteSpace('space_to_get_3');
|
||||
});
|
||||
|
||||
it('should allow us to get a space', async () => {
|
||||
await supertestAdminWithApiKey.get('/api/spaces/space/space_to_get_1').send().expect(200, {
|
||||
id: 'space_to_get_1',
|
||||
name: 'space_to_get_1',
|
||||
disabledFeatures: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('should allow us to get all spaces', async () => {
|
||||
const { body } = await supertestAdminWithApiKey.get('/api/spaces/space').send().expect(200);
|
||||
|
||||
expect(body).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
_reserved: true,
|
||||
color: '#00bfb3',
|
||||
description: 'This is your default space!',
|
||||
disabledFeatures: [],
|
||||
id: 'default',
|
||||
name: 'Default',
|
||||
},
|
||||
{ id: 'space_to_get_1', name: 'space_to_get_1', disabledFeatures: [] },
|
||||
{ id: 'space_to_get_2', name: 'space_to_get_2', disabledFeatures: [] },
|
||||
{ id: 'space_to_get_3', name: 'space_to_get_3', disabledFeatures: [] },
|
||||
])
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Update (PUT /api/spaces/space)', () => {
|
||||
before(async () => {
|
||||
await createSpace('space_to_update');
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await deleteSpace('space_to_update');
|
||||
});
|
||||
|
||||
it('should allow us to update a space', async () => {
|
||||
await supertestAdminWithApiKey
|
||||
.put('/api/spaces/space/space_to_update')
|
||||
.send({
|
||||
id: 'space_to_update',
|
||||
name: 'some new name',
|
||||
initials: 'SN',
|
||||
disabledFeatures: [],
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
await supertestAdminWithApiKey.get('/api/spaces/space/space_to_update').send().expect(200, {
|
||||
id: 'space_to_update',
|
||||
name: 'some new name',
|
||||
initials: 'SN',
|
||||
disabledFeatures: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('should not allow us to update a space with disabled features', async () => {
|
||||
await supertestAdminWithApiKey
|
||||
.put('/api/spaces/space/space_to_update')
|
||||
.send({
|
||||
id: 'space_to_update',
|
||||
name: 'some new name',
|
||||
initials: 'SN',
|
||||
disabledFeatures: ['discover'],
|
||||
})
|
||||
.expect(400);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Delete (DELETE /api/spaces/space)', () => {
|
||||
it('should allow us to delete a space', async () => {
|
||||
await createSpace('space_to_delete');
|
||||
|
||||
await supertestAdminWithApiKey.delete(`/api/spaces/space/space_to_delete`).expect(204);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Get active space (GET /internal/spaces/_active_space)', () => {
|
||||
before(async () => {
|
||||
await createSpace('foo-space');
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await deleteSpace('foo-space');
|
||||
});
|
||||
|
||||
it('returns the default space', async () => {
|
||||
const response = await supertestAdminWithCookieCredentials
|
||||
.get('/internal/spaces/_active_space')
|
||||
.expect(200);
|
||||
|
||||
const { id, name, _reserved } = response.body;
|
||||
expect({ id, name, _reserved }).toEqual({
|
||||
id: 'default',
|
||||
name: 'Default',
|
||||
_reserved: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('returns the default space when explicitly referenced', async () => {
|
||||
const response = await supertestAdminWithCookieCredentials
|
||||
.get('/s/default/internal/spaces/_active_space')
|
||||
.expect(200);
|
||||
|
||||
const { id, name, _reserved } = response.body;
|
||||
expect({ id, name, _reserved }).toEqual({
|
||||
id: 'default',
|
||||
name: 'Default',
|
||||
_reserved: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('returns the foo space', async () => {
|
||||
await supertestAdminWithCookieCredentials
|
||||
.get('/s/foo-space/internal/spaces/_active_space')
|
||||
.expect(200, {
|
||||
id: 'foo-space',
|
||||
name: 'foo-space',
|
||||
disabledFeatures: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('returns 404 when the space is not found', async () => {
|
||||
await supertestAdminWithCookieCredentials
|
||||
.get('/s/not-found-space/internal/spaces/_active_space')
|
||||
.expect(404, {
|
||||
statusCode: 404,
|
||||
error: 'Not Found',
|
||||
message: 'Saved object [space/not-found-space] not found',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// These tests just test access to API endpoints, in this case
|
||||
// when accessed without internal headers they will return 400
|
||||
// They will be included in deployment agnostic testing once spaces
|
||||
// are enabled in production.
|
||||
describe(`Access`, () => {
|
||||
describe(`internal`, () => {
|
||||
it('#getActiveSpace requires internal header', async () => {
|
||||
let body: any;
|
||||
let status: number;
|
||||
|
||||
({ body, status } = await supertestAdminWithApiKey
|
||||
.get('/internal/spaces/_active_space')
|
||||
.set(samlAuth.getCommonRequestHeader()));
|
||||
// expect a rejection because we're not using the internal header
|
||||
expect(body).toEqual({
|
||||
statusCode: 400,
|
||||
error: 'Bad Request',
|
||||
message: expect.stringContaining(
|
||||
'method [get] exists but is not available with the current configuration'
|
||||
),
|
||||
});
|
||||
expect(status).toBe(400);
|
||||
|
||||
({ body, status } = await supertestAdminWithApiKey
|
||||
.get('/internal/spaces/_active_space')
|
||||
.set(samlAuth.getInternalRequestHeader()));
|
||||
// expect success because we're using the internal header
|
||||
expect(body).toEqual(
|
||||
expect.objectContaining({
|
||||
id: 'default',
|
||||
})
|
||||
);
|
||||
expect(status).toBe(200);
|
||||
});
|
||||
|
||||
it('#copyToSpace requires internal header', async () => {
|
||||
let body: any;
|
||||
let status: number;
|
||||
|
||||
({ body, status } = await supertestAdminWithApiKey.post(
|
||||
'/api/spaces/_copy_saved_objects'
|
||||
));
|
||||
// expect a rejection because we're not using the internal header
|
||||
expect(body).toEqual({
|
||||
statusCode: 400,
|
||||
error: 'Bad Request',
|
||||
message: expect.stringContaining(
|
||||
'method [post] exists but is not available with the current configuration'
|
||||
),
|
||||
});
|
||||
|
||||
({ body, status } = await supertestAdminWithApiKey
|
||||
.post('/api/spaces/_copy_saved_objects')
|
||||
.set(samlAuth.getInternalRequestHeader()));
|
||||
|
||||
svlCommonApi.assertResponseStatusCode(400, status, body);
|
||||
|
||||
// expect 400 for missing body
|
||||
expect(body).toEqual({
|
||||
statusCode: 400,
|
||||
error: 'Bad Request',
|
||||
message: '[request body]: expected a plain object value, but found [null] instead.',
|
||||
});
|
||||
});
|
||||
|
||||
it('#resolveCopyToSpaceErrors requires internal header', async () => {
|
||||
let body: any;
|
||||
let status: number;
|
||||
|
||||
({ body, status } = await supertestAdminWithApiKey.post(
|
||||
'/api/spaces/_resolve_copy_saved_objects_errors'
|
||||
));
|
||||
// expect a rejection because we're not using the internal header
|
||||
expect(body).toEqual({
|
||||
statusCode: 400,
|
||||
error: 'Bad Request',
|
||||
message: expect.stringContaining(
|
||||
'method [post] exists but is not available with the current configuration'
|
||||
),
|
||||
});
|
||||
|
||||
({ body, status } = await supertestAdminWithApiKey
|
||||
.post('/api/spaces/_resolve_copy_saved_objects_errors')
|
||||
.set(samlAuth.getInternalRequestHeader()));
|
||||
|
||||
svlCommonApi.assertResponseStatusCode(400, status, body);
|
||||
|
||||
// expect 400 for missing body
|
||||
expect(body).toEqual({
|
||||
statusCode: 400,
|
||||
error: 'Bad Request',
|
||||
message: '[request body]: expected a plain object value, but found [null] instead.',
|
||||
});
|
||||
});
|
||||
|
||||
it('#updateObjectsSpaces requires internal header', async () => {
|
||||
let body: any;
|
||||
let status: number;
|
||||
|
||||
({ body, status } = await supertestAdminWithApiKey.post(
|
||||
'/api/spaces/_update_objects_spaces'
|
||||
));
|
||||
// expect a rejection because we're not using the internal header
|
||||
expect(body).toEqual({
|
||||
statusCode: 400,
|
||||
error: 'Bad Request',
|
||||
message: expect.stringContaining(
|
||||
'method [post] exists but is not available with the current configuration'
|
||||
),
|
||||
});
|
||||
|
||||
({ body, status } = await supertestAdminWithApiKey
|
||||
.post('/api/spaces/_update_objects_spaces')
|
||||
.set(samlAuth.getInternalRequestHeader()));
|
||||
|
||||
svlCommonApi.assertResponseStatusCode(400, status, body);
|
||||
|
||||
// expect 400 for missing body
|
||||
expect(body).toEqual({
|
||||
statusCode: 400,
|
||||
error: 'Bad Request',
|
||||
message: '[request body]: expected a plain object value, but found [null] instead.',
|
||||
});
|
||||
});
|
||||
|
||||
it('#getShareableReferences requires internal header', async () => {
|
||||
let body: any;
|
||||
let status: number;
|
||||
|
||||
({ body, status } = await supertestAdminWithApiKey.post(
|
||||
'/api/spaces/_get_shareable_references'
|
||||
));
|
||||
// expect a rejection because we're not using the internal header
|
||||
expect(body).toEqual({
|
||||
statusCode: 400,
|
||||
error: 'Bad Request',
|
||||
message: expect.stringContaining(
|
||||
'method [post] exists but is not available with the current configuration'
|
||||
),
|
||||
});
|
||||
|
||||
({ body, status } = await supertestAdminWithApiKey
|
||||
.post('/api/spaces/_get_shareable_references')
|
||||
.set(samlAuth.getInternalRequestHeader())
|
||||
.send({
|
||||
objects: [{ type: 'a', id: 'a' }],
|
||||
}));
|
||||
|
||||
svlCommonApi.assertResponseStatusCode(200, status, body);
|
||||
});
|
||||
});
|
||||
|
||||
describe(`disabled`, () => {
|
||||
it('#disableLegacyUrlAliases', async () => {
|
||||
const { body, status } = await supertestAdminWithApiKey.post(
|
||||
'/api/spaces/_disable_legacy_url_aliases'
|
||||
);
|
||||
// without a request body we would normally a 400 bad request if the endpoint was registered
|
||||
svlCommonApi.assertApiNotFound(body, status);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import expect from 'expect';
|
||||
import { SupertestWithRoleScopeType } from '@kbn/test-suites-xpack/api_integration/deployment_agnostic/services';
|
||||
import { asyncForEach } from '@kbn/std';
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
|
||||
export default function ({ getService }: FtrProviderContext) {
|
||||
|
@ -16,6 +17,21 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
let supertestAdminWithApiKey: SupertestWithRoleScopeType;
|
||||
let supertestAdminWithCookieCredentials: SupertestWithRoleScopeType;
|
||||
|
||||
async function createSpace(id: string) {
|
||||
await supertestAdminWithApiKey
|
||||
.post('/api/spaces/space')
|
||||
.send({
|
||||
id,
|
||||
name: id,
|
||||
disabledFeatures: [],
|
||||
})
|
||||
.expect(200);
|
||||
}
|
||||
|
||||
async function deleteSpace(id: string) {
|
||||
await supertestAdminWithApiKey.delete(`/api/spaces/space/${id}`).expect(204);
|
||||
}
|
||||
|
||||
describe('spaces', function () {
|
||||
before(async () => {
|
||||
// admin is the only predefined role that will work for all 3 solutions
|
||||
|
@ -34,76 +50,212 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
await supertestAdminWithApiKey.destroy();
|
||||
});
|
||||
|
||||
describe('route access', () => {
|
||||
describe('public (CRUD)', () => {
|
||||
// Skipped due to change in QA environment for role management and spaces
|
||||
// TODO: revisit once the change is rolled out to all environments
|
||||
it.skip('#create', async () => {
|
||||
const { body, status } = await supertestAdminWithApiKey.post('/api/spaces/space').send({
|
||||
id: 'custom',
|
||||
name: 'Custom',
|
||||
disabledFeatures: [],
|
||||
});
|
||||
// The create and update test cases are unique to serverless because
|
||||
// setting feature visibility is not possible in serverless
|
||||
describe('CRUD', () => {
|
||||
after(async () => {
|
||||
// delete any lingering spaces
|
||||
const { body } = await supertestAdminWithApiKey.get('/api/spaces/space').send().expect(200);
|
||||
|
||||
svlCommonApi.assertResponseStatusCode(400, status, body);
|
||||
const toDelete = (body as Array<{ id: string }>).filter((f) => f.id !== 'default');
|
||||
|
||||
// Should fail due to maximum spaces limit, not because of lacking internal header
|
||||
expect(body).toEqual({
|
||||
statusCode: 400,
|
||||
error: 'Bad Request',
|
||||
message:
|
||||
'Unable to create Space, this exceeds the maximum number of spaces set by the xpack.spaces.maxSpaces setting',
|
||||
});
|
||||
});
|
||||
|
||||
it('#get', async () => {
|
||||
const { body, status } = await supertestAdminWithApiKey.get('/api/spaces/space/default');
|
||||
// expect success because we're using the internal header
|
||||
expect(body).toEqual(expect.objectContaining({ id: 'default' }));
|
||||
expect(status).toBe(200);
|
||||
});
|
||||
|
||||
it('#getAll', async () => {
|
||||
const { body, status } = await supertestAdminWithApiKey.get('/api/spaces/space');
|
||||
// expect success because we're using the internal header
|
||||
expect(body).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: 'default',
|
||||
}),
|
||||
])
|
||||
);
|
||||
expect(status).toBe(200);
|
||||
});
|
||||
|
||||
it('#update', async () => {
|
||||
const { body, status } = await supertestAdminWithApiKey
|
||||
.put('/api/spaces/space/default')
|
||||
.send({
|
||||
id: 'default',
|
||||
name: 'UPDATED!',
|
||||
disabledFeatures: [],
|
||||
});
|
||||
|
||||
svlCommonApi.assertResponseStatusCode(200, status, body);
|
||||
});
|
||||
|
||||
it('#delete', async () => {
|
||||
const { body, status } = await supertestAdminWithApiKey.delete(
|
||||
'/api/spaces/space/default'
|
||||
);
|
||||
|
||||
svlCommonApi.assertResponseStatusCode(400, status, body);
|
||||
|
||||
// 400 with specific reason - cannot delete the default space
|
||||
expect(body).toEqual({
|
||||
statusCode: 400,
|
||||
error: 'Bad Request',
|
||||
message: 'The default space cannot be deleted because it is reserved.',
|
||||
});
|
||||
await asyncForEach(toDelete, async (space) => {
|
||||
await deleteSpace(space.id);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Create (POST /api/spaces/space)', () => {
|
||||
it('should allow us to create a space', async () => {
|
||||
await supertestAdminWithApiKey
|
||||
.post('/api/spaces/space')
|
||||
.send({
|
||||
id: 'custom_space_1',
|
||||
name: 'custom_space_1',
|
||||
disabledFeatures: [],
|
||||
})
|
||||
.expect(200);
|
||||
});
|
||||
|
||||
it('should not allow us to create a space with disabled features', async () => {
|
||||
await supertestAdminWithApiKey
|
||||
.post('/api/spaces/space')
|
||||
.send({
|
||||
id: 'custom_space_2',
|
||||
name: 'custom_space_2',
|
||||
disabledFeatures: ['discover'],
|
||||
})
|
||||
.expect(400);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Read (GET /api/spaces/space)', () => {
|
||||
before(async () => {
|
||||
await createSpace('space_to_get_1');
|
||||
await createSpace('space_to_get_2');
|
||||
await createSpace('space_to_get_3');
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await deleteSpace('space_to_get_1');
|
||||
await deleteSpace('space_to_get_2');
|
||||
await deleteSpace('space_to_get_3');
|
||||
});
|
||||
|
||||
it('should allow us to get a space', async () => {
|
||||
await supertestAdminWithApiKey
|
||||
.get('/api/spaces/space/space_to_get_1')
|
||||
.send()
|
||||
.expect(200, {
|
||||
id: 'space_to_get_1',
|
||||
name: 'space_to_get_1',
|
||||
disabledFeatures: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('should allow us to get all spaces', async () => {
|
||||
const { body } = await supertestAdminWithApiKey
|
||||
.get('/api/spaces/space')
|
||||
.send()
|
||||
.expect(200);
|
||||
|
||||
expect(body).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
_reserved: true,
|
||||
color: '#00bfb3',
|
||||
description: 'This is your default space!',
|
||||
disabledFeatures: [],
|
||||
id: 'default',
|
||||
name: 'Default',
|
||||
},
|
||||
{ id: 'space_to_get_1', name: 'space_to_get_1', disabledFeatures: [] },
|
||||
{ id: 'space_to_get_2', name: 'space_to_get_2', disabledFeatures: [] },
|
||||
{ id: 'space_to_get_3', name: 'space_to_get_3', disabledFeatures: [] },
|
||||
])
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Update (PUT /api/spaces/space)', () => {
|
||||
before(async () => {
|
||||
await createSpace('space_to_update');
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await deleteSpace('space_to_update');
|
||||
});
|
||||
|
||||
it('should allow us to update a space', async () => {
|
||||
await supertestAdminWithApiKey
|
||||
.put('/api/spaces/space/space_to_update')
|
||||
.send({
|
||||
id: 'space_to_update',
|
||||
name: 'some new name',
|
||||
initials: 'SN',
|
||||
disabledFeatures: [],
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
await supertestAdminWithApiKey
|
||||
.get('/api/spaces/space/space_to_update')
|
||||
.send()
|
||||
.expect(200, {
|
||||
id: 'space_to_update',
|
||||
name: 'some new name',
|
||||
initials: 'SN',
|
||||
disabledFeatures: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('should not allow us to update a space with disabled features', async () => {
|
||||
await supertestAdminWithApiKey
|
||||
.put('/api/spaces/space/space_to_update')
|
||||
.send({
|
||||
id: 'space_to_update',
|
||||
name: 'some new name',
|
||||
initials: 'SN',
|
||||
disabledFeatures: ['discover'],
|
||||
})
|
||||
.expect(400);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Delete (DELETE /api/spaces/space)', () => {
|
||||
it('should allow us to delete a space', async () => {
|
||||
await createSpace('space_to_delete');
|
||||
|
||||
await supertestAdminWithApiKey.delete(`/api/spaces/space/space_to_delete`).expect(204);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Get active space (GET /internal/spaces/_active_space)', () => {
|
||||
before(async () => {
|
||||
await createSpace('foo-space');
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await deleteSpace('foo-space');
|
||||
});
|
||||
|
||||
it('returns the default space', async () => {
|
||||
const response = await supertestAdminWithCookieCredentials
|
||||
.get('/internal/spaces/_active_space')
|
||||
.set(samlAuth.getInternalRequestHeader())
|
||||
.expect(200);
|
||||
|
||||
const { id, name, _reserved } = response.body;
|
||||
expect({ id, name, _reserved }).toEqual({
|
||||
id: 'default',
|
||||
name: 'Default',
|
||||
_reserved: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('returns the default space when explicitly referenced', async () => {
|
||||
const response = await supertestAdminWithCookieCredentials
|
||||
.get('/s/default/internal/spaces/_active_space')
|
||||
.set(samlAuth.getInternalRequestHeader())
|
||||
.expect(200);
|
||||
|
||||
const { id, name, _reserved } = response.body;
|
||||
expect({ id, name, _reserved }).toEqual({
|
||||
id: 'default',
|
||||
name: 'Default',
|
||||
_reserved: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('returns the foo space', async () => {
|
||||
await supertestAdminWithCookieCredentials
|
||||
.get('/s/foo-space/internal/spaces/_active_space')
|
||||
.set(samlAuth.getInternalRequestHeader())
|
||||
.expect(200, {
|
||||
id: 'foo-space',
|
||||
name: 'foo-space',
|
||||
disabledFeatures: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('returns 404 when the space is not found', async () => {
|
||||
await supertestAdminWithCookieCredentials
|
||||
.get('/s/not-found-space/internal/spaces/_active_space')
|
||||
.set(samlAuth.getInternalRequestHeader())
|
||||
.expect(404, {
|
||||
statusCode: 404,
|
||||
error: 'Not Found',
|
||||
message: 'Saved object [space/not-found-space] not found',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('route access', () => {
|
||||
// The 'internal route access' tests check that the internal header
|
||||
// is needed for these specific endpoints.
|
||||
// When accessed without internal headers they will return 400.
|
||||
// They could be moved to deployment agnostic testing if there is
|
||||
// a way to specify which tests to run when stateful vs serverles,
|
||||
// as internal vs disabled is different in serverless.
|
||||
describe('internal', () => {
|
||||
it('#getActiveSpace requires internal header', async () => {
|
||||
let body: any;
|
||||
|
@ -251,6 +403,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
});
|
||||
});
|
||||
|
||||
// Disabled in serverless, but public in stateful
|
||||
describe('disabled', () => {
|
||||
it('#disableLegacyUrlAliases', async () => {
|
||||
const { body, status } = await supertestAdminWithApiKey
|
||||
|
@ -262,26 +415,5 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
// TODO: Re-enable test-suite once users can create and update spaces in the Serverless offering.
|
||||
// it('rejects request to update a space with disabledFeatures', async () => {
|
||||
// const { body, status } = await supertest
|
||||
// .put('/api/spaces/space/default')
|
||||
// .set(svlCommonApi.getInternalRequestHeader())
|
||||
// .send({
|
||||
// id: 'custom',
|
||||
// name: 'Custom',
|
||||
// disabledFeatures: ['some-feature'],
|
||||
// });
|
||||
//
|
||||
// // in a non-serverless environment this would succeed with a 200
|
||||
// expect(body).toEqual({
|
||||
// statusCode: 400,
|
||||
// error: 'Bad Request',
|
||||
// message:
|
||||
// 'Unable to update Space, the disabledFeatures array must be empty when xpack.spaces.allowFeatureVisibility setting is disabled',
|
||||
// });
|
||||
// expect(status).toBe(400);
|
||||
// });
|
||||
});
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,951 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import expect from 'expect';
|
||||
import type { Role } from '@kbn/security-plugin-types-common';
|
||||
import { SupertestWithRoleScopeType } from '@kbn/test-suites-xpack/api_integration/deployment_agnostic/services';
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
|
||||
// Notes:
|
||||
// Test coverage comes from stateful test suite: x-pack/test/api_integration/apis/security/roles.ts
|
||||
// It has been modified to work for serverless by removing invalid options (run_as, allow_restricted_indices, etc).
|
||||
//
|
||||
// Note: this suite is currently only called from the feature flags test configs, e.g.
|
||||
// x-pack/test_serverless/api_integration/test_suites/search/config.feature_flags.ts
|
||||
//
|
||||
// This suite should be converted into a deployment agnostic suite when the native roles
|
||||
// feature flags are enabled permanently in serverless. Additionally, the route access tests
|
||||
// for the roles APIs in authorization.ts should also get updated at that time.
|
||||
// kbnServerArgs: ['--xpack.security.roleManagementEnabled=true'],
|
||||
// esServerArgs: ['xpack.security.authc.native_roles.enabled=true'],
|
||||
|
||||
export default function ({ getService }: FtrProviderContext) {
|
||||
const platformSecurityUtils = getService('platformSecurityUtils');
|
||||
const roleScopedSupertest = getService('roleScopedSupertest');
|
||||
const svlCommonApi = getService('svlCommonApi');
|
||||
let supertestAdminWithApiKey: SupertestWithRoleScopeType;
|
||||
let supertestAdminWithCookieCredentials: SupertestWithRoleScopeType;
|
||||
const es = getService('es');
|
||||
|
||||
describe('security', function () {
|
||||
describe('Roles', () => {
|
||||
before(async () => {
|
||||
supertestAdminWithCookieCredentials = await roleScopedSupertest.getSupertestWithRoleScope(
|
||||
'admin',
|
||||
{
|
||||
useCookieHeader: true,
|
||||
withInternalHeaders: true,
|
||||
}
|
||||
);
|
||||
supertestAdminWithApiKey = await roleScopedSupertest.getSupertestWithRoleScope('admin', {
|
||||
withCommonHeaders: true,
|
||||
});
|
||||
});
|
||||
after(async () => {
|
||||
await platformSecurityUtils.clearAllRoles();
|
||||
});
|
||||
|
||||
describe('Create Role', () => {
|
||||
it('should allow us to create an empty role', async () => {
|
||||
await supertestAdminWithApiKey.put('/api/security/role/empty_role').send({}).expect(204);
|
||||
});
|
||||
|
||||
it('should create a role with kibana and elasticsearch privileges', async () => {
|
||||
await supertestAdminWithApiKey
|
||||
.put('/api/security/role/role_with_privileges')
|
||||
.send({
|
||||
metadata: {
|
||||
foo: 'test-metadata',
|
||||
},
|
||||
elasticsearch: {
|
||||
cluster: ['manage'],
|
||||
indices: [
|
||||
{
|
||||
names: ['logstash-*'],
|
||||
privileges: ['read', 'view_index_metadata'],
|
||||
},
|
||||
],
|
||||
},
|
||||
kibana: [
|
||||
{
|
||||
base: ['read'],
|
||||
},
|
||||
{
|
||||
feature: {
|
||||
dashboard: ['read'],
|
||||
discover: ['all'],
|
||||
ml: ['all'],
|
||||
},
|
||||
spaces: ['marketing', 'sales'],
|
||||
},
|
||||
],
|
||||
})
|
||||
.expect(204);
|
||||
|
||||
const role = await es.security.getRole({ name: 'role_with_privileges' });
|
||||
expect(role).toEqual({
|
||||
role_with_privileges: {
|
||||
cluster: ['manage'],
|
||||
indices: [
|
||||
{
|
||||
names: ['logstash-*'],
|
||||
privileges: ['read', 'view_index_metadata'],
|
||||
allow_restricted_indices: false,
|
||||
},
|
||||
],
|
||||
applications: [
|
||||
{
|
||||
application: 'kibana-.kibana',
|
||||
privileges: ['read'],
|
||||
resources: ['*'],
|
||||
},
|
||||
{
|
||||
application: 'kibana-.kibana',
|
||||
privileges: ['feature_dashboard.read', 'feature_discover.all', 'feature_ml.all'],
|
||||
resources: ['space:marketing', 'space:sales'],
|
||||
},
|
||||
],
|
||||
metadata: {
|
||||
foo: 'test-metadata',
|
||||
},
|
||||
run_as: [],
|
||||
transient_metadata: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it(`should create a role with kibana and FLS/DLS elasticsearch privileges`, async () => {
|
||||
await supertestAdminWithApiKey
|
||||
.put('/api/security/role/role_with_privileges_dls_fls')
|
||||
.send({
|
||||
metadata: {
|
||||
foo: 'test-metadata',
|
||||
},
|
||||
elasticsearch: {
|
||||
cluster: ['manage'],
|
||||
indices: [
|
||||
{
|
||||
field_security: {
|
||||
grant: ['*'],
|
||||
except: ['geo.*'],
|
||||
},
|
||||
names: ['logstash-*'],
|
||||
privileges: ['read', 'view_index_metadata'],
|
||||
query: `{ "match": { "geo.src": "CN" } }`,
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
.expect(204);
|
||||
});
|
||||
|
||||
// serverless only (stateful will allow)
|
||||
it(`should not create a role with 'run as' privileges`, async () => {
|
||||
await supertestAdminWithApiKey
|
||||
.put('/api/security/role/role_with_privileges')
|
||||
.send({
|
||||
metadata: {
|
||||
foo: 'test-metadata',
|
||||
},
|
||||
elasticsearch: {
|
||||
cluster: ['manage'],
|
||||
indices: [
|
||||
{
|
||||
names: ['logstash-*'],
|
||||
privileges: ['read', 'view_index_metadata'],
|
||||
},
|
||||
],
|
||||
run_as: ['admin'],
|
||||
},
|
||||
kibana: [
|
||||
{
|
||||
base: ['read'],
|
||||
},
|
||||
{
|
||||
feature: {
|
||||
dashboard: ['read'],
|
||||
discover: ['all'],
|
||||
ml: ['all'],
|
||||
},
|
||||
spaces: ['marketing', 'sales'],
|
||||
},
|
||||
],
|
||||
})
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
// serverless only (stateful will allow)
|
||||
it(`should not create a role with remote cluster privileges`, async () => {
|
||||
await supertestAdminWithApiKey
|
||||
.put('/api/security/role/role_with_privileges')
|
||||
.send({
|
||||
metadata: {
|
||||
foo: 'test-metadata',
|
||||
},
|
||||
elasticsearch: {
|
||||
cluster: ['manage'],
|
||||
indices: [
|
||||
{
|
||||
names: ['logstash-*'],
|
||||
privileges: ['read', 'view_index_metadata'],
|
||||
},
|
||||
],
|
||||
remote_cluster: [
|
||||
{
|
||||
clusters: ['remote_cluster1'],
|
||||
privileges: ['monitor_enrich'],
|
||||
},
|
||||
],
|
||||
},
|
||||
kibana: [
|
||||
{
|
||||
base: ['read'],
|
||||
},
|
||||
{
|
||||
feature: {
|
||||
dashboard: ['read'],
|
||||
discover: ['all'],
|
||||
ml: ['all'],
|
||||
},
|
||||
spaces: ['marketing', 'sales'],
|
||||
},
|
||||
],
|
||||
})
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
// serverless only (stateful will allow)
|
||||
it(`should not create a role with remote index privileges`, async () => {
|
||||
await supertestAdminWithApiKey
|
||||
.put('/api/security/role/role_with_privileges')
|
||||
.send({
|
||||
metadata: {
|
||||
foo: 'test-metadata',
|
||||
},
|
||||
elasticsearch: {
|
||||
cluster: ['manage'],
|
||||
indices: [
|
||||
{
|
||||
names: ['logstash-*'],
|
||||
privileges: ['read', 'view_index_metadata'],
|
||||
},
|
||||
],
|
||||
remote_indices: [
|
||||
{
|
||||
clusters: ['remote_cluster1'],
|
||||
names: ['remote_index1', 'remote_index2'],
|
||||
privileges: ['all'],
|
||||
},
|
||||
],
|
||||
},
|
||||
kibana: [
|
||||
{
|
||||
base: ['read'],
|
||||
},
|
||||
{
|
||||
feature: {
|
||||
dashboard: ['read'],
|
||||
discover: ['all'],
|
||||
ml: ['all'],
|
||||
},
|
||||
spaces: ['marketing', 'sales'],
|
||||
},
|
||||
],
|
||||
})
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
describe('with the createOnly option enabled', () => {
|
||||
it('should fail when role already exists', async () => {
|
||||
await es.security.putRole({
|
||||
name: 'test_role',
|
||||
body: {
|
||||
cluster: ['monitor'],
|
||||
indices: [
|
||||
{
|
||||
names: ['beats-*'],
|
||||
privileges: ['write'],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
await supertestAdminWithApiKey
|
||||
.put('/api/security/role/test_role?createOnly=true')
|
||||
.send({})
|
||||
.expect(409);
|
||||
});
|
||||
|
||||
it('should succeed when role does not exist', async () => {
|
||||
await supertestAdminWithApiKey
|
||||
.put('/api/security/role/new_role?createOnly=true')
|
||||
.send({})
|
||||
.expect(204);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Read Role', () => {
|
||||
it('should get roles', async () => {
|
||||
await es.security.putRole({
|
||||
name: 'role_to_get',
|
||||
body: {
|
||||
cluster: ['manage'],
|
||||
indices: [
|
||||
{
|
||||
names: ['logstash-*'],
|
||||
privileges: ['read', 'view_index_metadata'],
|
||||
},
|
||||
],
|
||||
applications: [
|
||||
{
|
||||
application: 'kibana-.kibana',
|
||||
privileges: ['read'],
|
||||
resources: ['*'],
|
||||
},
|
||||
{
|
||||
application: 'kibana-.kibana',
|
||||
privileges: ['feature_dashboard.read', 'feature_discover.all', 'feature_ml.all'],
|
||||
resources: ['space:marketing', 'space:sales'],
|
||||
},
|
||||
{
|
||||
application: 'apm',
|
||||
privileges: ['apm-privilege'],
|
||||
resources: ['*'],
|
||||
},
|
||||
],
|
||||
metadata: {
|
||||
foo: 'test-metadata',
|
||||
},
|
||||
transient_metadata: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await supertestAdminWithApiKey.get('/api/security/role/role_to_get').expect(200, {
|
||||
name: 'role_to_get',
|
||||
metadata: {
|
||||
foo: 'test-metadata',
|
||||
},
|
||||
transient_metadata: { enabled: true },
|
||||
elasticsearch: {
|
||||
cluster: ['manage'],
|
||||
indices: [
|
||||
{
|
||||
names: ['logstash-*'],
|
||||
privileges: ['read', 'view_index_metadata'],
|
||||
allow_restricted_indices: false,
|
||||
},
|
||||
],
|
||||
run_as: [],
|
||||
},
|
||||
kibana: [
|
||||
{
|
||||
base: ['read'],
|
||||
feature: {},
|
||||
spaces: ['*'],
|
||||
},
|
||||
{
|
||||
base: [],
|
||||
feature: {
|
||||
dashboard: ['read'],
|
||||
discover: ['all'],
|
||||
ml: ['all'],
|
||||
},
|
||||
spaces: ['marketing', 'sales'],
|
||||
},
|
||||
],
|
||||
|
||||
_transform_error: [],
|
||||
_unrecognized_applications: ['apm'],
|
||||
});
|
||||
});
|
||||
|
||||
it('should get roles by space id', async () => {
|
||||
await es.security.putRole({
|
||||
name: 'space_role_not_to_get',
|
||||
body: {
|
||||
cluster: ['manage'],
|
||||
indices: [
|
||||
{
|
||||
names: ['logstash-*'],
|
||||
privileges: ['read', 'view_index_metadata'],
|
||||
},
|
||||
],
|
||||
applications: [
|
||||
{
|
||||
application: 'kibana-.kibana',
|
||||
privileges: ['feature_dashboard.read', 'feature_discover.all', 'feature_ml.all'],
|
||||
resources: ['space:marketing', 'space:sales'],
|
||||
},
|
||||
],
|
||||
metadata: {
|
||||
foo: 'test-metadata',
|
||||
},
|
||||
transient_metadata: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await es.security.putRole({
|
||||
name: 'space_role_to_get',
|
||||
body: {
|
||||
cluster: ['manage'],
|
||||
indices: [
|
||||
{
|
||||
names: ['logstash-*'],
|
||||
privileges: ['read', 'view_index_metadata'],
|
||||
},
|
||||
],
|
||||
applications: [
|
||||
{
|
||||
application: 'kibana-.kibana',
|
||||
privileges: ['feature_dashboard.read', 'feature_discover.all', 'feature_ml.all'],
|
||||
resources: ['space:engineering', 'space:sales'],
|
||||
},
|
||||
],
|
||||
metadata: {
|
||||
foo: 'test-metadata',
|
||||
},
|
||||
transient_metadata: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await supertestAdminWithCookieCredentials
|
||||
.get('/internal/security/roles/engineering')
|
||||
.expect(200)
|
||||
.expect((res: { body: Role[] }) => {
|
||||
const roles = res.body;
|
||||
|
||||
const success = roles.every((role) => {
|
||||
return (
|
||||
role.name !== 'space_role_not_to_get' &&
|
||||
role.kibana.some((privilege) => {
|
||||
return (
|
||||
privilege.spaces.includes('*') || privilege.spaces.includes('engineering')
|
||||
);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
const expectedRole = roles.find((role) => role.name === 'space_role_to_get');
|
||||
|
||||
expect(success).toBe(true);
|
||||
expect(expectedRole).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Update Role', () => {
|
||||
it('should update a role with elasticsearch, kibana and other applications privileges', async () => {
|
||||
await es.security.putRole({
|
||||
name: 'role_to_update',
|
||||
body: {
|
||||
cluster: ['monitor'],
|
||||
indices: [
|
||||
{
|
||||
names: ['beats-*'],
|
||||
privileges: ['write'],
|
||||
},
|
||||
],
|
||||
applications: [
|
||||
{
|
||||
application: 'kibana-.kibana',
|
||||
privileges: ['read'],
|
||||
resources: ['*'],
|
||||
},
|
||||
{
|
||||
application: 'apm',
|
||||
privileges: ['apm-privilege'],
|
||||
resources: ['*'],
|
||||
},
|
||||
],
|
||||
metadata: {
|
||||
bar: 'old-metadata',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await supertestAdminWithApiKey
|
||||
.put('/api/security/role/role_to_update')
|
||||
.send({
|
||||
metadata: {
|
||||
foo: 'test-metadata',
|
||||
},
|
||||
elasticsearch: {
|
||||
cluster: ['manage'],
|
||||
indices: [
|
||||
{
|
||||
names: ['logstash-*'],
|
||||
privileges: ['read', 'view_index_metadata'],
|
||||
},
|
||||
],
|
||||
},
|
||||
kibana: [
|
||||
{
|
||||
feature: {
|
||||
dashboard: ['read'],
|
||||
dev_tools: ['all'],
|
||||
},
|
||||
spaces: ['*'],
|
||||
},
|
||||
{
|
||||
base: ['all'],
|
||||
spaces: ['marketing', 'sales'],
|
||||
},
|
||||
],
|
||||
})
|
||||
.expect(204);
|
||||
|
||||
const role = await es.security.getRole({ name: 'role_to_update' });
|
||||
expect(role).toEqual({
|
||||
role_to_update: {
|
||||
cluster: ['manage'],
|
||||
indices: [
|
||||
{
|
||||
names: ['logstash-*'],
|
||||
privileges: ['read', 'view_index_metadata'],
|
||||
allow_restricted_indices: false,
|
||||
},
|
||||
],
|
||||
applications: [
|
||||
{
|
||||
application: 'kibana-.kibana',
|
||||
privileges: ['feature_dashboard.read', 'feature_dev_tools.all'],
|
||||
resources: ['*'],
|
||||
},
|
||||
{
|
||||
application: 'kibana-.kibana',
|
||||
privileges: ['space_all'],
|
||||
resources: ['space:marketing', 'space:sales'],
|
||||
},
|
||||
{
|
||||
application: 'apm',
|
||||
privileges: ['apm-privilege'],
|
||||
resources: ['*'],
|
||||
},
|
||||
],
|
||||
metadata: {
|
||||
foo: 'test-metadata',
|
||||
},
|
||||
run_as: [],
|
||||
transient_metadata: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it(`should update a role adding DLS and FLS privileges`, async () => {
|
||||
await es.security.putRole({
|
||||
name: 'role_to_update_with_dls_fls',
|
||||
body: {
|
||||
cluster: ['monitor'],
|
||||
indices: [
|
||||
{
|
||||
names: ['beats-*'],
|
||||
privileges: ['write'],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
await supertestAdminWithApiKey
|
||||
.put('/api/security/role/role_to_update_with_dls_fls')
|
||||
.send({
|
||||
elasticsearch: {
|
||||
cluster: ['manage'],
|
||||
indices: [
|
||||
{
|
||||
field_security: {
|
||||
grant: ['*'],
|
||||
except: ['geo.*'],
|
||||
},
|
||||
names: ['logstash-*'],
|
||||
privileges: ['read'],
|
||||
query: `{ "match": { "geo.src": "CN" } }`,
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
.expect(204);
|
||||
|
||||
const role = await es.security.getRole({ name: 'role_to_update_with_dls_fls' });
|
||||
|
||||
expect(role.role_to_update_with_dls_fls.cluster).toEqual(['manage']);
|
||||
expect(role.role_to_update_with_dls_fls.indices[0].names).toEqual(['logstash-*']);
|
||||
expect(role.role_to_update_with_dls_fls.indices[0].query).toEqual(
|
||||
`{ "match": { "geo.src": "CN" } }`
|
||||
);
|
||||
});
|
||||
|
||||
// serverless only (stateful will allow)
|
||||
it(`should not update a role with 'run as' privileges`, async () => {
|
||||
await es.security.putRole({
|
||||
name: 'role_to_update',
|
||||
body: {
|
||||
cluster: ['monitor'],
|
||||
indices: [
|
||||
{
|
||||
names: ['beats-*'],
|
||||
privileges: ['write'],
|
||||
},
|
||||
],
|
||||
applications: [
|
||||
{
|
||||
application: 'kibana-.kibana',
|
||||
privileges: ['read'],
|
||||
resources: ['*'],
|
||||
},
|
||||
{
|
||||
application: 'apm',
|
||||
privileges: ['apm-privilege'],
|
||||
resources: ['*'],
|
||||
},
|
||||
],
|
||||
metadata: {
|
||||
bar: 'old-metadata',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await supertestAdminWithApiKey
|
||||
.put('/api/security/role/role_to_update')
|
||||
.send({
|
||||
metadata: {
|
||||
foo: 'test-metadata',
|
||||
},
|
||||
elasticsearch: {
|
||||
cluster: ['manage'],
|
||||
indices: [
|
||||
{
|
||||
names: ['logstash-*'],
|
||||
privileges: ['read', 'view_index_metadata'],
|
||||
},
|
||||
],
|
||||
run_as: ['admin'],
|
||||
},
|
||||
kibana: [
|
||||
{
|
||||
feature: {
|
||||
dashboard: ['read'],
|
||||
dev_tools: ['all'],
|
||||
},
|
||||
spaces: ['*'],
|
||||
},
|
||||
{
|
||||
base: ['all'],
|
||||
spaces: ['marketing', 'sales'],
|
||||
},
|
||||
],
|
||||
})
|
||||
.expect(400);
|
||||
|
||||
const role = await es.security.getRole({ name: 'role_to_update' });
|
||||
expect(role).toEqual({
|
||||
role_to_update: {
|
||||
cluster: ['monitor'],
|
||||
indices: [
|
||||
{
|
||||
names: ['beats-*'],
|
||||
privileges: ['write'],
|
||||
allow_restricted_indices: false,
|
||||
},
|
||||
],
|
||||
applications: [
|
||||
{
|
||||
application: 'kibana-.kibana',
|
||||
privileges: ['read'],
|
||||
resources: ['*'],
|
||||
},
|
||||
{
|
||||
application: 'apm',
|
||||
privileges: ['apm-privilege'],
|
||||
resources: ['*'],
|
||||
},
|
||||
],
|
||||
metadata: {
|
||||
bar: 'old-metadata',
|
||||
},
|
||||
run_as: [],
|
||||
transient_metadata: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
// serverless only (stateful will allow)
|
||||
it(`should not update a role with remote cluster privileges`, async () => {
|
||||
await es.security.putRole({
|
||||
name: 'role_to_update',
|
||||
body: {
|
||||
cluster: ['monitor'],
|
||||
indices: [
|
||||
{
|
||||
names: ['beats-*'],
|
||||
privileges: ['write'],
|
||||
},
|
||||
],
|
||||
applications: [
|
||||
{
|
||||
application: 'kibana-.kibana',
|
||||
privileges: ['read'],
|
||||
resources: ['*'],
|
||||
},
|
||||
{
|
||||
application: 'apm',
|
||||
privileges: ['apm-privilege'],
|
||||
resources: ['*'],
|
||||
},
|
||||
],
|
||||
metadata: {
|
||||
bar: 'old-metadata',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await supertestAdminWithApiKey
|
||||
.put('/api/security/role/role_to_update')
|
||||
.send({
|
||||
metadata: {
|
||||
foo: 'test-metadata',
|
||||
},
|
||||
elasticsearch: {
|
||||
cluster: ['manage'],
|
||||
indices: [
|
||||
{
|
||||
names: ['logstash-*'],
|
||||
privileges: ['read', 'view_index_metadata'],
|
||||
},
|
||||
],
|
||||
remote_cluster: [
|
||||
{
|
||||
clusters: ['remote_cluster1'],
|
||||
privileges: ['monitor_enrich'],
|
||||
},
|
||||
],
|
||||
},
|
||||
kibana: [
|
||||
{
|
||||
feature: {
|
||||
dashboard: ['read'],
|
||||
dev_tools: ['all'],
|
||||
},
|
||||
spaces: ['*'],
|
||||
},
|
||||
{
|
||||
base: ['all'],
|
||||
spaces: ['marketing', 'sales'],
|
||||
},
|
||||
],
|
||||
})
|
||||
.expect(400);
|
||||
|
||||
const role = await es.security.getRole({ name: 'role_to_update' });
|
||||
expect(role).toEqual({
|
||||
role_to_update: {
|
||||
cluster: ['monitor'],
|
||||
indices: [
|
||||
{
|
||||
names: ['beats-*'],
|
||||
privileges: ['write'],
|
||||
allow_restricted_indices: false,
|
||||
},
|
||||
],
|
||||
applications: [
|
||||
{
|
||||
application: 'kibana-.kibana',
|
||||
privileges: ['read'],
|
||||
resources: ['*'],
|
||||
},
|
||||
{
|
||||
application: 'apm',
|
||||
privileges: ['apm-privilege'],
|
||||
resources: ['*'],
|
||||
},
|
||||
],
|
||||
metadata: {
|
||||
bar: 'old-metadata',
|
||||
},
|
||||
run_as: [],
|
||||
transient_metadata: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
// serverless only (stateful will allow)
|
||||
it(`should not update a role with remote index privileges`, async () => {
|
||||
await es.security.putRole({
|
||||
name: 'role_to_update',
|
||||
body: {
|
||||
cluster: ['monitor'],
|
||||
indices: [
|
||||
{
|
||||
names: ['beats-*'],
|
||||
privileges: ['write'],
|
||||
},
|
||||
],
|
||||
applications: [
|
||||
{
|
||||
application: 'kibana-.kibana',
|
||||
privileges: ['read'],
|
||||
resources: ['*'],
|
||||
},
|
||||
{
|
||||
application: 'apm',
|
||||
privileges: ['apm-privilege'],
|
||||
resources: ['*'],
|
||||
},
|
||||
],
|
||||
metadata: {
|
||||
bar: 'old-metadata',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await supertestAdminWithApiKey
|
||||
.put('/api/security/role/role_to_update')
|
||||
.send({
|
||||
metadata: {
|
||||
foo: 'test-metadata',
|
||||
},
|
||||
elasticsearch: {
|
||||
cluster: ['manage'],
|
||||
indices: [
|
||||
{
|
||||
names: ['logstash-*'],
|
||||
privileges: ['read', 'view_index_metadata'],
|
||||
},
|
||||
],
|
||||
remote_indices: [
|
||||
{
|
||||
clusters: ['remote_cluster1'],
|
||||
names: ['remote_index1', 'remote_index2'],
|
||||
privileges: ['all'],
|
||||
},
|
||||
],
|
||||
},
|
||||
kibana: [
|
||||
{
|
||||
feature: {
|
||||
dashboard: ['read'],
|
||||
dev_tools: ['all'],
|
||||
},
|
||||
spaces: ['*'],
|
||||
},
|
||||
{
|
||||
base: ['all'],
|
||||
spaces: ['marketing', 'sales'],
|
||||
},
|
||||
],
|
||||
})
|
||||
.expect(400);
|
||||
|
||||
const role = await es.security.getRole({ name: 'role_to_update' });
|
||||
expect(role).toEqual({
|
||||
role_to_update: {
|
||||
cluster: ['monitor'],
|
||||
indices: [
|
||||
{
|
||||
names: ['beats-*'],
|
||||
privileges: ['write'],
|
||||
allow_restricted_indices: false,
|
||||
},
|
||||
],
|
||||
applications: [
|
||||
{
|
||||
application: 'kibana-.kibana',
|
||||
privileges: ['read'],
|
||||
resources: ['*'],
|
||||
},
|
||||
{
|
||||
application: 'apm',
|
||||
privileges: ['apm-privilege'],
|
||||
resources: ['*'],
|
||||
},
|
||||
],
|
||||
metadata: {
|
||||
bar: 'old-metadata',
|
||||
},
|
||||
run_as: [],
|
||||
transient_metadata: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Delete Role', () => {
|
||||
it('should delete an existing role', async () => {
|
||||
await es.security.putRole({
|
||||
name: 'role_to_delete',
|
||||
body: {
|
||||
cluster: ['monitor'],
|
||||
indices: [
|
||||
{
|
||||
names: ['beats-*'],
|
||||
privileges: ['write'],
|
||||
},
|
||||
],
|
||||
applications: [
|
||||
{
|
||||
application: 'kibana-.kibana',
|
||||
privileges: ['read'],
|
||||
resources: ['*'],
|
||||
},
|
||||
{
|
||||
application: 'apm',
|
||||
privileges: ['apm-privilege'],
|
||||
resources: ['*'],
|
||||
},
|
||||
],
|
||||
metadata: {
|
||||
bar: 'old-metadata',
|
||||
},
|
||||
},
|
||||
});
|
||||
await supertestAdminWithApiKey.delete('/api/security/role/role_to_delete').expect(204);
|
||||
|
||||
const deletedRole = await es.security.getRole(
|
||||
{ name: 'role_to_delete' },
|
||||
{ ignore: [404] }
|
||||
);
|
||||
expect(deletedRole).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Access', () => {
|
||||
describe('public', () => {
|
||||
it('reset session page', async () => {
|
||||
const { status } = await supertestAdminWithCookieCredentials.get(
|
||||
'/internal/security/reset_session_page.js'
|
||||
);
|
||||
expect(status).toBe(200);
|
||||
});
|
||||
});
|
||||
describe('Disabled', () => {
|
||||
it('get shared saved object permissions', async () => {
|
||||
const { body, status } = await supertestAdminWithCookieCredentials.get(
|
||||
'/internal/security/_share_saved_object_permissions'
|
||||
);
|
||||
svlCommonApi.assertApiNotFound(body, status);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -5,7 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { expect } from 'expect';
|
||||
import expect from '@kbn/expect';
|
||||
import { expect as externalExpect } from 'expect';
|
||||
import { SupertestWithRoleScopeType } from '@kbn/test-suites-xpack/api_integration/deployment_agnostic/services';
|
||||
import { FtrProviderContext } from '../../../ftr_provider_context';
|
||||
|
||||
|
@ -13,6 +14,8 @@ export default function telemetryConfigTest({ getService }: FtrProviderContext)
|
|||
const roleScopedSupertest = getService('roleScopedSupertest');
|
||||
let supertestAdminWithApiKey: SupertestWithRoleScopeType;
|
||||
let supertestAdminWithCookieCredentials: SupertestWithRoleScopeType;
|
||||
const retry = getService('retry');
|
||||
const retryTimeout = 20 * 1000;
|
||||
|
||||
describe('/api/telemetry/v2/config API Telemetry config', function () {
|
||||
before(async () => {
|
||||
|
@ -42,7 +45,7 @@ export default function telemetryConfigTest({ getService }: FtrProviderContext)
|
|||
it('GET should get the default config', async () => {
|
||||
const { body } = await supertestAdminWithApiKey.get('/api/telemetry/v2/config').expect(200);
|
||||
|
||||
expect(body).toMatchObject(baseConfig);
|
||||
externalExpect(body).toMatchObject(baseConfig);
|
||||
});
|
||||
|
||||
it('GET should get updated labels after dynamically updating them', async () => {
|
||||
|
@ -71,7 +74,13 @@ export default function telemetryConfigTest({ getService }: FtrProviderContext)
|
|||
.send({ 'telemetry.labels.journeyName': null })
|
||||
.expect(200, { ok: true });
|
||||
|
||||
await supertestAdminWithApiKey.get('/api/telemetry/v2/config').expect(200, initialConfig);
|
||||
await retry.tryForTime(retryTimeout, async function retryTelemetryConfigGetRequest() {
|
||||
const { body } = await supertestAdminWithApiKey.get('/api/telemetry/v2/config').expect(200);
|
||||
expect(body).to.eql(
|
||||
initialConfig,
|
||||
`Expected the response body to match the intitial config, but got: [${body}]`
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -22,13 +22,12 @@ export default createTestConfig({
|
|||
// add feature flags
|
||||
kbnServerArgs: [
|
||||
'--xpack.infra.enabled=true',
|
||||
'--xpack.security.roleManagementEnabled=true', // enables custom roles
|
||||
`--xpack.spaces.maxSpaces=100`, // enables spaces UI capabilities
|
||||
'--xpack.security.roleManagementEnabled=true', // needed to check composite feautures in /observability/platform_security/authorization.ts
|
||||
],
|
||||
// load tests in the index file
|
||||
testFiles: [require.resolve('./index.feature_flags.ts')],
|
||||
|
||||
// include settings from project controller
|
||||
// https://github.com/elastic/project-controller/blob/main/internal/project/observability/config/elasticsearch.yml
|
||||
esServerArgs: ['xpack.ml.dfa.enabled=false', 'xpack.security.authc.native_roles.enabled=true'],
|
||||
esServerArgs: ['xpack.ml.dfa.enabled=false'],
|
||||
});
|
||||
|
|
|
@ -12,7 +12,5 @@ export default function ({ loadTestFile }: FtrProviderContext) {
|
|||
loadTestFile(require.resolve('./custom_threshold_rule'));
|
||||
loadTestFile(require.resolve('./infra'));
|
||||
loadTestFile(require.resolve('./platform_security'));
|
||||
loadTestFile(require.resolve('../common/platform_security/roles_routes_feature_flag.ts'));
|
||||
loadTestFile(require.resolve('../common/management/multiple_spaces_enabled.ts'));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -19,8 +19,6 @@ export default createTestConfig({
|
|||
suiteTags: { exclude: ['skipSvlSearch'] },
|
||||
// add feature flags
|
||||
kbnServerArgs: [
|
||||
'--xpack.security.roleManagementEnabled=true', // enables custom roles
|
||||
`--xpack.spaces.maxSpaces=100`, // enables spaces UI capabilities
|
||||
`--xpack.searchIndices.enabled=true`, // global empty state FF
|
||||
],
|
||||
// load tests in the index file
|
||||
|
@ -28,5 +26,5 @@ export default createTestConfig({
|
|||
|
||||
// include settings from project controller
|
||||
// https://github.com/elastic/project-controller/blob/main/internal/project/esproject/config/elasticsearch.yml
|
||||
esServerArgs: ['xpack.security.authc.native_roles.enabled=true'],
|
||||
esServerArgs: [],
|
||||
});
|
||||
|
|
|
@ -10,8 +10,5 @@ import { FtrProviderContext } from '../../ftr_provider_context';
|
|||
export default function ({ loadTestFile }: FtrProviderContext) {
|
||||
describe('Serverless search API - feature flags', function () {
|
||||
loadTestFile(require.resolve('./search_indices'));
|
||||
loadTestFile(require.resolve('./platform_security'));
|
||||
loadTestFile(require.resolve('../common/platform_security/roles_routes_feature_flag.ts'));
|
||||
loadTestFile(require.resolve('../common/management/multiple_spaces_enabled.ts'));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -14,5 +14,6 @@ export default function ({ loadTestFile }: FtrProviderContext) {
|
|||
loadTestFile(require.resolve('./cases/find_cases'));
|
||||
loadTestFile(require.resolve('./cases/post_case'));
|
||||
loadTestFile(require.resolve('./serverless_search'));
|
||||
loadTestFile(require.resolve('./platform_security'));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -18,14 +18,11 @@ export default createTestConfig({
|
|||
},
|
||||
suiteTags: { exclude: ['skipSvlSec'] },
|
||||
// add feature flags
|
||||
kbnServerArgs: [
|
||||
'--xpack.security.roleManagementEnabled=true', // enables custom roles
|
||||
`--xpack.spaces.maxSpaces=100`, // enables spaces UI capabilities
|
||||
],
|
||||
kbnServerArgs: [],
|
||||
// load tests in the index file
|
||||
testFiles: [require.resolve('./index.feature_flags.ts')],
|
||||
|
||||
// include settings from project controller
|
||||
// https://github.com/elastic/project-controller/blob/main/internal/project/security/config/elasticsearch.yml
|
||||
esServerArgs: ['xpack.ml.nlp.enabled=true', 'xpack.security.authc.native_roles.enabled=true'],
|
||||
esServerArgs: ['xpack.ml.nlp.enabled=true'],
|
||||
});
|
||||
|
|
|
@ -8,9 +8,5 @@
|
|||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
|
||||
export default function ({ loadTestFile }: FtrProviderContext) {
|
||||
describe('Serverless security API - feature flags', function () {
|
||||
loadTestFile(require.resolve('./platform_security'));
|
||||
loadTestFile(require.resolve('../common/platform_security/roles_routes_feature_flag.ts'));
|
||||
loadTestFile(require.resolve('../common/management/multiple_spaces_enabled.ts'));
|
||||
});
|
||||
describe('Serverless security API - feature flags', function () {});
|
||||
}
|
||||
|
|
|
@ -13,5 +13,6 @@ export default function ({ loadTestFile }: FtrProviderContext) {
|
|||
|
||||
loadTestFile(require.resolve('./cases'));
|
||||
loadTestFile(require.resolve('./cloud_security_posture'));
|
||||
loadTestFile(require.resolve('./platform_security'));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -41,10 +41,6 @@ export function createTestConfig(options: CreateTestConfigOptions) {
|
|||
`--xpack.trigger_actions_ui.enableExperimental=${JSON.stringify([
|
||||
'isUsingRuleCreateFlyout',
|
||||
])}`,
|
||||
// custom native roles are enabled only for search and security projects
|
||||
...(options.serverlessProject !== 'oblt'
|
||||
? ['--xpack.security.roleManagementEnabled=true']
|
||||
: []),
|
||||
...(options.kbnServerArgs ?? []),
|
||||
],
|
||||
},
|
||||
|
|
|
@ -13,6 +13,8 @@ export default function ({ loadTestFile }: FtrProviderContext) {
|
|||
|
||||
loadTestFile(require.resolve('./api_keys'));
|
||||
loadTestFile(require.resolve('./navigation/avatar_menu'));
|
||||
loadTestFile(require.resolve('./navigation/management_nav_cards'));
|
||||
loadTestFile(require.resolve('./user_profiles/user_profiles'));
|
||||
loadTestFile(require.resolve('./roles.ts'));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -46,16 +46,17 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
expect(url).to.contain('/management/security/api_keys');
|
||||
});
|
||||
|
||||
it('displays the roles management card, and will navigate to the Roles UI', async () => {
|
||||
await pageObjects.svlManagementPage.assertRoleManagementCardExists();
|
||||
await pageObjects.svlManagementPage.clickRoleManagementCard();
|
||||
|
||||
const url = await browser.getCurrentUrl();
|
||||
expect(url).to.contain('/management/security/roles');
|
||||
});
|
||||
|
||||
describe('Organization members', function () {
|
||||
describe('custom roles', function () {
|
||||
this.tags('skipSvlOblt'); // Observability will not support custom roles
|
||||
|
||||
it('displays the roles management card, and will navigate to the Roles UI', async () => {
|
||||
await pageObjects.svlManagementPage.assertRoleManagementCardExists();
|
||||
await pageObjects.svlManagementPage.clickRoleManagementCard();
|
||||
|
||||
const url = await browser.getCurrentUrl();
|
||||
expect(url).to.contain('/management/security/roles');
|
||||
});
|
||||
|
||||
it('displays the Organization members management card, and will navigate to the cloud organization URL', async () => {
|
||||
await pageObjects.svlManagementPage.assertOrgMembersManagementCardExists();
|
||||
await pageObjects.svlManagementPage.clickOrgMembersManagementCard();
|
||||
|
|
|
@ -19,6 +19,9 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
const platformSecurityUtils = getService('platformSecurityUtils');
|
||||
|
||||
describe('Roles', function () {
|
||||
// custom roles are not enabled for observability projects
|
||||
this.tags(['skipSvlOblt']);
|
||||
|
||||
describe('as Viewer', () => {
|
||||
before(async () => {
|
||||
await pageObjects.svlCommonPage.loginAsViewer();
|
||||
|
|
|
@ -11,6 +11,7 @@ export default ({ loadTestFile }: FtrProviderContext) => {
|
|||
describe('Spaces', function () {
|
||||
this.tags(['esGate']);
|
||||
|
||||
loadTestFile(require.resolve('./spaces_management.ts'));
|
||||
loadTestFile(require.resolve('./spaces_selection.ts'));
|
||||
});
|
||||
};
|
||||
|
|
|
@ -15,51 +15,9 @@ import { FtrProviderContext } from '../../../ftr_provider_context';
|
|||
export default function ({ getPageObject, getService }: FtrProviderContext) {
|
||||
const svlCommon = getPageObject('common');
|
||||
const svlCommonPage = getPageObject('svlCommonPage');
|
||||
const svlCommonNavigation = getService('svlCommonNavigation');
|
||||
const testSubjects = getService('testSubjects');
|
||||
|
||||
describe('spaces', function () {
|
||||
describe('selection', function () {
|
||||
describe('as Viewer', function () {
|
||||
before(async () => {
|
||||
await svlCommonPage.loginAsViewer();
|
||||
});
|
||||
|
||||
it('displays the space selection menu in header', async () => {
|
||||
await svlCommonNavigation.navigateToKibanaHome();
|
||||
await svlCommonPage.assertProjectHeaderExists();
|
||||
|
||||
await testSubjects.existOrFail('spacesNavSelector');
|
||||
});
|
||||
|
||||
it(`does not display the manage button in the space selection menu`, async () => {
|
||||
await svlCommonNavigation.navigateToKibanaHome();
|
||||
await svlCommonPage.assertProjectHeaderExists();
|
||||
await testSubjects.click('spacesNavSelector');
|
||||
await testSubjects.missingOrFail('manageSpaces');
|
||||
});
|
||||
});
|
||||
|
||||
describe('as Admin', function () {
|
||||
before(async () => {
|
||||
await svlCommonPage.loginAsAdmin();
|
||||
});
|
||||
|
||||
it('displays the space selection menu in header', async () => {
|
||||
await svlCommonNavigation.navigateToKibanaHome();
|
||||
await svlCommonPage.assertProjectHeaderExists();
|
||||
await testSubjects.existOrFail('spacesNavSelector');
|
||||
});
|
||||
|
||||
it(`displays the manage button in the space selection menu`, async () => {
|
||||
await svlCommonNavigation.navigateToKibanaHome();
|
||||
await svlCommonPage.assertProjectHeaderExists();
|
||||
await testSubjects.click('spacesNavSelector');
|
||||
await testSubjects.existOrFail('manageSpaces');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('management', function () {
|
||||
describe('as Viewer', function () {
|
||||
before(async () => {
|
|
@ -12,18 +12,46 @@ export default function ({ getPageObject, getService }: FtrProviderContext) {
|
|||
const svlCommonNavigation = getService('svlCommonNavigation');
|
||||
const testSubjects = getService('testSubjects');
|
||||
|
||||
// Skipped due to change in QA environment for role management and spaces
|
||||
// TODO: revisit once the change is rolled out to all environments
|
||||
describe.skip('space selection', function () {
|
||||
before(async () => {
|
||||
await svlCommonPage.loginAsViewer();
|
||||
});
|
||||
describe('spaces', function () {
|
||||
describe('selection', function () {
|
||||
describe('as Viewer', function () {
|
||||
before(async () => {
|
||||
await svlCommonPage.loginAsViewer();
|
||||
});
|
||||
|
||||
it('does not have the space selection menu in header', async () => {
|
||||
await svlCommonNavigation.navigateToKibanaHome();
|
||||
await svlCommonPage.assertProjectHeaderExists();
|
||||
it('displays the space selection menu in header', async () => {
|
||||
await svlCommonNavigation.navigateToKibanaHome();
|
||||
await svlCommonPage.assertProjectHeaderExists();
|
||||
|
||||
await testSubjects.missingOrFail('spacesNavSelector');
|
||||
await testSubjects.existOrFail('spacesNavSelector');
|
||||
});
|
||||
|
||||
it(`does not display the manage button in the space selection menu`, async () => {
|
||||
await svlCommonNavigation.navigateToKibanaHome();
|
||||
await svlCommonPage.assertProjectHeaderExists();
|
||||
await testSubjects.click('spacesNavSelector');
|
||||
await testSubjects.missingOrFail('manageSpaces');
|
||||
});
|
||||
});
|
||||
|
||||
describe('as Admin', function () {
|
||||
before(async () => {
|
||||
await svlCommonPage.loginAsAdmin();
|
||||
});
|
||||
|
||||
it('displays the space selection menu in header', async () => {
|
||||
await svlCommonNavigation.navigateToKibanaHome();
|
||||
await svlCommonPage.assertProjectHeaderExists();
|
||||
await testSubjects.existOrFail('spacesNavSelector');
|
||||
});
|
||||
|
||||
it(`displays the manage button in the space selection menu`, async () => {
|
||||
await svlCommonNavigation.navigateToKibanaHome();
|
||||
await svlCommonPage.assertProjectHeaderExists();
|
||||
await testSubjects.click('spacesNavSelector');
|
||||
await testSubjects.existOrFail('manageSpaces');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -22,10 +22,6 @@ export default createTestConfig({
|
|||
'--xpack.infra.enabled=true',
|
||||
'--xpack.infra.featureFlags.customThresholdAlertsEnabled=true',
|
||||
'--xpack.security.roleManagementEnabled=true',
|
||||
`--xpack.cloud.serverless.project_id='fakeprojectid'`,
|
||||
`--xpack.cloud.base_url='https://cloud.elastic.co'`,
|
||||
`--xpack.cloud.organization_url='/account/members'`,
|
||||
`--xpack.spaces.maxSpaces=100`, // enables spaces UI capabilities
|
||||
],
|
||||
// load tests in the index file
|
||||
testFiles: [require.resolve('./index.feature_flags.ts')],
|
||||
|
|
|
@ -12,8 +12,5 @@ export default function ({ loadTestFile }: FtrProviderContext) {
|
|||
// add tests that require feature flags, defined in config.feature_flags.ts
|
||||
loadTestFile(require.resolve('./role_management'));
|
||||
loadTestFile(require.resolve('./infra'));
|
||||
loadTestFile(require.resolve('../common/platform_security/navigation/management_nav_cards.ts'));
|
||||
loadTestFile(require.resolve('../common/platform_security/roles.ts'));
|
||||
loadTestFile(require.resolve('../common/spaces/multiple_spaces_enabled.ts'));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -20,19 +20,13 @@ export default createTestConfig({
|
|||
// add feature flags
|
||||
kbnServerArgs: [
|
||||
`--xpack.cloud.id=ES3_FTR_TESTS:ZmFrZS1kb21haW4uY2xkLmVsc3RjLmNvJGZha2Vwcm9qZWN0aWQuZXMkZmFrZXByb2plY3RpZC5rYg==`,
|
||||
`--xpack.cloud.serverless.project_id=fakeprojectid`,
|
||||
`--xpack.cloud.base_url=https://fake-cloud.elastic.co`,
|
||||
`--xpack.cloud.projects_url=/projects/`,
|
||||
`--xpack.cloud.organization_url=/account/members`,
|
||||
`--xpack.security.roleManagementEnabled=true`,
|
||||
`--xpack.spaces.maxSpaces=100`, // enables spaces UI capabilities
|
||||
],
|
||||
// load tests in the index file
|
||||
testFiles: [require.resolve('./index.feature_flags.ts')],
|
||||
|
||||
// include settings from project controller
|
||||
// https://github.com/elastic/project-controller/blob/main/internal/project/esproject/config/elasticsearch.yml
|
||||
esServerArgs: ['xpack.security.authc.native_roles.enabled=true'],
|
||||
esServerArgs: [],
|
||||
apps: {
|
||||
serverlessElasticsearch: {
|
||||
pathname: '/app/elasticsearch/getting_started',
|
||||
|
|
|
@ -20,16 +20,8 @@ export default createTestConfig({
|
|||
esServerArgs: [],
|
||||
kbnServerArgs: [
|
||||
`--xpack.cloud.id=ES3_FTR_TESTS:ZmFrZS1kb21haW4uY2xkLmVsc3RjLmNvJGZha2Vwcm9qZWN0aWQuZXMkZmFrZXByb2plY3RpZC5rYg==`,
|
||||
`--xpack.cloud.serverless.project_id=fakeprojectid`,
|
||||
`--xpack.cloud.serverless.project_name=ES3_FTR_TESTS`,
|
||||
`--xpack.cloud.base_url=https://fake-cloud.elastic.co`,
|
||||
`--xpack.cloud.profile_url=/user/settings/`,
|
||||
`--xpack.cloud.billing_url=/billing/overview/`,
|
||||
`--xpack.cloud.deployments_url=/deployments`,
|
||||
`--xpack.cloud.deployment_url=/projects/elasticsearch/fakeprojectid`,
|
||||
`--xpack.cloud.users_and_roles_url=/account/members/`,
|
||||
`--xpack.cloud.projects_url=/projects/`,
|
||||
`--xpack.cloud.organization_url=/account/`,
|
||||
],
|
||||
apps: {
|
||||
serverlessElasticsearch: {
|
||||
|
|
|
@ -10,9 +10,5 @@ import { FtrProviderContext } from '../../ftr_provider_context';
|
|||
export default function ({ loadTestFile }: FtrProviderContext) {
|
||||
describe('serverless search UI - feature flags', function () {
|
||||
// add tests that require feature flags, defined in config.feature_flags.ts
|
||||
|
||||
loadTestFile(require.resolve('../common/platform_security/navigation/management_nav_cards.ts'));
|
||||
loadTestFile(require.resolve('../common/platform_security/roles.ts'));
|
||||
loadTestFile(require.resolve('../common/spaces/multiple_spaces_enabled.ts'));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -19,6 +19,8 @@ export default function ({ getPageObject, getService }: FtrProviderContext) {
|
|||
const header = getPageObject('header');
|
||||
|
||||
describe('navigation', function () {
|
||||
// see details: https://github.com/elastic/kibana/issues/196823
|
||||
this.tags(['failsOnMKI']);
|
||||
before(async () => {
|
||||
await svlCommonPage.loginWithRole('developer');
|
||||
await svlSearchNavigation.navigateToLandingPage();
|
||||
|
|
|
@ -18,16 +18,11 @@ export default createTestConfig({
|
|||
},
|
||||
suiteTags: { exclude: ['skipSvlSec'] },
|
||||
// add feature flags
|
||||
kbnServerArgs: [
|
||||
`--xpack.security.roleManagementEnabled=true`,
|
||||
`--xpack.cloud.base_url='https://cloud.elastic.co'`,
|
||||
`--xpack.cloud.organization_url='/account/members'`,
|
||||
`--xpack.spaces.maxSpaces=100`, // enables spaces UI capabilities
|
||||
],
|
||||
kbnServerArgs: [],
|
||||
// load tests in the index file
|
||||
testFiles: [require.resolve('./index.feature_flags.ts')],
|
||||
|
||||
// include settings from project controller
|
||||
// https://github.com/elastic/project-controller/blob/main/internal/project/security/config/elasticsearch.yml
|
||||
esServerArgs: ['xpack.ml.nlp.enabled=true', 'xpack.security.authc.native_roles.enabled=true'],
|
||||
esServerArgs: ['xpack.ml.nlp.enabled=true'],
|
||||
});
|
||||
|
|
|
@ -10,8 +10,5 @@ import { FtrProviderContext } from '../../ftr_provider_context';
|
|||
export default function ({ loadTestFile }: FtrProviderContext) {
|
||||
describe('serverless security UI - feature flags', function () {
|
||||
// add tests that require feature flags, defined in config.feature_flags.ts
|
||||
loadTestFile(require.resolve('../common/platform_security/navigation/management_nav_cards.ts'));
|
||||
loadTestFile(require.resolve('../common/platform_security/roles.ts'));
|
||||
loadTestFile(require.resolve('../common/spaces/multiple_spaces_enabled.ts'));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -151,7 +151,6 @@ export default async () => {
|
|||
// This ensures that we register the Security SAML API endpoints.
|
||||
// In the real world the SAML config is injected by control plane.
|
||||
`--plugin-path=${samlIdPPlugin}`,
|
||||
'--xpack.cloud.id=ftr_fake_cloud_id',
|
||||
// Ensure that SAML is used as the default authentication method whenever a user navigates to Kibana. In other
|
||||
// words, Kibana should attempt to authenticate the user using the provider with the lowest order if the Login
|
||||
// Selector is disabled (which is how Serverless Kibana is configured). By declaring `cloud-basic` with a higher
|
||||
|
@ -167,6 +166,16 @@ export default async () => {
|
|||
// configure security reponse header report-to settings to mimic MKI configuration
|
||||
`--csp.report_to=${JSON.stringify(['violations-endpoint'])}`,
|
||||
`--permissionsPolicy.report_to=${JSON.stringify(['violations-endpoint'])}`,
|
||||
// normally below is injected by control plane
|
||||
'--xpack.cloud.id=ftr_fake_cloud_id',
|
||||
`--xpack.cloud.serverless.project_id=fakeprojectid`,
|
||||
`--xpack.cloud.base_url=https://fake-cloud.elastic.co`,
|
||||
`--xpack.cloud.projects_url=/projects/`,
|
||||
`--xpack.cloud.profile_url=/user/settings/`,
|
||||
`--xpack.cloud.billing_url=/billing/overview/`,
|
||||
`--xpack.cloud.deployments_url=/deployments`,
|
||||
`--xpack.cloud.organization_url=/account/`,
|
||||
`--xpack.cloud.users_and_roles_url=/account/members/`,
|
||||
],
|
||||
},
|
||||
|
||||
|
|
|
@ -97,8 +97,8 @@
|
|||
"@kbn/test-suites-src",
|
||||
"@kbn/console-plugin",
|
||||
"@kbn/cloud-security-posture-common",
|
||||
"@kbn/security-plugin-types-common",
|
||||
"@kbn/core-saved-objects-import-export-server-internal",
|
||||
"@kbn/security-plugin-types-common",
|
||||
"@kbn/ai-assistant-common",
|
||||
]
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue