Merge branch 'master' into core/theming
@ -2,6 +2,11 @@
|
|||||||
|
|
||||||
### Minor
|
### Minor
|
||||||
* **Pushover**: Adds support for images in pushover notifier [#10780](https://github.com/grafana/grafana/issues/10780), thx [@jpenalbae](https://github.com/jpenalbae)
|
* **Pushover**: Adds support for images in pushover notifier [#10780](https://github.com/grafana/grafana/issues/10780), thx [@jpenalbae](https://github.com/jpenalbae)
|
||||||
|
* **Stackdriver**: Template variables in filters using globbing format [#15182](https://github.com/grafana/grafana/issues/15182)
|
||||||
|
* **Cloudwatch**: Add `resource_arns` template variable query function [#8207](https://github.com/grafana/grafana/issues/8207), thx [@jeroenvollenbrock](https://github.com/jeroenvollenbrock)
|
||||||
|
* **Cloudwatch**: Add AWS/Neptune metrics [#14231](https://github.com/grafana/grafana/issues/14231), thx [@tcpatterson](https://github.com/tcpatterson)
|
||||||
|
* **Cloudwatch**: Add AWS RDS ServerlessDatabaseCapacity metric [#15265](https://github.com/grafana/grafana/pull/15265), thx [@larsjoergensen](https://github.com/larsjoergensen)
|
||||||
|
* **Annotations**: Support PATCH verb in annotations http api [#12546](https://github.com/grafana/grafana/issues/12546), thx [@SamuelToh](https://github.com/SamuelToh)
|
||||||
|
|
||||||
# 6.0.0-beta1 (2019-01-30)
|
# 6.0.0-beta1 (2019-01-30)
|
||||||
|
|
||||||
|
12
Gopkg.lock
generated
@ -37,6 +37,7 @@
|
|||||||
"aws/credentials",
|
"aws/credentials",
|
||||||
"aws/credentials/ec2rolecreds",
|
"aws/credentials/ec2rolecreds",
|
||||||
"aws/credentials/endpointcreds",
|
"aws/credentials/endpointcreds",
|
||||||
|
"aws/credentials/processcreds",
|
||||||
"aws/credentials/stscreds",
|
"aws/credentials/stscreds",
|
||||||
"aws/csm",
|
"aws/csm",
|
||||||
"aws/defaults",
|
"aws/defaults",
|
||||||
@ -45,13 +46,18 @@
|
|||||||
"aws/request",
|
"aws/request",
|
||||||
"aws/session",
|
"aws/session",
|
||||||
"aws/signer/v4",
|
"aws/signer/v4",
|
||||||
|
"internal/ini",
|
||||||
|
"internal/s3err",
|
||||||
"internal/sdkio",
|
"internal/sdkio",
|
||||||
"internal/sdkrand",
|
"internal/sdkrand",
|
||||||
|
"internal/sdkuri",
|
||||||
"internal/shareddefaults",
|
"internal/shareddefaults",
|
||||||
"private/protocol",
|
"private/protocol",
|
||||||
"private/protocol/ec2query",
|
"private/protocol/ec2query",
|
||||||
"private/protocol/eventstream",
|
"private/protocol/eventstream",
|
||||||
"private/protocol/eventstream/eventstreamapi",
|
"private/protocol/eventstream/eventstreamapi",
|
||||||
|
"private/protocol/json/jsonutil",
|
||||||
|
"private/protocol/jsonrpc",
|
||||||
"private/protocol/query",
|
"private/protocol/query",
|
||||||
"private/protocol/query/queryutil",
|
"private/protocol/query/queryutil",
|
||||||
"private/protocol/rest",
|
"private/protocol/rest",
|
||||||
@ -60,11 +66,13 @@
|
|||||||
"service/cloudwatch",
|
"service/cloudwatch",
|
||||||
"service/ec2",
|
"service/ec2",
|
||||||
"service/ec2/ec2iface",
|
"service/ec2/ec2iface",
|
||||||
|
"service/resourcegroupstaggingapi",
|
||||||
|
"service/resourcegroupstaggingapi/resourcegroupstaggingapiiface",
|
||||||
"service/s3",
|
"service/s3",
|
||||||
"service/sts"
|
"service/sts"
|
||||||
]
|
]
|
||||||
revision = "fde4ded7becdeae4d26bf1212916aabba79349b4"
|
revision = "62936e15518acb527a1a9cb4a39d96d94d0fd9a2"
|
||||||
version = "v1.14.12"
|
version = "v1.16.15"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
|
27
devenv/docker/blocks/loki/config.yaml
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
server:
|
||||||
|
http_listen_port: 9080
|
||||||
|
grpc_listen_port: 0
|
||||||
|
|
||||||
|
positions:
|
||||||
|
filename: /tmp/positions.yaml
|
||||||
|
|
||||||
|
client:
|
||||||
|
url: http://loki:3100/api/prom/push
|
||||||
|
|
||||||
|
scrape_configs:
|
||||||
|
- job_name: system
|
||||||
|
entry_parser: raw
|
||||||
|
static_configs:
|
||||||
|
- targets:
|
||||||
|
- localhost
|
||||||
|
labels:
|
||||||
|
job: varlogs
|
||||||
|
__path__: /var/log/*log
|
||||||
|
- job_name: grafana
|
||||||
|
entry_parser: raw
|
||||||
|
static_configs:
|
||||||
|
- targets:
|
||||||
|
- localhost
|
||||||
|
labels:
|
||||||
|
job: grafana
|
||||||
|
__path__: /var/log/grafana/*log
|
@ -1,22 +1,14 @@
|
|||||||
version: "3"
|
|
||||||
|
|
||||||
networks:
|
|
||||||
loki:
|
|
||||||
|
|
||||||
services:
|
|
||||||
loki:
|
loki:
|
||||||
image: grafana/loki:master
|
image: grafana/loki:master
|
||||||
ports:
|
ports:
|
||||||
- "3100:3100"
|
- "3100:3100"
|
||||||
command: -config.file=/etc/loki/local-config.yaml
|
command: -config.file=/etc/loki/local-config.yaml
|
||||||
networks:
|
|
||||||
- loki
|
|
||||||
|
|
||||||
promtail:
|
promtail:
|
||||||
image: grafana/promtail:master
|
image: grafana/promtail:master
|
||||||
volumes:
|
volumes:
|
||||||
|
- ./docker/blocks/loki/config.yaml:/etc/promtail/docker-config.yaml
|
||||||
- /var/log:/var/log
|
- /var/log:/var/log
|
||||||
|
- ../data/log:/var/log/grafana
|
||||||
command:
|
command:
|
||||||
-config.file=/etc/promtail/docker-config.yaml
|
-config.file=/etc/promtail/docker-config.yaml
|
||||||
networks:
|
|
||||||
- loki
|
|
||||||
|
@ -74,6 +74,12 @@ Here is a minimal policy example:
|
|||||||
"ec2:DescribeRegions"
|
"ec2:DescribeRegions"
|
||||||
],
|
],
|
||||||
"Resource": "*"
|
"Resource": "*"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Sid": "AllowReadingResourcesForTags",
|
||||||
|
"Effect" : "Allow",
|
||||||
|
"Action" : "tag:GetResources",
|
||||||
|
"Resource" : "*"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -128,6 +134,7 @@ Name | Description
|
|||||||
*dimension_values(region, namespace, metric, dimension_key, [filters])* | Returns a list of dimension values matching the specified `region`, `namespace`, `metric`, `dimension_key` or you can use dimension `filters` to get more specific result as well.
|
*dimension_values(region, namespace, metric, dimension_key, [filters])* | Returns a list of dimension values matching the specified `region`, `namespace`, `metric`, `dimension_key` or you can use dimension `filters` to get more specific result as well.
|
||||||
*ebs_volume_ids(region, instance_id)* | Returns a list of volume ids matching the specified `region`, `instance_id`.
|
*ebs_volume_ids(region, instance_id)* | Returns a list of volume ids matching the specified `region`, `instance_id`.
|
||||||
*ec2_instance_attribute(region, attribute_name, filters)* | Returns a list of attributes matching the specified `region`, `attribute_name`, `filters`.
|
*ec2_instance_attribute(region, attribute_name, filters)* | Returns a list of attributes matching the specified `region`, `attribute_name`, `filters`.
|
||||||
|
*resource_arns(region, resource_type, tags)* | Returns a list of ARNs matching the specified `region`, `resource_type` and `tags`.
|
||||||
|
|
||||||
For details about the metrics CloudWatch provides, please refer to the [CloudWatch documentation](https://docs.aws.amazon.com/AmazonCloudWatch/latest/DeveloperGuide/CW_Support_For_AWS.html).
|
For details about the metrics CloudWatch provides, please refer to the [CloudWatch documentation](https://docs.aws.amazon.com/AmazonCloudWatch/latest/DeveloperGuide/CW_Support_For_AWS.html).
|
||||||
|
|
||||||
@ -143,6 +150,8 @@ Query | Service
|
|||||||
*dimension_values(us-east-1,AWS/RDS,CPUUtilization,DBInstanceIdentifier)* | RDS
|
*dimension_values(us-east-1,AWS/RDS,CPUUtilization,DBInstanceIdentifier)* | RDS
|
||||||
*dimension_values(us-east-1,AWS/S3,BucketSizeBytes,BucketName)* | S3
|
*dimension_values(us-east-1,AWS/S3,BucketSizeBytes,BucketName)* | S3
|
||||||
*dimension_values(us-east-1,CWAgent,disk_used_percent,device,{"InstanceId":"$instance_id"})* | CloudWatch Agent
|
*dimension_values(us-east-1,CWAgent,disk_used_percent,device,{"InstanceId":"$instance_id"})* | CloudWatch Agent
|
||||||
|
*resource_arns(eu-west-1,elasticloadbalancing:loadbalancer,{"elasticbeanstalk:environment-name":["myApp-dev","myApp-prod"]})* | ELB
|
||||||
|
*resource_arns(eu-west-1,ec2:instance,{"elasticbeanstalk:environment-name":["myApp-dev","myApp-prod"]})* | EC2
|
||||||
|
|
||||||
## ec2_instance_attribute examples
|
## ec2_instance_attribute examples
|
||||||
|
|
||||||
@ -205,6 +214,16 @@ Example `ec2_instance_attribute()` query
|
|||||||
ec2_instance_attribute(us-east-1, Tags.Name, { "tag:Team": [ "sysops" ] })
|
ec2_instance_attribute(us-east-1, Tags.Name, { "tag:Team": [ "sysops" ] })
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Using json format template variables
|
||||||
|
|
||||||
|
Some of query takes JSON format filter. Grafana support to interpolate template variable to JSON format string, it can use as filter string.
|
||||||
|
|
||||||
|
If `env = 'production', 'staging'`, following query will return ARNs of EC2 instances which `Environment` tag is `production` or `staging`.
|
||||||
|
|
||||||
|
```
|
||||||
|
resource_arns(us-east-1, ec2:instance, {"Environment":${env:json}})
|
||||||
|
```
|
||||||
|
|
||||||
## Cost
|
## Cost
|
||||||
|
|
||||||
Amazon provides 1 million CloudWatch API requests each month at no additional charge. Past this,
|
Amazon provides 1 million CloudWatch API requests each month at no additional charge. Past this,
|
||||||
|
@ -97,7 +97,7 @@ Creates an annotation in the Grafana database. The `dashboardId` and `panelId` f
|
|||||||
|
|
||||||
**Example Request**:
|
**Example Request**:
|
||||||
|
|
||||||
```json
|
```http
|
||||||
POST /api/annotations HTTP/1.1
|
POST /api/annotations HTTP/1.1
|
||||||
Accept: application/json
|
Accept: application/json
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
@ -115,7 +115,7 @@ Content-Type: application/json
|
|||||||
|
|
||||||
**Example Response**:
|
**Example Response**:
|
||||||
|
|
||||||
```json
|
```http
|
||||||
HTTP/1.1 200
|
HTTP/1.1 200
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
@ -135,7 +135,7 @@ format (string with multiple tags being separated by a space).
|
|||||||
|
|
||||||
**Example Request**:
|
**Example Request**:
|
||||||
|
|
||||||
```json
|
```http
|
||||||
POST /api/annotations/graphite HTTP/1.1
|
POST /api/annotations/graphite HTTP/1.1
|
||||||
Accept: application/json
|
Accept: application/json
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
@ -150,7 +150,7 @@ Content-Type: application/json
|
|||||||
|
|
||||||
**Example Response**:
|
**Example Response**:
|
||||||
|
|
||||||
```json
|
```http
|
||||||
HTTP/1.1 200
|
HTTP/1.1 200
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
@ -164,11 +164,14 @@ Content-Type: application/json
|
|||||||
|
|
||||||
`PUT /api/annotations/:id`
|
`PUT /api/annotations/:id`
|
||||||
|
|
||||||
|
Updates all properties of an annotation that matches the specified id. To only update certain property, consider using the [Patch Annotation](#patch-annotation) operation.
|
||||||
|
|
||||||
**Example Request**:
|
**Example Request**:
|
||||||
|
|
||||||
```json
|
```http
|
||||||
PUT /api/annotations/1141 HTTP/1.1
|
PUT /api/annotations/1141 HTTP/1.1
|
||||||
Accept: application/json
|
Accept: application/json
|
||||||
|
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -180,6 +183,50 @@ Content-Type: application/json
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Example Response**:
|
||||||
|
|
||||||
|
```http
|
||||||
|
HTTP/1.1 200
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"message":"Annotation updated"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Patch Annotation
|
||||||
|
|
||||||
|
`PATCH /api/annotations/:id`
|
||||||
|
|
||||||
|
Updates one or more properties of an annotation that matches the specified id.
|
||||||
|
|
||||||
|
This operation currently supports updating of the `text`, `tags`, `time` and `timeEnd` properties. It does not handle updating of the `isRegion` and `regionId` properties. To make an annotation regional or vice versa, consider using the [Update Annotation](#update-annotation) operation.
|
||||||
|
|
||||||
|
**Example Request**:
|
||||||
|
|
||||||
|
```http
|
||||||
|
PATCH /api/annotations/1145 HTTP/1.1
|
||||||
|
Accept: application/json
|
||||||
|
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"text":"New Annotation Description",
|
||||||
|
"tags":["tag6","tag7","tag8"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example Response**:
|
||||||
|
|
||||||
|
```http
|
||||||
|
HTTP/1.1 200
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"message":"Annotation patched"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Delete Annotation By Id
|
## Delete Annotation By Id
|
||||||
|
|
||||||
`DELETE /api/annotations/:id`
|
`DELETE /api/annotations/:id`
|
||||||
@ -201,7 +248,9 @@ Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
|||||||
HTTP/1.1 200
|
HTTP/1.1 200
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
{"message":"Annotation deleted"}
|
{
|
||||||
|
"message":"Annotation deleted"
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Delete Annotation By RegionId
|
## Delete Annotation By RegionId
|
||||||
@ -225,5 +274,7 @@ Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
|||||||
HTTP/1.1 200
|
HTTP/1.1 200
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
{"message":"Annotation region deleted"}
|
{
|
||||||
|
"message":"Annotation region deleted"
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
@ -393,9 +393,7 @@ Analytics ID here. By default this feature is disabled.
|
|||||||
|
|
||||||
### check_for_updates
|
### check_for_updates
|
||||||
|
|
||||||
Set to false to disable all checks to https://grafana.com for new versions of Grafana and installed plugins. Check is used
|
Set to false to disable all checks to https://grafana.com for new versions of installed plugins and to the Grafana GitHub repository to check for a newer version of Grafana. The version information is used in some UI views to notify that a new Grafana update or a plugin update exists. This option does not cause any auto updates, nor send any sensitive information. The check is run every 10 minutes.
|
||||||
in some UI views to notify that a Grafana or plugin update exists. This option does not cause any auto updates, nor
|
|
||||||
send any sensitive information.
|
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
|
@ -50,6 +50,7 @@ Filter Option | Example | Raw | Interpolated | Description
|
|||||||
`regex` | ${servers:regex} | `'test.', 'test2'` | <code>(test\.|test2)</code> | Formats multi-value variable into a regex string
|
`regex` | ${servers:regex} | `'test.', 'test2'` | <code>(test\.|test2)</code> | Formats multi-value variable into a regex string
|
||||||
`pipe` | ${servers:pipe} | `'test.', 'test2'` | <code>test.|test2</code> | Formats multi-value variable into a pipe-separated string
|
`pipe` | ${servers:pipe} | `'test.', 'test2'` | <code>test.|test2</code> | Formats multi-value variable into a pipe-separated string
|
||||||
`csv`| ${servers:csv} | `'test1', 'test2'` | `test1,test2` | Formats multi-value variable as a comma-separated string
|
`csv`| ${servers:csv} | `'test1', 'test2'` | `test1,test2` | Formats multi-value variable as a comma-separated string
|
||||||
|
`json`| ${servers:json} | `'test1', 'test2'` | `["test1","test2"]` | Formats multi-value variable as a JSON string
|
||||||
`distributed`| ${servers:distributed} | `'test1', 'test2'` | `test1,servers=test2` | Formats multi-value variable in custom format for OpenTSDB.
|
`distributed`| ${servers:distributed} | `'test1', 'test2'` | `test1,servers=test2` | Formats multi-value variable in custom format for OpenTSDB.
|
||||||
`lucene`| ${servers:lucene} | `'test', 'test2'` | `("test" OR "test2")` | Formats multi-value variable as a lucene expression.
|
`lucene`| ${servers:lucene} | `'test', 'test2'` | `("test" OR "test2")` | Formats multi-value variable as a lucene expression.
|
||||||
`percentencode` | ${servers:percentencode} | `'foo()bar BAZ', 'test2'` | `{foo%28%29bar%20BAZ%2Ctest2}` | Formats multi-value variable into a glob, percent-encoded.
|
`percentencode` | ${servers:percentencode} | `'foo()bar BAZ', 'test2'` | `{foo%28%29bar%20BAZ%2Ctest2}` | Formats multi-value variable into a glob, percent-encoded.
|
||||||
|
@ -49,7 +49,7 @@ export default class SelectOptionGroup extends PureComponent<ExtendedGroupProps,
|
|||||||
return (
|
return (
|
||||||
<div className="gf-form-select-box__option-group">
|
<div className="gf-form-select-box__option-group">
|
||||||
<div className="gf-form-select-box__option-group__header" onClick={this.onToggleChildren}>
|
<div className="gf-form-select-box__option-group__header" onClick={this.onToggleChildren}>
|
||||||
<span className="flex-grow">{label}</span>
|
<span className="flex-grow-1">{label}</span>
|
||||||
<i className={`fa ${expanded ? 'fa-caret-left' : 'fa-caret-down'}`} />{' '}
|
<i className={`fa ${expanded ? 'fa-caret-left' : 'fa-caret-down'}`} />{' '}
|
||||||
</div>
|
</div>
|
||||||
{expanded && children}
|
{expanded && children}
|
||||||
|
@ -0,0 +1,10 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { storiesOf } from '@storybook/react';
|
||||||
|
import { action } from '@storybook/addon-actions';
|
||||||
|
import { ValueMappingsEditor } from './ValueMappingsEditor';
|
||||||
|
|
||||||
|
const ValueMappingsEditorStories = storiesOf('UI/ValueMappingsEditor', module);
|
||||||
|
|
||||||
|
ValueMappingsEditorStories.add('default', () => {
|
||||||
|
return <ValueMappingsEditor valueMappings={[]} onChange={action('Mapping changed')} />;
|
||||||
|
});
|
@ -1,6 +1,6 @@
|
|||||||
import { ComponentClass } from 'react';
|
import { ComponentClass } from 'react';
|
||||||
import { PanelProps, PanelOptionsProps } from './panel';
|
import { PanelProps, PanelOptionsProps } from './panel';
|
||||||
import { DataQueryOptions, DataQuery, DataQueryResponse, QueryHint } from './datasource';
|
import { DataQueryOptions, DataQuery, DataQueryResponse, QueryHint, QueryFixAction } from './datasource';
|
||||||
|
|
||||||
export interface DataSourceApi<TQuery extends DataQuery = DataQuery> {
|
export interface DataSourceApi<TQuery extends DataQuery = DataQuery> {
|
||||||
/**
|
/**
|
||||||
@ -41,6 +41,12 @@ export interface DataSourceApi<TQuery extends DataQuery = DataQuery> {
|
|||||||
pluginExports?: PluginExports;
|
pluginExports?: PluginExports;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ExploreDataSourceApi<TQuery extends DataQuery = DataQuery> extends DataSourceApi {
|
||||||
|
modifyQuery?(query: TQuery, action: QueryFixAction): TQuery;
|
||||||
|
getHighlighterExpression?(query: TQuery): string;
|
||||||
|
languageProvider?: any;
|
||||||
|
}
|
||||||
|
|
||||||
export interface QueryEditorProps<DSType extends DataSourceApi, TQuery extends DataQuery> {
|
export interface QueryEditorProps<DSType extends DataSourceApi, TQuery extends DataQuery> {
|
||||||
datasource: DSType;
|
datasource: DSType;
|
||||||
query: TQuery;
|
query: TQuery;
|
||||||
@ -48,15 +54,30 @@ export interface QueryEditorProps<DSType extends DataSourceApi, TQuery extends D
|
|||||||
onChange: (value: TQuery) => void;
|
onChange: (value: TQuery) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ExploreQueryFieldProps<DSType extends DataSourceApi, TQuery extends DataQuery> {
|
||||||
|
datasource: DSType;
|
||||||
|
query: TQuery;
|
||||||
|
error?: string | JSX.Element;
|
||||||
|
hint?: QueryHint;
|
||||||
|
history: any[];
|
||||||
|
onExecuteQuery?: () => void;
|
||||||
|
onQueryChange?: (value: TQuery) => void;
|
||||||
|
onExecuteHint?: (action: QueryFixAction) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExploreStartPageProps {
|
||||||
|
onClickExample: (query: DataQuery) => void;
|
||||||
|
}
|
||||||
|
|
||||||
export interface PluginExports {
|
export interface PluginExports {
|
||||||
Datasource?: DataSourceApi;
|
Datasource?: DataSourceApi;
|
||||||
QueryCtrl?: any;
|
QueryCtrl?: any;
|
||||||
QueryEditor?: ComponentClass<QueryEditorProps<DataSourceApi,DataQuery>>;
|
QueryEditor?: ComponentClass<QueryEditorProps<DataSourceApi, DataQuery>>;
|
||||||
ConfigCtrl?: any;
|
ConfigCtrl?: any;
|
||||||
AnnotationsQueryCtrl?: any;
|
AnnotationsQueryCtrl?: any;
|
||||||
VariableQueryEditor?: any;
|
VariableQueryEditor?: any;
|
||||||
ExploreQueryField?: any;
|
ExploreQueryField?: ComponentClass<ExploreQueryFieldProps<DataSourceApi, DataQuery>>;
|
||||||
ExploreStartPage?: any;
|
ExploreStartPage?: ComponentClass<ExploreStartPageProps>;
|
||||||
|
|
||||||
// Panel plugin
|
// Panel plugin
|
||||||
PanelCtrl?: any;
|
PanelCtrl?: any;
|
||||||
@ -114,5 +135,3 @@ export interface PluginMetaInfo {
|
|||||||
updated: string;
|
updated: string;
|
||||||
version: string;
|
version: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -210,6 +210,65 @@ func UpdateAnnotation(c *m.ReqContext, cmd dtos.UpdateAnnotationsCmd) Response {
|
|||||||
return Success("Annotation updated")
|
return Success("Annotation updated")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func PatchAnnotation(c *m.ReqContext, cmd dtos.PatchAnnotationsCmd) Response {
|
||||||
|
annotationID := c.ParamsInt64(":annotationId")
|
||||||
|
|
||||||
|
repo := annotations.GetRepository()
|
||||||
|
|
||||||
|
if resp := canSave(c, repo, annotationID); resp != nil {
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
|
||||||
|
items, err := repo.Find(&annotations.ItemQuery{AnnotationId: annotationID, OrgId: c.OrgId})
|
||||||
|
|
||||||
|
if err != nil || len(items) == 0 {
|
||||||
|
return Error(404, "Could not find annotation to update", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
existing := annotations.Item{
|
||||||
|
OrgId: c.OrgId,
|
||||||
|
UserId: c.UserId,
|
||||||
|
Id: annotationID,
|
||||||
|
Epoch: items[0].Time,
|
||||||
|
Text: items[0].Text,
|
||||||
|
Tags: items[0].Tags,
|
||||||
|
RegionId: items[0].RegionId,
|
||||||
|
}
|
||||||
|
|
||||||
|
if cmd.Tags != nil {
|
||||||
|
existing.Tags = cmd.Tags
|
||||||
|
}
|
||||||
|
|
||||||
|
if cmd.Text != "" && cmd.Text != existing.Text {
|
||||||
|
existing.Text = cmd.Text
|
||||||
|
}
|
||||||
|
|
||||||
|
if cmd.Time > 0 && cmd.Time != existing.Epoch {
|
||||||
|
existing.Epoch = cmd.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := repo.Update(&existing); err != nil {
|
||||||
|
return Error(500, "Failed to update annotation", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update region end time if provided
|
||||||
|
if existing.RegionId != 0 && cmd.TimeEnd > 0 {
|
||||||
|
itemRight := existing
|
||||||
|
itemRight.RegionId = existing.Id
|
||||||
|
itemRight.Epoch = cmd.TimeEnd
|
||||||
|
|
||||||
|
// We don't know id of region right event, so set it to 0 and find then using query like
|
||||||
|
// ... WHERE region_id = <item.RegionId> AND id != <item.RegionId> ...
|
||||||
|
itemRight.Id = 0
|
||||||
|
|
||||||
|
if err := repo.Update(&itemRight); err != nil {
|
||||||
|
return Error(500, "Failed to update annotation for region end time", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Success("Annotation patched")
|
||||||
|
}
|
||||||
|
|
||||||
func DeleteAnnotations(c *m.ReqContext, cmd dtos.DeleteAnnotationsCmd) Response {
|
func DeleteAnnotations(c *m.ReqContext, cmd dtos.DeleteAnnotationsCmd) Response {
|
||||||
repo := annotations.GetRepository()
|
repo := annotations.GetRepository()
|
||||||
|
|
||||||
|
@ -27,6 +27,12 @@ func TestAnnotationsApiEndpoint(t *testing.T) {
|
|||||||
IsRegion: false,
|
IsRegion: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
patchCmd := dtos.PatchAnnotationsCmd{
|
||||||
|
Time: 1000,
|
||||||
|
Text: "annotation text",
|
||||||
|
Tags: []string{"tag1", "tag2"},
|
||||||
|
}
|
||||||
|
|
||||||
Convey("When user is an Org Viewer", func() {
|
Convey("When user is an Org Viewer", func() {
|
||||||
role := m.ROLE_VIEWER
|
role := m.ROLE_VIEWER
|
||||||
Convey("Should not be allowed to save an annotation", func() {
|
Convey("Should not be allowed to save an annotation", func() {
|
||||||
@ -40,6 +46,11 @@ func TestAnnotationsApiEndpoint(t *testing.T) {
|
|||||||
So(sc.resp.Code, ShouldEqual, 403)
|
So(sc.resp.Code, ShouldEqual, 403)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
patchAnnotationScenario("When calling PATCH on", "/api/annotations/1", "/api/annotations/:annotationId", role, patchCmd, func(sc *scenarioContext) {
|
||||||
|
sc.fakeReqWithParams("PATCH", sc.url, map[string]string{}).exec()
|
||||||
|
So(sc.resp.Code, ShouldEqual, 403)
|
||||||
|
})
|
||||||
|
|
||||||
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/annotations/1", "/api/annotations/:annotationId", role, func(sc *scenarioContext) {
|
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/annotations/1", "/api/annotations/:annotationId", role, func(sc *scenarioContext) {
|
||||||
sc.handlerFunc = DeleteAnnotationByID
|
sc.handlerFunc = DeleteAnnotationByID
|
||||||
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
|
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
|
||||||
@ -67,6 +78,11 @@ func TestAnnotationsApiEndpoint(t *testing.T) {
|
|||||||
So(sc.resp.Code, ShouldEqual, 200)
|
So(sc.resp.Code, ShouldEqual, 200)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
patchAnnotationScenario("When calling PATCH on", "/api/annotations/1", "/api/annotations/:annotationId", role, patchCmd, func(sc *scenarioContext) {
|
||||||
|
sc.fakeReqWithParams("PATCH", sc.url, map[string]string{}).exec()
|
||||||
|
So(sc.resp.Code, ShouldEqual, 200)
|
||||||
|
})
|
||||||
|
|
||||||
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/annotations/1", "/api/annotations/:annotationId", role, func(sc *scenarioContext) {
|
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/annotations/1", "/api/annotations/:annotationId", role, func(sc *scenarioContext) {
|
||||||
sc.handlerFunc = DeleteAnnotationByID
|
sc.handlerFunc = DeleteAnnotationByID
|
||||||
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
|
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
|
||||||
@ -100,6 +116,13 @@ func TestAnnotationsApiEndpoint(t *testing.T) {
|
|||||||
Id: 1,
|
Id: 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
patchCmd := dtos.PatchAnnotationsCmd{
|
||||||
|
Time: 8000,
|
||||||
|
Text: "annotation text 50",
|
||||||
|
Tags: []string{"foo", "bar"},
|
||||||
|
Id: 1,
|
||||||
|
}
|
||||||
|
|
||||||
deleteCmd := dtos.DeleteAnnotationsCmd{
|
deleteCmd := dtos.DeleteAnnotationsCmd{
|
||||||
DashboardId: 1,
|
DashboardId: 1,
|
||||||
PanelId: 1,
|
PanelId: 1,
|
||||||
@ -136,6 +159,11 @@ func TestAnnotationsApiEndpoint(t *testing.T) {
|
|||||||
So(sc.resp.Code, ShouldEqual, 403)
|
So(sc.resp.Code, ShouldEqual, 403)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
patchAnnotationScenario("When calling PATCH on", "/api/annotations/1", "/api/annotations/:annotationId", role, patchCmd, func(sc *scenarioContext) {
|
||||||
|
sc.fakeReqWithParams("PATCH", sc.url, map[string]string{}).exec()
|
||||||
|
So(sc.resp.Code, ShouldEqual, 403)
|
||||||
|
})
|
||||||
|
|
||||||
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/annotations/1", "/api/annotations/:annotationId", role, func(sc *scenarioContext) {
|
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/annotations/1", "/api/annotations/:annotationId", role, func(sc *scenarioContext) {
|
||||||
sc.handlerFunc = DeleteAnnotationByID
|
sc.handlerFunc = DeleteAnnotationByID
|
||||||
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
|
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
|
||||||
@ -163,6 +191,11 @@ func TestAnnotationsApiEndpoint(t *testing.T) {
|
|||||||
So(sc.resp.Code, ShouldEqual, 200)
|
So(sc.resp.Code, ShouldEqual, 200)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
patchAnnotationScenario("When calling PATCH on", "/api/annotations/1", "/api/annotations/:annotationId", role, patchCmd, func(sc *scenarioContext) {
|
||||||
|
sc.fakeReqWithParams("PATCH", sc.url, map[string]string{}).exec()
|
||||||
|
So(sc.resp.Code, ShouldEqual, 200)
|
||||||
|
})
|
||||||
|
|
||||||
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/annotations/1", "/api/annotations/:annotationId", role, func(sc *scenarioContext) {
|
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/annotations/1", "/api/annotations/:annotationId", role, func(sc *scenarioContext) {
|
||||||
sc.handlerFunc = DeleteAnnotationByID
|
sc.handlerFunc = DeleteAnnotationByID
|
||||||
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
|
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
|
||||||
@ -189,6 +222,12 @@ func TestAnnotationsApiEndpoint(t *testing.T) {
|
|||||||
sc.fakeReqWithParams("PUT", sc.url, map[string]string{}).exec()
|
sc.fakeReqWithParams("PUT", sc.url, map[string]string{}).exec()
|
||||||
So(sc.resp.Code, ShouldEqual, 200)
|
So(sc.resp.Code, ShouldEqual, 200)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
patchAnnotationScenario("When calling PATCH on", "/api/annotations/1", "/api/annotations/:annotationId", role, patchCmd, func(sc *scenarioContext) {
|
||||||
|
sc.fakeReqWithParams("PATCH", sc.url, map[string]string{}).exec()
|
||||||
|
So(sc.resp.Code, ShouldEqual, 200)
|
||||||
|
})
|
||||||
|
|
||||||
deleteAnnotationsScenario("When calling POST on", "/api/annotations/mass-delete", "/api/annotations/mass-delete", role, deleteCmd, func(sc *scenarioContext) {
|
deleteAnnotationsScenario("When calling POST on", "/api/annotations/mass-delete", "/api/annotations/mass-delete", role, deleteCmd, func(sc *scenarioContext) {
|
||||||
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
|
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
|
||||||
So(sc.resp.Code, ShouldEqual, 200)
|
So(sc.resp.Code, ShouldEqual, 200)
|
||||||
@ -264,6 +303,29 @@ func putAnnotationScenario(desc string, url string, routePattern string, role m.
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func patchAnnotationScenario(desc string, url string, routePattern string, role m.RoleType, cmd dtos.PatchAnnotationsCmd, fn scenarioFunc) {
|
||||||
|
Convey(desc+" "+url, func() {
|
||||||
|
defer bus.ClearBusHandlers()
|
||||||
|
|
||||||
|
sc := setupScenarioContext(url)
|
||||||
|
sc.defaultHandler = Wrap(func(c *m.ReqContext) Response {
|
||||||
|
sc.context = c
|
||||||
|
sc.context.UserId = TestUserID
|
||||||
|
sc.context.OrgId = TestOrgID
|
||||||
|
sc.context.OrgRole = role
|
||||||
|
|
||||||
|
return PatchAnnotation(c, cmd)
|
||||||
|
})
|
||||||
|
|
||||||
|
fakeAnnoRepo = &fakeAnnotationsRepo{}
|
||||||
|
annotations.SetRepository(fakeAnnoRepo)
|
||||||
|
|
||||||
|
sc.m.Patch(routePattern, sc.defaultHandler)
|
||||||
|
|
||||||
|
fn(sc)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func deleteAnnotationsScenario(desc string, url string, routePattern string, role m.RoleType, cmd dtos.DeleteAnnotationsCmd, fn scenarioFunc) {
|
func deleteAnnotationsScenario(desc string, url string, routePattern string, role m.RoleType, cmd dtos.DeleteAnnotationsCmd, fn scenarioFunc) {
|
||||||
Convey(desc+" "+url, func() {
|
Convey(desc+" "+url, func() {
|
||||||
defer bus.ClearBusHandlers()
|
defer bus.ClearBusHandlers()
|
||||||
|
@ -108,8 +108,8 @@ func (hs *HTTPServer) registerRoutes() {
|
|||||||
r.Get("/api/snapshots-delete/:deleteKey", Wrap(DeleteDashboardSnapshotByDeleteKey))
|
r.Get("/api/snapshots-delete/:deleteKey", Wrap(DeleteDashboardSnapshotByDeleteKey))
|
||||||
r.Delete("/api/snapshots/:key", reqEditorRole, Wrap(DeleteDashboardSnapshot))
|
r.Delete("/api/snapshots/:key", reqEditorRole, Wrap(DeleteDashboardSnapshot))
|
||||||
|
|
||||||
// api renew session based on remember cookie
|
// api renew session based on cookie
|
||||||
r.Get("/api/login/ping", quota("session"), hs.LoginAPIPing)
|
r.Get("/api/login/ping", quota("session"), Wrap(hs.LoginAPIPing))
|
||||||
|
|
||||||
// authed api
|
// authed api
|
||||||
r.Group("/api", func(apiRoute routing.RouteRegister) {
|
r.Group("/api", func(apiRoute routing.RouteRegister) {
|
||||||
@ -354,6 +354,7 @@ func (hs *HTTPServer) registerRoutes() {
|
|||||||
annotationsRoute.Post("/", bind(dtos.PostAnnotationsCmd{}), Wrap(PostAnnotation))
|
annotationsRoute.Post("/", bind(dtos.PostAnnotationsCmd{}), Wrap(PostAnnotation))
|
||||||
annotationsRoute.Delete("/:annotationId", Wrap(DeleteAnnotationByID))
|
annotationsRoute.Delete("/:annotationId", Wrap(DeleteAnnotationByID))
|
||||||
annotationsRoute.Put("/:annotationId", bind(dtos.UpdateAnnotationsCmd{}), Wrap(UpdateAnnotation))
|
annotationsRoute.Put("/:annotationId", bind(dtos.UpdateAnnotationsCmd{}), Wrap(UpdateAnnotation))
|
||||||
|
annotationsRoute.Patch("/:annotationId", bind(dtos.PatchAnnotationsCmd{}), Wrap(PatchAnnotation))
|
||||||
annotationsRoute.Delete("/region/:regionId", Wrap(DeleteAnnotationRegion))
|
annotationsRoute.Delete("/region/:regionId", Wrap(DeleteAnnotationRegion))
|
||||||
annotationsRoute.Post("/graphite", reqEditorRole, bind(dtos.PostGraphiteAnnotationsCmd{}), Wrap(PostGraphiteAnnotation))
|
annotationsRoute.Post("/graphite", reqEditorRole, bind(dtos.PostGraphiteAnnotationsCmd{}), Wrap(PostGraphiteAnnotation))
|
||||||
})
|
})
|
||||||
|
@ -149,4 +149,4 @@ func (s *fakeUserAuthTokenService) UserAuthenticatedHook(user *m.User, c *m.ReqC
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *fakeUserAuthTokenService) UserSignedOutHook(c *m.ReqContext) {}
|
func (s *fakeUserAuthTokenService) SignOutUser(c *m.ReqContext) error { return nil }
|
||||||
|
@ -22,6 +22,14 @@ type UpdateAnnotationsCmd struct {
|
|||||||
TimeEnd int64 `json:"timeEnd"`
|
TimeEnd int64 `json:"timeEnd"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PatchAnnotationsCmd struct {
|
||||||
|
Id int64 `json:"id"`
|
||||||
|
Time int64 `json:"time"`
|
||||||
|
Text string `json:"text"`
|
||||||
|
Tags []string `json:"tags"`
|
||||||
|
TimeEnd int64 `json:"timeEnd"`
|
||||||
|
}
|
||||||
|
|
||||||
type DeleteAnnotationsCmd struct {
|
type DeleteAnnotationsCmd struct {
|
||||||
AlertId int64 `json:"alertId"`
|
AlertId int64 `json:"alertId"`
|
||||||
DashboardId int64 `json:"dashboardId"`
|
DashboardId int64 `json:"dashboardId"`
|
||||||
|
@ -136,7 +136,7 @@ func (hs *HTTPServer) loginUserWithUser(user *m.User, c *m.ReqContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (hs *HTTPServer) Logout(c *m.ReqContext) {
|
func (hs *HTTPServer) Logout(c *m.ReqContext) {
|
||||||
hs.AuthTokenService.UserSignedOutHook(c)
|
hs.AuthTokenService.SignOutUser(c)
|
||||||
|
|
||||||
if setting.SignoutRedirectUrl != "" {
|
if setting.SignoutRedirectUrl != "" {
|
||||||
c.Redirect(setting.SignoutRedirectUrl)
|
c.Redirect(setting.SignoutRedirectUrl)
|
||||||
|
@ -602,4 +602,4 @@ func (s *fakeUserAuthTokenService) UserAuthenticatedHook(user *m.User, c *m.ReqC
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *fakeUserAuthTokenService) UserSignedOutHook(c *m.ReqContext) {}
|
func (s *fakeUserAuthTokenService) SignOutUser(c *m.ReqContext) error { return nil }
|
||||||
|
@ -3,6 +3,7 @@ package auth
|
|||||||
import (
|
import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
@ -31,7 +32,7 @@ var (
|
|||||||
type UserAuthTokenService interface {
|
type UserAuthTokenService interface {
|
||||||
InitContextWithToken(ctx *models.ReqContext, orgID int64) bool
|
InitContextWithToken(ctx *models.ReqContext, orgID int64) bool
|
||||||
UserAuthenticatedHook(user *models.User, c *models.ReqContext) error
|
UserAuthenticatedHook(user *models.User, c *models.ReqContext) error
|
||||||
UserSignedOutHook(c *models.ReqContext)
|
SignOutUser(c *models.ReqContext) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserAuthTokenServiceImpl struct {
|
type UserAuthTokenServiceImpl struct {
|
||||||
@ -85,7 +86,7 @@ func (s *UserAuthTokenServiceImpl) InitContextWithToken(ctx *models.ReqContext,
|
|||||||
|
|
||||||
func (s *UserAuthTokenServiceImpl) writeSessionCookie(ctx *models.ReqContext, value string, maxAge int) {
|
func (s *UserAuthTokenServiceImpl) writeSessionCookie(ctx *models.ReqContext, value string, maxAge int) {
|
||||||
if setting.Env == setting.DEV {
|
if setting.Env == setting.DEV {
|
||||||
ctx.Logger.Info("new token", "unhashed token", value)
|
ctx.Logger.Debug("new token", "unhashed token", value)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Resp.Header().Del("Set-Cookie")
|
ctx.Resp.Header().Del("Set-Cookie")
|
||||||
@ -112,8 +113,19 @@ func (s *UserAuthTokenServiceImpl) UserAuthenticatedHook(user *models.User, c *m
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *UserAuthTokenServiceImpl) UserSignedOutHook(c *models.ReqContext) {
|
func (s *UserAuthTokenServiceImpl) SignOutUser(c *models.ReqContext) error {
|
||||||
|
unhashedToken := c.GetCookie(s.Cfg.LoginCookieName)
|
||||||
|
if unhashedToken == "" {
|
||||||
|
return errors.New("cannot logout without session token")
|
||||||
|
}
|
||||||
|
|
||||||
|
hashedToken := hashToken(unhashedToken)
|
||||||
|
|
||||||
|
sql := `DELETE FROM user_auth_token WHERE auth_token = ?`
|
||||||
|
_, err := s.SQLStore.NewSession().Exec(sql, hashedToken)
|
||||||
|
|
||||||
s.writeSessionCookie(c, "", -1)
|
s.writeSessionCookie(c, "", -1)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *UserAuthTokenServiceImpl) CreateToken(userId int64, clientIP, userAgent string) (*userAuthToken, error) {
|
func (s *UserAuthTokenServiceImpl) CreateToken(userId int64, clientIP, userAgent string) (*userAuthToken, error) {
|
||||||
|
@ -1,10 +1,15 @@
|
|||||||
package auth
|
package auth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/models"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
|
macaron "gopkg.in/macaron.v1"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/log"
|
"github.com/grafana/grafana/pkg/log"
|
||||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||||
@ -46,6 +51,40 @@ func TestUserAuthToken(t *testing.T) {
|
|||||||
So(err, ShouldEqual, ErrAuthTokenNotFound)
|
So(err, ShouldEqual, ErrAuthTokenNotFound)
|
||||||
So(LookupToken, ShouldBeNil)
|
So(LookupToken, ShouldBeNil)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Convey("signing out should delete token and cookie if present", func() {
|
||||||
|
httpreq := &http.Request{Header: make(http.Header)}
|
||||||
|
httpreq.AddCookie(&http.Cookie{Name: userAuthTokenService.Cfg.LoginCookieName, Value: token.UnhashedToken})
|
||||||
|
|
||||||
|
ctx := &models.ReqContext{Context: &macaron.Context{
|
||||||
|
Req: macaron.Request{Request: httpreq},
|
||||||
|
Resp: macaron.NewResponseWriter("POST", httptest.NewRecorder()),
|
||||||
|
},
|
||||||
|
Logger: log.New("fakelogger"),
|
||||||
|
}
|
||||||
|
|
||||||
|
err = userAuthTokenService.SignOutUser(ctx)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
// makes sure we tell the browser to overwrite the cookie
|
||||||
|
cookieHeader := fmt.Sprintf("%s=; Path=/; Max-Age=0; HttpOnly", userAuthTokenService.Cfg.LoginCookieName)
|
||||||
|
So(ctx.Resp.Header().Get("Set-Cookie"), ShouldEqual, cookieHeader)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("signing out an none existing session should return an error", func() {
|
||||||
|
httpreq := &http.Request{Header: make(http.Header)}
|
||||||
|
httpreq.AddCookie(&http.Cookie{Name: userAuthTokenService.Cfg.LoginCookieName, Value: ""})
|
||||||
|
|
||||||
|
ctx := &models.ReqContext{Context: &macaron.Context{
|
||||||
|
Req: macaron.Request{Request: httpreq},
|
||||||
|
Resp: macaron.NewResponseWriter("POST", httptest.NewRecorder()),
|
||||||
|
},
|
||||||
|
Logger: log.New("fakelogger"),
|
||||||
|
}
|
||||||
|
|
||||||
|
err = userAuthTokenService.SignOutUser(ctx)
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("expires correctly", func() {
|
Convey("expires correctly", func() {
|
||||||
|
@ -242,10 +242,7 @@ func (ss *SqlStore) buildConnectionString() (string, error) {
|
|||||||
|
|
||||||
cnnstr += ss.buildExtraConnectionString('&')
|
cnnstr += ss.buildExtraConnectionString('&')
|
||||||
case migrator.POSTGRES:
|
case migrator.POSTGRES:
|
||||||
host, port, err := util.SplitIPPort(ss.dbCfg.Host, "5432")
|
host, port := util.SplitHostPortDefault(ss.dbCfg.Host, "127.0.0.1", "5432")
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if ss.dbCfg.Pwd == "" {
|
if ss.dbCfg.Pwd == "" {
|
||||||
ss.dbCfg.Pwd = "''"
|
ss.dbCfg.Pwd = "''"
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ import (
|
|||||||
"github.com/aws/aws-sdk-go/aws/request"
|
"github.com/aws/aws-sdk-go/aws/request"
|
||||||
"github.com/aws/aws-sdk-go/service/cloudwatch"
|
"github.com/aws/aws-sdk-go/service/cloudwatch"
|
||||||
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
|
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
|
||||||
|
"github.com/aws/aws-sdk-go/service/resourcegroupstaggingapi/resourcegroupstaggingapiiface"
|
||||||
"github.com/grafana/grafana/pkg/components/null"
|
"github.com/grafana/grafana/pkg/components/null"
|
||||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||||
"github.com/grafana/grafana/pkg/metrics"
|
"github.com/grafana/grafana/pkg/metrics"
|
||||||
@ -28,7 +29,8 @@ import (
|
|||||||
|
|
||||||
type CloudWatchExecutor struct {
|
type CloudWatchExecutor struct {
|
||||||
*models.DataSource
|
*models.DataSource
|
||||||
ec2Svc ec2iface.EC2API
|
ec2Svc ec2iface.EC2API
|
||||||
|
rgtaSvc resourcegroupstaggingapiiface.ResourceGroupsTaggingAPIAPI
|
||||||
}
|
}
|
||||||
|
|
||||||
type DatasourceInfo struct {
|
type DatasourceInfo struct {
|
||||||
|
@ -15,6 +15,7 @@ import (
|
|||||||
"github.com/aws/aws-sdk-go/aws/session"
|
"github.com/aws/aws-sdk-go/aws/session"
|
||||||
"github.com/aws/aws-sdk-go/service/cloudwatch"
|
"github.com/aws/aws-sdk-go/service/cloudwatch"
|
||||||
"github.com/aws/aws-sdk-go/service/ec2"
|
"github.com/aws/aws-sdk-go/service/ec2"
|
||||||
|
"github.com/aws/aws-sdk-go/service/resourcegroupstaggingapi"
|
||||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||||
"github.com/grafana/grafana/pkg/metrics"
|
"github.com/grafana/grafana/pkg/metrics"
|
||||||
"github.com/grafana/grafana/pkg/tsdb"
|
"github.com/grafana/grafana/pkg/tsdb"
|
||||||
@ -95,10 +96,11 @@ func init() {
|
|||||||
"AWS/Logs": {"IncomingBytes", "IncomingLogEvents", "ForwardedBytes", "ForwardedLogEvents", "DeliveryErrors", "DeliveryThrottling"},
|
"AWS/Logs": {"IncomingBytes", "IncomingLogEvents", "ForwardedBytes", "ForwardedLogEvents", "DeliveryErrors", "DeliveryThrottling"},
|
||||||
"AWS/ML": {"PredictCount", "PredictFailureCount"},
|
"AWS/ML": {"PredictCount", "PredictFailureCount"},
|
||||||
"AWS/NATGateway": {"PacketsOutToDestination", "PacketsOutToSource", "PacketsInFromSource", "PacketsInFromDestination", "BytesOutToDestination", "BytesOutToSource", "BytesInFromSource", "BytesInFromDestination", "ErrorPortAllocation", "ActiveConnectionCount", "ConnectionAttemptCount", "ConnectionEstablishedCount", "IdleTimeoutCount", "PacketsDropCount"},
|
"AWS/NATGateway": {"PacketsOutToDestination", "PacketsOutToSource", "PacketsInFromSource", "PacketsInFromDestination", "BytesOutToDestination", "BytesOutToSource", "BytesInFromSource", "BytesInFromDestination", "ErrorPortAllocation", "ActiveConnectionCount", "ConnectionAttemptCount", "ConnectionEstablishedCount", "IdleTimeoutCount", "PacketsDropCount"},
|
||||||
|
"AWS/Neptune": {"CPUUtilization", "ClusterReplicaLag", "ClusterReplicaLagMaximum", "ClusterReplicaLagMinimum", "EngineUptime", "FreeableMemory", "FreeLocalStorage", "GremlinHttp1xx", "GremlinHttp2xx", "GremlinHttp4xx", "GremlinHttp5xx", "GremlinErrors", "GremlinRequests", "GremlinRequestsPerSec", "GremlinWebSocketSuccess", "GremlinWebSocketClientErrors", "GremlinWebSocketServerErrors", "GremlinWebSocketAvailableConnections", "Http1xx", "Http2xx", "Http4xx", "Http5xx", "Http100", "Http101", "Http200", "Http400", "Http403", "Http405", "Http413", "Http429", "Http500", "Http501", "LoaderErrors", "LoaderRequests", "NetworkReceiveThroughput", "NetworkThroughput", "NetworkTransmitThroughput", "SparqlHttp1xx", "SparqlHttp2xx", "SparqlHttp4xx", "SparqlHttp5xx", "SparqlErrors", "SparqlRequests", "SparqlRequestsPerSec", "StatusErrors", "StatusRequests", "VolumeBytesUsed", "VolumeReadIOPs", "VolumeWriteIOPs"},
|
||||||
"AWS/NetworkELB": {"ActiveFlowCount", "ConsumedLCUs", "HealthyHostCount", "NewFlowCount", "ProcessedBytes", "TCP_Client_Reset_Count", "TCP_ELB_Reset_Count", "TCP_Target_Reset_Count", "UnHealthyHostCount"},
|
"AWS/NetworkELB": {"ActiveFlowCount", "ConsumedLCUs", "HealthyHostCount", "NewFlowCount", "ProcessedBytes", "TCP_Client_Reset_Count", "TCP_ELB_Reset_Count", "TCP_Target_Reset_Count", "UnHealthyHostCount"},
|
||||||
"AWS/OpsWorks": {"cpu_idle", "cpu_nice", "cpu_system", "cpu_user", "cpu_waitio", "load_1", "load_5", "load_15", "memory_buffers", "memory_cached", "memory_free", "memory_swap", "memory_total", "memory_used", "procs"},
|
"AWS/OpsWorks": {"cpu_idle", "cpu_nice", "cpu_system", "cpu_user", "cpu_waitio", "load_1", "load_5", "load_15", "memory_buffers", "memory_cached", "memory_free", "memory_swap", "memory_total", "memory_used", "procs"},
|
||||||
"AWS/Redshift": {"CPUUtilization", "DatabaseConnections", "HealthStatus", "MaintenanceMode", "NetworkReceiveThroughput", "NetworkTransmitThroughput", "PercentageDiskSpaceUsed", "QueriesCompletedPerSecond", "QueryDuration", "QueryRuntimeBreakdown", "ReadIOPS", "ReadLatency", "ReadThroughput", "WLMQueriesCompletedPerSecond", "WLMQueryDuration", "WLMQueueLength", "WriteIOPS", "WriteLatency", "WriteThroughput"},
|
"AWS/Redshift": {"CPUUtilization", "DatabaseConnections", "HealthStatus", "MaintenanceMode", "NetworkReceiveThroughput", "NetworkTransmitThroughput", "PercentageDiskSpaceUsed", "QueriesCompletedPerSecond", "QueryDuration", "QueryRuntimeBreakdown", "ReadIOPS", "ReadLatency", "ReadThroughput", "WLMQueriesCompletedPerSecond", "WLMQueryDuration", "WLMQueueLength", "WriteIOPS", "WriteLatency", "WriteThroughput"},
|
||||||
"AWS/RDS": {"ActiveTransactions", "AuroraBinlogReplicaLag", "AuroraReplicaLag", "AuroraReplicaLagMaximum", "AuroraReplicaLagMinimum", "BinLogDiskUsage", "BlockedTransactions", "BufferCacheHitRatio", "BurstBalance", "CommitLatency", "CommitThroughput", "BinLogDiskUsage", "CPUCreditBalance", "CPUCreditUsage", "CPUUtilization", "DatabaseConnections", "DDLLatency", "DDLThroughput", "Deadlocks", "DeleteLatency", "DeleteThroughput", "DiskQueueDepth", "DMLLatency", "DMLThroughput", "EngineUptime", "FailedSqlStatements", "FreeableMemory", "FreeLocalStorage", "FreeStorageSpace", "InsertLatency", "InsertThroughput", "LoginFailures", "NetworkReceiveThroughput", "NetworkTransmitThroughput", "NetworkThroughput", "Queries", "ReadIOPS", "ReadLatency", "ReadThroughput", "ReplicaLag", "ResultSetCacheHitRatio", "SelectLatency", "SelectThroughput", "SwapUsage", "TotalConnections", "UpdateLatency", "UpdateThroughput", "VolumeBytesUsed", "VolumeReadIOPS", "VolumeWriteIOPS", "WriteIOPS", "WriteLatency", "WriteThroughput"},
|
"AWS/RDS": {"ActiveTransactions", "AuroraBinlogReplicaLag", "AuroraReplicaLag", "AuroraReplicaLagMaximum", "AuroraReplicaLagMinimum", "BinLogDiskUsage", "BlockedTransactions", "BufferCacheHitRatio", "BurstBalance", "CommitLatency", "CommitThroughput", "BinLogDiskUsage", "CPUCreditBalance", "CPUCreditUsage", "CPUUtilization", "DatabaseConnections", "DDLLatency", "DDLThroughput", "Deadlocks", "DeleteLatency", "DeleteThroughput", "DiskQueueDepth", "DMLLatency", "DMLThroughput", "EngineUptime", "FailedSqlStatements", "FreeableMemory", "FreeLocalStorage", "FreeStorageSpace", "InsertLatency", "InsertThroughput", "LoginFailures", "NetworkReceiveThroughput", "NetworkTransmitThroughput", "NetworkThroughput", "Queries", "ReadIOPS", "ReadLatency", "ReadThroughput", "ReplicaLag", "ResultSetCacheHitRatio", "SelectLatency", "SelectThroughput", "ServerlessDatabaseCapacity", "SwapUsage", "TotalConnections", "UpdateLatency", "UpdateThroughput", "VolumeBytesUsed", "VolumeReadIOPS", "VolumeWriteIOPS", "WriteIOPS", "WriteLatency", "WriteThroughput"},
|
||||||
"AWS/Route53": {"ChildHealthCheckHealthyCount", "HealthCheckStatus", "HealthCheckPercentageHealthy", "ConnectionTime", "SSLHandshakeTime", "TimeToFirstByte"},
|
"AWS/Route53": {"ChildHealthCheckHealthyCount", "HealthCheckStatus", "HealthCheckPercentageHealthy", "ConnectionTime", "SSLHandshakeTime", "TimeToFirstByte"},
|
||||||
"AWS/S3": {"BucketSizeBytes", "NumberOfObjects", "AllRequests", "GetRequests", "PutRequests", "DeleteRequests", "HeadRequests", "PostRequests", "ListRequests", "BytesDownloaded", "BytesUploaded", "4xxErrors", "5xxErrors", "FirstByteLatency", "TotalRequestLatency"},
|
"AWS/S3": {"BucketSizeBytes", "NumberOfObjects", "AllRequests", "GetRequests", "PutRequests", "DeleteRequests", "HeadRequests", "PostRequests", "ListRequests", "BytesDownloaded", "BytesUploaded", "4xxErrors", "5xxErrors", "FirstByteLatency", "TotalRequestLatency"},
|
||||||
"AWS/SES": {"Bounce", "Complaint", "Delivery", "Reject", "Send", "Reputation.BounceRate", "Reputation.ComplaintRate"},
|
"AWS/SES": {"Bounce", "Complaint", "Delivery", "Reject", "Send", "Reputation.BounceRate", "Reputation.ComplaintRate"},
|
||||||
@ -149,6 +151,7 @@ func init() {
|
|||||||
"AWS/Logs": {"LogGroupName", "DestinationType", "FilterName"},
|
"AWS/Logs": {"LogGroupName", "DestinationType", "FilterName"},
|
||||||
"AWS/ML": {"MLModelId", "RequestMode"},
|
"AWS/ML": {"MLModelId", "RequestMode"},
|
||||||
"AWS/NATGateway": {"NatGatewayId"},
|
"AWS/NATGateway": {"NatGatewayId"},
|
||||||
|
"AWS/Neptune": {"DBClusterIdentifier", "Role", "DatabaseClass", "EngineName"},
|
||||||
"AWS/NetworkELB": {"LoadBalancer", "TargetGroup", "AvailabilityZone"},
|
"AWS/NetworkELB": {"LoadBalancer", "TargetGroup", "AvailabilityZone"},
|
||||||
"AWS/OpsWorks": {"StackId", "LayerId", "InstanceId"},
|
"AWS/OpsWorks": {"StackId", "LayerId", "InstanceId"},
|
||||||
"AWS/Redshift": {"NodeID", "ClusterIdentifier", "latency", "service class", "wmlid"},
|
"AWS/Redshift": {"NodeID", "ClusterIdentifier", "latency", "service class", "wmlid"},
|
||||||
@ -198,6 +201,8 @@ func (e *CloudWatchExecutor) executeMetricFindQuery(ctx context.Context, queryCo
|
|||||||
data, err = e.handleGetEbsVolumeIds(ctx, parameters, queryContext)
|
data, err = e.handleGetEbsVolumeIds(ctx, parameters, queryContext)
|
||||||
case "ec2_instance_attribute":
|
case "ec2_instance_attribute":
|
||||||
data, err = e.handleGetEc2InstanceAttribute(ctx, parameters, queryContext)
|
data, err = e.handleGetEc2InstanceAttribute(ctx, parameters, queryContext)
|
||||||
|
case "resource_arns":
|
||||||
|
data, err = e.handleGetResourceArns(ctx, parameters, queryContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
transformToTable(data, queryResult)
|
transformToTable(data, queryResult)
|
||||||
@ -534,6 +539,65 @@ func (e *CloudWatchExecutor) handleGetEc2InstanceAttribute(ctx context.Context,
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *CloudWatchExecutor) ensureRGTAClientSession(region string) error {
|
||||||
|
if e.rgtaSvc == nil {
|
||||||
|
dsInfo := e.getDsInfo(region)
|
||||||
|
cfg, err := e.getAwsConfig(dsInfo)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed to call ec2:getAwsConfig, %v", err)
|
||||||
|
}
|
||||||
|
sess, err := session.NewSession(cfg)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed to call ec2:NewSession, %v", err)
|
||||||
|
}
|
||||||
|
e.rgtaSvc = resourcegroupstaggingapi.New(sess, cfg)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *CloudWatchExecutor) handleGetResourceArns(ctx context.Context, parameters *simplejson.Json, queryContext *tsdb.TsdbQuery) ([]suggestData, error) {
|
||||||
|
region := parameters.Get("region").MustString()
|
||||||
|
resourceType := parameters.Get("resourceType").MustString()
|
||||||
|
filterJson := parameters.Get("tags").MustMap()
|
||||||
|
|
||||||
|
err := e.ensureRGTAClientSession(region)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var filters []*resourcegroupstaggingapi.TagFilter
|
||||||
|
for k, v := range filterJson {
|
||||||
|
if vv, ok := v.([]interface{}); ok {
|
||||||
|
var vvvvv []*string
|
||||||
|
for _, vvv := range vv {
|
||||||
|
if vvvv, ok := vvv.(string); ok {
|
||||||
|
vvvvv = append(vvvvv, &vvvv)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
filters = append(filters, &resourcegroupstaggingapi.TagFilter{
|
||||||
|
Key: aws.String(k),
|
||||||
|
Values: vvvvv,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var resourceTypes []*string
|
||||||
|
resourceTypes = append(resourceTypes, &resourceType)
|
||||||
|
|
||||||
|
resources, err := e.resourceGroupsGetResources(region, filters, resourceTypes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([]suggestData, 0)
|
||||||
|
for _, resource := range resources.ResourceTagMappingList {
|
||||||
|
data := *resource.ResourceARN
|
||||||
|
result = append(result, suggestData{Text: data, Value: data})
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (e *CloudWatchExecutor) cloudwatchListMetrics(region string, namespace string, metricName string, dimensions []*cloudwatch.DimensionFilter) (*cloudwatch.ListMetricsOutput, error) {
|
func (e *CloudWatchExecutor) cloudwatchListMetrics(region string, namespace string, metricName string, dimensions []*cloudwatch.DimensionFilter) (*cloudwatch.ListMetricsOutput, error) {
|
||||||
svc, err := e.getClient(region)
|
svc, err := e.getClient(region)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -585,6 +649,28 @@ func (e *CloudWatchExecutor) ec2DescribeInstances(region string, filters []*ec2.
|
|||||||
return &resp, nil
|
return &resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *CloudWatchExecutor) resourceGroupsGetResources(region string, filters []*resourcegroupstaggingapi.TagFilter, resourceTypes []*string) (*resourcegroupstaggingapi.GetResourcesOutput, error) {
|
||||||
|
params := &resourcegroupstaggingapi.GetResourcesInput{
|
||||||
|
ResourceTypeFilters: resourceTypes,
|
||||||
|
TagFilters: filters,
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp resourcegroupstaggingapi.GetResourcesOutput
|
||||||
|
err := e.rgtaSvc.GetResourcesPages(params,
|
||||||
|
func(page *resourcegroupstaggingapi.GetResourcesOutput, lastPage bool) bool {
|
||||||
|
resources, _ := awsutil.ValuesAtPath(page, "ResourceTagMappingList")
|
||||||
|
for _, resource := range resources {
|
||||||
|
resp.ResourceTagMappingList = append(resp.ResourceTagMappingList, resource.(*resourcegroupstaggingapi.ResourceTagMapping))
|
||||||
|
}
|
||||||
|
return !lastPage
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("Failed to call tags:GetResources")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
func getAllMetrics(cwData *DatasourceInfo) (cloudwatch.ListMetricsOutput, error) {
|
func getAllMetrics(cwData *DatasourceInfo) (cloudwatch.ListMetricsOutput, error) {
|
||||||
creds, err := GetCredentials(cwData)
|
creds, err := GetCredentials(cwData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -8,6 +8,8 @@ import (
|
|||||||
"github.com/aws/aws-sdk-go/service/cloudwatch"
|
"github.com/aws/aws-sdk-go/service/cloudwatch"
|
||||||
"github.com/aws/aws-sdk-go/service/ec2"
|
"github.com/aws/aws-sdk-go/service/ec2"
|
||||||
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
|
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
|
||||||
|
"github.com/aws/aws-sdk-go/service/resourcegroupstaggingapi"
|
||||||
|
"github.com/aws/aws-sdk-go/service/resourcegroupstaggingapi/resourcegroupstaggingapiiface"
|
||||||
"github.com/bmizerany/assert"
|
"github.com/bmizerany/assert"
|
||||||
"github.com/grafana/grafana/pkg/components/securejsondata"
|
"github.com/grafana/grafana/pkg/components/securejsondata"
|
||||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||||
@ -22,6 +24,11 @@ type mockedEc2 struct {
|
|||||||
RespRegions ec2.DescribeRegionsOutput
|
RespRegions ec2.DescribeRegionsOutput
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type mockedRGTA struct {
|
||||||
|
resourcegroupstaggingapiiface.ResourceGroupsTaggingAPIAPI
|
||||||
|
Resp resourcegroupstaggingapi.GetResourcesOutput
|
||||||
|
}
|
||||||
|
|
||||||
func (m mockedEc2) DescribeInstancesPages(in *ec2.DescribeInstancesInput, fn func(*ec2.DescribeInstancesOutput, bool) bool) error {
|
func (m mockedEc2) DescribeInstancesPages(in *ec2.DescribeInstancesInput, fn func(*ec2.DescribeInstancesOutput, bool) bool) error {
|
||||||
fn(&m.Resp, true)
|
fn(&m.Resp, true)
|
||||||
return nil
|
return nil
|
||||||
@ -30,6 +37,11 @@ func (m mockedEc2) DescribeRegions(in *ec2.DescribeRegionsInput) (*ec2.DescribeR
|
|||||||
return &m.RespRegions, nil
|
return &m.RespRegions, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m mockedRGTA) GetResourcesPages(in *resourcegroupstaggingapi.GetResourcesInput, fn func(*resourcegroupstaggingapi.GetResourcesOutput, bool) bool) error {
|
||||||
|
fn(&m.Resp, true)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func TestCloudWatchMetrics(t *testing.T) {
|
func TestCloudWatchMetrics(t *testing.T) {
|
||||||
|
|
||||||
Convey("When calling getMetricsForCustomMetrics", t, func() {
|
Convey("When calling getMetricsForCustomMetrics", t, func() {
|
||||||
@ -209,6 +221,51 @@ func TestCloudWatchMetrics(t *testing.T) {
|
|||||||
So(result[7].Text, ShouldEqual, "vol-4-2")
|
So(result[7].Text, ShouldEqual, "vol-4-2")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Convey("When calling handleGetResourceArns", t, func() {
|
||||||
|
executor := &CloudWatchExecutor{
|
||||||
|
rgtaSvc: mockedRGTA{
|
||||||
|
Resp: resourcegroupstaggingapi.GetResourcesOutput{
|
||||||
|
ResourceTagMappingList: []*resourcegroupstaggingapi.ResourceTagMapping{
|
||||||
|
{
|
||||||
|
ResourceARN: aws.String("arn:aws:ec2:us-east-1:123456789012:instance/i-12345678901234567"),
|
||||||
|
Tags: []*resourcegroupstaggingapi.Tag{
|
||||||
|
{
|
||||||
|
Key: aws.String("Environment"),
|
||||||
|
Value: aws.String("production"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ResourceARN: aws.String("arn:aws:ec2:us-east-1:123456789012:instance/i-76543210987654321"),
|
||||||
|
Tags: []*resourcegroupstaggingapi.Tag{
|
||||||
|
{
|
||||||
|
Key: aws.String("Environment"),
|
||||||
|
Value: aws.String("production"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
json := simplejson.New()
|
||||||
|
json.Set("region", "us-east-1")
|
||||||
|
json.Set("resourceType", "ec2:instance")
|
||||||
|
tags := make(map[string]interface{})
|
||||||
|
tags["Environment"] = []string{"production"}
|
||||||
|
json.Set("tags", tags)
|
||||||
|
result, _ := executor.handleGetResourceArns(context.Background(), json, &tsdb.TsdbQuery{})
|
||||||
|
|
||||||
|
Convey("Should return all two instances", func() {
|
||||||
|
So(result[0].Text, ShouldEqual, "arn:aws:ec2:us-east-1:123456789012:instance/i-12345678901234567")
|
||||||
|
So(result[0].Value, ShouldEqual, "arn:aws:ec2:us-east-1:123456789012:instance/i-12345678901234567")
|
||||||
|
So(result[1].Text, ShouldEqual, "arn:aws:ec2:us-east-1:123456789012:instance/i-76543210987654321")
|
||||||
|
So(result[1].Value, ShouldEqual, "arn:aws:ec2:us-east-1:123456789012:instance/i-76543210987654321")
|
||||||
|
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseMultiSelectValue(t *testing.T) {
|
func TestParseMultiSelectValue(t *testing.T) {
|
||||||
|
@ -49,10 +49,7 @@ func generateConnectionString(datasource *models.DataSource) (string, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
server, port, err := util.SplitIPPort(datasource.Url, "1433")
|
server, port := util.SplitHostPortDefault(datasource.Url, "localhost", "1433")
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
encrypt := datasource.JsonData.Get("encrypt").MustString("false")
|
encrypt := datasource.JsonData.Get("encrypt").MustString("false")
|
||||||
connStr := fmt.Sprintf("server=%s;port=%s;database=%s;user id=%s;password=%s;",
|
connStr := fmt.Sprintf("server=%s;port=%s;database=%s;user id=%s;password=%s;",
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
package util
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SplitIPPort splits the ip string and port.
|
|
||||||
func SplitIPPort(ipStr string, portDefault string) (ip string, port string, err error) {
|
|
||||||
ipAddr := net.ParseIP(ipStr)
|
|
||||||
|
|
||||||
if ipAddr == nil {
|
|
||||||
// Port was included
|
|
||||||
ip, port, err = net.SplitHostPort(ipStr)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// No port was included
|
|
||||||
ip = ipAddr.String()
|
|
||||||
port = portDefault
|
|
||||||
}
|
|
||||||
|
|
||||||
return ip, port, nil
|
|
||||||
}
|
|
@ -7,23 +7,48 @@ import (
|
|||||||
|
|
||||||
// ParseIPAddress parses an IP address and removes port and/or IPV6 format
|
// ParseIPAddress parses an IP address and removes port and/or IPV6 format
|
||||||
func ParseIPAddress(input string) string {
|
func ParseIPAddress(input string) string {
|
||||||
s := input
|
host, _ := SplitHostPort(input)
|
||||||
lastIndex := strings.LastIndex(input, ":")
|
|
||||||
|
|
||||||
if lastIndex != -1 {
|
ip := net.ParseIP(host)
|
||||||
if lastIndex > 0 && input[lastIndex-1:lastIndex] != ":" {
|
|
||||||
s = input[:lastIndex]
|
if ip == nil {
|
||||||
}
|
return host
|
||||||
}
|
}
|
||||||
|
|
||||||
s = strings.Replace(s, "[", "", -1)
|
|
||||||
s = strings.Replace(s, "]", "", -1)
|
|
||||||
|
|
||||||
ip := net.ParseIP(s)
|
|
||||||
|
|
||||||
if ip.IsLoopback() {
|
if ip.IsLoopback() {
|
||||||
return "127.0.0.1"
|
return "127.0.0.1"
|
||||||
}
|
}
|
||||||
|
|
||||||
return ip.String()
|
return ip.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SplitHostPortDefault splits ip address/hostname string by host and port. Defaults used if no match found
|
||||||
|
func SplitHostPortDefault(input, defaultHost, defaultPort string) (host string, port string) {
|
||||||
|
port = defaultPort
|
||||||
|
s := input
|
||||||
|
lastIndex := strings.LastIndex(input, ":")
|
||||||
|
|
||||||
|
if lastIndex != -1 {
|
||||||
|
if lastIndex > 0 && input[lastIndex-1:lastIndex] != ":" {
|
||||||
|
s = input[:lastIndex]
|
||||||
|
port = input[lastIndex+1:]
|
||||||
|
} else if lastIndex == 0 {
|
||||||
|
s = defaultHost
|
||||||
|
port = input[lastIndex+1:]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
port = defaultPort
|
||||||
|
}
|
||||||
|
|
||||||
|
s = strings.Replace(s, "[", "", -1)
|
||||||
|
s = strings.Replace(s, "]", "", -1)
|
||||||
|
port = strings.Replace(port, "[", "", -1)
|
||||||
|
port = strings.Replace(port, "]", "", -1)
|
||||||
|
|
||||||
|
return s, port
|
||||||
|
}
|
||||||
|
|
||||||
|
// SplitHostPort splits ip address/hostname string by host and port
|
||||||
|
func SplitHostPort(input string) (host string, port string) {
|
||||||
|
return SplitHostPortDefault(input, "", "")
|
||||||
|
}
|
||||||
|
@ -9,8 +9,90 @@ import (
|
|||||||
func TestParseIPAddress(t *testing.T) {
|
func TestParseIPAddress(t *testing.T) {
|
||||||
Convey("Test parse ip address", t, func() {
|
Convey("Test parse ip address", t, func() {
|
||||||
So(ParseIPAddress("192.168.0.140:456"), ShouldEqual, "192.168.0.140")
|
So(ParseIPAddress("192.168.0.140:456"), ShouldEqual, "192.168.0.140")
|
||||||
|
So(ParseIPAddress("192.168.0.140"), ShouldEqual, "192.168.0.140")
|
||||||
So(ParseIPAddress("[::1:456]"), ShouldEqual, "127.0.0.1")
|
So(ParseIPAddress("[::1:456]"), ShouldEqual, "127.0.0.1")
|
||||||
So(ParseIPAddress("[::1]"), ShouldEqual, "127.0.0.1")
|
So(ParseIPAddress("[::1]"), ShouldEqual, "127.0.0.1")
|
||||||
So(ParseIPAddress("192.168.0.140"), ShouldEqual, "192.168.0.140")
|
So(ParseIPAddress("::1"), ShouldEqual, "127.0.0.1")
|
||||||
|
So(ParseIPAddress("::1:123"), ShouldEqual, "127.0.0.1")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSplitHostPortDefault(t *testing.T) {
|
||||||
|
Convey("Test split ip address to host and port", t, func() {
|
||||||
|
host, port := SplitHostPortDefault("192.168.0.140:456", "", "")
|
||||||
|
So(host, ShouldEqual, "192.168.0.140")
|
||||||
|
So(port, ShouldEqual, "456")
|
||||||
|
|
||||||
|
host, port = SplitHostPortDefault("192.168.0.140", "", "123")
|
||||||
|
So(host, ShouldEqual, "192.168.0.140")
|
||||||
|
So(port, ShouldEqual, "123")
|
||||||
|
|
||||||
|
host, port = SplitHostPortDefault("[::1:456]", "", "")
|
||||||
|
So(host, ShouldEqual, "::1")
|
||||||
|
So(port, ShouldEqual, "456")
|
||||||
|
|
||||||
|
host, port = SplitHostPortDefault("[::1]", "", "123")
|
||||||
|
So(host, ShouldEqual, "::1")
|
||||||
|
So(port, ShouldEqual, "123")
|
||||||
|
|
||||||
|
host, port = SplitHostPortDefault("::1:123", "", "")
|
||||||
|
So(host, ShouldEqual, "::1")
|
||||||
|
So(port, ShouldEqual, "123")
|
||||||
|
|
||||||
|
host, port = SplitHostPortDefault("::1", "", "123")
|
||||||
|
So(host, ShouldEqual, "::1")
|
||||||
|
So(port, ShouldEqual, "123")
|
||||||
|
|
||||||
|
host, port = SplitHostPortDefault(":456", "1.2.3.4", "")
|
||||||
|
So(host, ShouldEqual, "1.2.3.4")
|
||||||
|
So(port, ShouldEqual, "456")
|
||||||
|
|
||||||
|
host, port = SplitHostPortDefault("xyz.rds.amazonaws.com", "", "123")
|
||||||
|
So(host, ShouldEqual, "xyz.rds.amazonaws.com")
|
||||||
|
So(port, ShouldEqual, "123")
|
||||||
|
|
||||||
|
host, port = SplitHostPortDefault("xyz.rds.amazonaws.com:123", "", "")
|
||||||
|
So(host, ShouldEqual, "xyz.rds.amazonaws.com")
|
||||||
|
So(port, ShouldEqual, "123")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSplitHostPort(t *testing.T) {
|
||||||
|
Convey("Test split ip address to host and port", t, func() {
|
||||||
|
host, port := SplitHostPort("192.168.0.140:456")
|
||||||
|
So(host, ShouldEqual, "192.168.0.140")
|
||||||
|
So(port, ShouldEqual, "456")
|
||||||
|
|
||||||
|
host, port = SplitHostPort("192.168.0.140")
|
||||||
|
So(host, ShouldEqual, "192.168.0.140")
|
||||||
|
So(port, ShouldEqual, "")
|
||||||
|
|
||||||
|
host, port = SplitHostPort("[::1:456]")
|
||||||
|
So(host, ShouldEqual, "::1")
|
||||||
|
So(port, ShouldEqual, "456")
|
||||||
|
|
||||||
|
host, port = SplitHostPort("[::1]")
|
||||||
|
So(host, ShouldEqual, "::1")
|
||||||
|
So(port, ShouldEqual, "")
|
||||||
|
|
||||||
|
host, port = SplitHostPort("::1:123")
|
||||||
|
So(host, ShouldEqual, "::1")
|
||||||
|
So(port, ShouldEqual, "123")
|
||||||
|
|
||||||
|
host, port = SplitHostPort("::1")
|
||||||
|
So(host, ShouldEqual, "::1")
|
||||||
|
So(port, ShouldEqual, "")
|
||||||
|
|
||||||
|
host, port = SplitHostPort(":456")
|
||||||
|
So(host, ShouldEqual, "")
|
||||||
|
So(port, ShouldEqual, "456")
|
||||||
|
|
||||||
|
host, port = SplitHostPort("xyz.rds.amazonaws.com")
|
||||||
|
So(host, ShouldEqual, "xyz.rds.amazonaws.com")
|
||||||
|
So(port, ShouldEqual, "")
|
||||||
|
|
||||||
|
host, port = SplitHostPort("xyz.rds.amazonaws.com:123")
|
||||||
|
So(host, ShouldEqual, "xyz.rds.amazonaws.com")
|
||||||
|
So(port, ShouldEqual, "123")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,43 +0,0 @@
|
|||||||
package util
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
. "github.com/smartystreets/goconvey/convey"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSplitIPPort(t *testing.T) {
|
|
||||||
|
|
||||||
Convey("When parsing an IPv4 without explicit port", t, func() {
|
|
||||||
ip, port, err := SplitIPPort("1.2.3.4", "5678")
|
|
||||||
|
|
||||||
So(err, ShouldEqual, nil)
|
|
||||||
So(ip, ShouldEqual, "1.2.3.4")
|
|
||||||
So(port, ShouldEqual, "5678")
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("When parsing an IPv6 without explicit port", t, func() {
|
|
||||||
ip, port, err := SplitIPPort("::1", "5678")
|
|
||||||
|
|
||||||
So(err, ShouldEqual, nil)
|
|
||||||
So(ip, ShouldEqual, "::1")
|
|
||||||
So(port, ShouldEqual, "5678")
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("When parsing an IPv4 with explicit port", t, func() {
|
|
||||||
ip, port, err := SplitIPPort("1.2.3.4:56", "78")
|
|
||||||
|
|
||||||
So(err, ShouldEqual, nil)
|
|
||||||
So(ip, ShouldEqual, "1.2.3.4")
|
|
||||||
So(port, ShouldEqual, "56")
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("When parsing an IPv6 with explicit port", t, func() {
|
|
||||||
ip, port, err := SplitIPPort("[::1]:56", "78")
|
|
||||||
|
|
||||||
So(err, ShouldEqual, nil)
|
|
||||||
So(ip, ShouldEqual, "::1")
|
|
||||||
So(port, ShouldEqual, "56")
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
@ -1,6 +1,7 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import coreModule from 'app/core/core_module';
|
import coreModule from 'app/core/core_module';
|
||||||
import appEvents from 'app/core/app_events';
|
import appEvents from 'app/core/app_events';
|
||||||
|
import config from 'app/core/config';
|
||||||
import { DashboardModel } from 'app/features/dashboard/state/DashboardModel';
|
import { DashboardModel } from 'app/features/dashboard/state/DashboardModel';
|
||||||
|
|
||||||
export class BackendSrv {
|
export class BackendSrv {
|
||||||
@ -103,10 +104,17 @@ export class BackendSrv {
|
|||||||
err => {
|
err => {
|
||||||
// handle unauthorized
|
// handle unauthorized
|
||||||
if (err.status === 401 && this.contextSrv.user.isSignedIn && firstAttempt) {
|
if (err.status === 401 && this.contextSrv.user.isSignedIn && firstAttempt) {
|
||||||
return this.loginPing().then(() => {
|
return this.loginPing()
|
||||||
options.retry = 1;
|
.then(() => {
|
||||||
return this.request(options);
|
options.retry = 1;
|
||||||
});
|
return this.request(options);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
if (err.status === 401) {
|
||||||
|
window.location.href = config.appSubUrl + '/logout';
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$timeout(this.requestErrorHandler.bind(this, err), 50);
|
this.$timeout(this.requestErrorHandler.bind(this, err), 50);
|
||||||
@ -184,13 +192,20 @@ export class BackendSrv {
|
|||||||
|
|
||||||
// handle unauthorized for backend requests
|
// handle unauthorized for backend requests
|
||||||
if (requestIsLocal && firstAttempt && err.status === 401) {
|
if (requestIsLocal && firstAttempt && err.status === 401) {
|
||||||
return this.loginPing().then(() => {
|
return this.loginPing()
|
||||||
options.retry = 1;
|
.then(() => {
|
||||||
if (canceler) {
|
options.retry = 1;
|
||||||
canceler.resolve();
|
if (canceler) {
|
||||||
}
|
canceler.resolve();
|
||||||
return this.datasourceRequest(options);
|
}
|
||||||
});
|
return this.datasourceRequest(options);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
if (err.status === 401) {
|
||||||
|
window.location.href = config.appSubUrl + '/logout';
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// populate error obj on Internal Error
|
// populate error obj on Internal Error
|
||||||
|
@ -13,6 +13,11 @@ const DEFAULT_EXPLORE_STATE: ExploreUrlState = {
|
|||||||
datasource: null,
|
datasource: null,
|
||||||
queries: [],
|
queries: [],
|
||||||
range: DEFAULT_RANGE,
|
range: DEFAULT_RANGE,
|
||||||
|
ui: {
|
||||||
|
showingGraph: true,
|
||||||
|
showingTable: true,
|
||||||
|
showingLogs: true,
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('state functions', () => {
|
describe('state functions', () => {
|
||||||
@ -69,9 +74,11 @@ describe('state functions', () => {
|
|||||||
to: 'now',
|
to: 'now',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(serializeStateToUrlParam(state)).toBe(
|
expect(serializeStateToUrlParam(state)).toBe(
|
||||||
'{"datasource":"foo","queries":[{"expr":"metric{test=\\"a/b\\"}"},' +
|
'{"datasource":"foo","queries":[{"expr":"metric{test=\\"a/b\\"}"},' +
|
||||||
'{"expr":"super{foo=\\"x/z\\"}"}],"range":{"from":"now-5h","to":"now"}}'
|
'{"expr":"super{foo=\\"x/z\\"}"}],"range":{"from":"now-5h","to":"now"},' +
|
||||||
|
'"ui":{"showingGraph":true,"showingTable":true,"showingLogs":true}}'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -93,7 +100,7 @@ describe('state functions', () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
expect(serializeStateToUrlParam(state, true)).toBe(
|
expect(serializeStateToUrlParam(state, true)).toBe(
|
||||||
'["now-5h","now","foo",{"expr":"metric{test=\\"a/b\\"}"},{"expr":"super{foo=\\"x/z\\"}"}]'
|
'["now-5h","now","foo",{"expr":"metric{test=\\"a/b\\"}"},{"expr":"super{foo=\\"x/z\\"}"},{"ui":[true,true,true]}]'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -118,7 +125,28 @@ describe('state functions', () => {
|
|||||||
};
|
};
|
||||||
const serialized = serializeStateToUrlParam(state);
|
const serialized = serializeStateToUrlParam(state);
|
||||||
const parsed = parseUrlState(serialized);
|
const parsed = parseUrlState(serialized);
|
||||||
|
expect(state).toMatchObject(parsed);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can parse the compact serialized state into the original state', () => {
|
||||||
|
const state = {
|
||||||
|
...DEFAULT_EXPLORE_STATE,
|
||||||
|
datasource: 'foo',
|
||||||
|
queries: [
|
||||||
|
{
|
||||||
|
expr: 'metric{test="a/b"}',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
expr: 'super{foo="x/z"}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
range: {
|
||||||
|
from: 'now - 5h',
|
||||||
|
to: 'now',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const serialized = serializeStateToUrlParam(state, true);
|
||||||
|
const parsed = parseUrlState(serialized);
|
||||||
expect(state).toMatchObject(parsed);
|
expect(state).toMatchObject(parsed);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -11,7 +11,7 @@ import { colors } from '@grafana/ui';
|
|||||||
import TableModel, { mergeTablesIntoModel } from 'app/core/table_model';
|
import TableModel, { mergeTablesIntoModel } from 'app/core/table_model';
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
import { RawTimeRange, IntervalValues, DataQuery } from '@grafana/ui/src/types';
|
import { RawTimeRange, IntervalValues, DataQuery, DataSourceApi } from '@grafana/ui/src/types';
|
||||||
import TimeSeries from 'app/core/time_series2';
|
import TimeSeries from 'app/core/time_series2';
|
||||||
import {
|
import {
|
||||||
ExploreUrlState,
|
ExploreUrlState,
|
||||||
@ -27,6 +27,12 @@ export const DEFAULT_RANGE = {
|
|||||||
to: 'now',
|
to: 'now',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const DEFAULT_UI_STATE = {
|
||||||
|
showingTable: true,
|
||||||
|
showingGraph: true,
|
||||||
|
showingLogs: true,
|
||||||
|
};
|
||||||
|
|
||||||
const MAX_HISTORY_ITEMS = 100;
|
const MAX_HISTORY_ITEMS = 100;
|
||||||
|
|
||||||
export const LAST_USED_DATASOURCE_KEY = 'grafana.explore.datasource';
|
export const LAST_USED_DATASOURCE_KEY = 'grafana.explore.datasource';
|
||||||
@ -147,7 +153,12 @@ export function buildQueryTransaction(
|
|||||||
|
|
||||||
export const clearQueryKeys: ((query: DataQuery) => object) = ({ key, refId, ...rest }) => rest;
|
export const clearQueryKeys: ((query: DataQuery) => object) = ({ key, refId, ...rest }) => rest;
|
||||||
|
|
||||||
|
const isMetricSegment = (segment: { [key: string]: string }) => segment.hasOwnProperty('expr');
|
||||||
|
const isUISegment = (segment: { [key: string]: string }) => segment.hasOwnProperty('ui');
|
||||||
|
|
||||||
export function parseUrlState(initial: string | undefined): ExploreUrlState {
|
export function parseUrlState(initial: string | undefined): ExploreUrlState {
|
||||||
|
let uiState = DEFAULT_UI_STATE;
|
||||||
|
|
||||||
if (initial) {
|
if (initial) {
|
||||||
try {
|
try {
|
||||||
const parsed = JSON.parse(decodeURI(initial));
|
const parsed = JSON.parse(decodeURI(initial));
|
||||||
@ -160,20 +171,41 @@ export function parseUrlState(initial: string | undefined): ExploreUrlState {
|
|||||||
to: parsed[1],
|
to: parsed[1],
|
||||||
};
|
};
|
||||||
const datasource = parsed[2];
|
const datasource = parsed[2];
|
||||||
const queries = parsed.slice(3);
|
let queries = [];
|
||||||
return { datasource, queries, range };
|
|
||||||
|
parsed.slice(3).forEach(segment => {
|
||||||
|
if (isMetricSegment(segment)) {
|
||||||
|
queries = [...queries, segment];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isUISegment(segment)) {
|
||||||
|
uiState = {
|
||||||
|
showingGraph: segment.ui[0],
|
||||||
|
showingLogs: segment.ui[1],
|
||||||
|
showingTable: segment.ui[2],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return { datasource, queries, range, ui: uiState };
|
||||||
}
|
}
|
||||||
return parsed;
|
return parsed;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return { datasource: null, queries: [], range: DEFAULT_RANGE };
|
return { datasource: null, queries: [], range: DEFAULT_RANGE, ui: uiState };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function serializeStateToUrlParam(urlState: ExploreUrlState, compact?: boolean): string {
|
export function serializeStateToUrlParam(urlState: ExploreUrlState, compact?: boolean): string {
|
||||||
if (compact) {
|
if (compact) {
|
||||||
return JSON.stringify([urlState.range.from, urlState.range.to, urlState.datasource, ...urlState.queries]);
|
return JSON.stringify([
|
||||||
|
urlState.range.from,
|
||||||
|
urlState.range.to,
|
||||||
|
urlState.datasource,
|
||||||
|
...urlState.queries,
|
||||||
|
{ ui: [!!urlState.ui.showingGraph, !!urlState.ui.showingLogs, !!urlState.ui.showingTable] },
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
return JSON.stringify(urlState);
|
return JSON.stringify(urlState);
|
||||||
}
|
}
|
||||||
@ -304,3 +336,12 @@ export function clearHistory(datasourceId: string) {
|
|||||||
const historyKey = `grafana.explore.history.${datasourceId}`;
|
const historyKey = `grafana.explore.history.${datasourceId}`;
|
||||||
store.delete(historyKey);
|
store.delete(historyKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getQueryKeys = (queries: DataQuery[], datasourceInstance: DataSourceApi): string[] => {
|
||||||
|
const queryKeys = queries.reduce((newQueryKeys, query, index) => {
|
||||||
|
const primaryKey = datasourceInstance && datasourceInstance.name ? datasourceInstance.name : query.key;
|
||||||
|
return newQueryKeys.concat(`${primaryKey}-${index}`);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return queryKeys;
|
||||||
|
};
|
||||||
|
@ -0,0 +1,23 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { shallow } from 'enzyme';
|
||||||
|
import { AddPanelWidget, Props } from './AddPanelWidget';
|
||||||
|
import { DashboardModel, PanelModel } from '../../state';
|
||||||
|
|
||||||
|
const setup = (propOverrides?: object) => {
|
||||||
|
const props: Props = {
|
||||||
|
dashboard: {} as DashboardModel,
|
||||||
|
panel: {} as PanelModel,
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.assign(props, propOverrides);
|
||||||
|
|
||||||
|
return shallow(<AddPanelWidget {...props} />);
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('Render', () => {
|
||||||
|
it('should render component', () => {
|
||||||
|
const wrapper = setup();
|
||||||
|
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
@ -1,12 +1,20 @@
|
|||||||
|
// Libraries
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
// Utils
|
||||||
import config from 'app/core/config';
|
import config from 'app/core/config';
|
||||||
import { PanelModel } from '../../state/PanelModel';
|
|
||||||
import { DashboardModel } from '../../state/DashboardModel';
|
|
||||||
import store from 'app/core/store';
|
import store from 'app/core/store';
|
||||||
import { LS_PANEL_COPY_KEY } from 'app/core/constants';
|
|
||||||
import { updateLocation } from 'app/core/actions';
|
// Store
|
||||||
import { store as reduxStore } from 'app/store/store';
|
import { store as reduxStore } from 'app/store/store';
|
||||||
|
import { updateLocation } from 'app/core/actions';
|
||||||
|
|
||||||
|
// Types
|
||||||
|
import { PanelModel } from '../../state';
|
||||||
|
import { DashboardModel } from '../../state';
|
||||||
|
import { LS_PANEL_COPY_KEY } from 'app/core/constants';
|
||||||
|
import { LocationUpdate } from 'app/types';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
panel: PanelModel;
|
panel: PanelModel;
|
||||||
@ -46,6 +54,7 @@ export class AddPanelWidget extends React.Component<Props, State> {
|
|||||||
copiedPanels.push(pluginCopy);
|
copiedPanels.push(pluginCopy);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return _.sortBy(copiedPanels, 'sort');
|
return _.sortBy(copiedPanels, 'sort');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,28 +63,7 @@ export class AddPanelWidget extends React.Component<Props, State> {
|
|||||||
this.props.dashboard.removePanel(this.props.dashboard.panels[0]);
|
this.props.dashboard.removePanel(this.props.dashboard.panels[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
copyButton(panel) {
|
onCreateNewPanel = (tab = 'queries') => {
|
||||||
return (
|
|
||||||
<button className="btn-inverse btn" onClick={() => this.onPasteCopiedPanel(panel)} title={panel.name}>
|
|
||||||
Paste copied Panel
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
moveToEdit(panel) {
|
|
||||||
reduxStore.dispatch(
|
|
||||||
updateLocation({
|
|
||||||
query: {
|
|
||||||
panelId: panel.id,
|
|
||||||
edit: true,
|
|
||||||
fullscreen: true,
|
|
||||||
},
|
|
||||||
partial: true,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
onCreateNewPanel = () => {
|
|
||||||
const dashboard = this.props.dashboard;
|
const dashboard = this.props.dashboard;
|
||||||
const { gridPos } = this.props.panel;
|
const { gridPos } = this.props.panel;
|
||||||
|
|
||||||
@ -88,7 +76,21 @@ export class AddPanelWidget extends React.Component<Props, State> {
|
|||||||
dashboard.addPanel(newPanel);
|
dashboard.addPanel(newPanel);
|
||||||
dashboard.removePanel(this.props.panel);
|
dashboard.removePanel(this.props.panel);
|
||||||
|
|
||||||
this.moveToEdit(newPanel);
|
const location: LocationUpdate = {
|
||||||
|
query: {
|
||||||
|
panelId: newPanel.id,
|
||||||
|
edit: true,
|
||||||
|
fullscreen: true,
|
||||||
|
},
|
||||||
|
partial: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (tab === 'visualization') {
|
||||||
|
location.query.tab = 'visualization';
|
||||||
|
location.query.openVizPicker = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
reduxStore.dispatch(updateLocation(location));
|
||||||
};
|
};
|
||||||
|
|
||||||
onPasteCopiedPanel = panelPluginInfo => {
|
onPasteCopiedPanel = panelPluginInfo => {
|
||||||
@ -125,30 +127,50 @@ export class AddPanelWidget extends React.Component<Props, State> {
|
|||||||
dashboard.removePanel(this.props.panel);
|
dashboard.removePanel(this.props.panel);
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
renderOptionLink = (icon, text, onClick) => {
|
||||||
let addCopyButton;
|
return (
|
||||||
|
<div>
|
||||||
|
<a href="#" onClick={onClick} className="add-panel-widget__link btn btn-inverse">
|
||||||
|
<div className="add-panel-widget__icon">
|
||||||
|
<i className={`gicon gicon-${icon}`} />
|
||||||
|
</div>
|
||||||
|
<span>{text}</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
if (this.state.copiedPanelPlugins.length === 1) {
|
render() {
|
||||||
addCopyButton = this.copyButton(this.state.copiedPanelPlugins[0]);
|
const { copiedPanelPlugins } = this.state;
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="panel-container add-panel-widget-container">
|
<div className="panel-container add-panel-widget-container">
|
||||||
<div className="add-panel-widget">
|
<div className="add-panel-widget">
|
||||||
<div className="add-panel-widget__header grid-drag-handle">
|
<div className="add-panel-widget__header grid-drag-handle">
|
||||||
<i className="gicon gicon-add-panel" />
|
<i className="gicon gicon-add-panel" />
|
||||||
|
<span className="add-panel-widget__title">New Panel</span>
|
||||||
<button className="add-panel-widget__close" onClick={this.handleCloseAddPanel}>
|
<button className="add-panel-widget__close" onClick={this.handleCloseAddPanel}>
|
||||||
<i className="fa fa-close" />
|
<i className="fa fa-close" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="add-panel-widget__btn-container">
|
<div className="add-panel-widget__btn-container">
|
||||||
<button className="btn-success btn btn-large" onClick={this.onCreateNewPanel}>
|
<div className="add-panel-widget__create">
|
||||||
Edit Panel
|
{this.renderOptionLink('queries', 'Add Query', this.onCreateNewPanel)}
|
||||||
</button>
|
{this.renderOptionLink('visualization', 'Choose Visualization', () =>
|
||||||
{addCopyButton}
|
this.onCreateNewPanel('visualization')
|
||||||
<button className="btn-inverse btn" onClick={this.onCreateNewRow}>
|
)}
|
||||||
Add Row
|
</div>
|
||||||
</button>
|
<div className="add-panel-widget__actions">
|
||||||
|
<button className="btn btn-inverse add-panel-widget__action" onClick={this.onCreateNewRow}>Convert to row</button>
|
||||||
|
{copiedPanelPlugins.length === 1 && (
|
||||||
|
<button
|
||||||
|
className="btn btn-inverse add-panel-widget__action"
|
||||||
|
onClick={() => this.onPasteCopiedPanel(copiedPanelPlugins[0])}
|
||||||
|
>
|
||||||
|
Paste copied panel
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -14,6 +14,9 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
cursor: move;
|
cursor: move;
|
||||||
|
background: $page-header-bg;
|
||||||
|
box-shadow: $page-header-shadow;
|
||||||
|
border-bottom: 1px solid $page-header-border-color;
|
||||||
|
|
||||||
.gicon {
|
.gicon {
|
||||||
font-size: 30px;
|
font-size: 30px;
|
||||||
@ -26,6 +29,29 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.add-panel-widget__title {
|
||||||
|
font-size: $font-size-md;
|
||||||
|
font-weight: $font-weight-semi-bold;
|
||||||
|
margin-right: $spacer*2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-panel-widget__link {
|
||||||
|
margin: 0 8px;
|
||||||
|
width: 154px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-panel-widget__icon {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
|
||||||
|
.gicon {
|
||||||
|
color: white;
|
||||||
|
height: 44px;
|
||||||
|
width: 53px;
|
||||||
|
position: relative;
|
||||||
|
left: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.add-panel-widget__close {
|
.add-panel-widget__close {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
@ -34,14 +60,25 @@
|
|||||||
margin-right: -10px;
|
margin-right: -10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.add-panel-widget__create {
|
||||||
|
display: inherit;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
// this is to have the big button appear centered
|
||||||
|
margin-top: 55px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-panel-widget__actions {
|
||||||
|
display: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-panel-widget__action {
|
||||||
|
margin: 0 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.add-panel-widget__btn-container {
|
.add-panel-widget__btn-container {
|
||||||
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
height: 100%;
|
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
.btn {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,86 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`Render should render component 1`] = `
|
||||||
|
<div
|
||||||
|
className="panel-container add-panel-widget-container"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="add-panel-widget"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="add-panel-widget__header grid-drag-handle"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
className="gicon gicon-add-panel"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
className="add-panel-widget__title"
|
||||||
|
>
|
||||||
|
New Panel
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
className="add-panel-widget__close"
|
||||||
|
onClick={[Function]}
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
className="fa fa-close"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="add-panel-widget__btn-container"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="add-panel-widget__create"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<a
|
||||||
|
className="add-panel-widget__link btn btn-inverse"
|
||||||
|
href="#"
|
||||||
|
onClick={[Function]}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="add-panel-widget__icon"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
className="gicon gicon-queries"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span>
|
||||||
|
Add Query
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<a
|
||||||
|
className="add-panel-widget__link btn btn-inverse"
|
||||||
|
href="#"
|
||||||
|
onClick={[Function]}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="add-panel-widget__icon"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
className="gicon gicon-visualization"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span>
|
||||||
|
Choose Visualization
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="add-panel-widget__actions"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
className="btn btn-inverse add-panel-widget__action"
|
||||||
|
onClick={[Function]}
|
||||||
|
>
|
||||||
|
Convert to row
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
@ -1,2 +1,3 @@
|
|||||||
export { SaveDashboardAsModalCtrl } from './SaveDashboardAsModalCtrl';
|
export { SaveDashboardAsModalCtrl } from './SaveDashboardAsModalCtrl';
|
||||||
export { SaveDashboardModalCtrl } from './SaveDashboardModalCtrl';
|
export { SaveDashboardModalCtrl } from './SaveDashboardModalCtrl';
|
||||||
|
export { SaveProvisionedDashboardModalCtrl } from './SaveProvisionedDashboardModalCtrl';
|
||||||
|
@ -2,7 +2,7 @@ import React, { PureComponent } from 'react';
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import { QueriesTab } from './QueriesTab';
|
import { QueriesTab } from './QueriesTab';
|
||||||
import { VisualizationTab } from './VisualizationTab';
|
import VisualizationTab from './VisualizationTab';
|
||||||
import { GeneralTab } from './GeneralTab';
|
import { GeneralTab } from './GeneralTab';
|
||||||
import { AlertTab } from '../../alerting/AlertTab';
|
import { AlertTab } from '../../alerting/AlertTab';
|
||||||
|
|
||||||
@ -38,7 +38,7 @@ export class PanelEditor extends PureComponent<PanelEditorProps> {
|
|||||||
onChangeTab = (tab: PanelEditorTab) => {
|
onChangeTab = (tab: PanelEditorTab) => {
|
||||||
store.dispatch(
|
store.dispatch(
|
||||||
updateLocation({
|
updateLocation({
|
||||||
query: { tab: tab.id },
|
query: { tab: tab.id, openVizPicker: null },
|
||||||
partial: true,
|
partial: true,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
@ -133,7 +133,7 @@ export class QueriesTab extends PureComponent<Props, State> {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<DataSourcePicker datasources={this.datasources} onChange={this.onChangeDataSource} current={currentDS} />
|
<DataSourcePicker datasources={this.datasources} onChange={this.onChangeDataSource} current={currentDS} />
|
||||||
<div className="flex-grow" />
|
<div className="flex-grow-1" />
|
||||||
{!isAddingMixed && (
|
{!isAddingMixed && (
|
||||||
<button className="btn navbar-button navbar-button--primary" onClick={this.onAddQueryClick}>
|
<button className="btn navbar-button navbar-button--primary" onClick={this.onAddQueryClick}>
|
||||||
Add Query
|
Add Query
|
||||||
|
@ -3,6 +3,9 @@ import React, { PureComponent } from 'react';
|
|||||||
|
|
||||||
// Utils & Services
|
// Utils & Services
|
||||||
import { AngularComponent, getAngularLoader } from 'app/core/services/AngularLoader';
|
import { AngularComponent, getAngularLoader } from 'app/core/services/AngularLoader';
|
||||||
|
import { connectWithStore } from 'app/core/utils/connectWithReduxStore';
|
||||||
|
import { StoreState } from 'app/types';
|
||||||
|
import { updateLocation } from 'app/core/actions';
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import { EditorTabBody, EditorToolbarView } from './EditorTabBody';
|
import { EditorTabBody, EditorToolbarView } from './EditorTabBody';
|
||||||
@ -21,6 +24,8 @@ interface Props {
|
|||||||
plugin: PanelPlugin;
|
plugin: PanelPlugin;
|
||||||
angularPanel?: AngularComponent;
|
angularPanel?: AngularComponent;
|
||||||
onTypeChanged: (newType: PanelPlugin) => void;
|
onTypeChanged: (newType: PanelPlugin) => void;
|
||||||
|
updateLocation: typeof updateLocation;
|
||||||
|
urlOpenVizPicker: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
@ -38,7 +43,7 @@ export class VisualizationTab extends PureComponent<Props, State> {
|
|||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
isVizPickerOpen: false,
|
isVizPickerOpen: this.props.urlOpenVizPicker,
|
||||||
searchQuery: '',
|
searchQuery: '',
|
||||||
scrollTop: 0,
|
scrollTop: 0,
|
||||||
};
|
};
|
||||||
@ -149,6 +154,10 @@ export class VisualizationTab extends PureComponent<Props, State> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
onCloseVizPicker = () => {
|
onCloseVizPicker = () => {
|
||||||
|
if (this.props.urlOpenVizPicker) {
|
||||||
|
this.props.updateLocation({ query: { openVizPicker: null }, partial: true });
|
||||||
|
}
|
||||||
|
|
||||||
this.setState({ isVizPickerOpen: false });
|
this.setState({ isVizPickerOpen: false });
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -236,3 +245,13 @@ export class VisualizationTab extends PureComponent<Props, State> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const mapStateToProps = (state: StoreState) => ({
|
||||||
|
urlOpenVizPicker: !!state.location.query.openVizPicker
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapDispatchToProps = {
|
||||||
|
updateLocation
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connectWithStore(VisualizationTab, mapStateToProps, mapDispatchToProps);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// Libraries
|
// Libraries
|
||||||
import React from 'react';
|
import React, { ComponentClass } from 'react';
|
||||||
import { hot } from 'react-hot-loader';
|
import { hot } from 'react-hot-loader';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
@ -18,34 +18,26 @@ import TableContainer from './TableContainer';
|
|||||||
import TimePicker, { parseTime } from './TimePicker';
|
import TimePicker, { parseTime } from './TimePicker';
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
import {
|
import { changeSize, changeTime, initializeExplore, modifyQueries, scanStart, setQueries } from './state/actions';
|
||||||
changeSize,
|
|
||||||
changeTime,
|
|
||||||
initializeExplore,
|
|
||||||
modifyQueries,
|
|
||||||
scanStart,
|
|
||||||
scanStop,
|
|
||||||
setQueries,
|
|
||||||
} from './state/actions';
|
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
import { RawTimeRange, TimeRange, DataQuery } from '@grafana/ui';
|
import { RawTimeRange, TimeRange, DataQuery, ExploreStartPageProps, ExploreDataSourceApi } from '@grafana/ui';
|
||||||
import { ExploreItemState, ExploreUrlState, RangeScanner, ExploreId } from 'app/types/explore';
|
import { ExploreItemState, ExploreUrlState, RangeScanner, ExploreId } from 'app/types/explore';
|
||||||
import { StoreState } from 'app/types';
|
import { StoreState } from 'app/types';
|
||||||
import { LAST_USED_DATASOURCE_KEY, ensureQueries, DEFAULT_RANGE } from 'app/core/utils/explore';
|
import { LAST_USED_DATASOURCE_KEY, ensureQueries, DEFAULT_RANGE, DEFAULT_UI_STATE } from 'app/core/utils/explore';
|
||||||
import { Emitter } from 'app/core/utils/emitter';
|
import { Emitter } from 'app/core/utils/emitter';
|
||||||
import { ExploreToolbar } from './ExploreToolbar';
|
import { ExploreToolbar } from './ExploreToolbar';
|
||||||
|
import { scanStopAction } from './state/actionTypes';
|
||||||
|
|
||||||
interface ExploreProps {
|
interface ExploreProps {
|
||||||
StartPage?: any;
|
StartPage?: ComponentClass<ExploreStartPageProps>;
|
||||||
changeSize: typeof changeSize;
|
changeSize: typeof changeSize;
|
||||||
changeTime: typeof changeTime;
|
changeTime: typeof changeTime;
|
||||||
datasourceError: string;
|
datasourceError: string;
|
||||||
datasourceInstance: any;
|
datasourceInstance: ExploreDataSourceApi;
|
||||||
datasourceLoading: boolean | null;
|
datasourceLoading: boolean | null;
|
||||||
datasourceMissing: boolean;
|
datasourceMissing: boolean;
|
||||||
exploreId: ExploreId;
|
exploreId: ExploreId;
|
||||||
initialQueries: DataQuery[];
|
|
||||||
initializeExplore: typeof initializeExplore;
|
initializeExplore: typeof initializeExplore;
|
||||||
initialized: boolean;
|
initialized: boolean;
|
||||||
modifyQueries: typeof modifyQueries;
|
modifyQueries: typeof modifyQueries;
|
||||||
@ -54,7 +46,7 @@ interface ExploreProps {
|
|||||||
scanning?: boolean;
|
scanning?: boolean;
|
||||||
scanRange?: RawTimeRange;
|
scanRange?: RawTimeRange;
|
||||||
scanStart: typeof scanStart;
|
scanStart: typeof scanStart;
|
||||||
scanStop: typeof scanStop;
|
scanStopAction: typeof scanStopAction;
|
||||||
setQueries: typeof setQueries;
|
setQueries: typeof setQueries;
|
||||||
split: boolean;
|
split: boolean;
|
||||||
showingStartPage?: boolean;
|
showingStartPage?: boolean;
|
||||||
@ -62,6 +54,7 @@ interface ExploreProps {
|
|||||||
supportsLogs: boolean | null;
|
supportsLogs: boolean | null;
|
||||||
supportsTable: boolean | null;
|
supportsTable: boolean | null;
|
||||||
urlState: ExploreUrlState;
|
urlState: ExploreUrlState;
|
||||||
|
queryKeys: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -107,18 +100,20 @@ export class Explore extends React.PureComponent<ExploreProps> {
|
|||||||
// Don't initialize on split, but need to initialize urlparameters when present
|
// Don't initialize on split, but need to initialize urlparameters when present
|
||||||
if (!initialized) {
|
if (!initialized) {
|
||||||
// Load URL state and parse range
|
// Load URL state and parse range
|
||||||
const { datasource, queries, range = DEFAULT_RANGE } = (urlState || {}) as ExploreUrlState;
|
const { datasource, queries, range = DEFAULT_RANGE, ui = DEFAULT_UI_STATE } = (urlState || {}) as ExploreUrlState;
|
||||||
const initialDatasource = datasource || store.get(LAST_USED_DATASOURCE_KEY);
|
const initialDatasource = datasource || store.get(LAST_USED_DATASOURCE_KEY);
|
||||||
const initialQueries: DataQuery[] = ensureQueries(queries);
|
const initialQueries: DataQuery[] = ensureQueries(queries);
|
||||||
const initialRange = { from: parseTime(range.from), to: parseTime(range.to) };
|
const initialRange = { from: parseTime(range.from), to: parseTime(range.to) };
|
||||||
const width = this.el ? this.el.offsetWidth : 0;
|
const width = this.el ? this.el.offsetWidth : 0;
|
||||||
|
|
||||||
this.props.initializeExplore(
|
this.props.initializeExplore(
|
||||||
exploreId,
|
exploreId,
|
||||||
initialDatasource,
|
initialDatasource,
|
||||||
initialQueries,
|
initialQueries,
|
||||||
initialRange,
|
initialRange,
|
||||||
width,
|
width,
|
||||||
this.exploreEvents
|
this.exploreEvents,
|
||||||
|
ui
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -171,7 +166,7 @@ export class Explore extends React.PureComponent<ExploreProps> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
onStopScanning = () => {
|
onStopScanning = () => {
|
||||||
this.props.scanStop(this.props.exploreId);
|
this.props.scanStopAction({ exploreId: this.props.exploreId });
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@ -182,12 +177,12 @@ export class Explore extends React.PureComponent<ExploreProps> {
|
|||||||
datasourceLoading,
|
datasourceLoading,
|
||||||
datasourceMissing,
|
datasourceMissing,
|
||||||
exploreId,
|
exploreId,
|
||||||
initialQueries,
|
|
||||||
showingStartPage,
|
showingStartPage,
|
||||||
split,
|
split,
|
||||||
supportsGraph,
|
supportsGraph,
|
||||||
supportsLogs,
|
supportsLogs,
|
||||||
supportsTable,
|
supportsTable,
|
||||||
|
queryKeys,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const exploreClass = split ? 'explore explore-split' : 'explore';
|
const exploreClass = split ? 'explore explore-split' : 'explore';
|
||||||
|
|
||||||
@ -208,7 +203,7 @@ export class Explore extends React.PureComponent<ExploreProps> {
|
|||||||
{datasourceInstance &&
|
{datasourceInstance &&
|
||||||
!datasourceError && (
|
!datasourceError && (
|
||||||
<div className="explore-container">
|
<div className="explore-container">
|
||||||
<QueryRows exploreEvents={this.exploreEvents} exploreId={exploreId} initialQueries={initialQueries} />
|
<QueryRows exploreEvents={this.exploreEvents} exploreId={exploreId} queryKeys={queryKeys} />
|
||||||
<AutoSizer onResize={this.onResize} disableHeight>
|
<AutoSizer onResize={this.onResize} disableHeight>
|
||||||
{({ width }) => (
|
{({ width }) => (
|
||||||
<main className="m-t-2" style={{ width }}>
|
<main className="m-t-2" style={{ width }}>
|
||||||
@ -216,7 +211,7 @@ export class Explore extends React.PureComponent<ExploreProps> {
|
|||||||
{showingStartPage && <StartPage onClickExample={this.onClickExample} />}
|
{showingStartPage && <StartPage onClickExample={this.onClickExample} />}
|
||||||
{!showingStartPage && (
|
{!showingStartPage && (
|
||||||
<>
|
<>
|
||||||
{supportsGraph && !supportsLogs && <GraphContainer exploreId={exploreId} />}
|
{supportsGraph && !supportsLogs && <GraphContainer width={width} exploreId={exploreId} />}
|
||||||
{supportsTable && <TableContainer exploreId={exploreId} onClickCell={this.onClickLabel} />}
|
{supportsTable && <TableContainer exploreId={exploreId} onClickCell={this.onClickLabel} />}
|
||||||
{supportsLogs && (
|
{supportsLogs && (
|
||||||
<LogsContainer
|
<LogsContainer
|
||||||
@ -250,13 +245,13 @@ function mapStateToProps(state: StoreState, { exploreId }) {
|
|||||||
datasourceInstance,
|
datasourceInstance,
|
||||||
datasourceLoading,
|
datasourceLoading,
|
||||||
datasourceMissing,
|
datasourceMissing,
|
||||||
initialQueries,
|
|
||||||
initialized,
|
initialized,
|
||||||
range,
|
range,
|
||||||
showingStartPage,
|
showingStartPage,
|
||||||
supportsGraph,
|
supportsGraph,
|
||||||
supportsLogs,
|
supportsLogs,
|
||||||
supportsTable,
|
supportsTable,
|
||||||
|
queryKeys,
|
||||||
} = item;
|
} = item;
|
||||||
return {
|
return {
|
||||||
StartPage,
|
StartPage,
|
||||||
@ -264,7 +259,6 @@ function mapStateToProps(state: StoreState, { exploreId }) {
|
|||||||
datasourceInstance,
|
datasourceInstance,
|
||||||
datasourceLoading,
|
datasourceLoading,
|
||||||
datasourceMissing,
|
datasourceMissing,
|
||||||
initialQueries,
|
|
||||||
initialized,
|
initialized,
|
||||||
range,
|
range,
|
||||||
showingStartPage,
|
showingStartPage,
|
||||||
@ -272,6 +266,7 @@ function mapStateToProps(state: StoreState, { exploreId }) {
|
|||||||
supportsGraph,
|
supportsGraph,
|
||||||
supportsLogs,
|
supportsLogs,
|
||||||
supportsTable,
|
supportsTable,
|
||||||
|
queryKeys,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -281,7 +276,7 @@ const mapDispatchToProps = {
|
|||||||
initializeExplore,
|
initializeExplore,
|
||||||
modifyQueries,
|
modifyQueries,
|
||||||
scanStart,
|
scanStart,
|
||||||
scanStop,
|
scanStopAction,
|
||||||
setQueries,
|
setQueries,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ import { DataSourcePicker } from 'app/core/components/Select/DataSourcePicker';
|
|||||||
import { StoreState } from 'app/types/store';
|
import { StoreState } from 'app/types/store';
|
||||||
import { changeDatasource, clearQueries, splitClose, runQueries, splitOpen } from './state/actions';
|
import { changeDatasource, clearQueries, splitClose, runQueries, splitOpen } from './state/actions';
|
||||||
import TimePicker from './TimePicker';
|
import TimePicker from './TimePicker';
|
||||||
|
import { ClickOutsideWrapper } from 'app/core/components/ClickOutsideWrapper/ClickOutsideWrapper';
|
||||||
|
|
||||||
enum IconSide {
|
enum IconSide {
|
||||||
left = 'left',
|
left = 'left',
|
||||||
@ -79,6 +80,10 @@ export class UnConnectedExploreToolbar extends PureComponent<Props, {}> {
|
|||||||
this.props.runQuery(this.props.exploreId);
|
this.props.runQuery(this.props.exploreId);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onCloseTimePicker = () => {
|
||||||
|
this.props.timepickerRef.current.setState({ isOpen: false });
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
datasourceMissing,
|
datasourceMissing,
|
||||||
@ -137,7 +142,9 @@ export class UnConnectedExploreToolbar extends PureComponent<Props, {}> {
|
|||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
<div className="explore-toolbar-content-item timepicker">
|
<div className="explore-toolbar-content-item timepicker">
|
||||||
<TimePicker ref={timepickerRef} range={range} onChangeTime={this.props.onChangeTime} />
|
<ClickOutsideWrapper onClick={this.onCloseTimePicker}>
|
||||||
|
<TimePicker ref={timepickerRef} range={range} onChangeTime={this.props.onChangeTime} />
|
||||||
|
</ClickOutsideWrapper>
|
||||||
</div>
|
</div>
|
||||||
<div className="explore-toolbar-content-item">
|
<div className="explore-toolbar-content-item">
|
||||||
<button className="btn navbar-button navbar-button--no-icon" onClick={this.onClearAll}>
|
<button className="btn navbar-button navbar-button--no-icon" onClick={this.onClearAll}>
|
||||||
|
@ -5,6 +5,7 @@ import { mockData } from './__mocks__/mockData';
|
|||||||
|
|
||||||
const setup = (propOverrides?: object) => {
|
const setup = (propOverrides?: object) => {
|
||||||
const props = {
|
const props = {
|
||||||
|
size: { width: 10, height: 20 },
|
||||||
data: mockData().slice(0, 19),
|
data: mockData().slice(0, 19),
|
||||||
range: { from: 'now-6h', to: 'now' },
|
range: { from: 'now-6h', to: 'now' },
|
||||||
...propOverrides,
|
...propOverrides,
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { withSize } from 'react-sizeme';
|
|
||||||
|
|
||||||
import 'vendor/flot/jquery.flot';
|
import 'vendor/flot/jquery.flot';
|
||||||
import 'vendor/flot/jquery.flot.time';
|
import 'vendor/flot/jquery.flot.time';
|
||||||
@ -76,11 +75,11 @@ const FLOT_OPTIONS = {
|
|||||||
|
|
||||||
interface GraphProps {
|
interface GraphProps {
|
||||||
data: any[];
|
data: any[];
|
||||||
height?: string; // e.g., '200px'
|
height?: number;
|
||||||
|
width?: number;
|
||||||
id?: string;
|
id?: string;
|
||||||
range: RawTimeRange;
|
range: RawTimeRange;
|
||||||
split?: boolean;
|
split?: boolean;
|
||||||
size?: { width: number; height: number };
|
|
||||||
userOptions?: any;
|
userOptions?: any;
|
||||||
onChangeTime?: (range: RawTimeRange) => void;
|
onChangeTime?: (range: RawTimeRange) => void;
|
||||||
onToggleSeries?: (alias: string, hiddenSeries: Set<string>) => void;
|
onToggleSeries?: (alias: string, hiddenSeries: Set<string>) => void;
|
||||||
@ -122,7 +121,7 @@ export class Graph extends PureComponent<GraphProps, GraphState> {
|
|||||||
prevProps.range !== this.props.range ||
|
prevProps.range !== this.props.range ||
|
||||||
prevProps.split !== this.props.split ||
|
prevProps.split !== this.props.split ||
|
||||||
prevProps.height !== this.props.height ||
|
prevProps.height !== this.props.height ||
|
||||||
(prevProps.size && prevProps.size.width !== this.props.size.width) ||
|
prevProps.width !== this.props.width ||
|
||||||
!equal(prevState.hiddenSeries, this.state.hiddenSeries)
|
!equal(prevState.hiddenSeries, this.state.hiddenSeries)
|
||||||
) {
|
) {
|
||||||
this.draw();
|
this.draw();
|
||||||
@ -144,8 +143,8 @@ export class Graph extends PureComponent<GraphProps, GraphState> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
getDynamicOptions() {
|
getDynamicOptions() {
|
||||||
const { range, size } = this.props;
|
const { range, width } = this.props;
|
||||||
const ticks = (size.width || 0) / 100;
|
const ticks = (width || 0) / 100;
|
||||||
let { from, to } = range;
|
let { from, to } = range;
|
||||||
if (!moment.isMoment(from)) {
|
if (!moment.isMoment(from)) {
|
||||||
from = dateMath.parse(from, false);
|
from = dateMath.parse(from, false);
|
||||||
@ -237,7 +236,7 @@ export class Graph extends PureComponent<GraphProps, GraphState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { height = '100px', id = 'graph' } = this.props;
|
const { height = 100, id = 'graph' } = this.props;
|
||||||
const { hiddenSeries } = this.state;
|
const { hiddenSeries } = this.state;
|
||||||
const data = this.getGraphData();
|
const data = this.getGraphData();
|
||||||
|
|
||||||
@ -261,4 +260,4 @@ export class Graph extends PureComponent<GraphProps, GraphState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withSize()(Graph);
|
export default Graph;
|
||||||
|
@ -20,6 +20,7 @@ interface GraphContainerProps {
|
|||||||
split: boolean;
|
split: boolean;
|
||||||
toggleGraph: typeof toggleGraph;
|
toggleGraph: typeof toggleGraph;
|
||||||
changeTime: typeof changeTime;
|
changeTime: typeof changeTime;
|
||||||
|
width: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class GraphContainer extends PureComponent<GraphContainerProps> {
|
export class GraphContainer extends PureComponent<GraphContainerProps> {
|
||||||
@ -32,8 +33,8 @@ export class GraphContainer extends PureComponent<GraphContainerProps> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { exploreId, graphResult, loading, showingGraph, showingTable, range, split } = this.props;
|
const { exploreId, graphResult, loading, showingGraph, showingTable, range, split, width } = this.props;
|
||||||
const graphHeight = showingGraph && showingTable ? '200px' : '400px';
|
const graphHeight = showingGraph && showingTable ? 200 : 400;
|
||||||
|
|
||||||
if (!graphResult) {
|
if (!graphResult) {
|
||||||
return null;
|
return null;
|
||||||
@ -48,6 +49,7 @@ export class GraphContainer extends PureComponent<GraphContainerProps> {
|
|||||||
onChangeTime={this.onChangeTime}
|
onChangeTime={this.onChangeTime}
|
||||||
range={range}
|
range={range}
|
||||||
split={split}
|
split={split}
|
||||||
|
width={width}
|
||||||
/>
|
/>
|
||||||
</Panel>
|
</Panel>
|
||||||
);
|
);
|
||||||
|
@ -214,7 +214,7 @@ export default class Logs extends PureComponent<Props, State> {
|
|||||||
<div className="logs-panel-graph">
|
<div className="logs-panel-graph">
|
||||||
<Graph
|
<Graph
|
||||||
data={timeSeries}
|
data={timeSeries}
|
||||||
height="100px"
|
height={100}
|
||||||
range={range}
|
range={range}
|
||||||
id={`explore-logs-graph-${exploreId}`}
|
id={`explore-logs-graph-${exploreId}`}
|
||||||
onChangeTime={this.props.onChangeTime}
|
onChangeTime={this.props.onChangeTime}
|
||||||
|
@ -14,7 +14,7 @@ interface QueryEditorProps {
|
|||||||
datasource: any;
|
datasource: any;
|
||||||
error?: string | JSX.Element;
|
error?: string | JSX.Element;
|
||||||
onExecuteQuery?: () => void;
|
onExecuteQuery?: () => void;
|
||||||
onQueryChange?: (value: DataQuery, override?: boolean) => void;
|
onQueryChange?: (value: DataQuery) => void;
|
||||||
initialQuery: DataQuery;
|
initialQuery: DataQuery;
|
||||||
exploreEvents: Emitter;
|
exploreEvents: Emitter;
|
||||||
range: RawTimeRange;
|
range: RawTimeRange;
|
||||||
@ -40,20 +40,17 @@ export default class QueryEditor extends PureComponent<QueryEditorProps, any> {
|
|||||||
datasource,
|
datasource,
|
||||||
target,
|
target,
|
||||||
refresh: () => {
|
refresh: () => {
|
||||||
this.props.onQueryChange(target, false);
|
this.props.onQueryChange(target);
|
||||||
this.props.onExecuteQuery();
|
this.props.onExecuteQuery();
|
||||||
},
|
},
|
||||||
events: exploreEvents,
|
events: exploreEvents,
|
||||||
panel: {
|
panel: { datasource, targets: [target] },
|
||||||
datasource,
|
|
||||||
targets: [target],
|
|
||||||
},
|
|
||||||
dashboard: {},
|
dashboard: {},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
this.component = loader.load(this.element, scopeProps, template);
|
this.component = loader.load(this.element, scopeProps, template);
|
||||||
this.props.onQueryChange(target, false);
|
this.props.onQueryChange(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
|
@ -33,10 +33,9 @@ export interface QueryFieldProps {
|
|||||||
cleanText?: (text: string) => string;
|
cleanText?: (text: string) => string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
initialQuery: string | null;
|
initialQuery: string | null;
|
||||||
onBlur?: () => void;
|
onExecuteQuery?: () => void;
|
||||||
onFocus?: () => void;
|
onQueryChange?: (value: string) => void;
|
||||||
onTypeahead?: (typeahead: TypeaheadInput) => TypeaheadOutput;
|
onTypeahead?: (typeahead: TypeaheadInput) => TypeaheadOutput;
|
||||||
onValueChanged?: (value: string) => void;
|
|
||||||
onWillApplySuggestion?: (suggestion: string, state: QueryFieldState) => string;
|
onWillApplySuggestion?: (suggestion: string, state: QueryFieldState) => string;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
portalOrigin?: string;
|
portalOrigin?: string;
|
||||||
@ -51,6 +50,7 @@ export interface QueryFieldState {
|
|||||||
typeaheadPrefix: string;
|
typeaheadPrefix: string;
|
||||||
typeaheadText: string;
|
typeaheadText: string;
|
||||||
value: Value;
|
value: Value;
|
||||||
|
lastExecutedValue: Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TypeaheadInput {
|
export interface TypeaheadInput {
|
||||||
@ -90,6 +90,7 @@ export class QueryField extends React.PureComponent<QueryFieldProps, QueryFieldS
|
|||||||
typeaheadPrefix: '',
|
typeaheadPrefix: '',
|
||||||
typeaheadText: '',
|
typeaheadText: '',
|
||||||
value: makeValue(this.placeholdersBuffer.toString(), props.syntax),
|
value: makeValue(this.placeholdersBuffer.toString(), props.syntax),
|
||||||
|
lastExecutedValue: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,11 +133,11 @@ export class QueryField extends React.PureComponent<QueryFieldProps, QueryFieldS
|
|||||||
if (this.placeholdersBuffer.hasPlaceholders()) {
|
if (this.placeholdersBuffer.hasPlaceholders()) {
|
||||||
change.move(this.placeholdersBuffer.getNextMoveOffset()).focus();
|
change.move(this.placeholdersBuffer.getNextMoveOffset()).focus();
|
||||||
}
|
}
|
||||||
this.onChange(change);
|
this.onChange(change, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onChange = ({ value }) => {
|
onChange = ({ value }, invokeParentOnValueChanged?: boolean) => {
|
||||||
const documentChanged = value.document !== this.state.value.document;
|
const documentChanged = value.document !== this.state.value.document;
|
||||||
const prevValue = this.state.value;
|
const prevValue = this.state.value;
|
||||||
|
|
||||||
@ -144,8 +145,8 @@ export class QueryField extends React.PureComponent<QueryFieldProps, QueryFieldS
|
|||||||
this.setState({ value }, () => {
|
this.setState({ value }, () => {
|
||||||
if (documentChanged) {
|
if (documentChanged) {
|
||||||
const textChanged = Plain.serialize(prevValue) !== Plain.serialize(value);
|
const textChanged = Plain.serialize(prevValue) !== Plain.serialize(value);
|
||||||
if (textChanged) {
|
if (textChanged && invokeParentOnValueChanged) {
|
||||||
this.handleChangeValue();
|
this.executeOnQueryChangeAndExecuteQueries();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -159,11 +160,16 @@ export class QueryField extends React.PureComponent<QueryFieldProps, QueryFieldS
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
handleChangeValue = () => {
|
executeOnQueryChangeAndExecuteQueries = () => {
|
||||||
// Send text change to parent
|
// Send text change to parent
|
||||||
const { onValueChanged } = this.props;
|
const { onQueryChange, onExecuteQuery } = this.props;
|
||||||
if (onValueChanged) {
|
if (onQueryChange) {
|
||||||
onValueChanged(Plain.serialize(this.state.value));
|
onQueryChange(Plain.serialize(this.state.value));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onExecuteQuery) {
|
||||||
|
onExecuteQuery();
|
||||||
|
this.setState({ lastExecutedValue: this.state.value });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -288,8 +294,37 @@ export class QueryField extends React.PureComponent<QueryFieldProps, QueryFieldS
|
|||||||
.focus();
|
.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
onKeyDown = (event, change) => {
|
handleEnterAndTabKey = change => {
|
||||||
const { typeaheadIndex, suggestions } = this.state;
|
const { typeaheadIndex, suggestions } = this.state;
|
||||||
|
if (this.menuEl) {
|
||||||
|
// Dont blur input
|
||||||
|
event.preventDefault();
|
||||||
|
if (!suggestions || suggestions.length === 0) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const suggestion = getSuggestionByIndex(suggestions, typeaheadIndex);
|
||||||
|
const nextChange = this.applyTypeahead(change, suggestion);
|
||||||
|
|
||||||
|
const insertTextOperation = nextChange.operations.find(operation => operation.type === 'insert_text');
|
||||||
|
if (insertTextOperation) {
|
||||||
|
const suggestionText = insertTextOperation.text;
|
||||||
|
this.placeholdersBuffer.setNextPlaceholderValue(suggestionText);
|
||||||
|
if (this.placeholdersBuffer.hasPlaceholders()) {
|
||||||
|
nextChange.move(this.placeholdersBuffer.getNextMoveOffset()).focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
this.executeOnQueryChangeAndExecuteQueries();
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onKeyDown = (event, change) => {
|
||||||
|
const { typeaheadIndex } = this.state;
|
||||||
|
|
||||||
switch (event.key) {
|
switch (event.key) {
|
||||||
case 'Escape': {
|
case 'Escape': {
|
||||||
@ -312,27 +347,7 @@ export class QueryField extends React.PureComponent<QueryFieldProps, QueryFieldS
|
|||||||
}
|
}
|
||||||
case 'Enter':
|
case 'Enter':
|
||||||
case 'Tab': {
|
case 'Tab': {
|
||||||
if (this.menuEl) {
|
return this.handleEnterAndTabKey(change);
|
||||||
// Dont blur input
|
|
||||||
event.preventDefault();
|
|
||||||
if (!suggestions || suggestions.length === 0) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const suggestion = getSuggestionByIndex(suggestions, typeaheadIndex);
|
|
||||||
const nextChange = this.applyTypeahead(change, suggestion);
|
|
||||||
|
|
||||||
const insertTextOperation = nextChange.operations.find(operation => operation.type === 'insert_text');
|
|
||||||
if (insertTextOperation) {
|
|
||||||
const suggestionText = insertTextOperation.text;
|
|
||||||
this.placeholdersBuffer.setNextPlaceholderValue(suggestionText);
|
|
||||||
if (this.placeholdersBuffer.hasPlaceholders()) {
|
|
||||||
nextChange.move(this.placeholdersBuffer.getNextMoveOffset()).focus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -364,39 +379,33 @@ export class QueryField extends React.PureComponent<QueryFieldProps, QueryFieldS
|
|||||||
|
|
||||||
resetTypeahead = () => {
|
resetTypeahead = () => {
|
||||||
if (this.mounted) {
|
if (this.mounted) {
|
||||||
this.setState({
|
this.setState({ suggestions: [], typeaheadIndex: 0, typeaheadPrefix: '', typeaheadContext: null });
|
||||||
suggestions: [],
|
|
||||||
typeaheadIndex: 0,
|
|
||||||
typeaheadPrefix: '',
|
|
||||||
typeaheadContext: null,
|
|
||||||
});
|
|
||||||
this.resetTimer = null;
|
this.resetTimer = null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
handleBlur = () => {
|
handleBlur = (event, change) => {
|
||||||
const { onBlur } = this.props;
|
const { lastExecutedValue } = this.state;
|
||||||
|
const previousValue = lastExecutedValue ? Plain.serialize(this.state.lastExecutedValue) : null;
|
||||||
|
const currentValue = Plain.serialize(change.value);
|
||||||
|
|
||||||
// If we dont wait here, menu clicks wont work because the menu
|
// If we dont wait here, menu clicks wont work because the menu
|
||||||
// will be gone.
|
// will be gone.
|
||||||
this.resetTimer = setTimeout(this.resetTypeahead, 100);
|
this.resetTimer = setTimeout(this.resetTypeahead, 100);
|
||||||
// Disrupting placeholder entry wipes all remaining placeholders needing input
|
// Disrupting placeholder entry wipes all remaining placeholders needing input
|
||||||
this.placeholdersBuffer.clearPlaceholders();
|
this.placeholdersBuffer.clearPlaceholders();
|
||||||
if (onBlur) {
|
|
||||||
onBlur();
|
if (previousValue !== currentValue) {
|
||||||
|
this.executeOnQueryChangeAndExecuteQueries();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
handleFocus = () => {
|
handleFocus = () => {};
|
||||||
const { onFocus } = this.props;
|
|
||||||
if (onFocus) {
|
|
||||||
onFocus();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
onClickMenu = (item: CompletionItem) => {
|
onClickMenu = (item: CompletionItem) => {
|
||||||
// Manually triggering change
|
// Manually triggering change
|
||||||
const change = this.applyTypeahead(this.state.value.change(), item);
|
const change = this.applyTypeahead(this.state.value.change(), item);
|
||||||
this.onChange(change);
|
this.onChange(change, true);
|
||||||
};
|
};
|
||||||
|
|
||||||
updateMenu = () => {
|
updateMenu = () => {
|
||||||
@ -459,6 +468,14 @@ export class QueryField extends React.PureComponent<QueryFieldProps, QueryFieldS
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
handlePaste = (event: ClipboardEvent, change: Editor) => {
|
||||||
|
const pastedValue = event.clipboardData.getData('Text');
|
||||||
|
const newValue = change.value.change().insertText(pastedValue);
|
||||||
|
this.onChange(newValue);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { disabled } = this.props;
|
const { disabled } = this.props;
|
||||||
const wrapperClassName = classnames('slate-query-field__wrapper', {
|
const wrapperClassName = classnames('slate-query-field__wrapper', {
|
||||||
@ -475,6 +492,7 @@ export class QueryField extends React.PureComponent<QueryFieldProps, QueryFieldS
|
|||||||
onKeyDown={this.onKeyDown}
|
onKeyDown={this.onKeyDown}
|
||||||
onChange={this.onChange}
|
onChange={this.onChange}
|
||||||
onFocus={this.handleFocus}
|
onFocus={this.handleFocus}
|
||||||
|
onPaste={this.handlePaste}
|
||||||
placeholder={this.props.placeholder}
|
placeholder={this.props.placeholder}
|
||||||
plugins={this.plugins}
|
plugins={this.plugins}
|
||||||
spellCheck={false}
|
spellCheck={false}
|
||||||
|
@ -9,20 +9,14 @@ import QueryEditor from './QueryEditor';
|
|||||||
import QueryTransactionStatus from './QueryTransactionStatus';
|
import QueryTransactionStatus from './QueryTransactionStatus';
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
import {
|
import { changeQuery, modifyQueries, runQueries, addQueryRow } from './state/actions';
|
||||||
addQueryRow,
|
|
||||||
changeQuery,
|
|
||||||
highlightLogsExpression,
|
|
||||||
modifyQueries,
|
|
||||||
removeQueryRow,
|
|
||||||
runQueries,
|
|
||||||
} from './state/actions';
|
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
import { StoreState } from 'app/types';
|
import { StoreState } from 'app/types';
|
||||||
import { RawTimeRange, DataQuery, QueryHint } from '@grafana/ui';
|
import { RawTimeRange, DataQuery, ExploreDataSourceApi, QueryHint, QueryFixAction } from '@grafana/ui';
|
||||||
import { QueryTransaction, HistoryItem, ExploreItemState, ExploreId } from 'app/types/explore';
|
import { QueryTransaction, HistoryItem, ExploreItemState, ExploreId } from 'app/types/explore';
|
||||||
import { Emitter } from 'app/core/utils/emitter';
|
import { Emitter } from 'app/core/utils/emitter';
|
||||||
|
import { highlightLogsExpressionAction, removeQueryRowAction } from './state/actionTypes';
|
||||||
|
|
||||||
function getFirstHintFromTransactions(transactions: QueryTransaction[]): QueryHint {
|
function getFirstHintFromTransactions(transactions: QueryTransaction[]): QueryHint {
|
||||||
const transaction = transactions.find(qt => qt.hints && qt.hints.length > 0);
|
const transaction = transactions.find(qt => qt.hints && qt.hints.length > 0);
|
||||||
@ -37,16 +31,16 @@ interface QueryRowProps {
|
|||||||
changeQuery: typeof changeQuery;
|
changeQuery: typeof changeQuery;
|
||||||
className?: string;
|
className?: string;
|
||||||
exploreId: ExploreId;
|
exploreId: ExploreId;
|
||||||
datasourceInstance: any;
|
datasourceInstance: ExploreDataSourceApi;
|
||||||
highlightLogsExpression: typeof highlightLogsExpression;
|
highlightLogsExpressionAction: typeof highlightLogsExpressionAction;
|
||||||
history: HistoryItem[];
|
history: HistoryItem[];
|
||||||
index: number;
|
index: number;
|
||||||
initialQuery: DataQuery;
|
query: DataQuery;
|
||||||
modifyQueries: typeof modifyQueries;
|
modifyQueries: typeof modifyQueries;
|
||||||
queryTransactions: QueryTransaction[];
|
queryTransactions: QueryTransaction[];
|
||||||
exploreEvents: Emitter;
|
exploreEvents: Emitter;
|
||||||
range: RawTimeRange;
|
range: RawTimeRange;
|
||||||
removeQueryRow: typeof removeQueryRow;
|
removeQueryRowAction: typeof removeQueryRowAction;
|
||||||
runQueries: typeof runQueries;
|
runQueries: typeof runQueries;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,29 +72,30 @@ export class QueryRow extends PureComponent<QueryRowProps> {
|
|||||||
this.onChangeQuery(null, true);
|
this.onChangeQuery(null, true);
|
||||||
};
|
};
|
||||||
|
|
||||||
onClickHintFix = action => {
|
onClickHintFix = (action: QueryFixAction) => {
|
||||||
const { datasourceInstance, exploreId, index } = this.props;
|
const { datasourceInstance, exploreId, index } = this.props;
|
||||||
if (datasourceInstance && datasourceInstance.modifyQuery) {
|
if (datasourceInstance && datasourceInstance.modifyQuery) {
|
||||||
const modifier = (queries: DataQuery, action: any) => datasourceInstance.modifyQuery(queries, action);
|
const modifier = (queries: DataQuery, action: QueryFixAction) => datasourceInstance.modifyQuery(queries, action);
|
||||||
this.props.modifyQueries(exploreId, action, index, modifier);
|
this.props.modifyQueries(exploreId, action, index, modifier);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onClickRemoveButton = () => {
|
onClickRemoveButton = () => {
|
||||||
const { exploreId, index } = this.props;
|
const { exploreId, index } = this.props;
|
||||||
this.props.removeQueryRow(exploreId, index);
|
this.props.removeQueryRowAction({ exploreId, index });
|
||||||
};
|
};
|
||||||
|
|
||||||
updateLogsHighlights = _.debounce((value: DataQuery) => {
|
updateLogsHighlights = _.debounce((value: DataQuery) => {
|
||||||
const { datasourceInstance } = this.props;
|
const { datasourceInstance } = this.props;
|
||||||
if (datasourceInstance.getHighlighterExpression) {
|
if (datasourceInstance.getHighlighterExpression) {
|
||||||
|
const { exploreId } = this.props;
|
||||||
const expressions = [datasourceInstance.getHighlighterExpression(value)];
|
const expressions = [datasourceInstance.getHighlighterExpression(value)];
|
||||||
this.props.highlightLogsExpression(this.props.exploreId, expressions);
|
this.props.highlightLogsExpressionAction({ exploreId, expressions });
|
||||||
}
|
}
|
||||||
}, 500);
|
}, 500);
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { datasourceInstance, history, index, initialQuery, queryTransactions, exploreEvents, range } = this.props;
|
const { datasourceInstance, history, index, query, queryTransactions, exploreEvents, range } = this.props;
|
||||||
const transactions = queryTransactions.filter(t => t.rowIndex === index);
|
const transactions = queryTransactions.filter(t => t.rowIndex === index);
|
||||||
const transactionWithError = transactions.find(t => t.error !== undefined);
|
const transactionWithError = transactions.find(t => t.error !== undefined);
|
||||||
const hint = getFirstHintFromTransactions(transactions);
|
const hint = getFirstHintFromTransactions(transactions);
|
||||||
@ -111,16 +106,16 @@ export class QueryRow extends PureComponent<QueryRowProps> {
|
|||||||
<div className="query-row-status">
|
<div className="query-row-status">
|
||||||
<QueryTransactionStatus transactions={transactions} />
|
<QueryTransactionStatus transactions={transactions} />
|
||||||
</div>
|
</div>
|
||||||
<div className="query-row-field">
|
<div className="query-row-field flex-shrink-1">
|
||||||
{QueryField ? (
|
{QueryField ? (
|
||||||
<QueryField
|
<QueryField
|
||||||
datasource={datasourceInstance}
|
datasource={datasourceInstance}
|
||||||
|
query={query}
|
||||||
error={queryError}
|
error={queryError}
|
||||||
hint={hint}
|
hint={hint}
|
||||||
initialQuery={initialQuery}
|
|
||||||
history={history}
|
history={history}
|
||||||
onClickHintFix={this.onClickHintFix}
|
onExecuteQuery={this.onExecuteQuery}
|
||||||
onPressEnter={this.onExecuteQuery}
|
onExecuteHint={this.onClickHintFix}
|
||||||
onQueryChange={this.onChangeQuery}
|
onQueryChange={this.onChangeQuery}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
@ -129,13 +124,13 @@ export class QueryRow extends PureComponent<QueryRowProps> {
|
|||||||
error={queryError}
|
error={queryError}
|
||||||
onQueryChange={this.onChangeQuery}
|
onQueryChange={this.onChangeQuery}
|
||||||
onExecuteQuery={this.onExecuteQuery}
|
onExecuteQuery={this.onExecuteQuery}
|
||||||
initialQuery={initialQuery}
|
initialQuery={query}
|
||||||
exploreEvents={exploreEvents}
|
exploreEvents={exploreEvents}
|
||||||
range={range}
|
range={range}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="gf-form-inline">
|
<div className="gf-form-inline flex-shrink-0">
|
||||||
<div className="gf-form">
|
<div className="gf-form">
|
||||||
<button className="gf-form-label gf-form-label--btn" onClick={this.onClickClearButton}>
|
<button className="gf-form-label gf-form-label--btn" onClick={this.onClickClearButton}>
|
||||||
<i className="fa fa-times" />
|
<i className="fa fa-times" />
|
||||||
@ -160,17 +155,17 @@ export class QueryRow extends PureComponent<QueryRowProps> {
|
|||||||
function mapStateToProps(state: StoreState, { exploreId, index }) {
|
function mapStateToProps(state: StoreState, { exploreId, index }) {
|
||||||
const explore = state.explore;
|
const explore = state.explore;
|
||||||
const item: ExploreItemState = explore[exploreId];
|
const item: ExploreItemState = explore[exploreId];
|
||||||
const { datasourceInstance, history, initialQueries, queryTransactions, range } = item;
|
const { datasourceInstance, history, queries, queryTransactions, range } = item;
|
||||||
const initialQuery = initialQueries[index];
|
const query = queries[index];
|
||||||
return { datasourceInstance, history, initialQuery, queryTransactions, range };
|
return { datasourceInstance, history, query, queryTransactions, range };
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
const mapDispatchToProps = {
|
||||||
addQueryRow,
|
addQueryRow,
|
||||||
changeQuery,
|
changeQuery,
|
||||||
highlightLogsExpression,
|
highlightLogsExpressionAction,
|
||||||
modifyQueries,
|
modifyQueries,
|
||||||
removeQueryRow,
|
removeQueryRowAction,
|
||||||
runQueries,
|
runQueries,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -6,25 +6,23 @@ import QueryRow from './QueryRow';
|
|||||||
|
|
||||||
// Types
|
// Types
|
||||||
import { Emitter } from 'app/core/utils/emitter';
|
import { Emitter } from 'app/core/utils/emitter';
|
||||||
import { DataQuery } from '@grafana/ui/src/types';
|
|
||||||
import { ExploreId } from 'app/types/explore';
|
import { ExploreId } from 'app/types/explore';
|
||||||
|
|
||||||
interface QueryRowsProps {
|
interface QueryRowsProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
exploreEvents: Emitter;
|
exploreEvents: Emitter;
|
||||||
exploreId: ExploreId;
|
exploreId: ExploreId;
|
||||||
initialQueries: DataQuery[];
|
queryKeys: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class QueryRows extends PureComponent<QueryRowsProps> {
|
export default class QueryRows extends PureComponent<QueryRowsProps> {
|
||||||
render() {
|
render() {
|
||||||
const { className = '', exploreEvents, exploreId, initialQueries } = this.props;
|
const { className = '', exploreEvents, exploreId, queryKeys } = this.props;
|
||||||
return (
|
return (
|
||||||
<div className={className}>
|
<div className={className}>
|
||||||
{initialQueries.map((query, index) => (
|
{queryKeys.map((key, index) => {
|
||||||
// TODO instead of relying on initialQueries, move to react key list in redux
|
return <QueryRow key={key} exploreEvents={exploreEvents} exploreId={exploreId} index={index} />;
|
||||||
<QueryRow key={query.key} exploreEvents={exploreEvents} exploreId={exploreId} index={index} />
|
})}
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -7,16 +7,16 @@ import { StoreState } from 'app/types';
|
|||||||
import { ExploreId, ExploreUrlState } from 'app/types/explore';
|
import { ExploreId, ExploreUrlState } from 'app/types/explore';
|
||||||
import { parseUrlState } from 'app/core/utils/explore';
|
import { parseUrlState } from 'app/core/utils/explore';
|
||||||
|
|
||||||
import { initializeExploreSplit, resetExplore } from './state/actions';
|
|
||||||
import ErrorBoundary from './ErrorBoundary';
|
import ErrorBoundary from './ErrorBoundary';
|
||||||
import Explore from './Explore';
|
import Explore from './Explore';
|
||||||
import { CustomScrollbar } from '@grafana/ui';
|
import { CustomScrollbar } from '@grafana/ui';
|
||||||
|
import { initializeExploreSplitAction, resetExploreAction } from './state/actionTypes';
|
||||||
|
|
||||||
interface WrapperProps {
|
interface WrapperProps {
|
||||||
initializeExploreSplit: typeof initializeExploreSplit;
|
initializeExploreSplitAction: typeof initializeExploreSplitAction;
|
||||||
split: boolean;
|
split: boolean;
|
||||||
updateLocation: typeof updateLocation;
|
updateLocation: typeof updateLocation;
|
||||||
resetExplore: typeof resetExplore;
|
resetExploreAction: typeof resetExploreAction;
|
||||||
urlStates: { [key: string]: string };
|
urlStates: { [key: string]: string };
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,12 +39,12 @@ export class Wrapper extends Component<WrapperProps> {
|
|||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
if (this.initialSplit) {
|
if (this.initialSplit) {
|
||||||
this.props.initializeExploreSplit();
|
this.props.initializeExploreSplitAction();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this.props.resetExplore();
|
this.props.resetExploreAction();
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@ -77,9 +77,9 @@ const mapStateToProps = (state: StoreState) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
const mapDispatchToProps = {
|
||||||
initializeExploreSplit,
|
initializeExploreSplitAction,
|
||||||
updateLocation,
|
updateLocation,
|
||||||
resetExplore,
|
resetExploreAction,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(Wrapper));
|
export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(Wrapper));
|
||||||
|
@ -7,7 +7,7 @@ exports[`Render should render component 1`] = `
|
|||||||
id="graph"
|
id="graph"
|
||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
"height": "100px",
|
"height": 100,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@ -480,7 +480,7 @@ exports[`Render should render component with disclaimer 1`] = `
|
|||||||
id="graph"
|
id="graph"
|
||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
"height": "100px",
|
"height": 100,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@ -962,7 +962,7 @@ exports[`Render should show query return no time series 1`] = `
|
|||||||
id="graph"
|
id="graph"
|
||||||
style={
|
style={
|
||||||
Object {
|
Object {
|
||||||
"height": "100px",
|
"height": 100,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
@ -1,6 +1,13 @@
|
|||||||
// Types
|
// Types
|
||||||
import { Emitter } from 'app/core/core';
|
import { Emitter } from 'app/core/core';
|
||||||
import { RawTimeRange, TimeRange, DataQuery, DataSourceSelectItem, DataSourceApi } from '@grafana/ui/src/types';
|
import {
|
||||||
|
RawTimeRange,
|
||||||
|
TimeRange,
|
||||||
|
DataQuery,
|
||||||
|
DataSourceSelectItem,
|
||||||
|
DataSourceApi,
|
||||||
|
QueryFixAction,
|
||||||
|
} from '@grafana/ui/src/types';
|
||||||
import {
|
import {
|
||||||
ExploreId,
|
ExploreId,
|
||||||
ExploreItemState,
|
ExploreItemState,
|
||||||
@ -8,234 +15,28 @@ import {
|
|||||||
RangeScanner,
|
RangeScanner,
|
||||||
ResultType,
|
ResultType,
|
||||||
QueryTransaction,
|
QueryTransaction,
|
||||||
|
ExploreUIState,
|
||||||
} from 'app/types/explore';
|
} from 'app/types/explore';
|
||||||
|
import { actionCreatorFactory, noPayloadActionCreatorFactory, ActionOf } from 'app/core/redux/actionCreatorFactory';
|
||||||
|
|
||||||
|
/** Higher order actions
|
||||||
|
*
|
||||||
|
*/
|
||||||
export enum ActionTypes {
|
export enum ActionTypes {
|
||||||
AddQueryRow = 'explore/ADD_QUERY_ROW',
|
|
||||||
ChangeDatasource = 'explore/CHANGE_DATASOURCE',
|
|
||||||
ChangeQuery = 'explore/CHANGE_QUERY',
|
|
||||||
ChangeSize = 'explore/CHANGE_SIZE',
|
|
||||||
ChangeTime = 'explore/CHANGE_TIME',
|
|
||||||
ClearQueries = 'explore/CLEAR_QUERIES',
|
|
||||||
HighlightLogsExpression = 'explore/HIGHLIGHT_LOGS_EXPRESSION',
|
|
||||||
InitializeExplore = 'explore/INITIALIZE_EXPLORE',
|
|
||||||
InitializeExploreSplit = 'explore/INITIALIZE_EXPLORE_SPLIT',
|
InitializeExploreSplit = 'explore/INITIALIZE_EXPLORE_SPLIT',
|
||||||
LoadDatasourceFailure = 'explore/LOAD_DATASOURCE_FAILURE',
|
|
||||||
LoadDatasourceMissing = 'explore/LOAD_DATASOURCE_MISSING',
|
|
||||||
LoadDatasourcePending = 'explore/LOAD_DATASOURCE_PENDING',
|
|
||||||
LoadDatasourceSuccess = 'explore/LOAD_DATASOURCE_SUCCESS',
|
|
||||||
ModifyQueries = 'explore/MODIFY_QUERIES',
|
|
||||||
QueryTransactionFailure = 'explore/QUERY_TRANSACTION_FAILURE',
|
|
||||||
QueryTransactionStart = 'explore/QUERY_TRANSACTION_START',
|
|
||||||
QueryTransactionSuccess = 'explore/QUERY_TRANSACTION_SUCCESS',
|
|
||||||
RemoveQueryRow = 'explore/REMOVE_QUERY_ROW',
|
|
||||||
RunQueries = 'explore/RUN_QUERIES',
|
|
||||||
RunQueriesEmpty = 'explore/RUN_QUERIES_EMPTY',
|
|
||||||
ScanRange = 'explore/SCAN_RANGE',
|
|
||||||
ScanStart = 'explore/SCAN_START',
|
|
||||||
ScanStop = 'explore/SCAN_STOP',
|
|
||||||
SetQueries = 'explore/SET_QUERIES',
|
|
||||||
SplitClose = 'explore/SPLIT_CLOSE',
|
SplitClose = 'explore/SPLIT_CLOSE',
|
||||||
SplitOpen = 'explore/SPLIT_OPEN',
|
SplitOpen = 'explore/SPLIT_OPEN',
|
||||||
StateSave = 'explore/STATE_SAVE',
|
|
||||||
ToggleGraph = 'explore/TOGGLE_GRAPH',
|
|
||||||
ToggleLogs = 'explore/TOGGLE_LOGS',
|
|
||||||
ToggleTable = 'explore/TOGGLE_TABLE',
|
|
||||||
UpdateDatasourceInstance = 'explore/UPDATE_DATASOURCE_INSTANCE',
|
|
||||||
ResetExplore = 'explore/RESET_EXPLORE',
|
ResetExplore = 'explore/RESET_EXPLORE',
|
||||||
QueriesImported = 'explore/QueriesImported',
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AddQueryRowAction {
|
|
||||||
type: ActionTypes.AddQueryRow;
|
|
||||||
payload: {
|
|
||||||
exploreId: ExploreId;
|
|
||||||
index: number;
|
|
||||||
query: DataQuery;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ChangeQueryAction {
|
|
||||||
type: ActionTypes.ChangeQuery;
|
|
||||||
payload: {
|
|
||||||
exploreId: ExploreId;
|
|
||||||
query: DataQuery;
|
|
||||||
index: number;
|
|
||||||
override: boolean;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ChangeSizeAction {
|
|
||||||
type: ActionTypes.ChangeSize;
|
|
||||||
payload: {
|
|
||||||
exploreId: ExploreId;
|
|
||||||
width: number;
|
|
||||||
height: number;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ChangeTimeAction {
|
|
||||||
type: ActionTypes.ChangeTime;
|
|
||||||
payload: {
|
|
||||||
exploreId: ExploreId;
|
|
||||||
range: TimeRange;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ClearQueriesAction {
|
|
||||||
type: ActionTypes.ClearQueries;
|
|
||||||
payload: {
|
|
||||||
exploreId: ExploreId;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface HighlightLogsExpressionAction {
|
|
||||||
type: ActionTypes.HighlightLogsExpression;
|
|
||||||
payload: {
|
|
||||||
exploreId: ExploreId;
|
|
||||||
expressions: string[];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface InitializeExploreAction {
|
|
||||||
type: ActionTypes.InitializeExplore;
|
|
||||||
payload: {
|
|
||||||
exploreId: ExploreId;
|
|
||||||
containerWidth: number;
|
|
||||||
eventBridge: Emitter;
|
|
||||||
exploreDatasources: DataSourceSelectItem[];
|
|
||||||
queries: DataQuery[];
|
|
||||||
range: RawTimeRange;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface InitializeExploreSplitAction {
|
export interface InitializeExploreSplitAction {
|
||||||
type: ActionTypes.InitializeExploreSplit;
|
type: ActionTypes.InitializeExploreSplit;
|
||||||
}
|
payload: {};
|
||||||
|
|
||||||
export interface LoadDatasourceFailureAction {
|
|
||||||
type: ActionTypes.LoadDatasourceFailure;
|
|
||||||
payload: {
|
|
||||||
exploreId: ExploreId;
|
|
||||||
error: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LoadDatasourcePendingAction {
|
|
||||||
type: ActionTypes.LoadDatasourcePending;
|
|
||||||
payload: {
|
|
||||||
exploreId: ExploreId;
|
|
||||||
requestedDatasourceName: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LoadDatasourceMissingAction {
|
|
||||||
type: ActionTypes.LoadDatasourceMissing;
|
|
||||||
payload: {
|
|
||||||
exploreId: ExploreId;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LoadDatasourceSuccessAction {
|
|
||||||
type: ActionTypes.LoadDatasourceSuccess;
|
|
||||||
payload: {
|
|
||||||
exploreId: ExploreId;
|
|
||||||
StartPage?: any;
|
|
||||||
datasourceInstance: any;
|
|
||||||
history: HistoryItem[];
|
|
||||||
logsHighlighterExpressions?: any[];
|
|
||||||
showingStartPage: boolean;
|
|
||||||
supportsGraph: boolean;
|
|
||||||
supportsLogs: boolean;
|
|
||||||
supportsTable: boolean;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ModifyQueriesAction {
|
|
||||||
type: ActionTypes.ModifyQueries;
|
|
||||||
payload: {
|
|
||||||
exploreId: ExploreId;
|
|
||||||
modification: any;
|
|
||||||
index: number;
|
|
||||||
modifier: (queries: DataQuery[], modification: any) => DataQuery[];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface QueryTransactionFailureAction {
|
|
||||||
type: ActionTypes.QueryTransactionFailure;
|
|
||||||
payload: {
|
|
||||||
exploreId: ExploreId;
|
|
||||||
queryTransactions: QueryTransaction[];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface QueryTransactionStartAction {
|
|
||||||
type: ActionTypes.QueryTransactionStart;
|
|
||||||
payload: {
|
|
||||||
exploreId: ExploreId;
|
|
||||||
resultType: ResultType;
|
|
||||||
rowIndex: number;
|
|
||||||
transaction: QueryTransaction;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface QueryTransactionSuccessAction {
|
|
||||||
type: ActionTypes.QueryTransactionSuccess;
|
|
||||||
payload: {
|
|
||||||
exploreId: ExploreId;
|
|
||||||
history: HistoryItem[];
|
|
||||||
queryTransactions: QueryTransaction[];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RemoveQueryRowAction {
|
|
||||||
type: ActionTypes.RemoveQueryRow;
|
|
||||||
payload: {
|
|
||||||
exploreId: ExploreId;
|
|
||||||
index: number;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RunQueriesEmptyAction {
|
|
||||||
type: ActionTypes.RunQueriesEmpty;
|
|
||||||
payload: {
|
|
||||||
exploreId: ExploreId;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ScanStartAction {
|
|
||||||
type: ActionTypes.ScanStart;
|
|
||||||
payload: {
|
|
||||||
exploreId: ExploreId;
|
|
||||||
scanner: RangeScanner;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ScanRangeAction {
|
|
||||||
type: ActionTypes.ScanRange;
|
|
||||||
payload: {
|
|
||||||
exploreId: ExploreId;
|
|
||||||
range: RawTimeRange;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ScanStopAction {
|
|
||||||
type: ActionTypes.ScanStop;
|
|
||||||
payload: {
|
|
||||||
exploreId: ExploreId;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SetQueriesAction {
|
|
||||||
type: ActionTypes.SetQueries;
|
|
||||||
payload: {
|
|
||||||
exploreId: ExploreId;
|
|
||||||
queries: DataQuery[];
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SplitCloseAction {
|
export interface SplitCloseAction {
|
||||||
type: ActionTypes.SplitClose;
|
type: ActionTypes.SplitClose;
|
||||||
|
payload: {};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SplitOpenAction {
|
export interface SplitOpenAction {
|
||||||
@ -245,80 +46,385 @@ export interface SplitOpenAction {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StateSaveAction {
|
|
||||||
type: ActionTypes.StateSave;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ToggleTableAction {
|
|
||||||
type: ActionTypes.ToggleTable;
|
|
||||||
payload: {
|
|
||||||
exploreId: ExploreId;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ToggleGraphAction {
|
|
||||||
type: ActionTypes.ToggleGraph;
|
|
||||||
payload: {
|
|
||||||
exploreId: ExploreId;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ToggleLogsAction {
|
|
||||||
type: ActionTypes.ToggleLogs;
|
|
||||||
payload: {
|
|
||||||
exploreId: ExploreId;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UpdateDatasourceInstanceAction {
|
|
||||||
type: ActionTypes.UpdateDatasourceInstance;
|
|
||||||
payload: {
|
|
||||||
exploreId: ExploreId;
|
|
||||||
datasourceInstance: DataSourceApi;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ResetExploreAction {
|
export interface ResetExploreAction {
|
||||||
type: ActionTypes.ResetExplore;
|
type: ActionTypes.ResetExplore;
|
||||||
payload: {};
|
payload: {};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface QueriesImported {
|
/** Lower order actions
|
||||||
type: ActionTypes.QueriesImported;
|
*
|
||||||
payload: {
|
*/
|
||||||
exploreId: ExploreId;
|
export interface AddQueryRowPayload {
|
||||||
queries: DataQuery[];
|
exploreId: ExploreId;
|
||||||
};
|
index: number;
|
||||||
|
query: DataQuery;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Action =
|
export interface ChangeQueryPayload {
|
||||||
| AddQueryRowAction
|
exploreId: ExploreId;
|
||||||
| ChangeQueryAction
|
query: DataQuery;
|
||||||
| ChangeSizeAction
|
index: number;
|
||||||
| ChangeTimeAction
|
override: boolean;
|
||||||
| ClearQueriesAction
|
}
|
||||||
| HighlightLogsExpressionAction
|
|
||||||
| InitializeExploreAction
|
export interface ChangeSizePayload {
|
||||||
|
exploreId: ExploreId;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChangeTimePayload {
|
||||||
|
exploreId: ExploreId;
|
||||||
|
range: TimeRange;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ClearQueriesPayload {
|
||||||
|
exploreId: ExploreId;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HighlightLogsExpressionPayload {
|
||||||
|
exploreId: ExploreId;
|
||||||
|
expressions: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface InitializeExplorePayload {
|
||||||
|
exploreId: ExploreId;
|
||||||
|
containerWidth: number;
|
||||||
|
eventBridge: Emitter;
|
||||||
|
exploreDatasources: DataSourceSelectItem[];
|
||||||
|
queries: DataQuery[];
|
||||||
|
range: RawTimeRange;
|
||||||
|
ui: ExploreUIState;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LoadDatasourceFailurePayload {
|
||||||
|
exploreId: ExploreId;
|
||||||
|
error: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LoadDatasourceMissingPayload {
|
||||||
|
exploreId: ExploreId;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LoadDatasourcePendingPayload {
|
||||||
|
exploreId: ExploreId;
|
||||||
|
requestedDatasourceName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LoadDatasourceSuccessPayload {
|
||||||
|
exploreId: ExploreId;
|
||||||
|
StartPage?: any;
|
||||||
|
datasourceInstance: any;
|
||||||
|
history: HistoryItem[];
|
||||||
|
logsHighlighterExpressions?: any[];
|
||||||
|
showingStartPage: boolean;
|
||||||
|
supportsGraph: boolean;
|
||||||
|
supportsLogs: boolean;
|
||||||
|
supportsTable: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ModifyQueriesPayload {
|
||||||
|
exploreId: ExploreId;
|
||||||
|
modification: QueryFixAction;
|
||||||
|
index: number;
|
||||||
|
modifier: (query: DataQuery, modification: QueryFixAction) => DataQuery;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface QueryTransactionFailurePayload {
|
||||||
|
exploreId: ExploreId;
|
||||||
|
queryTransactions: QueryTransaction[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface QueryTransactionStartPayload {
|
||||||
|
exploreId: ExploreId;
|
||||||
|
resultType: ResultType;
|
||||||
|
rowIndex: number;
|
||||||
|
transaction: QueryTransaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface QueryTransactionSuccessPayload {
|
||||||
|
exploreId: ExploreId;
|
||||||
|
history: HistoryItem[];
|
||||||
|
queryTransactions: QueryTransaction[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RemoveQueryRowPayload {
|
||||||
|
exploreId: ExploreId;
|
||||||
|
index: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RunQueriesEmptyPayload {
|
||||||
|
exploreId: ExploreId;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ScanStartPayload {
|
||||||
|
exploreId: ExploreId;
|
||||||
|
scanner: RangeScanner;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ScanRangePayload {
|
||||||
|
exploreId: ExploreId;
|
||||||
|
range: RawTimeRange;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ScanStopPayload {
|
||||||
|
exploreId: ExploreId;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SetQueriesPayload {
|
||||||
|
exploreId: ExploreId;
|
||||||
|
queries: DataQuery[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SplitOpenPayload {
|
||||||
|
itemState: ExploreItemState;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ToggleTablePayload {
|
||||||
|
exploreId: ExploreId;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ToggleGraphPayload {
|
||||||
|
exploreId: ExploreId;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ToggleLogsPayload {
|
||||||
|
exploreId: ExploreId;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdateDatasourceInstancePayload {
|
||||||
|
exploreId: ExploreId;
|
||||||
|
datasourceInstance: DataSourceApi;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface QueriesImportedPayload {
|
||||||
|
exploreId: ExploreId;
|
||||||
|
queries: DataQuery[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a query row after the row with the given index.
|
||||||
|
*/
|
||||||
|
export const addQueryRowAction = actionCreatorFactory<AddQueryRowPayload>('explore/ADD_QUERY_ROW').create();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads a new datasource identified by the given name.
|
||||||
|
*/
|
||||||
|
export const changeDatasourceAction = noPayloadActionCreatorFactory('explore/CHANGE_DATASOURCE').create();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query change handler for the query row with the given index.
|
||||||
|
* If `override` is reset the query modifications and run the queries. Use this to set queries via a link.
|
||||||
|
*/
|
||||||
|
export const changeQueryAction = actionCreatorFactory<ChangeQueryPayload>('explore/CHANGE_QUERY').create();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keep track of the Explore container size, in particular the width.
|
||||||
|
* The width will be used to calculate graph intervals (number of datapoints).
|
||||||
|
*/
|
||||||
|
export const changeSizeAction = actionCreatorFactory<ChangeSizePayload>('explore/CHANGE_SIZE').create();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change the time range of Explore. Usually called from the Timepicker or a graph interaction.
|
||||||
|
*/
|
||||||
|
export const changeTimeAction = actionCreatorFactory<ChangeTimePayload>('explore/CHANGE_TIME').create();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear all queries and results.
|
||||||
|
*/
|
||||||
|
export const clearQueriesAction = actionCreatorFactory<ClearQueriesPayload>('explore/CLEAR_QUERIES').create();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Highlight expressions in the log results
|
||||||
|
*/
|
||||||
|
export const highlightLogsExpressionAction = actionCreatorFactory<HighlightLogsExpressionPayload>(
|
||||||
|
'explore/HIGHLIGHT_LOGS_EXPRESSION'
|
||||||
|
).create();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize Explore state with state from the URL and the React component.
|
||||||
|
* Call this only on components for with the Explore state has not been initialized.
|
||||||
|
*/
|
||||||
|
export const initializeExploreAction = actionCreatorFactory<InitializeExplorePayload>(
|
||||||
|
'explore/INITIALIZE_EXPLORE'
|
||||||
|
).create();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the wrapper split state
|
||||||
|
*/
|
||||||
|
export const initializeExploreSplitAction = noPayloadActionCreatorFactory('explore/INITIALIZE_EXPLORE_SPLIT').create();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display an error that happened during the selection of a datasource
|
||||||
|
*/
|
||||||
|
export const loadDatasourceFailureAction = actionCreatorFactory<LoadDatasourceFailurePayload>(
|
||||||
|
'explore/LOAD_DATASOURCE_FAILURE'
|
||||||
|
).create();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display an error when no datasources have been configured
|
||||||
|
*/
|
||||||
|
export const loadDatasourceMissingAction = actionCreatorFactory<LoadDatasourceMissingPayload>(
|
||||||
|
'explore/LOAD_DATASOURCE_MISSING'
|
||||||
|
).create();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start the async process of loading a datasource to display a loading indicator
|
||||||
|
*/
|
||||||
|
export const loadDatasourcePendingAction = actionCreatorFactory<LoadDatasourcePendingPayload>(
|
||||||
|
'explore/LOAD_DATASOURCE_PENDING'
|
||||||
|
).create();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Datasource loading was successfully completed. The instance is stored in the state as well in case we need to
|
||||||
|
* run datasource-specific code. Existing queries are imported to the new datasource if an importer exists,
|
||||||
|
* e.g., Prometheus -> Loki queries.
|
||||||
|
*/
|
||||||
|
export const loadDatasourceSuccessAction = actionCreatorFactory<LoadDatasourceSuccessPayload>(
|
||||||
|
'explore/LOAD_DATASOURCE_SUCCESS'
|
||||||
|
).create();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Action to modify a query given a datasource-specific modifier action.
|
||||||
|
* @param exploreId Explore area
|
||||||
|
* @param modification Action object with a type, e.g., ADD_FILTER
|
||||||
|
* @param index Optional query row index. If omitted, the modification is applied to all query rows.
|
||||||
|
* @param modifier Function that executes the modification, typically `datasourceInstance.modifyQueries`.
|
||||||
|
*/
|
||||||
|
export const modifyQueriesAction = actionCreatorFactory<ModifyQueriesPayload>('explore/MODIFY_QUERIES').create();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark a query transaction as failed with an error extracted from the query response.
|
||||||
|
* The transaction will be marked as `done`.
|
||||||
|
*/
|
||||||
|
export const queryTransactionFailureAction = actionCreatorFactory<QueryTransactionFailurePayload>(
|
||||||
|
'explore/QUERY_TRANSACTION_FAILURE'
|
||||||
|
).create();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start a query transaction for the given result type.
|
||||||
|
* @param exploreId Explore area
|
||||||
|
* @param transaction Query options and `done` status.
|
||||||
|
* @param resultType Associate the transaction with a result viewer, e.g., Graph
|
||||||
|
* @param rowIndex Index is used to associate latency for this transaction with a query row
|
||||||
|
*/
|
||||||
|
export const queryTransactionStartAction = actionCreatorFactory<QueryTransactionStartPayload>(
|
||||||
|
'explore/QUERY_TRANSACTION_START'
|
||||||
|
).create();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Complete a query transaction, mark the transaction as `done` and store query state in URL.
|
||||||
|
* If the transaction was started by a scanner, it keeps on scanning for more results.
|
||||||
|
* Side-effect: the query is stored in localStorage.
|
||||||
|
* @param exploreId Explore area
|
||||||
|
* @param transactionId ID
|
||||||
|
* @param result Response from `datasourceInstance.query()`
|
||||||
|
* @param latency Duration between request and response
|
||||||
|
* @param queries Queries from all query rows
|
||||||
|
* @param datasourceId Origin datasource instance, used to discard results if current datasource is different
|
||||||
|
*/
|
||||||
|
export const queryTransactionSuccessAction = actionCreatorFactory<QueryTransactionSuccessPayload>(
|
||||||
|
'explore/QUERY_TRANSACTION_SUCCESS'
|
||||||
|
).create();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove query row of the given index, as well as associated query results.
|
||||||
|
*/
|
||||||
|
export const removeQueryRowAction = actionCreatorFactory<RemoveQueryRowPayload>('explore/REMOVE_QUERY_ROW').create();
|
||||||
|
export const runQueriesAction = noPayloadActionCreatorFactory('explore/RUN_QUERIES').create();
|
||||||
|
export const runQueriesEmptyAction = actionCreatorFactory<RunQueriesEmptyPayload>('explore/RUN_QUERIES_EMPTY').create();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start a scan for more results using the given scanner.
|
||||||
|
* @param exploreId Explore area
|
||||||
|
* @param scanner Function that a) returns a new time range and b) triggers a query run for the new range
|
||||||
|
*/
|
||||||
|
export const scanStartAction = actionCreatorFactory<ScanStartPayload>('explore/SCAN_START').create();
|
||||||
|
export const scanRangeAction = actionCreatorFactory<ScanRangePayload>('explore/SCAN_RANGE').create();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop any scanning for more results.
|
||||||
|
*/
|
||||||
|
export const scanStopAction = actionCreatorFactory<ScanStopPayload>('explore/SCAN_STOP').create();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset queries to the given queries. Any modifications will be discarded.
|
||||||
|
* Use this action for clicks on query examples. Triggers a query run.
|
||||||
|
*/
|
||||||
|
export const setQueriesAction = actionCreatorFactory<SetQueriesPayload>('explore/SET_QUERIES').create();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close the split view and save URL state.
|
||||||
|
*/
|
||||||
|
export const splitCloseAction = noPayloadActionCreatorFactory('explore/SPLIT_CLOSE').create();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open the split view and copy the left state to be the right state.
|
||||||
|
* The right state is automatically initialized.
|
||||||
|
* The copy keeps all query modifications but wipes the query results.
|
||||||
|
*/
|
||||||
|
export const splitOpenAction = actionCreatorFactory<SplitOpenPayload>('explore/SPLIT_OPEN').create();
|
||||||
|
export const stateSaveAction = noPayloadActionCreatorFactory('explore/STATE_SAVE').create();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expand/collapse the table result viewer. When collapsed, table queries won't be run.
|
||||||
|
*/
|
||||||
|
export const toggleTableAction = actionCreatorFactory<ToggleTablePayload>('explore/TOGGLE_TABLE').create();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expand/collapse the graph result viewer. When collapsed, graph queries won't be run.
|
||||||
|
*/
|
||||||
|
export const toggleGraphAction = actionCreatorFactory<ToggleGraphPayload>('explore/TOGGLE_GRAPH').create();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expand/collapse the logs result viewer. When collapsed, log queries won't be run.
|
||||||
|
*/
|
||||||
|
export const toggleLogsAction = actionCreatorFactory<ToggleLogsPayload>('explore/TOGGLE_LOGS').create();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates datasource instance before datasouce loading has started
|
||||||
|
*/
|
||||||
|
export const updateDatasourceInstanceAction = actionCreatorFactory<UpdateDatasourceInstancePayload>(
|
||||||
|
'explore/UPDATE_DATASOURCE_INSTANCE'
|
||||||
|
).create();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets state for explore.
|
||||||
|
*/
|
||||||
|
export const resetExploreAction = noPayloadActionCreatorFactory('explore/RESET_EXPLORE').create();
|
||||||
|
export const queriesImportedAction = actionCreatorFactory<QueriesImportedPayload>('explore/QueriesImported').create();
|
||||||
|
|
||||||
|
export type HigherOrderAction =
|
||||||
| InitializeExploreSplitAction
|
| InitializeExploreSplitAction
|
||||||
| LoadDatasourceFailureAction
|
|
||||||
| LoadDatasourceMissingAction
|
|
||||||
| LoadDatasourcePendingAction
|
|
||||||
| LoadDatasourceSuccessAction
|
|
||||||
| ModifyQueriesAction
|
|
||||||
| QueryTransactionFailureAction
|
|
||||||
| QueryTransactionStartAction
|
|
||||||
| QueryTransactionSuccessAction
|
|
||||||
| RemoveQueryRowAction
|
|
||||||
| RunQueriesEmptyAction
|
|
||||||
| ScanRangeAction
|
|
||||||
| ScanStartAction
|
|
||||||
| ScanStopAction
|
|
||||||
| SetQueriesAction
|
|
||||||
| SplitCloseAction
|
| SplitCloseAction
|
||||||
| SplitOpenAction
|
| SplitOpenAction
|
||||||
| ToggleGraphAction
|
|
||||||
| ToggleLogsAction
|
|
||||||
| ToggleTableAction
|
|
||||||
| UpdateDatasourceInstanceAction
|
|
||||||
| ResetExploreAction
|
| ResetExploreAction
|
||||||
| QueriesImported;
|
| ActionOf<any>;
|
||||||
|
|
||||||
|
export type Action =
|
||||||
|
| ActionOf<AddQueryRowPayload>
|
||||||
|
| ActionOf<ChangeQueryPayload>
|
||||||
|
| ActionOf<ChangeSizePayload>
|
||||||
|
| ActionOf<ChangeTimePayload>
|
||||||
|
| ActionOf<ClearQueriesPayload>
|
||||||
|
| ActionOf<HighlightLogsExpressionPayload>
|
||||||
|
| ActionOf<InitializeExplorePayload>
|
||||||
|
| ActionOf<LoadDatasourceFailurePayload>
|
||||||
|
| ActionOf<LoadDatasourceMissingPayload>
|
||||||
|
| ActionOf<LoadDatasourcePendingPayload>
|
||||||
|
| ActionOf<LoadDatasourceSuccessPayload>
|
||||||
|
| ActionOf<ModifyQueriesPayload>
|
||||||
|
| ActionOf<QueryTransactionFailurePayload>
|
||||||
|
| ActionOf<QueryTransactionStartPayload>
|
||||||
|
| ActionOf<QueryTransactionSuccessPayload>
|
||||||
|
| ActionOf<RemoveQueryRowPayload>
|
||||||
|
| ActionOf<RunQueriesEmptyPayload>
|
||||||
|
| ActionOf<ScanStartPayload>
|
||||||
|
| ActionOf<ScanRangePayload>
|
||||||
|
| ActionOf<SetQueriesPayload>
|
||||||
|
| ActionOf<SplitOpenPayload>
|
||||||
|
| ActionOf<ToggleTablePayload>
|
||||||
|
| ActionOf<ToggleGraphPayload>
|
||||||
|
| ActionOf<ToggleLogsPayload>
|
||||||
|
| ActionOf<UpdateDatasourceInstancePayload>
|
||||||
|
| ActionOf<QueriesImportedPayload>;
|
||||||
|
@ -30,40 +30,54 @@ import {
|
|||||||
DataQuery,
|
DataQuery,
|
||||||
DataSourceSelectItem,
|
DataSourceSelectItem,
|
||||||
QueryHint,
|
QueryHint,
|
||||||
|
QueryFixAction,
|
||||||
} from '@grafana/ui/src/types';
|
} from '@grafana/ui/src/types';
|
||||||
|
import { ExploreId, ExploreUrlState, RangeScanner, ResultType, QueryOptions, ExploreUIState } from 'app/types/explore';
|
||||||
import {
|
import {
|
||||||
ExploreId,
|
Action,
|
||||||
ExploreUrlState,
|
updateDatasourceInstanceAction,
|
||||||
RangeScanner,
|
changeQueryAction,
|
||||||
ResultType,
|
changeSizeAction,
|
||||||
QueryOptions,
|
ChangeSizePayload,
|
||||||
QueryTransaction,
|
changeTimeAction,
|
||||||
} from 'app/types/explore';
|
scanStopAction,
|
||||||
|
clearQueriesAction,
|
||||||
import {
|
initializeExploreAction,
|
||||||
Action as ThunkableAction,
|
loadDatasourceMissingAction,
|
||||||
ActionTypes,
|
loadDatasourceFailureAction,
|
||||||
AddQueryRowAction,
|
loadDatasourcePendingAction,
|
||||||
ChangeSizeAction,
|
queriesImportedAction,
|
||||||
HighlightLogsExpressionAction,
|
LoadDatasourceSuccessPayload,
|
||||||
LoadDatasourceFailureAction,
|
loadDatasourceSuccessAction,
|
||||||
LoadDatasourceMissingAction,
|
modifyQueriesAction,
|
||||||
LoadDatasourcePendingAction,
|
queryTransactionFailureAction,
|
||||||
LoadDatasourceSuccessAction,
|
queryTransactionStartAction,
|
||||||
QueryTransactionStartAction,
|
queryTransactionSuccessAction,
|
||||||
ScanStopAction,
|
scanRangeAction,
|
||||||
UpdateDatasourceInstanceAction,
|
runQueriesEmptyAction,
|
||||||
QueriesImported,
|
scanStartAction,
|
||||||
|
setQueriesAction,
|
||||||
|
splitCloseAction,
|
||||||
|
splitOpenAction,
|
||||||
|
addQueryRowAction,
|
||||||
|
AddQueryRowPayload,
|
||||||
|
toggleGraphAction,
|
||||||
|
toggleLogsAction,
|
||||||
|
toggleTableAction,
|
||||||
|
ToggleGraphPayload,
|
||||||
|
ToggleLogsPayload,
|
||||||
|
ToggleTablePayload,
|
||||||
} from './actionTypes';
|
} from './actionTypes';
|
||||||
|
import { ActionOf, ActionCreator } from 'app/core/redux/actionCreatorFactory';
|
||||||
|
|
||||||
type ThunkResult<R> = ThunkAction<R, StoreState, undefined, ThunkableAction>;
|
type ThunkResult<R> = ThunkAction<R, StoreState, undefined, Action>;
|
||||||
|
|
||||||
/**
|
// /**
|
||||||
* Adds a query row after the row with the given index.
|
// * Adds a query row after the row with the given index.
|
||||||
*/
|
// */
|
||||||
export function addQueryRow(exploreId: ExploreId, index: number): AddQueryRowAction {
|
export function addQueryRow(exploreId: ExploreId, index: number): ActionOf<AddQueryRowPayload> {
|
||||||
const query = generateEmptyQuery(index + 1);
|
const query = generateEmptyQuery(index + 1);
|
||||||
return { type: ActionTypes.AddQueryRow, payload: { exploreId, index, query } };
|
return addQueryRowAction({ exploreId, index, query });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -73,12 +87,20 @@ export function changeDatasource(exploreId: ExploreId, datasource: string): Thun
|
|||||||
return async (dispatch, getState) => {
|
return async (dispatch, getState) => {
|
||||||
const newDataSourceInstance = await getDatasourceSrv().get(datasource);
|
const newDataSourceInstance = await getDatasourceSrv().get(datasource);
|
||||||
const currentDataSourceInstance = getState().explore[exploreId].datasourceInstance;
|
const currentDataSourceInstance = getState().explore[exploreId].datasourceInstance;
|
||||||
const modifiedQueries = getState().explore[exploreId].modifiedQueries;
|
const queries = getState().explore[exploreId].queries;
|
||||||
|
|
||||||
await dispatch(importQueries(exploreId, modifiedQueries, currentDataSourceInstance, newDataSourceInstance));
|
await dispatch(importQueries(exploreId, queries, currentDataSourceInstance, newDataSourceInstance));
|
||||||
|
|
||||||
dispatch(updateDatasourceInstance(exploreId, newDataSourceInstance));
|
dispatch(updateDatasourceInstanceAction({ exploreId, datasourceInstance: newDataSourceInstance }));
|
||||||
dispatch(loadDatasource(exploreId, newDataSourceInstance));
|
|
||||||
|
try {
|
||||||
|
await dispatch(loadDatasource(exploreId, newDataSourceInstance));
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch(runQueries(exploreId));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,7 +120,7 @@ export function changeQuery(
|
|||||||
query = { ...generateEmptyQuery(index) };
|
query = { ...generateEmptyQuery(index) };
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch({ type: ActionTypes.ChangeQuery, payload: { exploreId, query, index, override } });
|
dispatch(changeQueryAction({ exploreId, query, index, override }));
|
||||||
if (override) {
|
if (override) {
|
||||||
dispatch(runQueries(exploreId));
|
dispatch(runQueries(exploreId));
|
||||||
}
|
}
|
||||||
@ -112,8 +134,8 @@ export function changeQuery(
|
|||||||
export function changeSize(
|
export function changeSize(
|
||||||
exploreId: ExploreId,
|
exploreId: ExploreId,
|
||||||
{ height, width }: { height: number; width: number }
|
{ height, width }: { height: number; width: number }
|
||||||
): ChangeSizeAction {
|
): ActionOf<ChangeSizePayload> {
|
||||||
return { type: ActionTypes.ChangeSize, payload: { exploreId, height, width } };
|
return changeSizeAction({ exploreId, height, width });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -121,7 +143,7 @@ export function changeSize(
|
|||||||
*/
|
*/
|
||||||
export function changeTime(exploreId: ExploreId, range: TimeRange): ThunkResult<void> {
|
export function changeTime(exploreId: ExploreId, range: TimeRange): ThunkResult<void> {
|
||||||
return dispatch => {
|
return dispatch => {
|
||||||
dispatch({ type: ActionTypes.ChangeTime, payload: { exploreId, range } });
|
dispatch(changeTimeAction({ exploreId, range }));
|
||||||
dispatch(runQueries(exploreId));
|
dispatch(runQueries(exploreId));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -131,19 +153,12 @@ export function changeTime(exploreId: ExploreId, range: TimeRange): ThunkResult<
|
|||||||
*/
|
*/
|
||||||
export function clearQueries(exploreId: ExploreId): ThunkResult<void> {
|
export function clearQueries(exploreId: ExploreId): ThunkResult<void> {
|
||||||
return dispatch => {
|
return dispatch => {
|
||||||
dispatch(scanStop(exploreId));
|
dispatch(scanStopAction({ exploreId }));
|
||||||
dispatch({ type: ActionTypes.ClearQueries, payload: { exploreId } });
|
dispatch(clearQueriesAction({ exploreId }));
|
||||||
dispatch(stateSave());
|
dispatch(stateSave());
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Highlight expressions in the log results
|
|
||||||
*/
|
|
||||||
export function highlightLogsExpression(exploreId: ExploreId, expressions: string[]): HighlightLogsExpressionAction {
|
|
||||||
return { type: ActionTypes.HighlightLogsExpression, payload: { exploreId, expressions } };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize Explore state with state from the URL and the React component.
|
* Initialize Explore state with state from the URL and the React component.
|
||||||
* Call this only on components for with the Explore state has not been initialized.
|
* Call this only on components for with the Explore state has not been initialized.
|
||||||
@ -154,7 +169,8 @@ export function initializeExplore(
|
|||||||
queries: DataQuery[],
|
queries: DataQuery[],
|
||||||
range: RawTimeRange,
|
range: RawTimeRange,
|
||||||
containerWidth: number,
|
containerWidth: number,
|
||||||
eventBridge: Emitter
|
eventBridge: Emitter,
|
||||||
|
ui: ExploreUIState
|
||||||
): ThunkResult<void> {
|
): ThunkResult<void> {
|
||||||
return async dispatch => {
|
return async dispatch => {
|
||||||
const exploreDatasources: DataSourceSelectItem[] = getDatasourceSrv()
|
const exploreDatasources: DataSourceSelectItem[] = getDatasourceSrv()
|
||||||
@ -165,18 +181,17 @@ export function initializeExplore(
|
|||||||
meta: ds.meta,
|
meta: ds.meta,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
dispatch({
|
dispatch(
|
||||||
type: ActionTypes.InitializeExplore,
|
initializeExploreAction({
|
||||||
payload: {
|
|
||||||
exploreId,
|
exploreId,
|
||||||
containerWidth,
|
containerWidth,
|
||||||
datasourceName,
|
|
||||||
eventBridge,
|
eventBridge,
|
||||||
exploreDatasources,
|
exploreDatasources,
|
||||||
queries,
|
queries,
|
||||||
range,
|
range,
|
||||||
},
|
ui,
|
||||||
});
|
})
|
||||||
|
);
|
||||||
|
|
||||||
if (exploreDatasources.length >= 1) {
|
if (exploreDatasources.length >= 1) {
|
||||||
let instance;
|
let instance;
|
||||||
@ -193,75 +208,27 @@ export function initializeExplore(
|
|||||||
instance = await getDatasourceSrv().get();
|
instance = await getDatasourceSrv().get();
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(updateDatasourceInstance(exploreId, instance));
|
dispatch(updateDatasourceInstanceAction({ exploreId, datasourceInstance: instance }));
|
||||||
dispatch(loadDatasource(exploreId, instance));
|
|
||||||
|
try {
|
||||||
|
await dispatch(loadDatasource(exploreId, instance));
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dispatch(runQueries(exploreId, true));
|
||||||
} else {
|
} else {
|
||||||
dispatch(loadDatasourceMissing(exploreId));
|
dispatch(loadDatasourceMissingAction({ exploreId }));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize the wrapper split state
|
|
||||||
*/
|
|
||||||
export function initializeExploreSplit() {
|
|
||||||
return async dispatch => {
|
|
||||||
dispatch({ type: ActionTypes.InitializeExploreSplit });
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Display an error that happened during the selection of a datasource
|
|
||||||
*/
|
|
||||||
export const loadDatasourceFailure = (exploreId: ExploreId, error: string): LoadDatasourceFailureAction => ({
|
|
||||||
type: ActionTypes.LoadDatasourceFailure,
|
|
||||||
payload: {
|
|
||||||
exploreId,
|
|
||||||
error,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Display an error when no datasources have been configured
|
|
||||||
*/
|
|
||||||
export const loadDatasourceMissing = (exploreId: ExploreId): LoadDatasourceMissingAction => ({
|
|
||||||
type: ActionTypes.LoadDatasourceMissing,
|
|
||||||
payload: { exploreId },
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Start the async process of loading a datasource to display a loading indicator
|
|
||||||
*/
|
|
||||||
export const loadDatasourcePending = (
|
|
||||||
exploreId: ExploreId,
|
|
||||||
requestedDatasourceName: string
|
|
||||||
): LoadDatasourcePendingAction => ({
|
|
||||||
type: ActionTypes.LoadDatasourcePending,
|
|
||||||
payload: {
|
|
||||||
exploreId,
|
|
||||||
requestedDatasourceName,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const queriesImported = (exploreId: ExploreId, queries: DataQuery[]): QueriesImported => {
|
|
||||||
return {
|
|
||||||
type: ActionTypes.QueriesImported,
|
|
||||||
payload: {
|
|
||||||
exploreId,
|
|
||||||
queries,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Datasource loading was successfully completed. The instance is stored in the state as well in case we need to
|
* Datasource loading was successfully completed. The instance is stored in the state as well in case we need to
|
||||||
* run datasource-specific code. Existing queries are imported to the new datasource if an importer exists,
|
* run datasource-specific code. Existing queries are imported to the new datasource if an importer exists,
|
||||||
* e.g., Prometheus -> Loki queries.
|
* e.g., Prometheus -> Loki queries.
|
||||||
*/
|
*/
|
||||||
export const loadDatasourceSuccess = (
|
export const loadDatasourceSuccess = (exploreId: ExploreId, instance: any): ActionOf<LoadDatasourceSuccessPayload> => {
|
||||||
exploreId: ExploreId,
|
|
||||||
instance: any,
|
|
||||||
): LoadDatasourceSuccessAction => {
|
|
||||||
// Capabilities
|
// Capabilities
|
||||||
const supportsGraph = instance.meta.metrics;
|
const supportsGraph = instance.meta.metrics;
|
||||||
const supportsLogs = instance.meta.logs;
|
const supportsLogs = instance.meta.logs;
|
||||||
@ -274,37 +241,18 @@ export const loadDatasourceSuccess = (
|
|||||||
// Save last-used datasource
|
// Save last-used datasource
|
||||||
store.set(LAST_USED_DATASOURCE_KEY, instance.name);
|
store.set(LAST_USED_DATASOURCE_KEY, instance.name);
|
||||||
|
|
||||||
return {
|
return loadDatasourceSuccessAction({
|
||||||
type: ActionTypes.LoadDatasourceSuccess,
|
exploreId,
|
||||||
payload: {
|
StartPage,
|
||||||
exploreId,
|
datasourceInstance: instance,
|
||||||
StartPage,
|
history,
|
||||||
datasourceInstance: instance,
|
showingStartPage: Boolean(StartPage),
|
||||||
history,
|
supportsGraph,
|
||||||
showingStartPage: Boolean(StartPage),
|
supportsLogs,
|
||||||
supportsGraph,
|
supportsTable,
|
||||||
supportsLogs,
|
});
|
||||||
supportsTable,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates datasource instance before datasouce loading has started
|
|
||||||
*/
|
|
||||||
export function updateDatasourceInstance(
|
|
||||||
exploreId: ExploreId,
|
|
||||||
instance: DataSourceApi
|
|
||||||
): UpdateDatasourceInstanceAction {
|
|
||||||
return {
|
|
||||||
type: ActionTypes.UpdateDatasourceInstance,
|
|
||||||
payload: {
|
|
||||||
exploreId,
|
|
||||||
datasourceInstance: instance,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function importQueries(
|
export function importQueries(
|
||||||
exploreId: ExploreId,
|
exploreId: ExploreId,
|
||||||
queries: DataQuery[],
|
queries: DataQuery[],
|
||||||
@ -326,11 +274,11 @@ export function importQueries(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const nextQueries = importedQueries.map((q, i) => ({
|
const nextQueries = importedQueries.map((q, i) => ({
|
||||||
...importedQueries[i],
|
...q,
|
||||||
...generateEmptyQuery(i),
|
...generateEmptyQuery(i),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
dispatch(queriesImported(exploreId, nextQueries));
|
dispatch(queriesImportedAction({ exploreId, queries: nextQueries }));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -342,9 +290,9 @@ export function loadDatasource(exploreId: ExploreId, instance: DataSourceApi): T
|
|||||||
const datasourceName = instance.name;
|
const datasourceName = instance.name;
|
||||||
|
|
||||||
// Keep ID to track selection
|
// Keep ID to track selection
|
||||||
dispatch(loadDatasourcePending(exploreId, datasourceName));
|
dispatch(loadDatasourcePendingAction({ exploreId, requestedDatasourceName: datasourceName }));
|
||||||
|
|
||||||
let datasourceError = null;
|
let datasourceError = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const testResult = await instance.testDatasource();
|
const testResult = await instance.testDatasource();
|
||||||
datasourceError = testResult.status === 'success' ? null : testResult.message;
|
datasourceError = testResult.status === 'success' ? null : testResult.message;
|
||||||
@ -353,8 +301,8 @@ export function loadDatasource(exploreId: ExploreId, instance: DataSourceApi): T
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (datasourceError) {
|
if (datasourceError) {
|
||||||
dispatch(loadDatasourceFailure(exploreId, datasourceError));
|
dispatch(loadDatasourceFailureAction({ exploreId, error: datasourceError }));
|
||||||
return;
|
return Promise.reject(`${datasourceName} loading failed`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (datasourceName !== getState().explore[exploreId].requestedDatasourceName) {
|
if (datasourceName !== getState().explore[exploreId].requestedDatasourceName) {
|
||||||
@ -372,7 +320,7 @@ export function loadDatasource(exploreId: ExploreId, instance: DataSourceApi): T
|
|||||||
}
|
}
|
||||||
|
|
||||||
dispatch(loadDatasourceSuccess(exploreId, instance));
|
dispatch(loadDatasourceSuccess(exploreId, instance));
|
||||||
dispatch(runQueries(exploreId));
|
return Promise.resolve();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -385,12 +333,12 @@ export function loadDatasource(exploreId: ExploreId, instance: DataSourceApi): T
|
|||||||
*/
|
*/
|
||||||
export function modifyQueries(
|
export function modifyQueries(
|
||||||
exploreId: ExploreId,
|
exploreId: ExploreId,
|
||||||
modification: any,
|
modification: QueryFixAction,
|
||||||
index: number,
|
index: number,
|
||||||
modifier: any
|
modifier: any
|
||||||
): ThunkResult<void> {
|
): ThunkResult<void> {
|
||||||
return dispatch => {
|
return dispatch => {
|
||||||
dispatch({ type: ActionTypes.ModifyQueries, payload: { exploreId, modification, index, modifier } });
|
dispatch(modifyQueriesAction({ exploreId, modification, index, modifier }));
|
||||||
if (!modification.preventSubmit) {
|
if (!modification.preventSubmit) {
|
||||||
dispatch(runQueries(exploreId));
|
dispatch(runQueries(exploreId));
|
||||||
}
|
}
|
||||||
@ -455,29 +403,10 @@ export function queryTransactionFailure(
|
|||||||
return qt;
|
return qt;
|
||||||
});
|
});
|
||||||
|
|
||||||
dispatch({
|
dispatch(queryTransactionFailureAction({ exploreId, queryTransactions: nextQueryTransactions }));
|
||||||
type: ActionTypes.QueryTransactionFailure,
|
|
||||||
payload: { exploreId, queryTransactions: nextQueryTransactions },
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Start a query transaction for the given result type.
|
|
||||||
* @param exploreId Explore area
|
|
||||||
* @param transaction Query options and `done` status.
|
|
||||||
* @param resultType Associate the transaction with a result viewer, e.g., Graph
|
|
||||||
* @param rowIndex Index is used to associate latency for this transaction with a query row
|
|
||||||
*/
|
|
||||||
export function queryTransactionStart(
|
|
||||||
exploreId: ExploreId,
|
|
||||||
transaction: QueryTransaction,
|
|
||||||
resultType: ResultType,
|
|
||||||
rowIndex: number
|
|
||||||
): QueryTransactionStartAction {
|
|
||||||
return { type: ActionTypes.QueryTransactionStart, payload: { exploreId, resultType, rowIndex, transaction } };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Complete a query transaction, mark the transaction as `done` and store query state in URL.
|
* Complete a query transaction, mark the transaction as `done` and store query state in URL.
|
||||||
* If the transaction was started by a scanner, it keeps on scanning for more results.
|
* If the transaction was started by a scanner, it keeps on scanning for more results.
|
||||||
@ -534,14 +463,13 @@ export function queryTransactionSuccess(
|
|||||||
// Side-effect: Saving history in localstorage
|
// Side-effect: Saving history in localstorage
|
||||||
const nextHistory = updateHistory(history, datasourceId, queries);
|
const nextHistory = updateHistory(history, datasourceId, queries);
|
||||||
|
|
||||||
dispatch({
|
dispatch(
|
||||||
type: ActionTypes.QueryTransactionSuccess,
|
queryTransactionSuccessAction({
|
||||||
payload: {
|
|
||||||
exploreId,
|
exploreId,
|
||||||
history: nextHistory,
|
history: nextHistory,
|
||||||
queryTransactions: nextQueryTransactions,
|
queryTransactions: nextQueryTransactions,
|
||||||
},
|
})
|
||||||
});
|
);
|
||||||
|
|
||||||
// Keep scanning for results if this was the last scanning transaction
|
// Keep scanning for results if this was the last scanning transaction
|
||||||
if (scanning) {
|
if (scanning) {
|
||||||
@ -549,34 +477,24 @@ export function queryTransactionSuccess(
|
|||||||
const other = nextQueryTransactions.find(qt => qt.scanning && !qt.done);
|
const other = nextQueryTransactions.find(qt => qt.scanning && !qt.done);
|
||||||
if (!other) {
|
if (!other) {
|
||||||
const range = scanner();
|
const range = scanner();
|
||||||
dispatch({ type: ActionTypes.ScanRange, payload: { exploreId, range } });
|
dispatch(scanRangeAction({ exploreId, range }));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// We can stop scanning if we have a result
|
// We can stop scanning if we have a result
|
||||||
dispatch(scanStop(exploreId));
|
dispatch(scanStopAction({ exploreId }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove query row of the given index, as well as associated query results.
|
|
||||||
*/
|
|
||||||
export function removeQueryRow(exploreId: ExploreId, index: number): ThunkResult<void> {
|
|
||||||
return dispatch => {
|
|
||||||
dispatch({ type: ActionTypes.RemoveQueryRow, payload: { exploreId, index } });
|
|
||||||
dispatch(runQueries(exploreId));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main action to run queries and dispatches sub-actions based on which result viewers are active
|
* Main action to run queries and dispatches sub-actions based on which result viewers are active
|
||||||
*/
|
*/
|
||||||
export function runQueries(exploreId: ExploreId) {
|
export function runQueries(exploreId: ExploreId, ignoreUIState = false) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
const {
|
const {
|
||||||
datasourceInstance,
|
datasourceInstance,
|
||||||
modifiedQueries,
|
queries,
|
||||||
showingLogs,
|
showingLogs,
|
||||||
showingGraph,
|
showingGraph,
|
||||||
showingTable,
|
showingTable,
|
||||||
@ -585,8 +503,8 @@ export function runQueries(exploreId: ExploreId) {
|
|||||||
supportsTable,
|
supportsTable,
|
||||||
} = getState().explore[exploreId];
|
} = getState().explore[exploreId];
|
||||||
|
|
||||||
if (!hasNonEmptyQuery(modifiedQueries)) {
|
if (!hasNonEmptyQuery(queries)) {
|
||||||
dispatch({ type: ActionTypes.RunQueriesEmpty, payload: { exploreId } });
|
dispatch(runQueriesEmptyAction({ exploreId }));
|
||||||
dispatch(stateSave()); // Remember to saves to state and update location
|
dispatch(stateSave()); // Remember to saves to state and update location
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -596,7 +514,7 @@ export function runQueries(exploreId: ExploreId) {
|
|||||||
const interval = datasourceInstance.interval;
|
const interval = datasourceInstance.interval;
|
||||||
|
|
||||||
// Keep table queries first since they need to return quickly
|
// Keep table queries first since they need to return quickly
|
||||||
if (showingTable && supportsTable) {
|
if ((ignoreUIState || showingTable) && supportsTable) {
|
||||||
dispatch(
|
dispatch(
|
||||||
runQueriesForType(
|
runQueriesForType(
|
||||||
exploreId,
|
exploreId,
|
||||||
@ -611,7 +529,7 @@ export function runQueries(exploreId: ExploreId) {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (showingGraph && supportsGraph) {
|
if ((ignoreUIState || showingGraph) && supportsGraph) {
|
||||||
dispatch(
|
dispatch(
|
||||||
runQueriesForType(
|
runQueriesForType(
|
||||||
exploreId,
|
exploreId,
|
||||||
@ -625,9 +543,10 @@ export function runQueries(exploreId: ExploreId) {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (showingLogs && supportsLogs) {
|
if ((ignoreUIState || showingLogs) && supportsLogs) {
|
||||||
dispatch(runQueriesForType(exploreId, 'Logs', { interval, format: 'logs' }));
|
dispatch(runQueriesForType(exploreId, 'Logs', { interval, format: 'logs' }));
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(stateSave());
|
dispatch(stateSave());
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -646,14 +565,7 @@ function runQueriesForType(
|
|||||||
resultGetter?: any
|
resultGetter?: any
|
||||||
) {
|
) {
|
||||||
return async (dispatch, getState) => {
|
return async (dispatch, getState) => {
|
||||||
const {
|
const { datasourceInstance, eventBridge, queries, queryIntervals, range, scanning } = getState().explore[exploreId];
|
||||||
datasourceInstance,
|
|
||||||
eventBridge,
|
|
||||||
modifiedQueries: queries,
|
|
||||||
queryIntervals,
|
|
||||||
range,
|
|
||||||
scanning,
|
|
||||||
} = getState().explore[exploreId];
|
|
||||||
const datasourceId = datasourceInstance.meta.id;
|
const datasourceId = datasourceInstance.meta.id;
|
||||||
|
|
||||||
// Run all queries concurrently
|
// Run all queries concurrently
|
||||||
@ -667,7 +579,7 @@ function runQueriesForType(
|
|||||||
queryIntervals,
|
queryIntervals,
|
||||||
scanning
|
scanning
|
||||||
);
|
);
|
||||||
dispatch(queryTransactionStart(exploreId, transaction, resultType, rowIndex));
|
dispatch(queryTransactionStartAction({ exploreId, resultType, rowIndex, transaction }));
|
||||||
try {
|
try {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const res = await datasourceInstance.query(transaction.options);
|
const res = await datasourceInstance.query(transaction.options);
|
||||||
@ -691,21 +603,14 @@ function runQueriesForType(
|
|||||||
export function scanStart(exploreId: ExploreId, scanner: RangeScanner): ThunkResult<void> {
|
export function scanStart(exploreId: ExploreId, scanner: RangeScanner): ThunkResult<void> {
|
||||||
return dispatch => {
|
return dispatch => {
|
||||||
// Register the scanner
|
// Register the scanner
|
||||||
dispatch({ type: ActionTypes.ScanStart, payload: { exploreId, scanner } });
|
dispatch(scanStartAction({ exploreId, scanner }));
|
||||||
// Scanning must trigger query run, and return the new range
|
// Scanning must trigger query run, and return the new range
|
||||||
const range = scanner();
|
const range = scanner();
|
||||||
// Set the new range to be displayed
|
// Set the new range to be displayed
|
||||||
dispatch({ type: ActionTypes.ScanRange, payload: { exploreId, range } });
|
dispatch(scanRangeAction({ exploreId, range }));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Stop any scanning for more results.
|
|
||||||
*/
|
|
||||||
export function scanStop(exploreId: ExploreId): ScanStopAction {
|
|
||||||
return { type: ActionTypes.ScanStop, payload: { exploreId } };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset queries to the given queries. Any modifications will be discarded.
|
* Reset queries to the given queries. Any modifications will be discarded.
|
||||||
* Use this action for clicks on query examples. Triggers a query run.
|
* Use this action for clicks on query examples. Triggers a query run.
|
||||||
@ -714,13 +619,7 @@ export function setQueries(exploreId: ExploreId, rawQueries: DataQuery[]): Thunk
|
|||||||
return dispatch => {
|
return dispatch => {
|
||||||
// Inject react keys into query objects
|
// Inject react keys into query objects
|
||||||
const queries = rawQueries.map(q => ({ ...q, ...generateEmptyQuery() }));
|
const queries = rawQueries.map(q => ({ ...q, ...generateEmptyQuery() }));
|
||||||
dispatch({
|
dispatch(setQueriesAction({ exploreId, queries }));
|
||||||
type: ActionTypes.SetQueries,
|
|
||||||
payload: {
|
|
||||||
exploreId,
|
|
||||||
queries,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
dispatch(runQueries(exploreId));
|
dispatch(runQueries(exploreId));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -730,7 +629,7 @@ export function setQueries(exploreId: ExploreId, rawQueries: DataQuery[]): Thunk
|
|||||||
*/
|
*/
|
||||||
export function splitClose(): ThunkResult<void> {
|
export function splitClose(): ThunkResult<void> {
|
||||||
return dispatch => {
|
return dispatch => {
|
||||||
dispatch({ type: ActionTypes.SplitClose });
|
dispatch(splitCloseAction());
|
||||||
dispatch(stateSave());
|
dispatch(stateSave());
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -747,9 +646,9 @@ export function splitOpen(): ThunkResult<void> {
|
|||||||
const itemState = {
|
const itemState = {
|
||||||
...leftState,
|
...leftState,
|
||||||
queryTransactions: [],
|
queryTransactions: [],
|
||||||
initialQueries: leftState.modifiedQueries.slice(),
|
queries: leftState.queries.slice(),
|
||||||
};
|
};
|
||||||
dispatch({ type: ActionTypes.SplitOpen, payload: { itemState } });
|
dispatch(splitOpenAction({ itemState }));
|
||||||
dispatch(stateSave());
|
dispatch(stateSave());
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -764,63 +663,74 @@ export function stateSave() {
|
|||||||
const urlStates: { [index: string]: string } = {};
|
const urlStates: { [index: string]: string } = {};
|
||||||
const leftUrlState: ExploreUrlState = {
|
const leftUrlState: ExploreUrlState = {
|
||||||
datasource: left.datasourceInstance.name,
|
datasource: left.datasourceInstance.name,
|
||||||
queries: left.modifiedQueries.map(clearQueryKeys),
|
queries: left.queries.map(clearQueryKeys),
|
||||||
range: left.range,
|
range: left.range,
|
||||||
|
ui: {
|
||||||
|
showingGraph: left.showingGraph,
|
||||||
|
showingLogs: left.showingLogs,
|
||||||
|
showingTable: left.showingTable,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
urlStates.left = serializeStateToUrlParam(leftUrlState, true);
|
urlStates.left = serializeStateToUrlParam(leftUrlState, true);
|
||||||
if (split) {
|
if (split) {
|
||||||
const rightUrlState: ExploreUrlState = {
|
const rightUrlState: ExploreUrlState = {
|
||||||
datasource: right.datasourceInstance.name,
|
datasource: right.datasourceInstance.name,
|
||||||
queries: right.modifiedQueries.map(clearQueryKeys),
|
queries: right.queries.map(clearQueryKeys),
|
||||||
range: right.range,
|
range: right.range,
|
||||||
|
ui: { showingGraph: right.showingGraph, showingLogs: right.showingLogs, showingTable: right.showingTable },
|
||||||
};
|
};
|
||||||
|
|
||||||
urlStates.right = serializeStateToUrlParam(rightUrlState, true);
|
urlStates.right = serializeStateToUrlParam(rightUrlState, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(updateLocation({ query: urlStates }));
|
dispatch(updateLocation({ query: urlStates }));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Expand/collapse the graph result viewer. When collapsed, graph queries won't be run.
|
* Creates action to collapse graph/logs/table panel. When panel is collapsed,
|
||||||
|
* queries won't be run
|
||||||
*/
|
*/
|
||||||
export function toggleGraph(exploreId: ExploreId): ThunkResult<void> {
|
const togglePanelActionCreator = (
|
||||||
|
actionCreator:
|
||||||
|
| ActionCreator<ToggleGraphPayload>
|
||||||
|
| ActionCreator<ToggleLogsPayload>
|
||||||
|
| ActionCreator<ToggleTablePayload>
|
||||||
|
) => (exploreId: ExploreId) => {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
dispatch({ type: ActionTypes.ToggleGraph, payload: { exploreId } });
|
let shouldRunQueries;
|
||||||
if (getState().explore[exploreId].showingGraph) {
|
dispatch(actionCreator({ exploreId }));
|
||||||
|
dispatch(stateSave());
|
||||||
|
|
||||||
|
switch (actionCreator.type) {
|
||||||
|
case toggleGraphAction.type:
|
||||||
|
shouldRunQueries = getState().explore[exploreId].showingGraph;
|
||||||
|
break;
|
||||||
|
case toggleLogsAction.type:
|
||||||
|
shouldRunQueries = getState().explore[exploreId].showingLogs;
|
||||||
|
break;
|
||||||
|
case toggleTableAction.type:
|
||||||
|
shouldRunQueries = getState().explore[exploreId].showingTable;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldRunQueries) {
|
||||||
dispatch(runQueries(exploreId));
|
dispatch(runQueries(exploreId));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expand/collapse the graph result viewer. When collapsed, graph queries won't be run.
|
||||||
|
*/
|
||||||
|
export const toggleGraph = togglePanelActionCreator(toggleGraphAction);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Expand/collapse the logs result viewer. When collapsed, log queries won't be run.
|
* Expand/collapse the logs result viewer. When collapsed, log queries won't be run.
|
||||||
*/
|
*/
|
||||||
export function toggleLogs(exploreId: ExploreId): ThunkResult<void> {
|
export const toggleLogs = togglePanelActionCreator(toggleLogsAction);
|
||||||
return (dispatch, getState) => {
|
|
||||||
dispatch({ type: ActionTypes.ToggleLogs, payload: { exploreId } });
|
|
||||||
if (getState().explore[exploreId].showingLogs) {
|
|
||||||
dispatch(runQueries(exploreId));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Expand/collapse the table result viewer. When collapsed, table queries won't be run.
|
* Expand/collapse the table result viewer. When collapsed, table queries won't be run.
|
||||||
*/
|
*/
|
||||||
export function toggleTable(exploreId: ExploreId): ThunkResult<void> {
|
export const toggleTable = togglePanelActionCreator(toggleTableAction);
|
||||||
return (dispatch, getState) => {
|
|
||||||
dispatch({ type: ActionTypes.ToggleTable, payload: { exploreId } });
|
|
||||||
if (getState().explore[exploreId].showingTable) {
|
|
||||||
dispatch(runQueries(exploreId));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resets state for explore.
|
|
||||||
*/
|
|
||||||
export function resetExplore(): ThunkResult<void> {
|
|
||||||
return dispatch => {
|
|
||||||
dispatch({ type: ActionTypes.ResetExplore, payload: {} });
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
@ -1,42 +1,47 @@
|
|||||||
import { Action, ActionTypes } from './actionTypes';
|
|
||||||
import { itemReducer, makeExploreItemState } from './reducers';
|
import { itemReducer, makeExploreItemState } from './reducers';
|
||||||
import { ExploreId } from 'app/types/explore';
|
import { ExploreId, ExploreItemState } from 'app/types/explore';
|
||||||
|
import { reducerTester } from 'test/core/redux/reducerTester';
|
||||||
|
import { scanStartAction, scanStopAction } from './actionTypes';
|
||||||
|
import { Reducer } from 'redux';
|
||||||
|
import { ActionOf } from 'app/core/redux/actionCreatorFactory';
|
||||||
|
|
||||||
describe('Explore item reducer', () => {
|
describe('Explore item reducer', () => {
|
||||||
describe('scanning', () => {
|
describe('scanning', () => {
|
||||||
test('should start scanning', () => {
|
test('should start scanning', () => {
|
||||||
let state = makeExploreItemState();
|
const scanner = jest.fn();
|
||||||
const action: Action = {
|
const initalState = {
|
||||||
type: ActionTypes.ScanStart,
|
...makeExploreItemState(),
|
||||||
payload: {
|
scanning: false,
|
||||||
exploreId: ExploreId.left,
|
scanner: undefined,
|
||||||
scanner: jest.fn(),
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
state = itemReducer(state, action);
|
|
||||||
expect(state.scanning).toBeTruthy();
|
reducerTester()
|
||||||
expect(state.scanner).toBe(action.payload.scanner);
|
.givenReducer(itemReducer as Reducer<ExploreItemState, ActionOf<any>>, initalState)
|
||||||
|
.whenActionIsDispatched(scanStartAction({ exploreId: ExploreId.left, scanner }))
|
||||||
|
.thenStateShouldEqual({
|
||||||
|
...makeExploreItemState(),
|
||||||
|
scanning: true,
|
||||||
|
scanner,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
test('should stop scanning', () => {
|
test('should stop scanning', () => {
|
||||||
let state = makeExploreItemState();
|
const scanner = jest.fn();
|
||||||
const start: Action = {
|
const initalState = {
|
||||||
type: ActionTypes.ScanStart,
|
...makeExploreItemState(),
|
||||||
payload: {
|
scanning: true,
|
||||||
exploreId: ExploreId.left,
|
scanner,
|
||||||
scanner: jest.fn(),
|
scanRange: {},
|
||||||
},
|
|
||||||
};
|
};
|
||||||
state = itemReducer(state, start);
|
|
||||||
expect(state.scanning).toBeTruthy();
|
reducerTester()
|
||||||
const action: Action = {
|
.givenReducer(itemReducer as Reducer<ExploreItemState, ActionOf<any>>, initalState)
|
||||||
type: ActionTypes.ScanStop,
|
.whenActionIsDispatched(scanStopAction({ exploreId: ExploreId.left }))
|
||||||
payload: {
|
.thenStateShouldEqual({
|
||||||
exploreId: ExploreId.left,
|
...makeExploreItemState(),
|
||||||
},
|
scanning: false,
|
||||||
};
|
scanner: undefined,
|
||||||
state = itemReducer(state, action);
|
scanRange: undefined,
|
||||||
expect(state.scanning).toBeFalsy();
|
});
|
||||||
expect(state.scanner).toBeUndefined();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -3,11 +3,41 @@ import {
|
|||||||
generateEmptyQuery,
|
generateEmptyQuery,
|
||||||
getIntervals,
|
getIntervals,
|
||||||
ensureQueries,
|
ensureQueries,
|
||||||
|
getQueryKeys,
|
||||||
} from 'app/core/utils/explore';
|
} from 'app/core/utils/explore';
|
||||||
import { ExploreItemState, ExploreState, QueryTransaction } from 'app/types/explore';
|
import { ExploreItemState, ExploreState, QueryTransaction } from 'app/types/explore';
|
||||||
import { DataQuery } from '@grafana/ui/src/types';
|
import { DataQuery } from '@grafana/ui/src/types';
|
||||||
|
|
||||||
import { Action, ActionTypes } from './actionTypes';
|
import { HigherOrderAction, ActionTypes } from './actionTypes';
|
||||||
|
import { reducerFactory } from 'app/core/redux';
|
||||||
|
import {
|
||||||
|
addQueryRowAction,
|
||||||
|
changeQueryAction,
|
||||||
|
changeSizeAction,
|
||||||
|
changeTimeAction,
|
||||||
|
clearQueriesAction,
|
||||||
|
highlightLogsExpressionAction,
|
||||||
|
initializeExploreAction,
|
||||||
|
updateDatasourceInstanceAction,
|
||||||
|
loadDatasourceFailureAction,
|
||||||
|
loadDatasourceMissingAction,
|
||||||
|
loadDatasourcePendingAction,
|
||||||
|
loadDatasourceSuccessAction,
|
||||||
|
modifyQueriesAction,
|
||||||
|
queryTransactionFailureAction,
|
||||||
|
queryTransactionStartAction,
|
||||||
|
queryTransactionSuccessAction,
|
||||||
|
removeQueryRowAction,
|
||||||
|
runQueriesEmptyAction,
|
||||||
|
scanRangeAction,
|
||||||
|
scanStartAction,
|
||||||
|
scanStopAction,
|
||||||
|
setQueriesAction,
|
||||||
|
toggleGraphAction,
|
||||||
|
toggleLogsAction,
|
||||||
|
toggleTableAction,
|
||||||
|
queriesImportedAction,
|
||||||
|
} from './actionTypes';
|
||||||
|
|
||||||
export const DEFAULT_RANGE = {
|
export const DEFAULT_RANGE = {
|
||||||
from: 'now-6h',
|
from: 'now-6h',
|
||||||
@ -30,9 +60,8 @@ export const makeExploreItemState = (): ExploreItemState => ({
|
|||||||
datasourceMissing: false,
|
datasourceMissing: false,
|
||||||
exploreDatasources: [],
|
exploreDatasources: [],
|
||||||
history: [],
|
history: [],
|
||||||
initialQueries: [],
|
queries: [],
|
||||||
initialized: false,
|
initialized: false,
|
||||||
modifiedQueries: [],
|
|
||||||
queryTransactions: [],
|
queryTransactions: [],
|
||||||
queryIntervals: { interval: '15s', intervalMs: DEFAULT_GRAPH_INTERVAL },
|
queryIntervals: { interval: '15s', intervalMs: DEFAULT_GRAPH_INTERVAL },
|
||||||
range: DEFAULT_RANGE,
|
range: DEFAULT_RANGE,
|
||||||
@ -44,6 +73,7 @@ export const makeExploreItemState = (): ExploreItemState => ({
|
|||||||
supportsGraph: null,
|
supportsGraph: null,
|
||||||
supportsLogs: null,
|
supportsLogs: null,
|
||||||
supportsTable: null,
|
supportsTable: null,
|
||||||
|
queryKeys: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -58,21 +88,15 @@ export const initialExploreState: ExploreState = {
|
|||||||
/**
|
/**
|
||||||
* Reducer for an Explore area, to be used by the global Explore reducer.
|
* Reducer for an Explore area, to be used by the global Explore reducer.
|
||||||
*/
|
*/
|
||||||
export const itemReducer = (state, action: Action): ExploreItemState => {
|
export const itemReducer = reducerFactory<ExploreItemState>({} as ExploreItemState)
|
||||||
switch (action.type) {
|
.addMapper({
|
||||||
case ActionTypes.AddQueryRow: {
|
filter: addQueryRowAction,
|
||||||
const { initialQueries, modifiedQueries, queryTransactions } = state;
|
mapper: (state, action): ExploreItemState => {
|
||||||
|
const { queries, queryTransactions } = state;
|
||||||
const { index, query } = action.payload;
|
const { index, query } = action.payload;
|
||||||
|
|
||||||
// Add new query row after given index, keep modifications of existing rows
|
// Add to queries, which will cause a new row to be rendered
|
||||||
const nextModifiedQueries = [
|
const nextQueries = [...queries.slice(0, index + 1), { ...query }, ...queries.slice(index + 1)];
|
||||||
...modifiedQueries.slice(0, index + 1),
|
|
||||||
{ ...query },
|
|
||||||
...initialQueries.slice(index + 1),
|
|
||||||
];
|
|
||||||
|
|
||||||
// Add to initialQueries, which will cause a new row to be rendered
|
|
||||||
const nextQueries = [...initialQueries.slice(0, index + 1), { ...query }, ...initialQueries.slice(index + 1)];
|
|
||||||
|
|
||||||
// Ongoing transactions need to update their row indices
|
// Ongoing transactions need to update their row indices
|
||||||
const nextQueryTransactions = queryTransactions.map(qt => {
|
const nextQueryTransactions = queryTransactions.map(qt => {
|
||||||
@ -87,48 +111,38 @@ export const itemReducer = (state, action: Action): ExploreItemState => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
initialQueries: nextQueries,
|
queries: nextQueries,
|
||||||
logsHighlighterExpressions: undefined,
|
logsHighlighterExpressions: undefined,
|
||||||
modifiedQueries: nextModifiedQueries,
|
|
||||||
queryTransactions: nextQueryTransactions,
|
queryTransactions: nextQueryTransactions,
|
||||||
|
queryKeys: getQueryKeys(nextQueries, state.datasourceInstance),
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
|
})
|
||||||
case ActionTypes.ChangeQuery: {
|
.addMapper({
|
||||||
const { initialQueries, queryTransactions } = state;
|
filter: changeQueryAction,
|
||||||
let { modifiedQueries } = state;
|
mapper: (state, action): ExploreItemState => {
|
||||||
const { query, index, override } = action.payload;
|
const { queries, queryTransactions } = state;
|
||||||
|
const { query, index } = action.payload;
|
||||||
// Fast path: only change modifiedQueries to not trigger an update
|
|
||||||
modifiedQueries[index] = query;
|
|
||||||
if (!override) {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
modifiedQueries,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Override path: queries are completely reset
|
// Override path: queries are completely reset
|
||||||
const nextQuery: DataQuery = {
|
const nextQuery: DataQuery = { ...query, ...generateEmptyQuery(index) };
|
||||||
...query,
|
const nextQueries = [...queries];
|
||||||
...generateEmptyQuery(index),
|
|
||||||
};
|
|
||||||
const nextQueries = [...initialQueries];
|
|
||||||
nextQueries[index] = nextQuery;
|
nextQueries[index] = nextQuery;
|
||||||
modifiedQueries = [...nextQueries];
|
|
||||||
|
|
||||||
// Discard ongoing transaction related to row query
|
// Discard ongoing transaction related to row query
|
||||||
const nextQueryTransactions = queryTransactions.filter(qt => qt.rowIndex !== index);
|
const nextQueryTransactions = queryTransactions.filter(qt => qt.rowIndex !== index);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
initialQueries: nextQueries,
|
queries: nextQueries,
|
||||||
modifiedQueries: nextQueries.slice(),
|
|
||||||
queryTransactions: nextQueryTransactions,
|
queryTransactions: nextQueryTransactions,
|
||||||
|
queryKeys: getQueryKeys(nextQueries, state.datasourceInstance),
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
|
})
|
||||||
case ActionTypes.ChangeSize: {
|
.addMapper({
|
||||||
|
filter: changeSizeAction,
|
||||||
|
mapper: (state, action): ExploreItemState => {
|
||||||
const { range, datasourceInstance } = state;
|
const { range, datasourceInstance } = state;
|
||||||
let interval = '1s';
|
let interval = '1s';
|
||||||
if (datasourceInstance && datasourceInstance.interval) {
|
if (datasourceInstance && datasourceInstance.interval) {
|
||||||
@ -137,67 +151,79 @@ export const itemReducer = (state, action: Action): ExploreItemState => {
|
|||||||
const containerWidth = action.payload.width;
|
const containerWidth = action.payload.width;
|
||||||
const queryIntervals = getIntervals(range, interval, containerWidth);
|
const queryIntervals = getIntervals(range, interval, containerWidth);
|
||||||
return { ...state, containerWidth, queryIntervals };
|
return { ...state, containerWidth, queryIntervals };
|
||||||
}
|
},
|
||||||
|
})
|
||||||
case ActionTypes.ChangeTime: {
|
.addMapper({
|
||||||
return {
|
filter: changeTimeAction,
|
||||||
...state,
|
mapper: (state, action): ExploreItemState => {
|
||||||
range: action.payload.range,
|
return { ...state, range: action.payload.range };
|
||||||
};
|
},
|
||||||
}
|
})
|
||||||
|
.addMapper({
|
||||||
case ActionTypes.ClearQueries: {
|
filter: clearQueriesAction,
|
||||||
|
mapper: (state): ExploreItemState => {
|
||||||
const queries = ensureQueries();
|
const queries = ensureQueries();
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
initialQueries: queries.slice(),
|
queries: queries.slice(),
|
||||||
modifiedQueries: queries.slice(),
|
|
||||||
queryTransactions: [],
|
queryTransactions: [],
|
||||||
showingStartPage: Boolean(state.StartPage),
|
showingStartPage: Boolean(state.StartPage),
|
||||||
|
queryKeys: getQueryKeys(queries, state.datasourceInstance),
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
|
})
|
||||||
case ActionTypes.HighlightLogsExpression: {
|
.addMapper({
|
||||||
|
filter: highlightLogsExpressionAction,
|
||||||
|
mapper: (state, action): ExploreItemState => {
|
||||||
const { expressions } = action.payload;
|
const { expressions } = action.payload;
|
||||||
return { ...state, logsHighlighterExpressions: expressions };
|
return { ...state, logsHighlighterExpressions: expressions };
|
||||||
}
|
},
|
||||||
|
})
|
||||||
case ActionTypes.InitializeExplore: {
|
.addMapper({
|
||||||
const { containerWidth, eventBridge, exploreDatasources, queries, range } = action.payload;
|
filter: initializeExploreAction,
|
||||||
|
mapper: (state, action): ExploreItemState => {
|
||||||
|
const { containerWidth, eventBridge, exploreDatasources, queries, range, ui } = action.payload;
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
containerWidth,
|
containerWidth,
|
||||||
eventBridge,
|
eventBridge,
|
||||||
exploreDatasources,
|
exploreDatasources,
|
||||||
range,
|
range,
|
||||||
initialQueries: queries,
|
queries,
|
||||||
initialized: true,
|
initialized: true,
|
||||||
modifiedQueries: queries.slice(),
|
queryKeys: getQueryKeys(queries, state.datasourceInstance),
|
||||||
|
...ui,
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
|
})
|
||||||
case ActionTypes.UpdateDatasourceInstance: {
|
.addMapper({
|
||||||
|
filter: updateDatasourceInstanceAction,
|
||||||
|
mapper: (state, action): ExploreItemState => {
|
||||||
const { datasourceInstance } = action.payload;
|
const { datasourceInstance } = action.payload;
|
||||||
return {
|
return { ...state, datasourceInstance, queryKeys: getQueryKeys(state.queries, datasourceInstance) };
|
||||||
...state,
|
},
|
||||||
datasourceInstance,
|
})
|
||||||
datasourceName: datasourceInstance.name,
|
.addMapper({
|
||||||
};
|
filter: loadDatasourceFailureAction,
|
||||||
}
|
mapper: (state, action): ExploreItemState => {
|
||||||
|
|
||||||
case ActionTypes.LoadDatasourceFailure: {
|
|
||||||
return { ...state, datasourceError: action.payload.error, datasourceLoading: false };
|
return { ...state, datasourceError: action.payload.error, datasourceLoading: false };
|
||||||
}
|
},
|
||||||
|
})
|
||||||
case ActionTypes.LoadDatasourceMissing: {
|
.addMapper({
|
||||||
|
filter: loadDatasourceMissingAction,
|
||||||
|
mapper: (state): ExploreItemState => {
|
||||||
return { ...state, datasourceMissing: true, datasourceLoading: false };
|
return { ...state, datasourceMissing: true, datasourceLoading: false };
|
||||||
}
|
},
|
||||||
|
})
|
||||||
case ActionTypes.LoadDatasourcePending: {
|
.addMapper({
|
||||||
|
filter: loadDatasourcePendingAction,
|
||||||
|
mapper: (state, action): ExploreItemState => {
|
||||||
return { ...state, datasourceLoading: true, requestedDatasourceName: action.payload.requestedDatasourceName };
|
return { ...state, datasourceLoading: true, requestedDatasourceName: action.payload.requestedDatasourceName };
|
||||||
}
|
},
|
||||||
|
})
|
||||||
case ActionTypes.LoadDatasourceSuccess: {
|
.addMapper({
|
||||||
|
filter: loadDatasourceSuccessAction,
|
||||||
|
mapper: (state, action): ExploreItemState => {
|
||||||
const { containerWidth, range } = state;
|
const { containerWidth, range } = state;
|
||||||
const {
|
const {
|
||||||
StartPage,
|
StartPage,
|
||||||
@ -226,32 +252,29 @@ export const itemReducer = (state, action: Action): ExploreItemState => {
|
|||||||
logsHighlighterExpressions: undefined,
|
logsHighlighterExpressions: undefined,
|
||||||
queryTransactions: [],
|
queryTransactions: [],
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
|
})
|
||||||
case ActionTypes.ModifyQueries: {
|
.addMapper({
|
||||||
const { initialQueries, modifiedQueries, queryTransactions } = state;
|
filter: modifyQueriesAction,
|
||||||
const { modification, index, modifier } = action.payload as any;
|
mapper: (state, action): ExploreItemState => {
|
||||||
|
const { queries, queryTransactions } = state;
|
||||||
|
const { modification, index, modifier } = action.payload;
|
||||||
let nextQueries: DataQuery[];
|
let nextQueries: DataQuery[];
|
||||||
let nextQueryTransactions;
|
let nextQueryTransactions;
|
||||||
if (index === undefined) {
|
if (index === undefined) {
|
||||||
// Modify all queries
|
// Modify all queries
|
||||||
nextQueries = initialQueries.map((query, i) => ({
|
nextQueries = queries.map((query, i) => ({
|
||||||
...modifier(modifiedQueries[i], modification),
|
...modifier({ ...query }, modification),
|
||||||
...generateEmptyQuery(i),
|
...generateEmptyQuery(i),
|
||||||
}));
|
}));
|
||||||
// Discard all ongoing transactions
|
// Discard all ongoing transactions
|
||||||
nextQueryTransactions = [];
|
nextQueryTransactions = [];
|
||||||
} else {
|
} else {
|
||||||
// Modify query only at index
|
// Modify query only at index
|
||||||
nextQueries = initialQueries.map((query, i) => {
|
nextQueries = queries.map((query, i) => {
|
||||||
// Synchronize all queries with local query cache to ensure consistency
|
// Synchronize all queries with local query cache to ensure consistency
|
||||||
// TODO still needed?
|
// TODO still needed?
|
||||||
return i === index
|
return i === index ? { ...modifier({ ...query }, modification), ...generateEmptyQuery(i) } : query;
|
||||||
? {
|
|
||||||
...modifier(modifiedQueries[i], modification),
|
|
||||||
...generateEmptyQuery(i),
|
|
||||||
}
|
|
||||||
: query;
|
|
||||||
});
|
});
|
||||||
nextQueryTransactions = queryTransactions
|
nextQueryTransactions = queryTransactions
|
||||||
// Consume the hint corresponding to the action
|
// Consume the hint corresponding to the action
|
||||||
@ -266,22 +289,22 @@ export const itemReducer = (state, action: Action): ExploreItemState => {
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
initialQueries: nextQueries,
|
queries: nextQueries,
|
||||||
modifiedQueries: nextQueries.slice(),
|
queryKeys: getQueryKeys(nextQueries, state.datasourceInstance),
|
||||||
queryTransactions: nextQueryTransactions,
|
queryTransactions: nextQueryTransactions,
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
|
})
|
||||||
case ActionTypes.QueryTransactionFailure: {
|
.addMapper({
|
||||||
|
filter: queryTransactionFailureAction,
|
||||||
|
mapper: (state, action): ExploreItemState => {
|
||||||
const { queryTransactions } = action.payload;
|
const { queryTransactions } = action.payload;
|
||||||
return {
|
return { ...state, queryTransactions, showingStartPage: false };
|
||||||
...state,
|
},
|
||||||
queryTransactions,
|
})
|
||||||
showingStartPage: false,
|
.addMapper({
|
||||||
};
|
filter: queryTransactionStartAction,
|
||||||
}
|
mapper: (state, action): ExploreItemState => {
|
||||||
|
|
||||||
case ActionTypes.QueryTransactionStart: {
|
|
||||||
const { queryTransactions } = state;
|
const { queryTransactions } = state;
|
||||||
const { resultType, rowIndex, transaction } = action.payload;
|
const { resultType, rowIndex, transaction } = action.payload;
|
||||||
// Discarding existing transactions of same type
|
// Discarding existing transactions of same type
|
||||||
@ -292,14 +315,12 @@ export const itemReducer = (state, action: Action): ExploreItemState => {
|
|||||||
// Append new transaction
|
// Append new transaction
|
||||||
const nextQueryTransactions: QueryTransaction[] = [...remainingTransactions, transaction];
|
const nextQueryTransactions: QueryTransaction[] = [...remainingTransactions, transaction];
|
||||||
|
|
||||||
return {
|
return { ...state, queryTransactions: nextQueryTransactions, showingStartPage: false };
|
||||||
...state,
|
},
|
||||||
queryTransactions: nextQueryTransactions,
|
})
|
||||||
showingStartPage: false,
|
.addMapper({
|
||||||
};
|
filter: queryTransactionSuccessAction,
|
||||||
}
|
mapper: (state, action): ExploreItemState => {
|
||||||
|
|
||||||
case ActionTypes.QueryTransactionSuccess: {
|
|
||||||
const { datasourceInstance, queryIntervals } = state;
|
const { datasourceInstance, queryIntervals } = state;
|
||||||
const { history, queryTransactions } = action.payload;
|
const { history, queryTransactions } = action.payload;
|
||||||
const results = calculateResultsFromQueryTransactions(
|
const results = calculateResultsFromQueryTransactions(
|
||||||
@ -308,30 +329,24 @@ export const itemReducer = (state, action: Action): ExploreItemState => {
|
|||||||
queryIntervals.intervalMs
|
queryIntervals.intervalMs
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return { ...state, ...results, history, queryTransactions, showingStartPage: false };
|
||||||
...state,
|
},
|
||||||
...results,
|
})
|
||||||
history,
|
.addMapper({
|
||||||
queryTransactions,
|
filter: removeQueryRowAction,
|
||||||
showingStartPage: false,
|
mapper: (state, action): ExploreItemState => {
|
||||||
};
|
const { datasourceInstance, queries, queryIntervals, queryTransactions, queryKeys } = state;
|
||||||
}
|
|
||||||
|
|
||||||
case ActionTypes.RemoveQueryRow: {
|
|
||||||
const { datasourceInstance, initialQueries, queryIntervals, queryTransactions } = state;
|
|
||||||
let { modifiedQueries } = state;
|
|
||||||
const { index } = action.payload;
|
const { index } = action.payload;
|
||||||
|
|
||||||
modifiedQueries = [...modifiedQueries.slice(0, index), ...modifiedQueries.slice(index + 1)];
|
if (queries.length <= 1) {
|
||||||
|
|
||||||
if (initialQueries.length <= 1) {
|
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
const nextQueries = [...initialQueries.slice(0, index), ...initialQueries.slice(index + 1)];
|
const nextQueries = [...queries.slice(0, index), ...queries.slice(index + 1)];
|
||||||
|
const nextQueryKeys = [...queryKeys.slice(0, index), ...queryKeys.slice(index + 1)];
|
||||||
|
|
||||||
// Discard transactions related to row query
|
// Discard transactions related to row query
|
||||||
const nextQueryTransactions = queryTransactions.filter(qt => qt.rowIndex !== index);
|
const nextQueryTransactions = queryTransactions.filter(qt => nextQueries.some(nq => nq.key === qt.query.key));
|
||||||
const results = calculateResultsFromQueryTransactions(
|
const results = calculateResultsFromQueryTransactions(
|
||||||
nextQueryTransactions,
|
nextQueryTransactions,
|
||||||
datasourceInstance,
|
datasourceInstance,
|
||||||
@ -341,26 +356,34 @@ export const itemReducer = (state, action: Action): ExploreItemState => {
|
|||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
...results,
|
...results,
|
||||||
initialQueries: nextQueries,
|
queries: nextQueries,
|
||||||
logsHighlighterExpressions: undefined,
|
logsHighlighterExpressions: undefined,
|
||||||
modifiedQueries: nextQueries.slice(),
|
|
||||||
queryTransactions: nextQueryTransactions,
|
queryTransactions: nextQueryTransactions,
|
||||||
|
queryKeys: nextQueryKeys,
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
|
})
|
||||||
case ActionTypes.RunQueriesEmpty: {
|
.addMapper({
|
||||||
|
filter: runQueriesEmptyAction,
|
||||||
|
mapper: (state): ExploreItemState => {
|
||||||
return { ...state, queryTransactions: [] };
|
return { ...state, queryTransactions: [] };
|
||||||
}
|
},
|
||||||
|
})
|
||||||
case ActionTypes.ScanRange: {
|
.addMapper({
|
||||||
|
filter: scanRangeAction,
|
||||||
|
mapper: (state, action): ExploreItemState => {
|
||||||
return { ...state, scanRange: action.payload.range };
|
return { ...state, scanRange: action.payload.range };
|
||||||
}
|
},
|
||||||
|
})
|
||||||
case ActionTypes.ScanStart: {
|
.addMapper({
|
||||||
|
filter: scanStartAction,
|
||||||
|
mapper: (state, action): ExploreItemState => {
|
||||||
return { ...state, scanning: true, scanner: action.payload.scanner };
|
return { ...state, scanning: true, scanner: action.payload.scanner };
|
||||||
}
|
},
|
||||||
|
})
|
||||||
case ActionTypes.ScanStop: {
|
.addMapper({
|
||||||
|
filter: scanStopAction,
|
||||||
|
mapper: (state): ExploreItemState => {
|
||||||
const { queryTransactions } = state;
|
const { queryTransactions } = state;
|
||||||
const nextQueryTransactions = queryTransactions.filter(qt => qt.scanning && !qt.done);
|
const nextQueryTransactions = queryTransactions.filter(qt => qt.scanning && !qt.done);
|
||||||
return {
|
return {
|
||||||
@ -370,14 +393,22 @@ export const itemReducer = (state, action: Action): ExploreItemState => {
|
|||||||
scanRange: undefined,
|
scanRange: undefined,
|
||||||
scanner: undefined,
|
scanner: undefined,
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
|
})
|
||||||
case ActionTypes.SetQueries: {
|
.addMapper({
|
||||||
|
filter: setQueriesAction,
|
||||||
|
mapper: (state, action): ExploreItemState => {
|
||||||
const { queries } = action.payload;
|
const { queries } = action.payload;
|
||||||
return { ...state, initialQueries: queries.slice(), modifiedQueries: queries.slice() };
|
return {
|
||||||
}
|
...state,
|
||||||
|
queries: queries.slice(),
|
||||||
case ActionTypes.ToggleGraph: {
|
queryKeys: getQueryKeys(queries, state.datasourceInstance),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.addMapper({
|
||||||
|
filter: toggleGraphAction,
|
||||||
|
mapper: (state): ExploreItemState => {
|
||||||
const showingGraph = !state.showingGraph;
|
const showingGraph = !state.showingGraph;
|
||||||
let nextQueryTransactions = state.queryTransactions;
|
let nextQueryTransactions = state.queryTransactions;
|
||||||
if (!showingGraph) {
|
if (!showingGraph) {
|
||||||
@ -385,9 +416,11 @@ export const itemReducer = (state, action: Action): ExploreItemState => {
|
|||||||
nextQueryTransactions = state.queryTransactions.filter(qt => qt.resultType !== 'Graph');
|
nextQueryTransactions = state.queryTransactions.filter(qt => qt.resultType !== 'Graph');
|
||||||
}
|
}
|
||||||
return { ...state, queryTransactions: nextQueryTransactions, showingGraph };
|
return { ...state, queryTransactions: nextQueryTransactions, showingGraph };
|
||||||
}
|
},
|
||||||
|
})
|
||||||
case ActionTypes.ToggleLogs: {
|
.addMapper({
|
||||||
|
filter: toggleLogsAction,
|
||||||
|
mapper: (state): ExploreItemState => {
|
||||||
const showingLogs = !state.showingLogs;
|
const showingLogs = !state.showingLogs;
|
||||||
let nextQueryTransactions = state.queryTransactions;
|
let nextQueryTransactions = state.queryTransactions;
|
||||||
if (!showingLogs) {
|
if (!showingLogs) {
|
||||||
@ -395,9 +428,11 @@ export const itemReducer = (state, action: Action): ExploreItemState => {
|
|||||||
nextQueryTransactions = state.queryTransactions.filter(qt => qt.resultType !== 'Logs');
|
nextQueryTransactions = state.queryTransactions.filter(qt => qt.resultType !== 'Logs');
|
||||||
}
|
}
|
||||||
return { ...state, queryTransactions: nextQueryTransactions, showingLogs };
|
return { ...state, queryTransactions: nextQueryTransactions, showingLogs };
|
||||||
}
|
},
|
||||||
|
})
|
||||||
case ActionTypes.ToggleTable: {
|
.addMapper({
|
||||||
|
filter: toggleTableAction,
|
||||||
|
mapper: (state): ExploreItemState => {
|
||||||
const showingTable = !state.showingTable;
|
const showingTable = !state.showingTable;
|
||||||
if (showingTable) {
|
if (showingTable) {
|
||||||
return { ...state, showingTable, queryTransactions: state.queryTransactions };
|
return { ...state, showingTable, queryTransactions: state.queryTransactions };
|
||||||
@ -412,25 +447,26 @@ export const itemReducer = (state, action: Action): ExploreItemState => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return { ...state, ...results, queryTransactions: nextQueryTransactions, showingTable };
|
return { ...state, ...results, queryTransactions: nextQueryTransactions, showingTable };
|
||||||
}
|
},
|
||||||
|
})
|
||||||
case ActionTypes.QueriesImported: {
|
.addMapper({
|
||||||
|
filter: queriesImportedAction,
|
||||||
|
mapper: (state, action): ExploreItemState => {
|
||||||
|
const { queries } = action.payload;
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
initialQueries: action.payload.queries,
|
queries,
|
||||||
modifiedQueries: action.payload.queries.slice(),
|
queryKeys: getQueryKeys(queries, state.datasourceInstance),
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
}
|
})
|
||||||
|
.create();
|
||||||
return state;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Global Explore reducer that handles multiple Explore areas (left and right).
|
* Global Explore reducer that handles multiple Explore areas (left and right).
|
||||||
* Actions that have an `exploreId` get routed to the ExploreItemReducer.
|
* Actions that have an `exploreId` get routed to the ExploreItemReducer.
|
||||||
*/
|
*/
|
||||||
export const exploreReducer = (state = initialExploreState, action: Action): ExploreState => {
|
export const exploreReducer = (state = initialExploreState, action: HigherOrderAction): ExploreState => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case ActionTypes.SplitClose: {
|
case ActionTypes.SplitClose: {
|
||||||
return { ...state, split: false };
|
return { ...state, split: false };
|
||||||
@ -453,10 +489,7 @@ export const exploreReducer = (state = initialExploreState, action: Action): Exp
|
|||||||
const { exploreId } = action.payload as any;
|
const { exploreId } = action.payload as any;
|
||||||
if (exploreId !== undefined) {
|
if (exploreId !== undefined) {
|
||||||
const exploreItemState = state[exploreId];
|
const exploreItemState = state[exploreId];
|
||||||
return {
|
return { ...state, [exploreId]: itemReducer(exploreItemState, action) };
|
||||||
...state,
|
|
||||||
[exploreId]: itemReducer(exploreItemState, action),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,6 +156,9 @@ export class TemplateSrv {
|
|||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
case 'json': {
|
||||||
|
return JSON.stringify(value);
|
||||||
|
}
|
||||||
case 'percentencode': {
|
case 'percentencode': {
|
||||||
// like glob, but url escaped
|
// like glob, but url escaped
|
||||||
if (_.isArray(value)) {
|
if (_.isArray(value)) {
|
||||||
|
@ -232,6 +232,14 @@ export default class CloudWatchDatasource {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getResourceARNs(region, resourceType, tags) {
|
||||||
|
return this.doMetricQueryRequest('resource_arns', {
|
||||||
|
region: this.templateSrv.replace(this.getActualRegion(region)),
|
||||||
|
resourceType: this.templateSrv.replace(resourceType),
|
||||||
|
tags: tags,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
metricFindQuery(query) {
|
metricFindQuery(query) {
|
||||||
let region;
|
let region;
|
||||||
let namespace;
|
let namespace;
|
||||||
@ -293,6 +301,15 @@ export default class CloudWatchDatasource {
|
|||||||
return this.getEc2InstanceAttribute(region, targetAttributeName, filterJson);
|
return this.getEc2InstanceAttribute(region, targetAttributeName, filterJson);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const resourceARNsQuery = query.match(/^resource_arns\(([^,]+?),\s?([^,]+?),\s?(.+?)\)/);
|
||||||
|
if (resourceARNsQuery) {
|
||||||
|
region = resourceARNsQuery[1];
|
||||||
|
const resourceType = resourceARNsQuery[2];
|
||||||
|
const tagsJSON = JSON.parse(this.templateSrv.replace(resourceARNsQuery[3]));
|
||||||
|
return this.getResourceARNs(region, resourceType, tagsJSON);
|
||||||
|
}
|
||||||
|
|
||||||
return this.$q.when([]);
|
return this.$q.when([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -380,6 +380,29 @@ describe('CloudWatchDatasource', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describeMetricFindQuery('resource_arns(default,ec2:instance,{"environment":["production"]})', scenario => {
|
||||||
|
scenario.setup(() => {
|
||||||
|
scenario.requestResponse = {
|
||||||
|
results: {
|
||||||
|
metricFindQuery: {
|
||||||
|
tables: [{
|
||||||
|
rows: [[
|
||||||
|
'arn:aws:ec2:us-east-1:123456789012:instance/i-12345678901234567',
|
||||||
|
'arn:aws:ec2:us-east-1:123456789012:instance/i-76543210987654321'
|
||||||
|
]]
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call __ListMetrics and return result', () => {
|
||||||
|
expect(scenario.result[0].text).toContain('arn:aws:ec2:us-east-1:123456789012:instance/i-12345678901234567');
|
||||||
|
expect(scenario.request.queries[0].type).toBe('metricFindQuery');
|
||||||
|
expect(scenario.request.queries[0].subtype).toBe('resource_arns');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should caclculate the correct period', () => {
|
it('should caclculate the correct period', () => {
|
||||||
const hourSec = 60 * 60;
|
const hourSec = 60 * 60;
|
||||||
const daySec = hourSec * 24;
|
const daySec = hourSec * 24;
|
||||||
|
@ -33,7 +33,7 @@ export class LokiQueryEditor extends PureComponent<Props> {
|
|||||||
query: {
|
query: {
|
||||||
...this.state.query,
|
...this.state.query,
|
||||||
expr: query.expr,
|
expr: query.expr,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -59,14 +59,20 @@ export class LokiQueryEditor extends PureComponent<Props> {
|
|||||||
<div>
|
<div>
|
||||||
<LokiQueryField
|
<LokiQueryField
|
||||||
datasource={datasource}
|
datasource={datasource}
|
||||||
initialQuery={query}
|
query={query}
|
||||||
onQueryChange={this.onFieldChange}
|
onQueryChange={this.onFieldChange}
|
||||||
onPressEnter={this.onRunQuery}
|
onExecuteQuery={this.onRunQuery}
|
||||||
|
history={[]}
|
||||||
/>
|
/>
|
||||||
<div className="gf-form-inline">
|
<div className="gf-form-inline">
|
||||||
<div className="gf-form">
|
<div className="gf-form">
|
||||||
<div className="gf-form-label">Format as</div>
|
<div className="gf-form-label">Format as</div>
|
||||||
<Select isSearchable={false} options={formatOptions} onChange={this.onFormatChanged} value={currentFormat} />
|
<Select
|
||||||
|
isSearchable={false}
|
||||||
|
options={formatOptions}
|
||||||
|
onChange={this.onFormatChanged}
|
||||||
|
value={currentFormat}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="gf-form gf-form--grow">
|
<div className="gf-form gf-form--grow">
|
||||||
<div className="gf-form-label gf-form-label--grow" />
|
<div className="gf-form-label gf-form-label--grow" />
|
||||||
|
@ -12,12 +12,12 @@ import QueryField, { TypeaheadInput, QueryFieldState } from 'app/features/explor
|
|||||||
import { getNextCharacter, getPreviousCousin } from 'app/features/explore/utils/dom';
|
import { getNextCharacter, getPreviousCousin } from 'app/features/explore/utils/dom';
|
||||||
import BracesPlugin from 'app/features/explore/slate-plugins/braces';
|
import BracesPlugin from 'app/features/explore/slate-plugins/braces';
|
||||||
import RunnerPlugin from 'app/features/explore/slate-plugins/runner';
|
import RunnerPlugin from 'app/features/explore/slate-plugins/runner';
|
||||||
import LokiDatasource from '../datasource';
|
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
import { LokiQuery } from '../types';
|
import { LokiQuery } from '../types';
|
||||||
import { TypeaheadOutput } from 'app/types/explore';
|
import { TypeaheadOutput, HistoryItem } from 'app/types/explore';
|
||||||
import { makePromiseCancelable, CancelablePromise } from 'app/core/utils/CancelablePromise';
|
import { makePromiseCancelable, CancelablePromise } from 'app/core/utils/CancelablePromise';
|
||||||
|
import { ExploreDataSourceApi, ExploreQueryFieldProps } from '@grafana/ui';
|
||||||
|
|
||||||
const PRISM_SYNTAX = 'promql';
|
const PRISM_SYNTAX = 'promql';
|
||||||
|
|
||||||
@ -65,15 +65,8 @@ interface CascaderOption {
|
|||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface LokiQueryFieldProps {
|
interface LokiQueryFieldProps extends ExploreQueryFieldProps<ExploreDataSourceApi, LokiQuery> {
|
||||||
datasource: LokiDatasource;
|
history: HistoryItem[];
|
||||||
error?: string | JSX.Element;
|
|
||||||
hint?: any;
|
|
||||||
history?: any[];
|
|
||||||
initialQuery?: LokiQuery;
|
|
||||||
onClickHintFix?: (action: any) => void;
|
|
||||||
onPressEnter?: () => void;
|
|
||||||
onQueryChange?: (value: LokiQuery, override?: boolean) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface LokiQueryFieldState {
|
interface LokiQueryFieldState {
|
||||||
@ -98,14 +91,14 @@ export class LokiQueryField extends React.PureComponent<LokiQueryFieldProps, Lok
|
|||||||
|
|
||||||
this.plugins = [
|
this.plugins = [
|
||||||
BracesPlugin(),
|
BracesPlugin(),
|
||||||
RunnerPlugin({ handler: props.onPressEnter }),
|
RunnerPlugin({ handler: props.onExecuteQuery }),
|
||||||
PluginPrism({
|
PluginPrism({
|
||||||
onlyIn: node => node.type === 'code_block',
|
onlyIn: node => node.type === 'code_block',
|
||||||
getSyntax: node => 'promql',
|
getSyntax: node => 'promql',
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
this.pluginsSearch = [RunnerPlugin({ handler: props.onPressEnter })];
|
this.pluginsSearch = [RunnerPlugin({ handler: props.onExecuteQuery })];
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
logLabelOptions: [],
|
logLabelOptions: [],
|
||||||
@ -169,20 +162,21 @@ export class LokiQueryField extends React.PureComponent<LokiQueryFieldProps, Lok
|
|||||||
|
|
||||||
onChangeQuery = (value: string, override?: boolean) => {
|
onChangeQuery = (value: string, override?: boolean) => {
|
||||||
// Send text change to parent
|
// Send text change to parent
|
||||||
const { initialQuery, onQueryChange } = this.props;
|
const { query, onQueryChange, onExecuteQuery } = this.props;
|
||||||
if (onQueryChange) {
|
if (onQueryChange) {
|
||||||
const query = {
|
const nextQuery = { ...query, expr: value };
|
||||||
...initialQuery,
|
onQueryChange(nextQuery);
|
||||||
expr: value,
|
|
||||||
};
|
if (override && onExecuteQuery) {
|
||||||
onQueryChange(query, override);
|
onExecuteQuery();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onClickHintFix = () => {
|
onClickHintFix = () => {
|
||||||
const { hint, onClickHintFix } = this.props;
|
const { hint, onExecuteHint } = this.props;
|
||||||
if (onClickHintFix && hint && hint.fix) {
|
if (onExecuteHint && hint && hint.fix) {
|
||||||
onClickHintFix(hint.fix.action);
|
onExecuteHint(hint.fix.action);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -220,7 +214,7 @@ export class LokiQueryField extends React.PureComponent<LokiQueryFieldProps, Lok
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { error, hint, initialQuery } = this.props;
|
const { error, hint, query } = this.props;
|
||||||
const { logLabelOptions, syntaxLoaded } = this.state;
|
const { logLabelOptions, syntaxLoaded } = this.state;
|
||||||
const cleanText = this.languageProvider ? this.languageProvider.cleanText : undefined;
|
const cleanText = this.languageProvider ? this.languageProvider.cleanText : undefined;
|
||||||
const hasLogLabels = logLabelOptions && logLabelOptions.length > 0;
|
const hasLogLabels = logLabelOptions && logLabelOptions.length > 0;
|
||||||
@ -240,10 +234,11 @@ export class LokiQueryField extends React.PureComponent<LokiQueryFieldProps, Lok
|
|||||||
<QueryField
|
<QueryField
|
||||||
additionalPlugins={this.plugins}
|
additionalPlugins={this.plugins}
|
||||||
cleanText={cleanText}
|
cleanText={cleanText}
|
||||||
initialQuery={initialQuery.expr}
|
initialQuery={query.expr}
|
||||||
onTypeahead={this.onTypeahead}
|
onTypeahead={this.onTypeahead}
|
||||||
onWillApplySuggestion={willApplySuggestion}
|
onWillApplySuggestion={willApplySuggestion}
|
||||||
onValueChanged={this.onChangeQuery}
|
onQueryChange={this.onChangeQuery}
|
||||||
|
onExecuteQuery={this.props.onExecuteQuery}
|
||||||
placeholder="Enter a Loki query"
|
placeholder="Enter a Loki query"
|
||||||
portalOrigin="loki"
|
portalOrigin="loki"
|
||||||
syntaxLoaded={syntaxLoaded}
|
syntaxLoaded={syntaxLoaded}
|
||||||
|
@ -1,11 +1,8 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import LokiCheatSheet from './LokiCheatSheet';
|
import LokiCheatSheet from './LokiCheatSheet';
|
||||||
|
import { ExploreStartPageProps } from '@grafana/ui';
|
||||||
|
|
||||||
interface Props {
|
export default class LokiStartPage extends PureComponent<ExploreStartPageProps> {
|
||||||
onClickExample: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class LokiStartPage extends PureComponent<Props> {
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className="grafana-info-box grafana-info-box--max-lg">
|
<div className="grafana-info-box grafana-info-box--max-lg">
|
||||||
|
@ -4,7 +4,7 @@ import Cascader from 'rc-cascader';
|
|||||||
import PluginPrism from 'slate-prism';
|
import PluginPrism from 'slate-prism';
|
||||||
import Prism from 'prismjs';
|
import Prism from 'prismjs';
|
||||||
|
|
||||||
import { TypeaheadOutput } from 'app/types/explore';
|
import { TypeaheadOutput, HistoryItem } from 'app/types/explore';
|
||||||
|
|
||||||
// dom also includes Element polyfills
|
// dom also includes Element polyfills
|
||||||
import { getNextCharacter, getPreviousCousin } from 'app/features/explore/utils/dom';
|
import { getNextCharacter, getPreviousCousin } from 'app/features/explore/utils/dom';
|
||||||
@ -13,6 +13,7 @@ import RunnerPlugin from 'app/features/explore/slate-plugins/runner';
|
|||||||
import QueryField, { TypeaheadInput, QueryFieldState } from 'app/features/explore/QueryField';
|
import QueryField, { TypeaheadInput, QueryFieldState } from 'app/features/explore/QueryField';
|
||||||
import { PromQuery } from '../types';
|
import { PromQuery } from '../types';
|
||||||
import { CancelablePromise, makePromiseCancelable } from 'app/core/utils/CancelablePromise';
|
import { CancelablePromise, makePromiseCancelable } from 'app/core/utils/CancelablePromise';
|
||||||
|
import { ExploreDataSourceApi, ExploreQueryFieldProps } from '@grafana/ui';
|
||||||
|
|
||||||
const HISTOGRAM_GROUP = '__histograms__';
|
const HISTOGRAM_GROUP = '__histograms__';
|
||||||
const METRIC_MARK = 'metric';
|
const METRIC_MARK = 'metric';
|
||||||
@ -86,15 +87,8 @@ interface CascaderOption {
|
|||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PromQueryFieldProps {
|
interface PromQueryFieldProps extends ExploreQueryFieldProps<ExploreDataSourceApi, PromQuery> {
|
||||||
datasource: any;
|
history: HistoryItem[];
|
||||||
error?: string | JSX.Element;
|
|
||||||
initialQuery: PromQuery;
|
|
||||||
hint?: any;
|
|
||||||
history?: any[];
|
|
||||||
onClickHintFix?: (action: any) => void;
|
|
||||||
onPressEnter?: () => void;
|
|
||||||
onQueryChange?: (value: PromQuery, override?: boolean) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PromQueryFieldState {
|
interface PromQueryFieldState {
|
||||||
@ -116,7 +110,7 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
|
|||||||
|
|
||||||
this.plugins = [
|
this.plugins = [
|
||||||
BracesPlugin(),
|
BracesPlugin(),
|
||||||
RunnerPlugin({ handler: props.onPressEnter }),
|
RunnerPlugin({ handler: props.onExecuteQuery }),
|
||||||
PluginPrism({
|
PluginPrism({
|
||||||
onlyIn: node => node.type === 'code_block',
|
onlyIn: node => node.type === 'code_block',
|
||||||
getSyntax: node => 'promql',
|
getSyntax: node => 'promql',
|
||||||
@ -174,20 +168,21 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
|
|||||||
|
|
||||||
onChangeQuery = (value: string, override?: boolean) => {
|
onChangeQuery = (value: string, override?: boolean) => {
|
||||||
// Send text change to parent
|
// Send text change to parent
|
||||||
const { initialQuery, onQueryChange } = this.props;
|
const { query, onQueryChange, onExecuteQuery } = this.props;
|
||||||
if (onQueryChange) {
|
if (onQueryChange) {
|
||||||
const query: PromQuery = {
|
const nextQuery: PromQuery = { ...query, expr: value };
|
||||||
...initialQuery,
|
onQueryChange(nextQuery);
|
||||||
expr: value,
|
|
||||||
};
|
if (override && onExecuteQuery) {
|
||||||
onQueryChange(query, override);
|
onExecuteQuery();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onClickHintFix = () => {
|
onClickHintFix = () => {
|
||||||
const { hint, onClickHintFix } = this.props;
|
const { hint, onExecuteHint } = this.props;
|
||||||
if (onClickHintFix && hint && hint.fix) {
|
if (onExecuteHint && hint && hint.fix) {
|
||||||
onClickHintFix(hint.fix.action);
|
onExecuteHint(hint.fix.action);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -242,29 +237,30 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { error, hint, initialQuery } = this.props;
|
const { error, hint, query } = this.props;
|
||||||
const { metricsOptions, syntaxLoaded } = this.state;
|
const { metricsOptions, syntaxLoaded } = this.state;
|
||||||
const cleanText = this.languageProvider ? this.languageProvider.cleanText : undefined;
|
const cleanText = this.languageProvider ? this.languageProvider.cleanText : undefined;
|
||||||
const chooserText = syntaxLoaded ? 'Metrics' : 'Loading metrics...';
|
const chooserText = syntaxLoaded ? 'Metrics' : 'Loading metrics...';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="gf-form-inline">
|
<div className="gf-form-inline gf-form-inline--nowrap">
|
||||||
<div className="gf-form">
|
<div className="gf-form flex-shrink-0">
|
||||||
<Cascader options={metricsOptions} onChange={this.onChangeMetrics}>
|
<Cascader options={metricsOptions} onChange={this.onChangeMetrics}>
|
||||||
<button className="gf-form-label gf-form-label--btn" disabled={!syntaxLoaded}>
|
<button className="gf-form-label gf-form-label--btn" disabled={!syntaxLoaded}>
|
||||||
{chooserText} <i className="fa fa-caret-down" />
|
{chooserText} <i className="fa fa-caret-down" />
|
||||||
</button>
|
</button>
|
||||||
</Cascader>
|
</Cascader>
|
||||||
</div>
|
</div>
|
||||||
<div className="gf-form gf-form--grow">
|
<div className="gf-form gf-form--grow flex-shrink-1">
|
||||||
<QueryField
|
<QueryField
|
||||||
additionalPlugins={this.plugins}
|
additionalPlugins={this.plugins}
|
||||||
cleanText={cleanText}
|
cleanText={cleanText}
|
||||||
initialQuery={initialQuery.expr}
|
initialQuery={query.expr}
|
||||||
onTypeahead={this.onTypeahead}
|
onTypeahead={this.onTypeahead}
|
||||||
onWillApplySuggestion={willApplySuggestion}
|
onWillApplySuggestion={willApplySuggestion}
|
||||||
onValueChanged={this.onChangeQuery}
|
onQueryChange={this.onChangeQuery}
|
||||||
|
onExecuteQuery={this.props.onExecuteQuery}
|
||||||
placeholder="Enter a PromQL query"
|
placeholder="Enter a PromQL query"
|
||||||
portalOrigin="prometheus"
|
portalOrigin="prometheus"
|
||||||
syntaxLoaded={syntaxLoaded}
|
syntaxLoaded={syntaxLoaded}
|
||||||
|
@ -1,11 +1,8 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import PromCheatSheet from './PromCheatSheet';
|
import PromCheatSheet from './PromCheatSheet';
|
||||||
|
import { ExploreStartPageProps } from '@grafana/ui';
|
||||||
|
|
||||||
interface Props {
|
export default class PromStart extends PureComponent<ExploreStartPageProps> {
|
||||||
onClickExample: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class PromStart extends PureComponent<Props> {
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className="grafana-info-box grafana-info-box--max-lg">
|
<div className="grafana-info-box grafana-info-box--max-lg">
|
||||||
|
@ -10,21 +10,21 @@ import { Alignments } from './Alignments';
|
|||||||
import { AlignmentPeriods } from './AlignmentPeriods';
|
import { AlignmentPeriods } from './AlignmentPeriods';
|
||||||
import { AliasBy } from './AliasBy';
|
import { AliasBy } from './AliasBy';
|
||||||
import { Help } from './Help';
|
import { Help } from './Help';
|
||||||
import { Target, MetricDescriptor } from '../types';
|
import { StackdriverQuery, MetricDescriptor } from '../types';
|
||||||
import { getAlignmentPickerData } from '../functions';
|
import { getAlignmentPickerData } from '../functions';
|
||||||
import StackdriverDatasource from '../datasource';
|
import StackdriverDatasource from '../datasource';
|
||||||
import { SelectOptionItem } from '@grafana/ui';
|
import { SelectOptionItem } from '@grafana/ui';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
onQueryChange: (target: Target) => void;
|
onQueryChange: (target: StackdriverQuery) => void;
|
||||||
onExecuteQuery: () => void;
|
onExecuteQuery: () => void;
|
||||||
target: Target;
|
target: StackdriverQuery;
|
||||||
events: any;
|
events: any;
|
||||||
datasource: StackdriverDatasource;
|
datasource: StackdriverDatasource;
|
||||||
templateSrv: TemplateSrv;
|
templateSrv: TemplateSrv;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface State extends Target {
|
interface State extends StackdriverQuery {
|
||||||
alignOptions: SelectOptionItem[];
|
alignOptions: SelectOptionItem[];
|
||||||
lastQuery: string;
|
lastQuery: string;
|
||||||
lastQueryError: string;
|
lastQueryError: string;
|
||||||
|
@ -2,9 +2,10 @@ import { stackdriverUnitMappings } from './constants';
|
|||||||
import appEvents from 'app/core/app_events';
|
import appEvents from 'app/core/app_events';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import StackdriverMetricFindQuery from './StackdriverMetricFindQuery';
|
import StackdriverMetricFindQuery from './StackdriverMetricFindQuery';
|
||||||
import { MetricDescriptor } from './types';
|
import { StackdriverQuery, MetricDescriptor } from './types';
|
||||||
|
import { DataSourceApi, DataQueryOptions } from '@grafana/ui/src/types';
|
||||||
|
|
||||||
export default class StackdriverDatasource {
|
export default class StackdriverDatasource implements DataSourceApi<StackdriverQuery> {
|
||||||
id: number;
|
id: number;
|
||||||
url: string;
|
url: string;
|
||||||
baseUrl: string;
|
baseUrl: string;
|
||||||
@ -39,9 +40,7 @@ export default class StackdriverDatasource {
|
|||||||
alignmentPeriod: this.templateSrv.replace(t.alignmentPeriod, options.scopedVars || {}),
|
alignmentPeriod: this.templateSrv.replace(t.alignmentPeriod, options.scopedVars || {}),
|
||||||
groupBys: this.interpolateGroupBys(t.groupBys, options.scopedVars),
|
groupBys: this.interpolateGroupBys(t.groupBys, options.scopedVars),
|
||||||
view: t.view || 'FULL',
|
view: t.view || 'FULL',
|
||||||
filters: (t.filters || []).map(f => {
|
filters: this.interpolateFilters(t.filters, options.scopedVars),
|
||||||
return this.templateSrv.replace(f, options.scopedVars || {});
|
|
||||||
}),
|
|
||||||
aliasBy: this.templateSrv.replace(t.aliasBy, options.scopedVars || {}),
|
aliasBy: this.templateSrv.replace(t.aliasBy, options.scopedVars || {}),
|
||||||
type: 'timeSeriesQuery',
|
type: 'timeSeriesQuery',
|
||||||
};
|
};
|
||||||
@ -63,7 +62,13 @@ export default class StackdriverDatasource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getLabels(metricType, refId) {
|
interpolateFilters(filters: string[], scopedVars: object) {
|
||||||
|
return (filters || []).map(f => {
|
||||||
|
return this.templateSrv.replace(f, scopedVars || {}, 'regex');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getLabels(metricType: string, refId: string) {
|
||||||
const response = await this.getTimeSeries({
|
const response = await this.getTimeSeries({
|
||||||
targets: [
|
targets: [
|
||||||
{
|
{
|
||||||
@ -103,7 +108,7 @@ export default class StackdriverDatasource {
|
|||||||
return unit;
|
return unit;
|
||||||
}
|
}
|
||||||
|
|
||||||
async query(options) {
|
async query(options: DataQueryOptions<StackdriverQuery>) {
|
||||||
const result = [];
|
const result = [];
|
||||||
const data = await this.getTimeSeries(options);
|
const data = await this.getTimeSeries(options);
|
||||||
if (data.results) {
|
if (data.results) {
|
||||||
|
Before Width: | Height: | Size: 15 KiB |
@ -0,0 +1 @@
|
|||||||
|
<svg width="100" height="90" xmlns="http://www.w3.org/2000/svg"><path fill="none" d="M-1-1h102v92H-1z"/><g><path d="M151.637 29.785c-1.659.621-3.32 1.241-4.783 2.055-1.548-7.686-18.278-8.18-18.117 1.021.148 8.228 18.35 8.414 22.9 16.065 3.456 5.808 1.064 14.28-3.417 17.433-1.805 1.271-4.625 3.234-10.936 3.076-7.568-.19-13.65-5.277-16.065-12.305 1.474-1.151 3.464-1.777 5.468-2.393.087 9.334 18.304 12.687 20.509 3.418 3.661-15.375-24.686-9.097-24.267-25.636.375-14.998 25.388-16.197 28.708-2.734zM207.347 68.413h-5.466v-4.444c-2.872 2.517-5.263 5.222-10.254 5.467-10.316.51-17.038-10.377-10.256-17.773 4.38-4.774 13.169-5.41 20.512-2.05 1.548-10.171-13.626-11.842-16.407-4.44-1.698-.697-3.195-1.592-5.126-2.054 2.832-10.246 20.01-9.729 24.949-2.392 4.608 6.837.757 17.618 2.048 27.686zm-22.216-7.866c4.483 6.856 17.435 2.377 16.751-6.154-5.161-3.469-18.501-3.389-16.751 6.154zM416.873 53.029c-7.868.794-17.201.117-25.638.343-1.48 10.76 16.123 14.618 19.144 5.127 1.631.754 3.326 1.457 5.127 2.048-2.477 9.824-18.37 11.251-25.294 4.445-9.549-9.386-4.276-31.335 12.987-29.735 8.89.826 13.149 7.176 13.674 17.772zm-25.295-4.444h18.801c-.04-11.168-18.433-9.957-18.801 0zM347.486 36.283v32.13h-5.81v-32.13h5.81zM352.273 36.283h6.153c3.048 8.342 6.48 16.303 9.224 24.949 4.33-7.408 6.575-16.895 10.251-24.949h6.155c-4.39 10.646-8.865 21.217-12.988 32.13h-6.152c-3.907-11.019-8.635-21.217-12.643-32.13zM427.354 36.225h-5.525v32.111h5.982V48.885s1.845-9.322 11.396-7.021l2.417-5.867s-8.978-2.532-14.155 5.407l-.115-5.179zM322.434 36.225h-5.522v32.111h5.987V48.885s1.84-9.322 11.395-7.021l2.417-5.867s-8.976-2.532-14.159 5.407l-.118-5.179zM304.139 51.998c0 6.579-4.645 11.919-10.372 11.919-5.725 0-10.366-5.34-10.366-11.919 0-6.586 4.642-11.92 10.366-11.92 5.727 0 10.372 5.334 10.372 11.92zm-.107 11.916v4.19h5.742V21.038h-5.812v19.325c-2.789-3.472-6.805-5.649-11.269-5.649-8.424 0-15.253 7.768-15.253 17.341 0 9.576 6.829 17.344 15.253 17.344 4.496 0 8.536-2.21 11.33-5.724l.009.239z" fill="#6F6F6F"/><circle r="4.185" cy="25.306" cx="344.584" fill="#6F6F6F"/><path fill="#6F6F6F" d="M253.751 50.332l13.835-14.078h7.603l-12.337 12.711 13.21 19.321h-7.354l-10.346-14.959-4.738 4.861v10.225h-5.856V21.422h5.856v28.443zM236.855 46.471c-1.762-3.644-5.163-6.109-9.065-6.109-5.713 0-10.348 5.282-10.348 11.799 0 6.524 4.635 11.806 10.348 11.806 3.93 0 7.347-2.496 9.101-6.183l5.394 2.556c-2.779 5.419-8.227 9.097-14.497 9.097-9.083 0-16.451-7.733-16.451-17.275 0-9.537 7.368-17.269 16.451-17.269 6.247 0 11.683 3.653 14.467 9.041l-5.4 2.537zM160.884 26.693v9.747h-5.849v5.479h5.727v17.052s-.37 13.157 15.103 9.383l-1.947-4.995s-7.065 2.434-7.065-3.896V41.919h7.796V36.56h-7.674v-9.866h-6.091v-.001z"/><path fill="#009245" d="M50.794 41.715L27.708 84.812l46.207-.008z"/><path fill="#006837" d="M27.699 84.804L4.833 44.994h45.958z"/><path fill="#39B54A" d="M50.913 45.008H4.833L27.898 5.12H74.031z"/><path fill="#8CC63F" d="M74.031 5.12l23.236 39.84-23.352 39.844-23.002-39.796z"/></g></svg>
|
After Width: | Height: | Size: 2.9 KiB |
@ -14,8 +14,8 @@
|
|||||||
"description": "Google Stackdriver Datasource for Grafana",
|
"description": "Google Stackdriver Datasource for Grafana",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"logos": {
|
"logos": {
|
||||||
"small": "img/stackdriver_logo.png",
|
"small": "img/stackdriver_logo.svg",
|
||||||
"large": "img/stackdriver_logo.png"
|
"large": "img/stackdriver_logo.svg"
|
||||||
},
|
},
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Grafana Project",
|
"name": "Grafana Project",
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
import { QueryCtrl } from 'app/plugins/sdk';
|
import { QueryCtrl } from 'app/plugins/sdk';
|
||||||
import { Target } from './types';
|
import { StackdriverQuery } from './types';
|
||||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||||
|
|
||||||
export class StackdriverQueryCtrl extends QueryCtrl {
|
export class StackdriverQueryCtrl extends QueryCtrl {
|
||||||
@ -16,7 +16,7 @@ export class StackdriverQueryCtrl extends QueryCtrl {
|
|||||||
this.onExecuteQuery = this.onExecuteQuery.bind(this);
|
this.onExecuteQuery = this.onExecuteQuery.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
onQueryChange(target: Target) {
|
onQueryChange(target: StackdriverQuery) {
|
||||||
Object.assign(this.target, target);
|
Object.assign(this.target, target);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import StackdriverDataSource from '../datasource';
|
import StackdriverDataSource from '../datasource';
|
||||||
import { metricDescriptors } from './testData';
|
import { metricDescriptors } from './testData';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { TemplateSrvStub } from 'test/specs/helpers';
|
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||||
|
import { CustomVariable } from 'app/features/templating/all';
|
||||||
|
|
||||||
describe('StackdriverDataSource', () => {
|
describe('StackdriverDataSource', () => {
|
||||||
const instanceSettings = {
|
const instanceSettings = {
|
||||||
@ -9,7 +10,7 @@ describe('StackdriverDataSource', () => {
|
|||||||
defaultProject: 'testproject',
|
defaultProject: 'testproject',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const templateSrv = new TemplateSrvStub();
|
const templateSrv = new TemplateSrv();
|
||||||
const timeSrv = {};
|
const timeSrv = {};
|
||||||
|
|
||||||
describe('when performing testDataSource', () => {
|
describe('when performing testDataSource', () => {
|
||||||
@ -154,15 +155,41 @@ describe('StackdriverDataSource', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('when interpolating a template variable for the filter', () => {
|
||||||
|
let interpolated;
|
||||||
|
describe('and is single value variable', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
const filterTemplateSrv = initTemplateSrv('filtervalue1');
|
||||||
|
const ds = new StackdriverDataSource(instanceSettings, {}, filterTemplateSrv, timeSrv);
|
||||||
|
interpolated = ds.interpolateFilters(['resource.label.zone', '=~', '${test}'], {});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should replace the variable with the value', () => {
|
||||||
|
expect(interpolated.length).toBe(3);
|
||||||
|
expect(interpolated[2]).toBe('filtervalue1');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('and is multi value variable', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
const filterTemplateSrv = initTemplateSrv(['filtervalue1', 'filtervalue2'], true);
|
||||||
|
const ds = new StackdriverDataSource(instanceSettings, {}, filterTemplateSrv, timeSrv);
|
||||||
|
interpolated = ds.interpolateFilters(['resource.label.zone', '=~', '[[test]]'], {});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should replace the variable with a regex expression', () => {
|
||||||
|
expect(interpolated[2]).toBe('(filtervalue1|filtervalue2)');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('when interpolating a template variable for group bys', () => {
|
describe('when interpolating a template variable for group bys', () => {
|
||||||
let interpolated;
|
let interpolated;
|
||||||
|
|
||||||
describe('and is single value variable', () => {
|
describe('and is single value variable', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
templateSrv.data = {
|
const groupByTemplateSrv = initTemplateSrv('groupby1');
|
||||||
test: 'groupby1',
|
const ds = new StackdriverDataSource(instanceSettings, {}, groupByTemplateSrv, timeSrv);
|
||||||
};
|
|
||||||
const ds = new StackdriverDataSource(instanceSettings, {}, templateSrv, timeSrv);
|
|
||||||
interpolated = ds.interpolateGroupBys(['[[test]]'], {});
|
interpolated = ds.interpolateGroupBys(['[[test]]'], {});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -174,10 +201,8 @@ describe('StackdriverDataSource', () => {
|
|||||||
|
|
||||||
describe('and is multi value variable', () => {
|
describe('and is multi value variable', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
templateSrv.data = {
|
const groupByTemplateSrv = initTemplateSrv(['groupby1', 'groupby2'], true);
|
||||||
test: 'groupby1,groupby2',
|
const ds = new StackdriverDataSource(instanceSettings, {}, groupByTemplateSrv, timeSrv);
|
||||||
};
|
|
||||||
const ds = new StackdriverDataSource(instanceSettings, {}, templateSrv, timeSrv);
|
|
||||||
interpolated = ds.interpolateGroupBys(['[[test]]'], {});
|
interpolated = ds.interpolateGroupBys(['[[test]]'], {});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -241,3 +266,19 @@ describe('StackdriverDataSource', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
function initTemplateSrv(values: any, multi = false) {
|
||||||
|
const templateSrv = new TemplateSrv();
|
||||||
|
templateSrv.init([
|
||||||
|
new CustomVariable(
|
||||||
|
{
|
||||||
|
name: 'test',
|
||||||
|
current: {
|
||||||
|
value: values,
|
||||||
|
},
|
||||||
|
multi: multi,
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
return templateSrv;
|
||||||
|
}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { DataQuery } from '@grafana/ui/src/types';
|
||||||
|
|
||||||
export enum MetricFindQueryTypes {
|
export enum MetricFindQueryTypes {
|
||||||
Services = 'services',
|
Services = 'services',
|
||||||
MetricTypes = 'metricTypes',
|
MetricTypes = 'metricTypes',
|
||||||
@ -20,20 +22,22 @@ export interface VariableQueryData {
|
|||||||
services: Array<{ value: string; name: string }>;
|
services: Array<{ value: string; name: string }>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Target {
|
export interface StackdriverQuery extends DataQuery {
|
||||||
defaultProject: string;
|
defaultProject?: string;
|
||||||
unit: string;
|
unit?: string;
|
||||||
metricType: string;
|
metricType: string;
|
||||||
service: string;
|
service?: string;
|
||||||
refId: string;
|
refId: string;
|
||||||
crossSeriesReducer: string;
|
crossSeriesReducer: string;
|
||||||
alignmentPeriod: string;
|
alignmentPeriod?: string;
|
||||||
perSeriesAligner: string;
|
perSeriesAligner: string;
|
||||||
groupBys: string[];
|
groupBys?: string[];
|
||||||
filters: string[];
|
filters?: string[];
|
||||||
aliasBy: string;
|
aliasBy?: string;
|
||||||
metricKind: string;
|
metricKind: string;
|
||||||
valueType: string;
|
valueType: string;
|
||||||
|
datasourceId?: number;
|
||||||
|
view?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AnnotationTarget {
|
export interface AnnotationTarget {
|
||||||
|
@ -280,6 +280,28 @@ export function grafanaAppDirective(playlistSrv, contextSrv, $timeout, $rootScop
|
|||||||
if (popover.length > 0 && target.parents('.graph-legend').length === 0) {
|
if (popover.length > 0 && target.parents('.graph-legend').length === 0) {
|
||||||
popover.hide();
|
popover.hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// hide time picker
|
||||||
|
const timePickerDropDownIsOpen = elem.find('.gf-timepicker-dropdown').length > 0;
|
||||||
|
if (timePickerDropDownIsOpen) {
|
||||||
|
const targetIsInTimePickerDropDown = target.parents('.gf-timepicker-dropdown').length > 0;
|
||||||
|
const targetIsInTimePickerNav = target.parents('.gf-timepicker-nav').length > 0;
|
||||||
|
const targetIsDatePickerRowBtn = target.parents('td[id^="datepicker-"]').length > 0;
|
||||||
|
const targetIsDatePickerHeaderBtn = target.parents('button[id^="datepicker-"]').length > 0;
|
||||||
|
|
||||||
|
if (
|
||||||
|
targetIsInTimePickerNav ||
|
||||||
|
targetIsInTimePickerDropDown ||
|
||||||
|
targetIsDatePickerRowBtn ||
|
||||||
|
targetIsDatePickerHeaderBtn
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
scope.$apply(() => {
|
||||||
|
scope.appEvent('closeTimepicker');
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { createStore, applyMiddleware, compose, combineReducers } from 'redux';
|
import { createStore, applyMiddleware, compose, combineReducers } from 'redux';
|
||||||
import thunk from 'redux-thunk';
|
import thunk from 'redux-thunk';
|
||||||
// import { createLogger } from 'redux-logger';
|
import { createLogger } from 'redux-logger';
|
||||||
import sharedReducers from 'app/core/reducers';
|
import sharedReducers from 'app/core/reducers';
|
||||||
import alertingReducers from 'app/features/alerting/state/reducers';
|
import alertingReducers from 'app/features/alerting/state/reducers';
|
||||||
import teamsReducers from 'app/features/teams/state/reducers';
|
import teamsReducers from 'app/features/teams/state/reducers';
|
||||||
@ -39,7 +39,7 @@ export function configureStore() {
|
|||||||
|
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
// DEV builds we had the logger middleware
|
// DEV builds we had the logger middleware
|
||||||
setStore(createStore(rootReducer, {}, composeEnhancers(applyMiddleware(thunk))));
|
setStore(createStore(rootReducer, {}, composeEnhancers(applyMiddleware(thunk, createLogger()))));
|
||||||
} else {
|
} else {
|
||||||
setStore(createStore(rootReducer, {}, composeEnhancers(applyMiddleware(thunk))));
|
setStore(createStore(rootReducer, {}, composeEnhancers(applyMiddleware(thunk))));
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,14 @@
|
|||||||
|
import { ComponentClass } from 'react';
|
||||||
import { Value } from 'slate';
|
import { Value } from 'slate';
|
||||||
import { RawTimeRange, TimeRange, DataQuery, DataSourceSelectItem, DataSourceApi, QueryHint } from '@grafana/ui';
|
import {
|
||||||
|
RawTimeRange,
|
||||||
|
TimeRange,
|
||||||
|
DataQuery,
|
||||||
|
DataSourceSelectItem,
|
||||||
|
DataSourceApi,
|
||||||
|
QueryHint,
|
||||||
|
ExploreStartPageProps,
|
||||||
|
} from '@grafana/ui';
|
||||||
|
|
||||||
import { Emitter } from 'app/core/core';
|
import { Emitter } from 'app/core/core';
|
||||||
import { LogsModel } from 'app/core/logs_model';
|
import { LogsModel } from 'app/core/logs_model';
|
||||||
@ -102,7 +111,7 @@ export interface ExploreItemState {
|
|||||||
/**
|
/**
|
||||||
* React component to be shown when no queries have been run yet, e.g., for a query language cheat sheet.
|
* React component to be shown when no queries have been run yet, e.g., for a query language cheat sheet.
|
||||||
*/
|
*/
|
||||||
StartPage?: any;
|
StartPage?: ComponentClass<ExploreStartPageProps>;
|
||||||
/**
|
/**
|
||||||
* Width used for calculating the graph interval (can't have more datapoints than pixels)
|
* Width used for calculating the graph interval (can't have more datapoints than pixels)
|
||||||
*/
|
*/
|
||||||
@ -144,10 +153,10 @@ export interface ExploreItemState {
|
|||||||
*/
|
*/
|
||||||
history: HistoryItem[];
|
history: HistoryItem[];
|
||||||
/**
|
/**
|
||||||
* Initial queries for this Explore, e.g., set via URL. Each query will be
|
* Queries for this Explore, e.g., set via URL. Each query will be
|
||||||
* converted to a query row. Query edits should be tracked in `modifiedQueries` though.
|
* converted to a query row.
|
||||||
*/
|
*/
|
||||||
initialQueries: DataQuery[];
|
queries: DataQuery[];
|
||||||
/**
|
/**
|
||||||
* True if this Explore area has been initialized.
|
* True if this Explore area has been initialized.
|
||||||
* Used to distinguish URL state injection versus split view state injection.
|
* Used to distinguish URL state injection versus split view state injection.
|
||||||
@ -162,12 +171,6 @@ export interface ExploreItemState {
|
|||||||
* Log query result to be displayed in the logs result viewer.
|
* Log query result to be displayed in the logs result viewer.
|
||||||
*/
|
*/
|
||||||
logsResult?: LogsModel;
|
logsResult?: LogsModel;
|
||||||
/**
|
|
||||||
* Copy of `initialQueries` that tracks user edits.
|
|
||||||
* Don't connect this property to a react component as it is updated on every query change.
|
|
||||||
* Used when running queries. Needs to be reset to `initialQueries` when those are reset as well.
|
|
||||||
*/
|
|
||||||
modifiedQueries: DataQuery[];
|
|
||||||
/**
|
/**
|
||||||
* Query intervals for graph queries to determine how many datapoints to return.
|
* Query intervals for graph queries to determine how many datapoints to return.
|
||||||
* Needs to be updated when `datasourceInstance` or `containerWidth` is changed.
|
* Needs to be updated when `datasourceInstance` or `containerWidth` is changed.
|
||||||
@ -229,12 +232,24 @@ export interface ExploreItemState {
|
|||||||
* Table model that combines all query table results into a single table.
|
* Table model that combines all query table results into a single table.
|
||||||
*/
|
*/
|
||||||
tableResult?: TableModel;
|
tableResult?: TableModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* React keys for rendering of QueryRows
|
||||||
|
*/
|
||||||
|
queryKeys: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExploreUIState {
|
||||||
|
showingTable: boolean;
|
||||||
|
showingGraph: boolean;
|
||||||
|
showingLogs: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ExploreUrlState {
|
export interface ExploreUrlState {
|
||||||
datasource: string;
|
datasource: string;
|
||||||
queries: any[]; // Should be a DataQuery, but we're going to strip refIds, so typing makes less sense
|
queries: any[]; // Should be a DataQuery, but we're going to strip refIds, so typing makes less sense
|
||||||
range: RawTimeRange;
|
range: RawTimeRange;
|
||||||
|
ui: ExploreUIState;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HistoryItem<TQuery extends DataQuery = DataQuery> {
|
export interface HistoryItem<TQuery extends DataQuery = DataQuery> {
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
width="121px" height="100px" viewBox="0 0 121 100" style="enable-background:new 0 0 100 100;" xml:space="preserve">
|
width="121px" height="100px" viewBox="0 0 121 100" style="enable-background:new 0 0 100 100;" xml:space="preserve">
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
.st0{fill:#0A0A0C;}
|
.st0{fill:#161719;}
|
||||||
.st1{fill:#E3E2E2;}
|
.st1{fill:#E3E2E2;}
|
||||||
</style>
|
</style>
|
||||||
<g>
|
<g>
|
||||||
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
@ -5,7 +5,7 @@
|
|||||||
width="121px" height="100px" viewBox="0 0 121 100" style="enable-background:new 0 0 121 100;" xml:space="preserve">
|
width="121px" height="100px" viewBox="0 0 121 100" style="enable-background:new 0 0 121 100;" xml:space="preserve">
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
.st0{fill:url(#SVGID_1_);}
|
.st0{fill:url(#SVGID_1_);}
|
||||||
.st1{fill:#0A0A0C;}
|
.st1{fill:#161719;}
|
||||||
.st2{fill:url(#SVGID_2_);}
|
.st2{fill:url(#SVGID_2_);}
|
||||||
.st3{fill:url(#SVGID_3_);}
|
.st3{fill:url(#SVGID_3_);}
|
||||||
.st4{fill:url(#SVGID_4_);}
|
.st4{fill:url(#SVGID_4_);}
|
||||||
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.4 KiB |
@ -4,7 +4,7 @@
|
|||||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
width="121px" height="100px" viewBox="0 0 121 100" style="enable-background:new 0 0 100 100;" xml:space="preserve">
|
width="121px" height="100px" viewBox="0 0 121 100" style="enable-background:new 0 0 100 100;" xml:space="preserve">
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
.st0{fill:#0A0A0C;}
|
.st0{fill:#161719;}
|
||||||
.st1{fill:#E3E2E2;}
|
.st1{fill:#E3E2E2;}
|
||||||
</style>
|
</style>
|
||||||
<g>
|
<g>
|
||||||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
@ -5,7 +5,7 @@
|
|||||||
width="121px" height="100px" viewBox="0 0 121 100" style="enable-background:new 0 0 121 100;" xml:space="preserve">
|
width="121px" height="100px" viewBox="0 0 121 100" style="enable-background:new 0 0 121 100;" xml:space="preserve">
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
.st0{fill:url(#SVGID_1_);}
|
.st0{fill:url(#SVGID_1_);}
|
||||||
.st1{fill:#0A0A0C;}
|
.st1{fill:#161719;}
|
||||||
.st2{fill:url(#SVGID_2_);}
|
.st2{fill:url(#SVGID_2_);}
|
||||||
.st3{fill:url(#SVGID_3_);}
|
.st3{fill:url(#SVGID_3_);}
|
||||||
</style>
|
</style>
|
||||||
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
@ -4,7 +4,7 @@
|
|||||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
width="121px" height="100px" viewBox="0 0 121 100" style="enable-background:new 0 0 100 100;" xml:space="preserve">
|
width="121px" height="100px" viewBox="0 0 121 100" style="enable-background:new 0 0 100 100;" xml:space="preserve">
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
.st0{fill:#0A0A0C;}
|
.st0{fill:#161719;}
|
||||||
.st1{fill:#E3E2E2;}
|
.st1{fill:#E3E2E2;}
|
||||||
</style>
|
</style>
|
||||||
<g>
|
<g>
|
||||||
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
@ -5,7 +5,7 @@
|
|||||||
width="121px" height="100px" viewBox="0 0 121 100" style="enable-background:new 0 0 121 100;" xml:space="preserve">
|
width="121px" height="100px" viewBox="0 0 121 100" style="enable-background:new 0 0 121 100;" xml:space="preserve">
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
.st0{fill:url(#SVGID_1_);}
|
.st0{fill:url(#SVGID_1_);}
|
||||||
.st1{fill:#0A0A0C;}
|
.st1{fill:#161719;}
|
||||||
.st2{fill:url(#SVGID_2_);}
|
.st2{fill:url(#SVGID_2_);}
|
||||||
.st3{fill:url(#SVGID_3_);}
|
.st3{fill:url(#SVGID_3_);}
|
||||||
.st4{fill:url(#SVGID_4_);}
|
.st4{fill:url(#SVGID_4_);}
|
||||||
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
@ -4,7 +4,7 @@
|
|||||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
width="121px" height="100px" viewBox="0 0 121 100" style="enable-background:new 0 0 100 100;" xml:space="preserve">
|
width="121px" height="100px" viewBox="0 0 121 100" style="enable-background:new 0 0 100 100;" xml:space="preserve">
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
.st0{fill:#0A0A0C;}
|
.st0{fill:#161719;}
|
||||||
.st1{fill:#E3E2E2;}
|
.st1{fill:#E3E2E2;}
|
||||||
</style>
|
</style>
|
||||||
<path class="st0" d="M94.3,50C94.3,25.6,74.4,5.7,50,5.7S5.7,25.6,5.7,50S25.6,94.3,50,94.3S94.3,74.4,94.3,50z"/>
|
<path class="st0" d="M94.3,50C94.3,25.6,74.4,5.7,50,5.7S5.7,25.6,5.7,50S25.6,94.3,50,94.3S94.3,74.4,94.3,50z"/>
|
||||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
@ -5,7 +5,7 @@
|
|||||||
width="121px" height="100px" viewBox="0 0 121 100" style="enable-background:new 0 0 121 100;" xml:space="preserve">
|
width="121px" height="100px" viewBox="0 0 121 100" style="enable-background:new 0 0 121 100;" xml:space="preserve">
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
.st0{fill:url(#SVGID_1_);}
|
.st0{fill:url(#SVGID_1_);}
|
||||||
.st1{fill:#0A0A0C;}
|
.st1{fill:#161719;}
|
||||||
.st2{fill:url(#SVGID_2_);}
|
.st2{fill:url(#SVGID_2_);}
|
||||||
.st3{fill:url(#SVGID_3_);}
|
.st3{fill:url(#SVGID_3_);}
|
||||||
</style>
|
</style>
|
||||||
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
@ -66,6 +66,7 @@ $text-color-emphasis: getThemeVariable('colors.textColorEmphasis', $theme-name);
|
|||||||
|
|
||||||
$text-shadow-strong: 1px 1px 4px getThemeVariable('colors.black', $theme-name);
|
$text-shadow-strong: 1px 1px 4px getThemeVariable('colors.black', $theme-name);
|
||||||
$text-shadow-faint: 1px 1px 4px #2d2d2d;
|
$text-shadow-faint: 1px 1px 4px #2d2d2d;
|
||||||
|
$textShadow: none;
|
||||||
|
|
||||||
// gradients
|
// gradients
|
||||||
$brand-gradient: linear-gradient(
|
$brand-gradient: linear-gradient(
|
||||||
@ -97,8 +98,7 @@ $hr-border-color: $dark-4;
|
|||||||
// Panel
|
// Panel
|
||||||
// -------------------------
|
// -------------------------
|
||||||
$panel-bg: #212124;
|
$panel-bg: #212124;
|
||||||
$panel-border-color: $dark-1;
|
$panel-border: solid 1px $dark-1;
|
||||||
$panel-border: solid 1px $panel-border-color;
|
|
||||||
$panel-header-hover-bg: $dark-4;
|
$panel-header-hover-bg: $dark-4;
|
||||||
$panel-corner: $panel-bg;
|
$panel-corner: $panel-bg;
|
||||||
|
|
||||||
@ -107,12 +107,12 @@ $page-header-bg: linear-gradient(90deg, #292a2d, black);
|
|||||||
$page-header-shadow: inset 0px -4px 14px $dark-2;
|
$page-header-shadow: inset 0px -4px 14px $dark-2;
|
||||||
$page-header-border-color: $dark-4;
|
$page-header-border-color: $dark-4;
|
||||||
|
|
||||||
$divider-border-color: #555;
|
$divider-border-color: $gray-1;
|
||||||
|
|
||||||
// Graphite Target Editor
|
// Graphite Target Editor
|
||||||
$tight-form-bg: $dark-3;
|
$tight-form-bg: $dark-3;
|
||||||
$tight-form-func-bg: #333334;
|
$tight-form-func-bg: $dark-4;
|
||||||
$tight-form-func-highlight-bg: #444445;
|
$tight-form-func-highlight-bg: $dark-5;
|
||||||
|
|
||||||
$modal-backdrop-bg: #353c42;
|
$modal-backdrop-bg: #353c42;
|
||||||
$code-tag-bg: $dark-1;
|
$code-tag-bg: $dark-1;
|
||||||
@ -134,14 +134,12 @@ $empty-list-cta-bg: $gray-blue;
|
|||||||
// Scrollbars
|
// Scrollbars
|
||||||
$scrollbarBackground: #aeb5df;
|
$scrollbarBackground: #aeb5df;
|
||||||
$scrollbarBackground2: #3a3a3a;
|
$scrollbarBackground2: #3a3a3a;
|
||||||
|
|
||||||
$scrollbarBorder: black;
|
$scrollbarBorder: black;
|
||||||
|
|
||||||
// Tables
|
// Tables
|
||||||
// -------------------------
|
// -------------------------
|
||||||
$table-bg: transparent; // overall background-color
|
$table-bg: transparent; // overall background-color
|
||||||
$table-bg-accent: $dark-3; // for striping
|
$table-bg-accent: $dark-3; // for striping
|
||||||
$table-bg-hover: $dark-4; // for hover
|
|
||||||
$table-border: $dark-3; // table and cell border
|
$table-border: $dark-3; // table and cell border
|
||||||
|
|
||||||
$table-bg-odd: $dark-2;
|
$table-bg-odd: $dark-2;
|
||||||
@ -149,7 +147,6 @@ $table-bg-hover: $dark-3;
|
|||||||
|
|
||||||
// Buttons
|
// Buttons
|
||||||
// -------------------------
|
// -------------------------
|
||||||
|
|
||||||
$btn-primary-bg: #ff6600;
|
$btn-primary-bg: #ff6600;
|
||||||
$btn-primary-bg-hl: #bc3e06;
|
$btn-primary-bg-hl: #bc3e06;
|
||||||
|
|
||||||
@ -170,9 +167,6 @@ $btn-inverse-bg-hl: lighten($dark-3, 4%);
|
|||||||
$btn-inverse-text-color: $link-color;
|
$btn-inverse-text-color: $link-color;
|
||||||
$btn-inverse-text-shadow: 0px 1px 0 rgba(0, 0, 0, 0.1);
|
$btn-inverse-text-shadow: 0px 1px 0 rgba(0, 0, 0, 0.1);
|
||||||
|
|
||||||
$btn-active-bg: $gray-4;
|
|
||||||
$btn-active-text-color: $blue-dark;
|
|
||||||
|
|
||||||
$btn-link-color: $gray-3;
|
$btn-link-color: $gray-3;
|
||||||
|
|
||||||
$iconContainerBackground: $black;
|
$iconContainerBackground: $black;
|
||||||
@ -197,6 +191,9 @@ $input-label-bg: $gray-blue;
|
|||||||
$input-label-border-color: $dark-3;
|
$input-label-border-color: $dark-3;
|
||||||
$input-color-select-arrow: $white;
|
$input-color-select-arrow: $white;
|
||||||
|
|
||||||
|
// Input placeholder text color
|
||||||
|
$placeholderText: darken($text-color, 25%);
|
||||||
|
|
||||||
// Search
|
// Search
|
||||||
$search-shadow: 0 0 30px 0 $black;
|
$search-shadow: 0 0 30px 0 $black;
|
||||||
$search-filter-box-bg: $gray-blue;
|
$search-filter-box-bg: $gray-blue;
|
||||||
@ -212,28 +209,19 @@ $dropdownBackground: $dark-3;
|
|||||||
$dropdownBorder: rgba(0, 0, 0, 0.2);
|
$dropdownBorder: rgba(0, 0, 0, 0.2);
|
||||||
$dropdownDividerTop: transparent;
|
$dropdownDividerTop: transparent;
|
||||||
$dropdownDividerBottom: #444;
|
$dropdownDividerBottom: #444;
|
||||||
$dropdownDivider: $dropdownDividerBottom;
|
|
||||||
|
|
||||||
$dropdownLinkColor: $text-color;
|
$dropdownLinkColor: $text-color;
|
||||||
$dropdownLinkColorHover: $white;
|
$dropdownLinkColorHover: $white;
|
||||||
$dropdownLinkColorActive: $white;
|
$dropdownLinkColorActive: $white;
|
||||||
|
|
||||||
$dropdownLinkBackgroundActive: $dark-4;
|
|
||||||
$dropdownLinkBackgroundHover: $dark-4;
|
$dropdownLinkBackgroundHover: $dark-4;
|
||||||
|
|
||||||
// COMPONENT VARIABLES
|
|
||||||
// --------------------------------------------------
|
|
||||||
|
|
||||||
// -------------------------
|
|
||||||
$placeholderText: darken($text-color, 25%);
|
|
||||||
|
|
||||||
// Horizontal forms & lists
|
// Horizontal forms & lists
|
||||||
// -------------------------
|
// -------------------------
|
||||||
$horizontalComponentOffset: 180px;
|
$horizontalComponentOffset: 180px;
|
||||||
|
|
||||||
// Wells
|
// Navbar
|
||||||
// -------------------------
|
// -------------------------
|
||||||
|
|
||||||
$navbarHeight: 55px;
|
$navbarHeight: 55px;
|
||||||
|
|
||||||
$navbarBackground: $panel-bg;
|
$navbarBackground: $panel-bg;
|
||||||
@ -261,9 +249,6 @@ $menu-dropdown-bg: $body-bg;
|
|||||||
$menu-dropdown-hover-bg: $dark-2;
|
$menu-dropdown-hover-bg: $dark-2;
|
||||||
$menu-dropdown-shadow: 5px 5px 20px -5px $black;
|
$menu-dropdown-shadow: 5px 5px 20px -5px $black;
|
||||||
|
|
||||||
// Breadcrumb
|
|
||||||
// -------------------------
|
|
||||||
|
|
||||||
// Tabs
|
// Tabs
|
||||||
// -------------------------
|
// -------------------------
|
||||||
$tab-border-color: $dark-4;
|
$tab-border-color: $dark-4;
|
||||||
@ -271,9 +256,6 @@ $tab-border-color: $dark-4;
|
|||||||
// Toolbar
|
// Toolbar
|
||||||
$toolbar-bg: $input-black;
|
$toolbar-bg: $input-black;
|
||||||
|
|
||||||
// Pagination
|
|
||||||
// -------------------------
|
|
||||||
|
|
||||||
// Form states and alerts
|
// Form states and alerts
|
||||||
// -------------------------
|
// -------------------------
|
||||||
$warning-text-color: $warn;
|
$warning-text-color: $warn;
|
||||||
@ -308,7 +290,6 @@ $tooltipBackground: $black;
|
|||||||
$tooltipColor: $gray-4;
|
$tooltipColor: $gray-4;
|
||||||
$tooltipArrowColor: $tooltipBackground;
|
$tooltipArrowColor: $tooltipBackground;
|
||||||
$tooltipBackgroundError: $brand-danger;
|
$tooltipBackgroundError: $brand-danger;
|
||||||
$tooltipBackgroundBrand: $brand-primary;
|
|
||||||
|
|
||||||
// images
|
// images
|
||||||
$checkboxImageUrl: '../img/checkbox.png';
|
$checkboxImageUrl: '../img/checkbox.png';
|
||||||
@ -377,9 +358,7 @@ $checkbox-color: $dark-1;
|
|||||||
//Panel Edit
|
//Panel Edit
|
||||||
// -------------------------
|
// -------------------------
|
||||||
$panel-editor-shadow: 0 0 20px black;
|
$panel-editor-shadow: 0 0 20px black;
|
||||||
$panel-editor-border: 1px solid $dark-3;
|
|
||||||
$panel-editor-side-menu-shadow: drop-shadow(0 0 10px $black);
|
$panel-editor-side-menu-shadow: drop-shadow(0 0 10px $black);
|
||||||
$panel-editor-toolbar-view-bg: $input-black;
|
|
||||||
$panel-editor-viz-item-shadow: 0 0 8px $dark-5;
|
$panel-editor-viz-item-shadow: 0 0 8px $dark-5;
|
||||||
$panel-editor-viz-item-border: 1px solid $dark-5;
|
$panel-editor-viz-item-border: 1px solid $dark-5;
|
||||||
$panel-editor-viz-item-shadow-hover: 0 0 4px $blue;
|
$panel-editor-viz-item-shadow-hover: 0 0 4px $blue;
|
||||||
@ -387,7 +366,6 @@ $panel-editor-viz-item-border-hover: 1px solid $blue;
|
|||||||
$panel-editor-viz-item-bg: $input-black;
|
$panel-editor-viz-item-bg: $input-black;
|
||||||
$panel-editor-tabs-line-color: #e3e3e3;
|
$panel-editor-tabs-line-color: #e3e3e3;
|
||||||
$panel-editor-viz-item-bg-hover: darken($blue, 47%);
|
$panel-editor-viz-item-bg-hover: darken($blue, 47%);
|
||||||
$panel-editor-viz-item-bg-hover-active: darken($orange, 45%);
|
|
||||||
|
|
||||||
$panel-options-group-border: none;
|
$panel-options-group-border: none;
|
||||||
$panel-options-group-header-bg: $gray-blue;
|
$panel-options-group-header-bg: $gray-blue;
|
||||||
|
@ -1,7 +1,3 @@
|
|||||||
// Cosmo 2.3.2
|
|
||||||
// Variables
|
|
||||||
// --------------------------------------------------
|
|
||||||
|
|
||||||
// Global values
|
// Global values
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
|
|
||||||
@ -71,12 +67,17 @@ $text-color-weak: getThemeVariable('colors.textColorWeak', $theme-name);
|
|||||||
$text-color-faint: getThemeVariable('colors.textColorFaint', $theme-name);
|
$text-color-faint: getThemeVariable('colors.textColorFaint', $theme-name);
|
||||||
$text-color-emphasis: getThemeVariable('colors.textColorEmphasis', $theme-name);
|
$text-color-emphasis: getThemeVariable('colors.textColorEmphasis', $theme-name);
|
||||||
|
|
||||||
$text-shadow-strong: none;
|
|
||||||
$text-shadow-faint: none;
|
$text-shadow-faint: none;
|
||||||
$textShadow: none;
|
$textShadow: none;
|
||||||
|
|
||||||
// gradients
|
// gradients
|
||||||
$brand-gradient: linear-gradient(to right, hsl(50, 100%, 50%) 0%, rgba(255, 68, 0, 1) 99%, rgba(255, 68, 0, 1) 100%);
|
$brand-gradient: linear-gradient(
|
||||||
|
to right,
|
||||||
|
rgba(255, 213, 0, 1) 0%,
|
||||||
|
rgba(255, 68, 0, 1) 99%,
|
||||||
|
rgba(255, 68, 0, 1) 100%
|
||||||
|
);
|
||||||
|
|
||||||
$page-gradient: linear-gradient(180deg, $white 10px, $gray-7 100px);
|
$page-gradient: linear-gradient(180deg, $white 10px, $gray-7 100px);
|
||||||
$edit-gradient: linear-gradient(-60deg, $gray-7, #f5f6f9 70%, $gray-7 98%);
|
$edit-gradient: linear-gradient(-60deg, $gray-7, #f5f6f9 70%, $gray-7 98%);
|
||||||
|
|
||||||
@ -98,10 +99,8 @@ $hr-border-color: $dark-3 !default;
|
|||||||
|
|
||||||
// Panel
|
// Panel
|
||||||
// -------------------------
|
// -------------------------
|
||||||
|
|
||||||
$panel-bg: $white;
|
$panel-bg: $white;
|
||||||
$panel-border-color: $gray-5;
|
$panel-border: solid 1px $gray-5;
|
||||||
$panel-border: solid 1px $panel-border-color;
|
|
||||||
$panel-header-hover-bg: $gray-6;
|
$panel-header-hover-bg: $gray-6;
|
||||||
$panel-corner: $gray-4;
|
$panel-corner: $gray-4;
|
||||||
|
|
||||||
@ -114,7 +113,6 @@ $divider-border-color: $gray-2;
|
|||||||
|
|
||||||
// Graphite Target Editor
|
// Graphite Target Editor
|
||||||
$tight-form-bg: #eaebee;
|
$tight-form-bg: #eaebee;
|
||||||
|
|
||||||
$tight-form-func-bg: $gray-5;
|
$tight-form-func-bg: $gray-5;
|
||||||
$tight-form-func-highlight-bg: $gray-6;
|
$tight-form-func-highlight-bg: $gray-6;
|
||||||
|
|
||||||
@ -132,24 +130,23 @@ $list-item-bg: linear-gradient(135deg, $gray-5, $gray-6); //$card-background;
|
|||||||
$list-item-hover-bg: darken($gray-5, 5%);
|
$list-item-hover-bg: darken($gray-5, 5%);
|
||||||
$list-item-link-color: $text-color;
|
$list-item-link-color: $text-color;
|
||||||
$list-item-shadow: $card-shadow;
|
$list-item-shadow: $card-shadow;
|
||||||
|
|
||||||
$empty-list-cta-bg: $gray-6;
|
$empty-list-cta-bg: $gray-6;
|
||||||
|
|
||||||
// Tables
|
|
||||||
// -------------------------
|
|
||||||
$table-bg: transparent; // overall background-color
|
|
||||||
$table-bg-accent: $gray-5; // for striping
|
|
||||||
$table-bg-hover: $gray-5; // for hover
|
|
||||||
$table-bg-active: $table-bg-hover !default;
|
|
||||||
$table-border: $gray-3; // table and cell border
|
|
||||||
|
|
||||||
$table-bg-odd: $gray-6;
|
|
||||||
$table-bg-hover: $gray-5;
|
|
||||||
|
|
||||||
// Scrollbars
|
// Scrollbars
|
||||||
$scrollbarBackground: $gray-5;
|
$scrollbarBackground: $gray-5;
|
||||||
$scrollbarBackground2: $gray-5;
|
$scrollbarBackground2: $gray-5;
|
||||||
$scrollbarBorder: $gray-4;
|
$scrollbarBorder: $gray-4;
|
||||||
|
|
||||||
|
// Tables
|
||||||
|
// -------------------------
|
||||||
|
$table-bg: transparent; // overall background-color
|
||||||
|
$table-bg-accent: $gray-5; // for striping
|
||||||
|
$table-border: $gray-3; // table and cell border
|
||||||
|
|
||||||
|
$table-bg-odd: $gray-6;
|
||||||
|
$table-bg-hover: $gray-5;
|
||||||
|
|
||||||
// Buttons
|
// Buttons
|
||||||
// -------------------------
|
// -------------------------
|
||||||
$btn-primary-bg: $brand-primary;
|
$btn-primary-bg: $brand-primary;
|
||||||
@ -172,16 +169,14 @@ $btn-inverse-bg-hl: darken($gray-6, 5%);
|
|||||||
$btn-inverse-text-color: $gray-1;
|
$btn-inverse-text-color: $gray-1;
|
||||||
$btn-inverse-text-shadow: 0 1px 0 rgba(255, 255, 255, 0.4);
|
$btn-inverse-text-shadow: 0 1px 0 rgba(255, 255, 255, 0.4);
|
||||||
|
|
||||||
$btn-active-bg: $white;
|
|
||||||
$btn-active-text-color: $blue;
|
|
||||||
|
|
||||||
$btn-link-color: $gray-1;
|
$btn-link-color: $gray-1;
|
||||||
|
|
||||||
|
$iconContainerBackground: $white;
|
||||||
|
|
||||||
$btn-divider-left: $gray-4;
|
$btn-divider-left: $gray-4;
|
||||||
$btn-divider-right: $gray-7;
|
$btn-divider-right: $gray-7;
|
||||||
$btn-drag-image: '../img/grab_light.svg';
|
|
||||||
|
|
||||||
$iconContainerBackground: $white;
|
$btn-drag-image: '../img/grab_light.svg';
|
||||||
|
|
||||||
// Forms
|
// Forms
|
||||||
// -------------------------
|
// -------------------------
|
||||||
@ -198,29 +193,8 @@ $input-label-bg: $gray-5;
|
|||||||
$input-label-border-color: $gray-5;
|
$input-label-border-color: $gray-5;
|
||||||
$input-color-select-arrow: $gray-1;
|
$input-color-select-arrow: $gray-1;
|
||||||
|
|
||||||
// Sidemenu
|
// Input placeholder text color
|
||||||
// -------------------------
|
$placeholderText: $gray-2;
|
||||||
$side-menu-bg: $dark-2;
|
|
||||||
$side-menu-bg-mobile: rgba(0, 0, 0, 0); //$gray-6;
|
|
||||||
$side-menu-item-hover-bg: $gray-1;
|
|
||||||
$side-menu-shadow: 5px 0px 10px -5px $gray-1;
|
|
||||||
$side-menu-link-color: $gray-6;
|
|
||||||
|
|
||||||
// Menu dropdowns
|
|
||||||
// -------------------------
|
|
||||||
$menu-dropdown-bg: $gray-7;
|
|
||||||
$menu-dropdown-hover-bg: $gray-6;
|
|
||||||
$menu-dropdown-shadow: 5px 5px 10px -5px $gray-1;
|
|
||||||
|
|
||||||
// Breadcrumb
|
|
||||||
// -------------------------
|
|
||||||
|
|
||||||
// Tabs
|
|
||||||
// -------------------------
|
|
||||||
$tab-border-color: $gray-5;
|
|
||||||
|
|
||||||
// Toolbar
|
|
||||||
$toolbar-bg: white;
|
|
||||||
|
|
||||||
// search
|
// search
|
||||||
$search-shadow: 0 5px 30px 0 $gray-4;
|
$search-shadow: 0 5px 30px 0 $gray-4;
|
||||||
@ -237,52 +211,52 @@ $dropdownBackground: $white;
|
|||||||
$dropdownBorder: $gray-4;
|
$dropdownBorder: $gray-4;
|
||||||
$dropdownDividerTop: $gray-6;
|
$dropdownDividerTop: $gray-6;
|
||||||
$dropdownDividerBottom: $white;
|
$dropdownDividerBottom: $white;
|
||||||
$dropdownDivider: $dropdownDividerTop;
|
|
||||||
|
|
||||||
$dropdownLinkColor: $dark-3;
|
$dropdownLinkColor: $dark-3;
|
||||||
$dropdownLinkColorHover: $link-color;
|
$dropdownLinkColorHover: $link-color;
|
||||||
$dropdownLinkColorActive: $link-color;
|
$dropdownLinkColorActive: $link-color;
|
||||||
|
|
||||||
$dropdownLinkBackgroundActive: $gray-6;
|
|
||||||
$dropdownLinkBackgroundHover: $gray-6;
|
$dropdownLinkBackgroundHover: $gray-6;
|
||||||
|
|
||||||
// COMPONENT VARIABLES
|
|
||||||
// --------------------------------------------------
|
|
||||||
|
|
||||||
// Input placeholder text color
|
|
||||||
// -------------------------
|
|
||||||
$placeholderText: $gray-2;
|
|
||||||
|
|
||||||
// Hr border color
|
|
||||||
// -------------------------
|
|
||||||
$hrBorder: $gray-3;
|
|
||||||
|
|
||||||
// Horizontal forms & lists
|
// Horizontal forms & lists
|
||||||
// -------------------------
|
// -------------------------
|
||||||
$horizontalComponentOffset: 180px;
|
$horizontalComponentOffset: 180px;
|
||||||
|
|
||||||
// Wells
|
|
||||||
// -------------------------
|
|
||||||
|
|
||||||
// Navbar
|
// Navbar
|
||||||
// -------------------------
|
// -------------------------
|
||||||
|
|
||||||
$navbarHeight: 52px;
|
$navbarHeight: 52px;
|
||||||
|
|
||||||
$navbarBackground: $white;
|
$navbarBackground: $white;
|
||||||
$navbarBorder: 1px solid $gray-4;
|
$navbarBorder: 1px solid $gray-4;
|
||||||
$navbarShadow: 0 0 3px #c1c1c1;
|
$navbarShadow: 0 0 3px #c1c1c1;
|
||||||
|
|
||||||
$navbarLinkColor: #444;
|
$navbarLinkColor: #444;
|
||||||
|
|
||||||
$navbarBrandColor: $navbarLinkColor;
|
|
||||||
|
|
||||||
$navbarButtonBackground: lighten($navbarBackground, 3%);
|
$navbarButtonBackground: lighten($navbarBackground, 3%);
|
||||||
$navbarButtonBackgroundHighlight: lighten($navbarBackground, 5%);
|
$navbarButtonBackgroundHighlight: lighten($navbarBackground, 5%);
|
||||||
|
|
||||||
$navbar-button-border: $gray-4;
|
$navbar-button-border: $gray-4;
|
||||||
|
|
||||||
// Pagination
|
// Sidemenu
|
||||||
// -------------------------
|
// -------------------------
|
||||||
|
$side-menu-bg: $dark-2;
|
||||||
|
$side-menu-bg-mobile: rgba(0, 0, 0, 0); //$gray-6;
|
||||||
|
$side-menu-item-hover-bg: $gray-1;
|
||||||
|
$side-menu-shadow: 5px 0px 10px -5px $gray-1;
|
||||||
|
$side-menu-link-color: $gray-6;
|
||||||
|
|
||||||
|
// Menu dropdowns
|
||||||
|
// -------------------------
|
||||||
|
$menu-dropdown-bg: $gray-7;
|
||||||
|
$menu-dropdown-hover-bg: $gray-6;
|
||||||
|
$menu-dropdown-shadow: 5px 5px 10px -5px $gray-1;
|
||||||
|
|
||||||
|
// Tabs
|
||||||
|
// -------------------------
|
||||||
|
$tab-border-color: $gray-5;
|
||||||
|
|
||||||
|
// Toolbar
|
||||||
|
$toolbar-bg: white;
|
||||||
|
|
||||||
// Form states and alerts
|
// Form states and alerts
|
||||||
// -------------------------
|
// -------------------------
|
||||||
@ -304,6 +278,7 @@ $popover-shadow: 0 0 20px $white;
|
|||||||
|
|
||||||
$popover-help-bg: $blue;
|
$popover-help-bg: $blue;
|
||||||
$popover-help-color: $gray-6;
|
$popover-help-color: $gray-6;
|
||||||
|
|
||||||
$popover-error-bg: $btn-danger-bg;
|
$popover-error-bg: $btn-danger-bg;
|
||||||
|
|
||||||
// Tooltips and popovers
|
// Tooltips and popovers
|
||||||
@ -317,7 +292,6 @@ $tooltipBackground: $gray-1;
|
|||||||
$tooltipColor: $gray-7;
|
$tooltipColor: $gray-7;
|
||||||
$tooltipArrowColor: $tooltipBackground; // Used by Angular tooltip
|
$tooltipArrowColor: $tooltipBackground; // Used by Angular tooltip
|
||||||
$tooltipBackgroundError: $brand-danger;
|
$tooltipBackgroundError: $brand-danger;
|
||||||
$tooltipBackgroundBrand: $brand-primary;
|
|
||||||
|
|
||||||
// images
|
// images
|
||||||
$checkboxImageUrl: '../img/checkbox_white.png';
|
$checkboxImageUrl: '../img/checkbox_white.png';
|
||||||
@ -329,8 +303,6 @@ $info-box-border-color: lighten($blue, 20%);
|
|||||||
$footer-link-color: $gray-3;
|
$footer-link-color: $gray-3;
|
||||||
$footer-link-hover: $dark-5;
|
$footer-link-hover: $dark-5;
|
||||||
|
|
||||||
// collapse box
|
|
||||||
|
|
||||||
// json explorer
|
// json explorer
|
||||||
$json-explorer-default-color: black;
|
$json-explorer-default-color: black;
|
||||||
$json-explorer-string-color: green;
|
$json-explorer-string-color: green;
|
||||||
@ -350,9 +322,6 @@ $json-explorer-url-color: blue;
|
|||||||
$diff-label-bg: $gray-5;
|
$diff-label-bg: $gray-5;
|
||||||
$diff-label-fg: $gray-2;
|
$diff-label-fg: $gray-2;
|
||||||
|
|
||||||
$diff-switch-bg: $gray-7;
|
|
||||||
$diff-switch-disabled: $gray-5;
|
|
||||||
|
|
||||||
$diff-arrow-color: $dark-3;
|
$diff-arrow-color: $dark-3;
|
||||||
$diff-group-bg: $gray-7;
|
$diff-group-bg: $gray-7;
|
||||||
|
|
||||||
@ -367,6 +336,7 @@ $diff-json-new: #664e33;
|
|||||||
|
|
||||||
$diff-json-changed-fg: $gray-6;
|
$diff-json-changed-fg: $gray-6;
|
||||||
$diff-json-changed-num: $gray-4;
|
$diff-json-changed-num: $gray-4;
|
||||||
|
|
||||||
$diff-json-icon: $gray-4;
|
$diff-json-icon: $gray-4;
|
||||||
|
|
||||||
//Submenu
|
//Submenu
|
||||||
@ -390,9 +360,7 @@ $checkbox-color: $gray-7;
|
|||||||
//Panel Edit
|
//Panel Edit
|
||||||
// -------------------------
|
// -------------------------
|
||||||
$panel-editor-shadow: 0px 0px 8px $gray-3;
|
$panel-editor-shadow: 0px 0px 8px $gray-3;
|
||||||
$panel-editor-border: 1px solid $dark-4;
|
|
||||||
$panel-editor-side-menu-shadow: drop-shadow(0 0 2px $gray-3);
|
$panel-editor-side-menu-shadow: drop-shadow(0 0 2px $gray-3);
|
||||||
$panel-editor-toolbar-view-bg: $white;
|
|
||||||
$panel-editor-viz-item-shadow: 0 0 4px $gray-3;
|
$panel-editor-viz-item-shadow: 0 0 4px $gray-3;
|
||||||
$panel-editor-viz-item-border: 1px solid $gray-3;
|
$panel-editor-viz-item-border: 1px solid $gray-3;
|
||||||
$panel-editor-viz-item-shadow-hover: 0 0 4px $blue-light;
|
$panel-editor-viz-item-shadow-hover: 0 0 4px $blue-light;
|
||||||
@ -400,7 +368,6 @@ $panel-editor-viz-item-border-hover: 1px solid $blue-light;
|
|||||||
$panel-editor-viz-item-bg: $white;
|
$panel-editor-viz-item-bg: $white;
|
||||||
$panel-editor-tabs-line-color: $dark-5;
|
$panel-editor-tabs-line-color: $dark-5;
|
||||||
$panel-editor-viz-item-bg-hover: lighten($blue, 62%);
|
$panel-editor-viz-item-bg-hover: lighten($blue, 62%);
|
||||||
$panel-editor-viz-item-bg-hover-active: lighten($orange, 34%);
|
|
||||||
|
|
||||||
$panel-options-group-border: none;
|
$panel-options-group-border: none;
|
||||||
$panel-options-group-header-bg: $gray-5;
|
$panel-options-group-header-bg: $gray-5;
|
||||||
|
@ -212,7 +212,7 @@
|
|||||||
padding-right: 5px;
|
padding-right: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.panel-editor-tabs {
|
.panel-editor-tabs, .add-panel-widget__icon {
|
||||||
.gicon-advanced-active {
|
.gicon-advanced-active {
|
||||||
background-image: url('../img/icons_#{$theme-name}_theme/icon_advanced_active.svg');
|
background-image: url('../img/icons_#{$theme-name}_theme/icon_advanced_active.svg');
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
&.ace_editor {
|
&.ace_editor {
|
||||||
@include font-family-monospace();
|
@include font-family-monospace();
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
min-height: 2.6rem;
|
min-height: 3.6rem; // Include space for horizontal scrollbar
|
||||||
|
|
||||||
@include border-radius($input-border-radius-sm);
|
@include border-radius($input-border-radius-sm);
|
||||||
border: $input-btn-border-width solid $input-border-color;
|
border: $input-btn-border-width solid $input-border-color;
|
||||||
|
@ -84,6 +84,10 @@ $input-border: 1px solid $input-border-color;
|
|||||||
.gf-form + .gf-form {
|
.gf-form + .gf-form {
|
||||||
margin-left: $gf-form-margin;
|
margin-left: $gf-form-margin;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&--nowrap {
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.gf-form-button-row {
|
.gf-form-button-row {
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 3px 20px 3px 20px;
|
padding: 3px 20px 3px 20px;
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
background: $toolbar-bg;
|
background: $toolbar-bg;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
|
@ -83,10 +83,18 @@ button.close {
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
}
|
}
|
||||||
|
|
||||||
.flex-grow {
|
.flex-grow-1 {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.flex-shrink-1 {
|
||||||
|
flex-shrink: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-shrink-0 {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.center-vh {
|
.center-vh {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
25
vendor/github.com/aws/aws-sdk-go/aws/awsutil/string_value.go
generated
vendored
@ -23,28 +23,27 @@ func stringValue(v reflect.Value, indent int, buf *bytes.Buffer) {
|
|||||||
case reflect.Struct:
|
case reflect.Struct:
|
||||||
buf.WriteString("{\n")
|
buf.WriteString("{\n")
|
||||||
|
|
||||||
names := []string{}
|
|
||||||
for i := 0; i < v.Type().NumField(); i++ {
|
for i := 0; i < v.Type().NumField(); i++ {
|
||||||
name := v.Type().Field(i).Name
|
ft := v.Type().Field(i)
|
||||||
f := v.Field(i)
|
fv := v.Field(i)
|
||||||
if name[0:1] == strings.ToLower(name[0:1]) {
|
|
||||||
|
if ft.Name[0:1] == strings.ToLower(ft.Name[0:1]) {
|
||||||
continue // ignore unexported fields
|
continue // ignore unexported fields
|
||||||
}
|
}
|
||||||
if (f.Kind() == reflect.Ptr || f.Kind() == reflect.Slice) && f.IsNil() {
|
if (fv.Kind() == reflect.Ptr || fv.Kind() == reflect.Slice) && fv.IsNil() {
|
||||||
continue // ignore unset fields
|
continue // ignore unset fields
|
||||||
}
|
}
|
||||||
names = append(names, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, n := range names {
|
|
||||||
val := v.FieldByName(n)
|
|
||||||
buf.WriteString(strings.Repeat(" ", indent+2))
|
buf.WriteString(strings.Repeat(" ", indent+2))
|
||||||
buf.WriteString(n + ": ")
|
buf.WriteString(ft.Name + ": ")
|
||||||
stringValue(val, indent+2, buf)
|
|
||||||
|
|
||||||
if i < len(names)-1 {
|
if tag := ft.Tag.Get("sensitive"); tag == "true" {
|
||||||
buf.WriteString(",\n")
|
buf.WriteString("<sensitive>")
|
||||||
|
} else {
|
||||||
|
stringValue(fv, indent+2, buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buf.WriteString(",\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
buf.WriteString("\n" + strings.Repeat(" ", indent) + "}")
|
buf.WriteString("\n" + strings.Repeat(" ", indent) + "}")
|
||||||
|
2
vendor/github.com/aws/aws-sdk-go/aws/client/client.go
generated
vendored
@ -18,7 +18,7 @@ type Config struct {
|
|||||||
|
|
||||||
// States that the signing name did not come from a modeled source but
|
// States that the signing name did not come from a modeled source but
|
||||||
// was derived based on other data. Used by service client constructors
|
// was derived based on other data. Used by service client constructors
|
||||||
// to determine if the signin name can be overriden based on metadata the
|
// to determine if the signin name can be overridden based on metadata the
|
||||||
// service has.
|
// service has.
|
||||||
SigningNameDerived bool
|
SigningNameDerived bool
|
||||||
}
|
}
|
||||||
|
60
vendor/github.com/aws/aws-sdk-go/aws/config.go
generated
vendored
@ -18,7 +18,7 @@ const UseServiceDefaultRetries = -1
|
|||||||
type RequestRetryer interface{}
|
type RequestRetryer interface{}
|
||||||
|
|
||||||
// A Config provides service configuration for service clients. By default,
|
// A Config provides service configuration for service clients. By default,
|
||||||
// all clients will use the defaults.DefaultConfig tructure.
|
// all clients will use the defaults.DefaultConfig structure.
|
||||||
//
|
//
|
||||||
// // Create Session with MaxRetry configuration to be shared by multiple
|
// // Create Session with MaxRetry configuration to be shared by multiple
|
||||||
// // service clients.
|
// // service clients.
|
||||||
@ -45,8 +45,8 @@ type Config struct {
|
|||||||
// that overrides the default generated endpoint for a client. Set this
|
// that overrides the default generated endpoint for a client. Set this
|
||||||
// to `""` to use the default generated endpoint.
|
// to `""` to use the default generated endpoint.
|
||||||
//
|
//
|
||||||
// @note You must still provide a `Region` value when specifying an
|
// Note: You must still provide a `Region` value when specifying an
|
||||||
// endpoint for a client.
|
// endpoint for a client.
|
||||||
Endpoint *string
|
Endpoint *string
|
||||||
|
|
||||||
// The resolver to use for looking up endpoints for AWS service clients
|
// The resolver to use for looking up endpoints for AWS service clients
|
||||||
@ -65,8 +65,8 @@ type Config struct {
|
|||||||
// noted. A full list of regions is found in the "Regions and Endpoints"
|
// noted. A full list of regions is found in the "Regions and Endpoints"
|
||||||
// document.
|
// document.
|
||||||
//
|
//
|
||||||
// @see http://docs.aws.amazon.com/general/latest/gr/rande.html
|
// See http://docs.aws.amazon.com/general/latest/gr/rande.html for AWS
|
||||||
// AWS Regions and Endpoints
|
// Regions and Endpoints.
|
||||||
Region *string
|
Region *string
|
||||||
|
|
||||||
// Set this to `true` to disable SSL when sending requests. Defaults
|
// Set this to `true` to disable SSL when sending requests. Defaults
|
||||||
@ -120,9 +120,10 @@ type Config struct {
|
|||||||
// will use virtual hosted bucket addressing when possible
|
// will use virtual hosted bucket addressing when possible
|
||||||
// (`http://BUCKET.s3.amazonaws.com/KEY`).
|
// (`http://BUCKET.s3.amazonaws.com/KEY`).
|
||||||
//
|
//
|
||||||
// @note This configuration option is specific to the Amazon S3 service.
|
// Note: This configuration option is specific to the Amazon S3 service.
|
||||||
// @see http://docs.aws.amazon.com/AmazonS3/latest/dev/VirtualHosting.html
|
//
|
||||||
// Amazon S3: Virtual Hosting of Buckets
|
// See http://docs.aws.amazon.com/AmazonS3/latest/dev/VirtualHosting.html
|
||||||
|
// for Amazon S3: Virtual Hosting of Buckets
|
||||||
S3ForcePathStyle *bool
|
S3ForcePathStyle *bool
|
||||||
|
|
||||||
// Set this to `true` to disable the SDK adding the `Expect: 100-Continue`
|
// Set this to `true` to disable the SDK adding the `Expect: 100-Continue`
|
||||||
@ -223,6 +224,28 @@ type Config struct {
|
|||||||
// Key: aws.String("//foo//bar//moo"),
|
// Key: aws.String("//foo//bar//moo"),
|
||||||
// })
|
// })
|
||||||
DisableRestProtocolURICleaning *bool
|
DisableRestProtocolURICleaning *bool
|
||||||
|
|
||||||
|
// EnableEndpointDiscovery will allow for endpoint discovery on operations that
|
||||||
|
// have the definition in its model. By default, endpoint discovery is off.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
// sess := session.Must(session.NewSession(&aws.Config{
|
||||||
|
// EnableEndpointDiscovery: aws.Bool(true),
|
||||||
|
// }))
|
||||||
|
//
|
||||||
|
// svc := s3.New(sess)
|
||||||
|
// out, err := svc.GetObject(&s3.GetObjectInput {
|
||||||
|
// Bucket: aws.String("bucketname"),
|
||||||
|
// Key: aws.String("/foo/bar/moo"),
|
||||||
|
// })
|
||||||
|
EnableEndpointDiscovery *bool
|
||||||
|
|
||||||
|
// DisableEndpointHostPrefix will disable the SDK's behavior of prefixing
|
||||||
|
// request endpoint hosts with modeled information.
|
||||||
|
//
|
||||||
|
// Disabling this feature is useful when you want to use local endpoints
|
||||||
|
// for testing that do not support the modeled host prefix pattern.
|
||||||
|
DisableEndpointHostPrefix *bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewConfig returns a new Config pointer that can be chained with builder
|
// NewConfig returns a new Config pointer that can be chained with builder
|
||||||
@ -377,6 +400,19 @@ func (c *Config) WithSleepDelay(fn func(time.Duration)) *Config {
|
|||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithEndpointDiscovery will set whether or not to use endpoint discovery.
|
||||||
|
func (c *Config) WithEndpointDiscovery(t bool) *Config {
|
||||||
|
c.EnableEndpointDiscovery = &t
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithDisableEndpointHostPrefix will set whether or not to use modeled host prefix
|
||||||
|
// when making requests.
|
||||||
|
func (c *Config) WithDisableEndpointHostPrefix(t bool) *Config {
|
||||||
|
c.DisableEndpointHostPrefix = &t
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
// MergeIn merges the passed in configs into the existing config object.
|
// MergeIn merges the passed in configs into the existing config object.
|
||||||
func (c *Config) MergeIn(cfgs ...*Config) {
|
func (c *Config) MergeIn(cfgs ...*Config) {
|
||||||
for _, other := range cfgs {
|
for _, other := range cfgs {
|
||||||
@ -476,6 +512,14 @@ func mergeInConfig(dst *Config, other *Config) {
|
|||||||
if other.EnforceShouldRetryCheck != nil {
|
if other.EnforceShouldRetryCheck != nil {
|
||||||
dst.EnforceShouldRetryCheck = other.EnforceShouldRetryCheck
|
dst.EnforceShouldRetryCheck = other.EnforceShouldRetryCheck
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if other.EnableEndpointDiscovery != nil {
|
||||||
|
dst.EnableEndpointDiscovery = other.EnableEndpointDiscovery
|
||||||
|
}
|
||||||
|
|
||||||
|
if other.DisableEndpointHostPrefix != nil {
|
||||||
|
dst.DisableEndpointHostPrefix = other.DisableEndpointHostPrefix
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy will return a shallow copy of the Config object. If any additional
|
// Copy will return a shallow copy of the Config object. If any additional
|
||||||
|
4
vendor/github.com/aws/aws-sdk-go/aws/corehandlers/handlers.go
generated
vendored
@ -72,9 +72,9 @@ var ValidateReqSigHandler = request.NamedHandler{
|
|||||||
signedTime = r.LastSignedAt
|
signedTime = r.LastSignedAt
|
||||||
}
|
}
|
||||||
|
|
||||||
// 10 minutes to allow for some clock skew/delays in transmission.
|
// 5 minutes to allow for some clock skew/delays in transmission.
|
||||||
// Would be improved with aws/aws-sdk-go#423
|
// Would be improved with aws/aws-sdk-go#423
|
||||||
if signedTime.Add(10 * time.Minute).After(time.Now()) {
|
if signedTime.Add(5 * time.Minute).After(time.Now()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
2
vendor/github.com/aws/aws-sdk-go/aws/corehandlers/user_agent.go
generated
vendored
@ -17,7 +17,7 @@ var SDKVersionUserAgentHandler = request.NamedHandler{
|
|||||||
}
|
}
|
||||||
|
|
||||||
const execEnvVar = `AWS_EXECUTION_ENV`
|
const execEnvVar = `AWS_EXECUTION_ENV`
|
||||||
const execEnvUAKey = `exec_env`
|
const execEnvUAKey = `exec-env`
|
||||||
|
|
||||||
// AddHostExecEnvUserAgentHander is a request handler appending the SDK's
|
// AddHostExecEnvUserAgentHander is a request handler appending the SDK's
|
||||||
// execution environment to the user agent.
|
// execution environment to the user agent.
|
||||||
|
4
vendor/github.com/aws/aws-sdk-go/aws/credentials/chain_provider.go
generated
vendored
@ -9,9 +9,7 @@ var (
|
|||||||
// providers in the ChainProvider.
|
// providers in the ChainProvider.
|
||||||
//
|
//
|
||||||
// This has been deprecated. For verbose error messaging set
|
// This has been deprecated. For verbose error messaging set
|
||||||
// aws.Config.CredentialsChainVerboseErrors to true
|
// aws.Config.CredentialsChainVerboseErrors to true.
|
||||||
//
|
|
||||||
// @readonly
|
|
||||||
ErrNoValidProvidersFoundInChain = awserr.New("NoCredentialProviders",
|
ErrNoValidProvidersFoundInChain = awserr.New("NoCredentialProviders",
|
||||||
`no valid providers in chain. Deprecated.
|
`no valid providers in chain. Deprecated.
|
||||||
For verbose messaging see aws.Config.CredentialsChainVerboseErrors`,
|
For verbose messaging see aws.Config.CredentialsChainVerboseErrors`,
|
||||||
|
46
vendor/github.com/aws/aws-sdk-go/aws/credentials/credentials.go
generated
vendored
@ -49,6 +49,8 @@
|
|||||||
package credentials
|
package credentials
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@ -64,8 +66,6 @@ import (
|
|||||||
// Credentials: credentials.AnonymousCredentials,
|
// Credentials: credentials.AnonymousCredentials,
|
||||||
// })))
|
// })))
|
||||||
// // Access public S3 buckets.
|
// // Access public S3 buckets.
|
||||||
//
|
|
||||||
// @readonly
|
|
||||||
var AnonymousCredentials = NewStaticCredentials("", "", "")
|
var AnonymousCredentials = NewStaticCredentials("", "", "")
|
||||||
|
|
||||||
// A Value is the AWS credentials value for individual credential fields.
|
// A Value is the AWS credentials value for individual credential fields.
|
||||||
@ -99,6 +99,14 @@ type Provider interface {
|
|||||||
IsExpired() bool
|
IsExpired() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// An Expirer is an interface that Providers can implement to expose the expiration
|
||||||
|
// time, if known. If the Provider cannot accurately provide this info,
|
||||||
|
// it should not implement this interface.
|
||||||
|
type Expirer interface {
|
||||||
|
// The time at which the credentials are no longer valid
|
||||||
|
ExpiresAt() time.Time
|
||||||
|
}
|
||||||
|
|
||||||
// An ErrorProvider is a stub credentials provider that always returns an error
|
// An ErrorProvider is a stub credentials provider that always returns an error
|
||||||
// this is used by the SDK when construction a known provider is not possible
|
// this is used by the SDK when construction a known provider is not possible
|
||||||
// due to an error.
|
// due to an error.
|
||||||
@ -158,13 +166,19 @@ func (e *Expiry) SetExpiration(expiration time.Time, window time.Duration) {
|
|||||||
|
|
||||||
// IsExpired returns if the credentials are expired.
|
// IsExpired returns if the credentials are expired.
|
||||||
func (e *Expiry) IsExpired() bool {
|
func (e *Expiry) IsExpired() bool {
|
||||||
if e.CurrentTime == nil {
|
curTime := e.CurrentTime
|
||||||
e.CurrentTime = time.Now
|
if curTime == nil {
|
||||||
|
curTime = time.Now
|
||||||
}
|
}
|
||||||
return e.expiration.Before(e.CurrentTime())
|
return e.expiration.Before(curTime())
|
||||||
}
|
}
|
||||||
|
|
||||||
// A Credentials provides synchronous safe retrieval of AWS credentials Value.
|
// ExpiresAt returns the expiration time of the credential
|
||||||
|
func (e *Expiry) ExpiresAt() time.Time {
|
||||||
|
return e.expiration
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Credentials provides concurrency safe retrieval of AWS credentials Value.
|
||||||
// Credentials will cache the credentials value until they expire. Once the value
|
// Credentials will cache the credentials value until they expire. Once the value
|
||||||
// expires the next Get will attempt to retrieve valid credentials.
|
// expires the next Get will attempt to retrieve valid credentials.
|
||||||
//
|
//
|
||||||
@ -256,3 +270,23 @@ func (c *Credentials) IsExpired() bool {
|
|||||||
func (c *Credentials) isExpired() bool {
|
func (c *Credentials) isExpired() bool {
|
||||||
return c.forceRefresh || c.provider.IsExpired()
|
return c.forceRefresh || c.provider.IsExpired()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExpiresAt provides access to the functionality of the Expirer interface of
|
||||||
|
// the underlying Provider, if it supports that interface. Otherwise, it returns
|
||||||
|
// an error.
|
||||||
|
func (c *Credentials) ExpiresAt() (time.Time, error) {
|
||||||
|
c.m.RLock()
|
||||||
|
defer c.m.RUnlock()
|
||||||
|
|
||||||
|
expirer, ok := c.provider.(Expirer)
|
||||||
|
if !ok {
|
||||||
|
return time.Time{}, awserr.New("ProviderNotExpirer",
|
||||||
|
fmt.Sprintf("provider %s does not support ExpiresAt()", c.creds.ProviderName),
|
||||||
|
nil)
|
||||||
|
}
|
||||||
|
if c.forceRefresh {
|
||||||
|
// set expiration time to the distant past
|
||||||
|
return time.Time{}, nil
|
||||||
|
}
|
||||||
|
return expirer.ExpiresAt(), nil
|
||||||
|
}
|
||||||
|
6
vendor/github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds/ec2_role_provider.go
generated
vendored
@ -4,7 +4,6 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"path"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -12,6 +11,7 @@ import (
|
|||||||
"github.com/aws/aws-sdk-go/aws/client"
|
"github.com/aws/aws-sdk-go/aws/client"
|
||||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||||
"github.com/aws/aws-sdk-go/aws/ec2metadata"
|
"github.com/aws/aws-sdk-go/aws/ec2metadata"
|
||||||
|
"github.com/aws/aws-sdk-go/internal/sdkuri"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ProviderName provides a name of EC2Role provider
|
// ProviderName provides a name of EC2Role provider
|
||||||
@ -125,7 +125,7 @@ type ec2RoleCredRespBody struct {
|
|||||||
Message string
|
Message string
|
||||||
}
|
}
|
||||||
|
|
||||||
const iamSecurityCredsPath = "/iam/security-credentials"
|
const iamSecurityCredsPath = "iam/security-credentials/"
|
||||||
|
|
||||||
// requestCredList requests a list of credentials from the EC2 service.
|
// requestCredList requests a list of credentials from the EC2 service.
|
||||||
// If there are no credentials, or there is an error making or receiving the request
|
// If there are no credentials, or there is an error making or receiving the request
|
||||||
@ -153,7 +153,7 @@ func requestCredList(client *ec2metadata.EC2Metadata) ([]string, error) {
|
|||||||
// If the credentials cannot be found, or there is an error reading the response
|
// If the credentials cannot be found, or there is an error reading the response
|
||||||
// and error will be returned.
|
// and error will be returned.
|
||||||
func requestCred(client *ec2metadata.EC2Metadata, credsName string) (ec2RoleCredRespBody, error) {
|
func requestCred(client *ec2metadata.EC2Metadata, credsName string) (ec2RoleCredRespBody, error) {
|
||||||
resp, err := client.GetMetadata(path.Join(iamSecurityCredsPath, credsName))
|
resp, err := client.GetMetadata(sdkuri.PathJoin(iamSecurityCredsPath, credsName))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ec2RoleCredRespBody{},
|
return ec2RoleCredRespBody{},
|
||||||
awserr.New("EC2RoleRequestError",
|
awserr.New("EC2RoleRequestError",
|
||||||
|
7
vendor/github.com/aws/aws-sdk-go/aws/credentials/endpointcreds/provider.go
generated
vendored
@ -65,6 +65,10 @@ type Provider struct {
|
|||||||
//
|
//
|
||||||
// If ExpiryWindow is 0 or less it will be ignored.
|
// If ExpiryWindow is 0 or less it will be ignored.
|
||||||
ExpiryWindow time.Duration
|
ExpiryWindow time.Duration
|
||||||
|
|
||||||
|
// Optional authorization token value if set will be used as the value of
|
||||||
|
// the Authorization header of the endpoint credential request.
|
||||||
|
AuthorizationToken string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewProviderClient returns a credentials Provider for retrieving AWS credentials
|
// NewProviderClient returns a credentials Provider for retrieving AWS credentials
|
||||||
@ -152,6 +156,9 @@ func (p *Provider) getCredentials() (*getCredentialsOutput, error) {
|
|||||||
out := &getCredentialsOutput{}
|
out := &getCredentialsOutput{}
|
||||||
req := p.Client.NewRequest(op, nil, out)
|
req := p.Client.NewRequest(op, nil, out)
|
||||||
req.HTTPRequest.Header.Set("Accept", "application/json")
|
req.HTTPRequest.Header.Set("Accept", "application/json")
|
||||||
|
if authToken := p.AuthorizationToken; len(authToken) != 0 {
|
||||||
|
req.HTTPRequest.Header.Set("Authorization", authToken)
|
||||||
|
}
|
||||||
|
|
||||||
return out, req.Send()
|
return out, req.Send()
|
||||||
}
|
}
|
||||||
|
4
vendor/github.com/aws/aws-sdk-go/aws/credentials/env_provider.go
generated
vendored
@ -12,14 +12,10 @@ const EnvProviderName = "EnvProvider"
|
|||||||
var (
|
var (
|
||||||
// ErrAccessKeyIDNotFound is returned when the AWS Access Key ID can't be
|
// ErrAccessKeyIDNotFound is returned when the AWS Access Key ID can't be
|
||||||
// found in the process's environment.
|
// found in the process's environment.
|
||||||
//
|
|
||||||
// @readonly
|
|
||||||
ErrAccessKeyIDNotFound = awserr.New("EnvAccessKeyNotFound", "AWS_ACCESS_KEY_ID or AWS_ACCESS_KEY not found in environment", nil)
|
ErrAccessKeyIDNotFound = awserr.New("EnvAccessKeyNotFound", "AWS_ACCESS_KEY_ID or AWS_ACCESS_KEY not found in environment", nil)
|
||||||
|
|
||||||
// ErrSecretAccessKeyNotFound is returned when the AWS Secret Access Key
|
// ErrSecretAccessKeyNotFound is returned when the AWS Secret Access Key
|
||||||
// can't be found in the process's environment.
|
// can't be found in the process's environment.
|
||||||
//
|
|
||||||
// @readonly
|
|
||||||
ErrSecretAccessKeyNotFound = awserr.New("EnvSecretNotFound", "AWS_SECRET_ACCESS_KEY or AWS_SECRET_KEY not found in environment", nil)
|
ErrSecretAccessKeyNotFound = awserr.New("EnvSecretNotFound", "AWS_SECRET_ACCESS_KEY or AWS_SECRET_KEY not found in environment", nil)
|
||||||
)
|
)
|
||||||
|
|
||||||
|