Compare commits

...

44 Commits

Author SHA1 Message Date
bergquist
cf9cb45fe8 Merge branch 'cp_6.1.6' into v6.1.x
* cp_6.1.6:
  Revert "CloudWatch: Use default alias if there is no alias for metrics (#16732)"
  bump version to 6.1.6
  security: bump jquery version to 3.4.0
  playlist: fix loading dashboards by tag (#16727)
  CloudWatch: Use default alias if there is no alias for metrics (#16732)
  phantomjs: set web-security to true
2019-04-29 15:29:28 +02:00
bergquist
44c5c0e0a6 Revert "CloudWatch: Use default alias if there is no alias for metrics (#16732)"
This reverts commit c9e67140cc.
2019-04-29 15:10:48 +02:00
bergquist
b658b3e13e bump version to 6.1.6 2019-04-29 15:06:32 +02:00
bergquist
62bd94949b security: bump jquery version to 3.4.0 2019-04-29 14:53:20 +02:00
Marcus Efraimsson
02222a753a playlist: fix loading dashboards by tag (#16727)
Fixes #16704
(cherry picked from commit 7dadebb3f0)
2019-04-29 14:46:03 +02:00
Utkarsh Bhatnagar
c9e67140cc CloudWatch: Use default alias if there is no alias for metrics (#16732)
Fixes #16605
(cherry picked from commit 89a01a680c)
2019-04-29 14:45:35 +02:00
bergquist
e097fb1166 phantomjs: set web-security to true
(cherry picked from commit f7c8d90d1a)
2019-04-29 14:40:12 +02:00
Torkel Ödegaard
7b86c393c0 Extensions: Backport of #16622 (#16626) 2019-04-17 09:41:13 +02:00
Dominik Prokop
fef1733b0e Release: bump version 2019-04-16 11:04:07 +02:00
Torkel Ödegaard
7219633952 Heatmap: Fixed auto decimals when bucket name is not number but contains dots, fixes #13019 (#16609)
(cherry picked from commit 057577dcc5)
2019-04-16 11:03:12 +02:00
Torkel Ödegaard
2926f9b0b1 DataPanel: Added built-in interval variables to scopedVars (#16556)
For react panels the scopedVars did not contain the built in __interval and __interval_ms
variables.

Fixes #16546

(cherry picked from commit 6a315bd09d)
2019-04-16 11:03:01 +02:00
Torkel Ödegaard
db39c105ed QueryInspector: Now shows error responses (#16514)
While converting this to React we missed subscribing to errors

Fixed #16512

(cherry picked from commit e44d049a8e)
2019-04-16 11:02:42 +02:00
Hugo Häggmark
2985098f8e Fix: Pass missing maxDataPoints to query in Explore (#16513)
Fixes: #16490
(cherry picked from commit e5c1cbabb1)
2019-04-16 11:02:16 +02:00
Hugo Häggmark
bd42db2743 Fix: Recalculate intervals in Explore on run queries (#16510)
Fixes: #16485
(cherry picked from commit 1341f4517a)
2019-04-16 11:00:04 +02:00
Alexander Zobnin
c4222bb65a Heatmap: Fix empty graph if panel is too narrow (#16460)
Fixes #16378

(cherry picked from commit 180772f169)
2019-04-16 10:43:28 +02:00
Torkel Ödegaard
9504db83bf Graph: Fixed auto decimals in legend values (#16455)
Fixes #16448

(cherry picked from commit 52c3990412)
2019-04-09 15:26:42 +02:00
Torkel Ödegaard
0992fbc4be Build: Updated version to v6.1.3 2019-04-09 15:23:19 +02:00
Torkel Ödegaard
1f35ce691c Graph: fixed png rendering with legend to the right (#16463)
(cherry picked from commit fed75695a1)
2019-04-09 15:22:52 +02:00
Torkel Ödegaard
be84bffa57 Singlestat: Use decimal override when manually specified (#16451)
Fixes #16373

(cherry picked from commit b46b84f156)
2019-04-09 15:22:18 +02:00
Dominik Prokop
a2236de67f Fix: Bring back styles on Switch components when checked
Fixed bug introduced by replacing native input with @grafana/ui/Input component.

Switch's styling relies on native input checked attribute used in adjacent sibling selector. Because React based Input is wrapped in div, there was no chance for styling to work

(cherry picked from commit 6b2c81bcf2)
2019-04-09 15:22:10 +02:00
Torkel Ödegaard
de8f6ac4b1 Bumped version to 6.1.2 2019-04-08 11:16:37 +02:00
Patrick O'Carroll
0c7d43b999 Graph: Fixed series legend color for hidden series (#16438)
* replaced colors for headingColor, link and linkDisabled with colors from grayscale, replaced colors for linkDisabled and linkHover with colors from grayscale, changed color for sha-modal-in-text to text-color-empahises

* fixed snapshot

(cherry picked from commit 70dcb6a22a)
2019-04-08 11:15:58 +02:00
Torkel Ödegaard
0974663079 Styles: Fixed left menu highlight (#16431)
Upgrades to webpack & css optimization caused changes
in production build that removed the left brand
gradient.

Fixes #16430

(cherry picked from commit 0968fbed49)
2019-04-08 11:15:51 +02:00
Torkel Ödegaard
9eba279b8d Graph: Fixed tooltip highlight on white theme (#16429)
Fixes #16359

(cherry picked from commit 9b39dbc2fb)
2019-04-08 11:15:41 +02:00
Torkel Ödegaard
da0302f15e Units: Correctly use the override decimals (#16413)
Fixes bugs introduced in PR #14716 and #15146 also restores unit tests that where lost in the
move from kbn units to @grafana/ui units

Fixes #16068
Fixes #16373

(cherry picked from commit 8a4a6b4dc1)
2019-04-08 11:15:31 +02:00
Torkel Ödegaard
eff01d2b54 Updated version to 6.1.1 2019-04-05 11:42:24 +02:00
Torkel Ödegaard
cbc515b22c Fix: Graphite query rendering fix (#16390)
Only interpolate string parameters

Fixes #16367

(cherry picked from commit 173e7fd839)
2019-04-05 11:42:03 +02:00
Torkel Ödegaard
638d49dd6e Fix: align panel padding between sass & js theme (#16404)
(cherry picked from commit 35e68b868e)
2019-04-05 11:37:24 +02:00
Torkel Ödegaard
9d452256bf Fix: playlist now preserve the correct url query params (#16403)
Fixes #16377

(cherry picked from commit 6d3b6f3c2f)
2019-04-05 11:37:15 +02:00
Sean Lafferty
f941f25831 Fix: Query editor toggle edit mode fix (#16394)
Increase timeout when waiting for datasource toggleEditorMode check 

Fixes  #16393

(cherry picked from commit c2d399b059)
2019-04-05 11:37:07 +02:00
Marcus Efraimsson
b585fdec6f Alerting: Notification channel http api fixes (#16379)
Fixes so it's possible to create new notification channel and providing uid.
Fixes better error/result handling when updating a notifcation channel.

Fixes #16372
Ref #16219 #16012

(cherry picked from commit 5da1faf454)
2019-04-05 11:36:58 +02:00
Leonard Gram
e6c639f6f0 build: Fixed incorrect permissions for repo folders in ci-deploy. (#16360)
(cherry picked from commit 6baba64935)
2019-04-05 11:36:51 +02:00
Torkel Ödegaard
03346b6f6f Build: updated version to 6.1.0 2019-04-03 10:08:03 +02:00
Mitsuhiro Tanda
9a575f93ad Fix: Cloudwatch fix for dimension value (#16356)
Fixes autocomplete suggestion after changing dimension key

Fixes #15984

(cherry picked from commit 58eb74660d)
2019-04-03 09:57:28 +02:00
Torkel Ödegaard
21957ad515 Fix: Autoprefixer is now working (#16351)
The autoprefixer not working broke the phantomjs backend png rendering

Fixes #16345

(cherry picked from commit 2e59166daa)
2019-04-03 09:57:16 +02:00
Leonard Gram
bd93aad63d build: Fix for renamed package for armv6.
(cherry picked from commit b48c18a1c5)
2019-04-03 09:57:07 +02:00
Torkel Ödegaard
8ae5980c23 Fix: Graphite query ast to string fix (#16297)
Fixes #16291

(cherry picked from commit 74ae756d02)
2019-04-03 09:56:55 +02:00
Torkel Ödegaard
eb38581dc1 Fix: Template query editor this bind exception fix (#16299)
Also fixes the default 100% width of inputs.
Fixes #16298

(cherry picked from commit 5c3a0a624a)
2019-04-03 09:56:43 +02:00
Marcus Efraimsson
be217d8c0e Fix: Alerting Notification channel http api fixes (#16288)
Fix so that uid can be changed when updating notification
channels through the http api.
Update documentation

(cherry picked from commit 79b86466fd)
2019-04-03 09:56:11 +02:00
Floyd May
dfbc3bfb1f InfluxDB: Fix tag names with periods in alerting (#16255)
Updates regex to match tag names with periods when generating
series names in alerting evaluation (backend).

Fixes #9148

(cherry picked from commit 33d1d427bc)
2019-04-03 09:56:01 +02:00
Torkel Ödegaard
a6c8cd7f3a Automation: Updates to yarn cli cherrypick & changelog tasks (#16357)
* Automation: Updated cherrypick task to show merge sha

* Fixed changelog milestone filtering

(cherry picked from commit 5aea77fc95)
2019-04-03 09:55:19 +02:00
Daniel Lee
56e4032db3 Chore: docs whats new article for the 6.1 release (#16251)
(cherry picked from commit 0e2d279e3a)
2019-03-27 13:49:01 +01:00
Daniel Lee
944e526eb9 release 6.1.0-beta1 2019-03-27 12:06:23 +01:00
Daniel Lee
0d6db7e6b8 Chore: scripts update publish script before 6.1 release
(cherry picked from commit cbe2543717894ace2a8347859bff22dfbbf57a73)
2019-03-27 12:02:59 +01:00
95 changed files with 3158 additions and 159 deletions

4
.browserslistrc Normal file
View File

@@ -0,0 +1,4 @@
>1%,
Chrome > 20
last 4 versions,
Firefox ESR

View File

@@ -322,7 +322,7 @@ jobs:
deploy-enterprise-master:
docker:
- image: grafana/grafana-ci-deploy:1.2.1
- image: grafana/grafana-ci-deploy:1.2.2
steps:
- attach_workspace:
at: .
@@ -347,7 +347,7 @@ jobs:
deploy-enterprise-release:
docker:
- image: grafana/grafana-ci-deploy:1.2.1
- image: grafana/grafana-ci-deploy:1.2.2
steps:
- checkout
- attach_workspace:
@@ -380,7 +380,7 @@ jobs:
deploy-master:
docker:
- image: grafana/grafana-ci-deploy:1.2.1
- image: grafana/grafana-ci-deploy:1.2.2
steps:
- attach_workspace:
at: .
@@ -411,7 +411,7 @@ jobs:
deploy-release:
docker:
- image: grafana/grafana-ci-deploy:1.2.1
- image: grafana/grafana-ci-deploy:1.2.2
steps:
- checkout
- attach_workspace:

21
Gopkg.lock generated
View File

@@ -250,6 +250,23 @@
revision = "1f39c590c64924f358c0d89016ac9b2bb84e9125"
version = "v0.7.1"
[[projects]]
digest = "1:6a7159c5f8f8826207545407a458b43ab6599c1c8a2271465c2e979ecea1dcd4"
name = "github.com/gobwas/glob"
packages = [
".",
"compiler",
"match",
"syntax",
"syntax/ast",
"syntax/lexer",
"util/runes",
"util/strings",
]
pruneopts = "NUT"
revision = "5ccd90ef52e1e632236f7326478d4faa74f99438"
version = "v0.2.3"
[[projects]]
branch = "master"
digest = "1:ffbb19fb66f140b5ea059428d1f84246a055d1bc3d9456c1e5c3d143611f03d0"
@@ -885,6 +902,7 @@
"github.com/aws/aws-sdk-go/service/sts",
"github.com/benbjohnson/clock",
"github.com/bmizerany/assert",
"github.com/bradfitz/gomemcache/memcache",
"github.com/codegangsta/cli",
"github.com/davecgh/go-spew/spew",
"github.com/denisenkom/go-mssqldb",
@@ -900,6 +918,7 @@
"github.com/go-stack/stack",
"github.com/go-xorm/core",
"github.com/go-xorm/xorm",
"github.com/gobwas/glob",
"github.com/gorilla/websocket",
"github.com/gosimple/slug",
"github.com/grafana/grafana-plugin-model/go/datasource",
@@ -915,7 +934,6 @@
"github.com/opentracing/opentracing-go/ext",
"github.com/opentracing/opentracing-go/log",
"github.com/patrickmn/go-cache",
"github.com/pkg/errors",
"github.com/prometheus/client_golang/api",
"github.com/prometheus/client_golang/api/prometheus/v1",
"github.com/prometheus/client_golang/prometheus",
@@ -937,6 +955,7 @@
"gopkg.in/ldap.v3",
"gopkg.in/macaron.v1",
"gopkg.in/mail.v2",
"gopkg.in/redis.v2",
"gopkg.in/square/go-jose.v2",
"gopkg.in/yaml.v2",
]

View File

@@ -361,6 +361,7 @@ func createPackage(options linuxPackageOptions) {
fmt.Printf("pkgArch is set to '%s', generated arch is '%s'\n", pkgArch, options.packageArch)
if pkgArch == "armv6" {
name += "-rpi"
args = append(args, "--replaces", "grafana")
}
args = append(args, "--name", name)

View File

@@ -0,0 +1,55 @@
+++
title = "What's New in Grafana v6.1"
description = "Feature & improvement highlights for Grafana v6.1"
keywords = ["grafana", "new", "documentation", "6.1"]
type = "docs"
[menu.docs]
name = "Version 6.1"
identifier = "v6.1"
parent = "whatsnew"
weight = -12
+++
# What's New in Grafana v6.1
## Highlights
### Ad hoc Filtering for Prometheus
{{< imgbox max-width="30%" img="/img/docs/v61/prometheus-ad-hoc.gif" caption="Ad-hoc filters variable for Prometheus" >}}
The ad hoc filter feature allows you to create new key/value filters on the fly with autocomplete for both key and values. The filter condition is then automatically applied to all queries on the dashboard. This makes it easier to explore your data in a dashboard without changing queries and without having to add new template variables.
Other timeseries databases with label-based query languages have had this feature for a while. Recently Prometheus added support for fetching label names from their API and thanks to [Mitsuhiro Tanda](https://github.com/mtanda) implementing it in Grafana, the Prometheus datasource finally supports ad hoc filtering.
Support for fetching a list of label names was released in Prometheus v2.6.0 so that is a requirement for this feature to work in Grafana.
### Permissions: Editors can own dashboards, folders and teams they create
When the dashboard folders feature and permissions system was released in Grafana 5.0, users with the editor role were not allowed to administrate dashboards, folders or teams. In the 6.1 release, we have added a config option so that by default editors are admins for any Dashboard, Folder or Team they create.
This feature also adds a new Team permission that can be assigned to any user with the editor or viewer role and lets that user add other users to the Team.
We believe that this is more in line with the Grafana philosophy, as it will allow teams to be more self-organizing. This option will be made permanent if it gets positive feedback from the community so let us know what you think in the [issue on GitHub](https://github.com/grafana/grafana/issues/15590).
To turn this feature on add the following [config option](/installation/configuration/#editors-can-admin) to your Grafana ini file in the `users` section and then restart the Grafana server:
```ini
[users]
editors_can_admin = true
```
### Minor Features and Fixes
This release contains a lot of small features and fixes:
- A new keyboard shortcut `d l` toggles all Graph legends in a dashboard.
- A small bug fix for Elasticsearch - template variables in the alias field now work properly.
- Some new capabilities have been added for datasource plugins that will be of interest to plugin authors:
- a new oauth pass-through option.
- it is now possible to add user details to requests sent to the dataproxy.
- Heatmap and Explore fixes.
Checkout the [CHANGELOG.md](https://github.com/grafana/grafana/blob/master/CHANGELOG.md) file for a complete list of new features, changes, and bug fixes.
A huge thanks to our community for all the reported issues, bug fixes and feedback.

View File

@@ -152,6 +152,7 @@ Content-Type: application/json
PUT /api/alert-notifications/1 HTTP/1.1
Accept: application/json
Content-Type: application/json
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
```
@@ -170,7 +171,7 @@ Content-Type: application/json
Deletes an existing notification channel identified by uid.
**Example Request**:
**Example Request**:
```http
DELETE /api/alert-notifications/uid/team-a-email-notifier HTTP/1.1
Accept: application/json
@@ -198,6 +199,7 @@ Content-Type: application/json
DELETE /api/alert-notifications/1 HTTP/1.1
Accept: application/json
Content-Type: application/json
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
```
**Example Response**:
@@ -217,7 +219,7 @@ Content-Type: application/json
**Example Request**:
**Example Request**:
```http
POST /api/alert-notifications/test HTTP/1.1
Accept: application/json
Content-Type: application/json
@@ -247,7 +249,7 @@ Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
{
"id": 1,
"uid": "cIBgcSjkk",
"uid": "new-alert-notification", // optional
"name": "new alert notification", //Required
"type": "email", //Required
"isDefault": false,
@@ -267,7 +269,7 @@ Content-Type: application/json
{
"id": 1,
"uid": "cIBgcSjkk",
"uid": "new-alert-notification",
"name": "new alert notification",
"type": "email",
"isDefault": false,

View File

@@ -5,7 +5,7 @@
"company": "Grafana Labs"
},
"name": "grafana",
"version": "6.1.0-pre",
"version": "6.1.6",
"repository": {
"type": "git",
"url": "http://github.com/grafana/grafana.git"
@@ -185,7 +185,7 @@
"eventemitter3": "^2.0.3",
"file-saver": "^1.3.3",
"immutable": "^3.8.2",
"jquery": "^3.2.1",
"jquery": "3.4.0",
"lodash": "^4.17.10",
"moment": "^2.22.2",
"mousetrap": "^1.6.0",

View File

@@ -22,7 +22,7 @@
"@types/react-color": "^2.14.0",
"classnames": "^2.2.5",
"d3": "^5.7.0",
"jquery": "^3.2.1",
"jquery": "3.4.0",
"lodash": "^4.17.10",
"moment": "^2.22.2",
"papaparse": "^4.6.3",

View File

@@ -72,7 +72,7 @@ export class Input extends PureComponent<Props> {
const inputElementProps = this.populateEventPropsWithStatus(restProps, validationEvents);
return (
<div>
<div style={{ flexGrow: 1 }}>
<input {...inputElementProps} className={inputClassName} />
{error && !hideErrorMessage && <span>{error}</span>}
</div>

View File

@@ -1,7 +1,13 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Input renders correctly 1`] = `
<div>
<div
style={
Object {
"flexGrow": 1,
}
}
>
<input
className="gf-form-input"
/>

View File

@@ -1,6 +1,5 @@
import React, { PureComponent } from 'react';
import uniqueId from 'lodash/uniqueId';
import { Input } from '@grafana/ui';
export interface Props {
label: string;
@@ -39,7 +38,7 @@ export class Switch extends PureComponent<Props, State> {
<label htmlFor={labelId} className={`gf-form gf-form-switch-container ${className || ''}`}>
{label && <div className={labelClassName}>{label}</div>}
<div className={switchClassName}>
<Input id={labelId} type="checkbox" checked={checked} onChange={this.internalOnChange} />
<input id={labelId} type="checkbox" checked={checked} onChange={this.internalOnChange} />
<span className="gf-form-switch__slider" />
</div>
</label>

View File

@@ -148,10 +148,10 @@ exports[`Render should render with base threshold 1`] = `
"grayBlue": "#212327",
"greenBase": "#299c46",
"greenShade": "#23843b",
"headingColor": "#e3e3e3",
"headingColor": "#d8d9da",
"inputBlack": "#09090b",
"link": "#e3e3e3",
"linkDisabled": "#e3e3e3",
"link": "#d8d9da",
"linkDisabled": "#8e8e8e",
"linkExternal": "#33b5e5",
"linkHover": "#ffffff",
"online": "#299c46",
@@ -178,8 +178,8 @@ exports[`Render should render with base threshold 1`] = `
},
"name": "Grafana Dark",
"panelPadding": Object {
"horizontal": 10,
"vertical": 5,
"horizontal": 16,
"vertical": 8,
},
"spacing": Object {
"d": "14px",
@@ -305,10 +305,10 @@ exports[`Render should render with base threshold 1`] = `
"grayBlue": "#212327",
"greenBase": "#299c46",
"greenShade": "#23843b",
"headingColor": "#e3e3e3",
"headingColor": "#d8d9da",
"inputBlack": "#09090b",
"link": "#e3e3e3",
"linkDisabled": "#e3e3e3",
"link": "#d8d9da",
"linkDisabled": "#8e8e8e",
"linkExternal": "#33b5e5",
"linkHover": "#ffffff",
"online": "#299c46",
@@ -335,8 +335,8 @@ exports[`Render should render with base threshold 1`] = `
},
"name": "Grafana Dark",
"panelPadding": Object {
"horizontal": 10,
"vertical": 5,
"horizontal": 16,
"vertical": 8,
},
"spacing": Object {
"d": "14px",
@@ -465,7 +465,13 @@ exports[`Render should render with base threshold 1`] = `
type="text"
value="Base"
>
<div>
<div
style={
Object {
"flexGrow": 1,
}
}
>
<input
className="gf-form-input"
readOnly={true}

View File

@@ -197,7 +197,9 @@ $side-menu-width: 60px;
// dashboard
$dashboard-padding: $space-md;
$panel-padding: 0 $space-md $space-sm $space-md;
$panel-padding: 0 ${theme.panelPadding.horizontal}px ${theme.panelPadding.vertical}px ${
theme.panelPadding.horizontal
}px;
// tabs
$tabs-padding: 10px 15px 9px;

View File

@@ -1,4 +1,3 @@
import tinycolor from 'tinycolor2';
import defaultTheme from './default';
import { GrafanaTheme, GrafanaThemeType } from '../types/theme';
@@ -66,11 +65,11 @@ const darkTheme: GrafanaTheme = {
textWeak: basicColors.gray2,
textEmphasis: basicColors.gray5,
textFaint: basicColors.dark5,
link: new tinycolor(basicColors.white).darken(11).toString(),
linkDisabled: new tinycolor(basicColors.white).darken(11).toString(),
link: basicColors.gray4,
linkDisabled: basicColors.gray2,
linkHover: basicColors.white,
linkExternal: basicColors.blue,
headingColor: new tinycolor(basicColors.white).darken(11).toString(),
headingColor: basicColors.gray4,
},
background: {
dropdown: basicColors.dark3,

View File

@@ -67,8 +67,8 @@ const theme: GrafanaThemeCommons = {
},
},
panelPadding: {
horizontal: 10,
vertical: 5,
horizontal: 16,
vertical: 8,
},
zIndex: {
dropdown: '1000',

View File

@@ -1,4 +1,3 @@
import tinycolor from 'tinycolor2';
import defaultTheme from './default';
import { GrafanaTheme, GrafanaThemeType } from '../types/theme';
@@ -65,11 +64,11 @@ const lightTheme: GrafanaTheme = {
text: basicColors.gray1,
textStrong: basicColors.dark2,
textWeak: basicColors.gray2,
textEmphasis: basicColors.gray5,
textEmphasis: basicColors.dark5,
textFaint: basicColors.dark4,
link: basicColors.gray1,
linkDisabled: new tinycolor(basicColors.gray1).lighten(30).toString(),
linkHover: new tinycolor(basicColors.gray1).darken(20).toString(),
linkDisabled: basicColors.gray3,
linkHover: basicColors.dark1,
linkExternal: basicColors.blueLight,
headingColor: basicColors.gray1,
},

View File

@@ -5,7 +5,9 @@ export interface DisplayValue {
title?: string;
}
export type DecimalCount = number | null | undefined;
export interface DecimalInfo {
decimals: number;
scaledDecimals: number;
decimals: DecimalCount;
scaledDecimals: DecimalCount;
}

View File

@@ -158,6 +158,12 @@ describe('Format value', () => {
expect(instance(value).text).toEqual('0.02');
});
it('should use override decimals', () => {
const value = 100030303;
const instance = getDisplayProcessor({ decimals: 2, unit: 'bytes' });
expect(instance(value).text).toEqual('95.40 MiB');
});
it('should return mapped value if there are matching value mappings', () => {
const valueMappings: ValueMapping[] = [
{ id: 0, operator: '', text: '1-20', type: MappingType.RangeToText, from: '1', to: '20' },
@@ -182,5 +188,6 @@ describe('getDecimalsForValue()', () => {
expect(getDecimalsForValue(20000)).toEqual({ decimals: 0, scaledDecimals: -2 });
expect(getDecimalsForValue(200000)).toEqual({ decimals: 0, scaledDecimals: -3 });
expect(getDecimalsForValue(200000000)).toEqual({ decimals: 0, scaledDecimals: -6 });
expect(getDecimalsForValue(100, 2)).toEqual({ decimals: 2, scaledDecimals: null });
});
});

View File

@@ -8,8 +8,15 @@ import { getMappedValue } from './valueMappings';
import { getColorFromHexRgbOrName } from './namedColorsPalette';
// Types
import { Threshold, ValueMapping, DecimalInfo, DisplayValue, GrafanaTheme, GrafanaThemeType } from '../types';
import { DecimalCount } from './valueFormats/valueFormats';
import {
Threshold,
ValueMapping,
DecimalInfo,
DisplayValue,
GrafanaTheme,
GrafanaThemeType,
DecimalCount,
} from '../types';
export type DisplayProcessor = (value: any) => DisplayValue;
@@ -69,18 +76,7 @@ export function getDisplayProcessor(options?: DisplayValueOptions): DisplayProce
if (!isNaN(numeric)) {
if (shouldFormat && !_.isBoolean(value)) {
let decimals;
let scaledDecimals = 0;
if (!options.decimals) {
const decimalInfo = getDecimalsForValue(value);
decimals = decimalInfo.decimals;
scaledDecimals = decimalInfo.scaledDecimals;
} else {
decimals = options.decimals;
}
const { decimals, scaledDecimals } = getDecimalsForValue(value, options.decimals);
text = formatFunc(numeric, decimals, scaledDecimals, options.isUtc);
}
if (thresholds && thresholds.length > 0) {
@@ -159,7 +155,12 @@ export function getColorFromThreshold(value: number, thresholds: Threshold[], th
return getColorFromHexRgbOrName(thresholds[0].color, themeType);
}
export function getDecimalsForValue(value: number): DecimalInfo {
export function getDecimalsForValue(value: number, decimalOverride?: DecimalCount): DecimalInfo {
if (_.isNumber(decimalOverride)) {
// It's important that scaledDecimals is null here
return { decimals: decimalOverride, scaledDecimals: null };
}
const delta = value / 2;
let dec = -Math.floor(Math.log(delta) / Math.LN10);

View File

@@ -1,4 +1,5 @@
import { toFixed, DecimalCount } from './valueFormats';
import { toFixed } from './valueFormats';
import { DecimalCount } from '../../types';
export function toPercent(size: number, decimals: DecimalCount) {
if (size === null) {

View File

@@ -1,4 +1,5 @@
import { toFixed, toFixedScaled, DecimalCount } from './valueFormats';
import { toFixed, toFixedScaled } from './valueFormats';
import { DecimalCount } from '../../types';
import moment from 'moment';
interface IntervalsInSeconds {

View File

@@ -1,4 +1,5 @@
import { scaledUnits, DecimalCount } from './valueFormats';
import { scaledUnits } from './valueFormats';
import { DecimalCount } from '../../types';
export function currency(symbol: string) {
const units = ['', 'K', 'M', 'B', 'T'];

View File

@@ -0,0 +1,38 @@
import { toFixed, getValueFormat } from './valueFormats';
describe('valueFormats', () => {
describe('toFixed and negative decimals', () => {
it('should treat as zero decimals', () => {
const str = toFixed(186.123, -2);
expect(str).toBe('186');
});
});
describe('ms format when scaled decimals is null do not use it', () => {
it('should use specified decimals', () => {
const str = getValueFormat('ms')(10000086.123, 1, null);
expect(str).toBe('2.8 hour');
});
});
describe('kbytes format when scaled decimals is null do not use it', () => {
it('should use specified decimals', () => {
const str = getValueFormat('kbytes')(10000000, 3, null);
expect(str).toBe('9.537 GiB');
});
});
describe('deckbytes format when scaled decimals is null do not use it', () => {
it('should use specified decimals', () => {
const str = getValueFormat('deckbytes')(10000000, 3, null);
expect(str).toBe('10.000 GB');
});
});
describe('ms format when scaled decimals is 0', () => {
it('should use scaledDecimals and add 3', () => {
const str = getValueFormat('ms')(1200, 0, 0);
expect(str).toBe('1.200 s');
});
});
});

View File

@@ -1,6 +1,5 @@
import { getCategories } from './categories';
export type DecimalCount = number | null | undefined;
import { DecimalCount } from '../../types';
export type ValueFormatter = (
value: number,
@@ -57,17 +56,15 @@ export function toFixed(value: number, decimals?: DecimalCount): string {
export function toFixedScaled(
value: number,
decimals?: DecimalCount,
scaledDecimals?: DecimalCount,
additionalDecimals?: DecimalCount,
decimals: DecimalCount,
scaledDecimals: DecimalCount,
additionalDecimals: number,
ext?: string
) {
if (scaledDecimals) {
if (additionalDecimals) {
return toFixed(value, scaledDecimals + additionalDecimals) + ext;
} else {
return toFixed(value, scaledDecimals) + ext;
}
if (scaledDecimals === null || scaledDecimals === undefined) {
return toFixed(value, decimals) + ext;
} else {
return toFixed(value, scaledDecimals + additionalDecimals) + ext;
}
return toFixed(value, decimals) + ext;

View File

@@ -261,6 +261,10 @@ func UpdateAlertNotification(c *m.ReqContext, cmd m.UpdateAlertNotificationComma
return Error(500, "Failed to update alert notification", err)
}
if cmd.Result == nil {
return Error(404, "Alert notification not found", nil)
}
return JSON(200, dtos.NewAlertNotification(cmd.Result))
}
@@ -272,6 +276,10 @@ func UpdateAlertNotificationByUID(c *m.ReqContext, cmd m.UpdateAlertNotification
return Error(500, "Failed to update alert notification", err)
}
if cmd.Result == nil {
return Error(404, "Alert notification not found", nil)
}
return JSON(200, dtos.NewAlertNotification(cmd.Result))
}

View File

@@ -55,7 +55,7 @@ func populateDashboardsByTag(orgID int64, signedInUser *m.SignedInUser, dashboar
Slug: item.Slug,
Title: item.Title,
Uri: item.Uri,
Url: m.GetDashboardUrl(item.Uid, item.Slug),
Url: item.Url,
Order: dashboardTagOrder[tag],
})
}

View File

@@ -1,6 +1,7 @@
package extensions
import (
_ "github.com/gobwas/glob"
_ "gopkg.in/square/go-jose.v2"
)

View File

@@ -39,7 +39,7 @@ type AlertNotification struct {
}
type CreateAlertNotificationCommand struct {
Uid string `json:"-"`
Uid string `json:"uid"`
Name string `json:"name" binding:"Required"`
Type string `json:"type" binding:"Required"`
SendReminder bool `json:"sendReminder"`
@@ -54,6 +54,7 @@ type CreateAlertNotificationCommand struct {
type UpdateAlertNotificationCommand struct {
Id int64 `json:"id" binding:"Required"`
Uid string `json:"uid"`
Name string `json:"name" binding:"Required"`
Type string `json:"type" binding:"Required"`
SendReminder bool `json:"sendReminder"`
@@ -68,6 +69,7 @@ type UpdateAlertNotificationCommand struct {
type UpdateAlertNotificationWithUidCommand struct {
Uid string `json:"-"`
NewUid string `json:"uid"`
Name string `json:"name" binding:"Required"`
Type string `json:"type" binding:"Required"`
SendReminder bool `json:"sendReminder"`

View File

@@ -42,7 +42,8 @@ func (rs *RenderingService) renderViaPhantomJS(ctx context.Context, opts Opts) (
cmdArgs := []string{
"--ignore-ssl-errors=true",
"--web-security=false",
"--web-security=true",
"--local-url-access=false",
phantomDebugArg,
scriptPath,
fmt.Sprintf("url=%v", url),

View File

@@ -317,6 +317,10 @@ func UpdateAlertNotification(cmd *m.UpdateAlertNotificationCommand) error {
current.SendReminder = cmd.SendReminder
current.DisableResolveMessage = cmd.DisableResolveMessage
if cmd.Uid != "" {
current.Uid = cmd.Uid
}
if current.SendReminder {
if cmd.Frequency == "" {
return m.ErrNotificationFrequencyNotFound
@@ -356,8 +360,13 @@ func UpdateAlertNotificationWithUid(cmd *m.UpdateAlertNotificationWithUidCommand
return fmt.Errorf("Cannot update, alert notification uid %s doesn't exist", cmd.Uid)
}
if cmd.NewUid == "" {
cmd.NewUid = cmd.Uid
}
updateNotification := &m.UpdateAlertNotificationCommand{
Id: current.Id,
Uid: cmd.NewUid,
Name: cmd.Name,
Type: cmd.Type,
SendReminder: cmd.SendReminder,
@@ -373,6 +382,8 @@ func UpdateAlertNotificationWithUid(cmd *m.UpdateAlertNotificationWithUidCommand
return err
}
cmd.Result = updateNotification.Result
return nil
}

View File

@@ -18,7 +18,7 @@ var (
)
func init() {
legendFormat = regexp.MustCompile(`\[\[(\w+?)*\]\]*|\$\s*(\w+?)*`)
legendFormat = regexp.MustCompile(`\[\[(\w+)(\.\w+)*\]\]*|\$\s*(\w+?)*`)
}
func (rp *ResponseParser) Parse(response *Response, query *Query) *tsdb.QueryResult {

View File

@@ -75,7 +75,10 @@ func TestInfluxdbResponseParser(t *testing.T) {
{
Name: "cpu.upc",
Columns: []string{"time", "mean", "sum"},
Tags: map[string]string{"datacenter": "America"},
Tags: map[string]string{
"datacenter": "America",
"dc.region.name": "Northeast",
},
Values: [][]interface{}{
{json.Number("111"), json.Number("222"), json.Number("333")},
},
@@ -159,6 +162,13 @@ func TestInfluxdbResponseParser(t *testing.T) {
So(result.Series[0].Name, ShouldEqual, "alias America")
})
Convey("tag alias with periods", func() {
query := &Query{Alias: "alias [[tag_dc.region.name]]"}
result := parser.Parse(response, query)
So(result.Series[0].Name, ShouldEqual, "alias Northeast")
})
})
})
})

View File

@@ -22,4 +22,13 @@ describe('ticks', () => {
expect(dec.scaledDecimals).toBe(3);
});
});
describe('getStringPrecision()', () => {
it('"3.12" should return 2', () => {
expect(ticks.getStringPrecision('3.12')).toBe(2);
});
it('"asd" should return 0', () => {
expect(ticks.getStringPrecision('asd.asd')).toBe(0);
});
});
});

View File

@@ -141,6 +141,7 @@ export function buildQueryTransaction(
__interval: { text: interval, value: interval },
__interval_ms: { text: intervalMs, value: intervalMs },
},
maxDataPoints: queryOptions.maxDataPoints,
};
return {

View File

@@ -201,6 +201,10 @@ export function getPrecision(num: number): number {
* Get decimal precision of number stored as a string ("3.14" => 2)
*/
export function getStringPrecision(num: string): number {
if (isNaN((num as unknown) as number)) {
return 0;
}
const dotIndex = num.indexOf('.');
if (dotIndex === -1) {
return 0;

View File

@@ -136,10 +136,16 @@ export class DataPanel extends Component<Props, State> {
try {
const ds = await this.dataSourceSrv.get(datasource, scopedVars);
// TODO interpolate variables
const minInterval = this.props.minInterval || ds.interval;
const intervalRes = kbn.calculateInterval(timeRange, widthPixels, minInterval);
// make shallow copy of scoped vars,
// and add built in variables interval and interval_ms
const scopedVarsWithInterval = Object.assign({}, scopedVars, {
__interval: { text: intervalRes.interval, value: intervalRes.interval },
__interval_ms: { text: intervalRes.intervalMs.toString(), value: intervalRes.intervalMs },
});
const queryOptions: DataQueryOptions = {
timezone: 'browser',
panelId: panelId,
@@ -150,7 +156,7 @@ export class DataPanel extends Component<Props, State> {
intervalMs: intervalRes.intervalMs,
targets: queries,
maxDataPoints: maxDataPoints || widthPixels,
scopedVars: scopedVars || {},
scopedVars: scopedVarsWithInterval,
cacheTimeout: null,
};

View File

@@ -139,7 +139,7 @@ export class QueryEditorRow extends PureComponent<Props, State> {
// give angular time to compile
setTimeout(() => {
this.setState({ hasTextEditMode: !!this.angularScope.toggleEditorMode });
}, 10);
}, 100);
}
onToggleCollapse = () => {

View File

@@ -39,14 +39,20 @@ export class QueryInspector extends PureComponent<Props, State> {
componentDidMount() {
const { panel } = this.props;
panel.events.on('refresh', this.onPanelRefresh);
appEvents.on('ds-request-response', this.onDataSourceResponse);
appEvents.on('ds-request-error', this.onRequestError);
panel.events.on('refresh', this.onPanelRefresh);
panel.refresh();
}
componentWillUnmount() {
const { panel } = this.props;
appEvents.off('ds-request-response', this.onDataSourceResponse);
appEvents.on('ds-request-error', this.onRequestError);
panel.events.off('refresh', this.onPanelRefresh);
}
@@ -73,6 +79,10 @@ export class QueryInspector extends PureComponent<Props, State> {
}));
};
onRequestError = (err: any) => {
this.onDataSourceResponse(err);
};
onDataSourceResponse = (response: any = {}) => {
if (this.state.isMocking) {
this.handleMocking(response);

View File

@@ -199,6 +199,15 @@ export interface QueriesImportedPayload {
queries: DataQuery[];
}
export interface LoadExploreDataSourcesPayload {
exploreId: ExploreId;
exploreDatasources: DataSourceSelectItem[];
}
export interface RunQueriesPayload {
exploreId: ExploreId;
}
/**
* Adds a query row after the row with the given index.
*/
@@ -323,7 +332,8 @@ export const queryTransactionSuccessAction = actionCreatorFactory<QueryTransacti
* 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 runQueriesAction = actionCreatorFactory<RunQueriesPayload>('explore/RUN_QUERIES').create();
/**
* Start a scan for more results using the given scanner.

View File

@@ -513,6 +513,7 @@ export function runQueries(exploreId: ExploreId, ignoreUIState = false) {
supportsGraph,
supportsLogs,
supportsTable,
containerWidth,
} = getState().explore[exploreId];
if (!hasNonEmptyQuery(queries)) {
@@ -525,7 +526,7 @@ export function runQueries(exploreId: ExploreId, ignoreUIState = false) {
// but we're using the datasource interval limit for now
const interval = datasourceInstance.interval;
dispatch(runQueriesAction());
dispatch(runQueriesAction({ exploreId }));
// Keep table queries first since they need to return quickly
if ((ignoreUIState || showingTable) && supportsTable) {
dispatch(
@@ -551,6 +552,7 @@ export function runQueries(exploreId: ExploreId, ignoreUIState = false) {
interval,
format: 'time_series',
instant: false,
maxDataPoints: containerWidth,
},
makeTimeSeriesList
)

View File

@@ -12,7 +12,14 @@ import {
import { ExploreItemState, ExploreState, QueryTransaction, ExploreId, ExploreUpdateState } from 'app/types/explore';
import { DataQuery } from '@grafana/ui/src/types';
import { HigherOrderAction, ActionTypes, SplitCloseActionPayload, splitCloseAction } from './actionTypes';
import {
HigherOrderAction,
ActionTypes,
SplitCloseActionPayload,
splitCloseAction,
runQueriesAction,
} from './actionTypes';
import { reducerFactory } from 'app/core/redux';
import {
addQueryRowAction,
@@ -158,14 +165,8 @@ export const itemReducer = reducerFactory<ExploreItemState>({} as ExploreItemSta
.addMapper({
filter: changeSizeAction,
mapper: (state, action): ExploreItemState => {
const { range, datasourceInstance } = state;
let interval = '1s';
if (datasourceInstance && datasourceInstance.interval) {
interval = datasourceInstance.interval;
}
const containerWidth = action.payload.width;
const queryIntervals = getIntervals(range, interval, containerWidth);
return { ...state, containerWidth, queryIntervals };
return { ...state, containerWidth };
},
})
.addMapper({
@@ -250,7 +251,6 @@ export const itemReducer = reducerFactory<ExploreItemState>({} as ExploreItemSta
.addMapper({
filter: loadDatasourceSuccessAction,
mapper: (state, action): ExploreItemState => {
const { containerWidth, range } = state;
const {
StartPage,
datasourceInstance,
@@ -260,11 +260,9 @@ export const itemReducer = reducerFactory<ExploreItemState>({} as ExploreItemSta
supportsLogs,
supportsTable,
} = action.payload;
const queryIntervals = getIntervals(range, datasourceInstance.interval, containerWidth);
return {
...state,
queryIntervals,
StartPage,
datasourceInstance,
history,
@@ -517,6 +515,21 @@ export const itemReducer = reducerFactory<ExploreItemState>({} as ExploreItemSta
};
},
})
.addMapper({
filter: runQueriesAction,
mapper: (state): ExploreItemState => {
const { range, datasourceInstance, containerWidth } = state;
let interval = '1s';
if (datasourceInstance && datasourceInstance.interval) {
interval = datasourceInstance.interval;
}
const queryIntervals = getIntervals(range, interval, containerWidth);
return {
...state,
queryIntervals,
};
},
})
.create();
export const updateChildRefreshState = (

View File

@@ -9,6 +9,12 @@ import locationUtil from 'app/core/utils/location_util';
import kbn from 'app/core/utils/kbn';
import { store } from 'app/store/store';
export const queryParamsToPreserve: { [key: string]: boolean } = {
kiosk: true,
autofitpanels: true,
orgId: true,
};
export class PlaylistSrv {
private cancelPromise: any;
private dashboards: Array<{ url: string }>;
@@ -41,9 +47,7 @@ export class PlaylistSrv {
const dash = this.dashboards[this.index];
const queryParams = this.$location.search();
const filteredParams = _.pickBy(queryParams, key => {
return key === 'kiosk' || key === 'autofitpanels' || key === 'orgId';
});
const filteredParams = _.pickBy(queryParams, (value: any, key: string) => queryParamsToPreserve[key]);
const nextDashboardUrl = locationUtil.stripBaseFromUrl(dash.url);
// this is done inside timeout to make sure digest happens after

View File

@@ -8,13 +8,13 @@ export default class DefaultVariableQueryEditor extends PureComponent<VariableQu
this.state = { value: props.query };
}
handleChange(event) {
this.setState({ value: event.target.value });
}
onChange = (event: React.FormEvent<HTMLInputElement>) => {
this.setState({ value: event.currentTarget.value });
};
handleBlur(event) {
this.props.onChange(event.target.value, event.target.value);
}
onBlur = (event: React.FormEvent<HTMLInputElement>) => {
this.props.onChange(event.currentTarget.value, event.currentTarget.value);
};
render() {
return (
@@ -24,8 +24,8 @@ export default class DefaultVariableQueryEditor extends PureComponent<VariableQu
type="text"
className="gf-form-input"
value={this.state.value}
onChange={this.handleChange}
onBlur={this.handleBlur}
onChange={this.onChange}
onBlur={this.onBlur}
placeholder="metric name or tags query"
required
/>

View File

@@ -112,6 +112,7 @@ export class CloudWatchQueryParameterCtrl {
query = $scope.datasource.getDimensionKeys($scope.target.namespace, $scope.target.region);
} else if (segment.type === 'value') {
const dimensionKey = $scope.dimSegments[$index - 2].value;
delete target.dimensions[dimensionKey];
query = $scope.datasource.getDimensionValues(
target.region,
target.namespace,

View File

@@ -966,7 +966,6 @@ export class FuncInstance {
const str = this.def.name + '(';
const parameters = _.map(this.params, (value, index) => {
const valueInterpolated = replaceVariables(value);
let paramType;
if (index < this.def.params.length) {
@@ -980,6 +979,8 @@ export class FuncInstance {
return value;
}
const valueInterpolated = _.isString(value) ? replaceVariables(value) : value;
// param types that might be quoted
// To quote variables correctly we need to interpolate it to check if it contains a numeric or string value
if (_.includes(['int_or_interval', 'node_or_tag'], paramType) && _.isFinite(+valueInterpolated)) {

View File

@@ -19,6 +19,7 @@ export default class GraphiteQuery {
this.datasource = datasource;
this.target = target;
this.templateSrv = templateSrv;
this.scopedVars = scopedVars;
this.parseTarget();
this.removeTagValue = '-- remove tag --';
@@ -162,7 +163,9 @@ export default class GraphiteQuery {
updateModelTarget(targets) {
const wrapFunction = (target: string, func: any) => {
return func.render(target, this.templateSrv.replace);
return func.render(target, (value: string) => {
return this.templateSrv.replace(value, this.scopedVars);
});
};
if (!this.target.textEditor) {

View File

@@ -31,7 +31,8 @@ describe('when creating func instance from func names', () => {
});
function replaceVariablesDummy(str: string) {
return str;
// important that this does replace
return str.replace('asdasdas', 'asdsad');
}
describe('when rendering func instance', () => {

View File

@@ -398,7 +398,13 @@ Array [
>
Alias By
</label>
<div>
<div
style={
Object {
"flexGrow": 1,
}
}
>
<input
className="gf-form-input gf-form-input width-24"
onChange={[Function]}
@@ -426,7 +432,13 @@ Array [
>
Project
</span>
<div>
<div
style={
Object {
"flexGrow": 1,
}
}
>
<input
className="gf-form-input gf-form-input width-15"
disabled={true}

View File

@@ -594,7 +594,8 @@ export class HeatmapRenderer {
yGridSize = Math.floor((this.yScale(1) - this.yScale(base)) / splitFactor);
}
this.cardWidth = xGridSize - this.cardPadding * 2;
const cardWidth = xGridSize - this.cardPadding * 2;
this.cardWidth = Math.max(cardWidth, MIN_CARD_SIZE);
this.cardHeight = yGridSize ? yGridSize - this.cardPadding * 2 : 0;
}
@@ -611,16 +612,13 @@ export class HeatmapRenderer {
}
getCardWidth(d) {
let w;
let w = this.cardWidth;
if (this.xScale(d.x) < 0) {
// Cut card left to prevent overlay
const cuttedWidth = this.xScale(d.x) + this.cardWidth;
w = cuttedWidth > 0 ? cuttedWidth : 0;
w = this.xScale(d.x) + this.cardWidth;
} else if (this.xScale(d.x) + this.cardWidth > this.chartWidth) {
// Cut card right to prevent overlay
w = this.chartWidth - this.xScale(d.x) - this.cardPadding;
} else {
w = this.cardWidth;
}
// Card width should be MIN_CARD_SIZE at least, but cut cards shouldn't be displayed

View File

@@ -191,7 +191,7 @@ class SingleStatCtrl extends MetricsPanelCtrl {
data.value = 0;
data.valueRounded = 0;
} else {
const decimalInfo = getDecimalsForValue(data.value);
const decimalInfo = getDecimalsForValue(data.value, this.panel.decimals);
const formatFunc = getValueFormat(this.panel.format);
data.valueFormatted = formatFunc(
@@ -199,7 +199,7 @@ class SingleStatCtrl extends MetricsPanelCtrl {
decimalInfo.decimals,
decimalInfo.scaledDecimals
);
data.valueRounded = kbn.roundValue(data.value, this.panel.decimals || 0);
data.valueRounded = kbn.roundValue(data.value, decimalInfo.decimals);
}
this.setValueMapping(data);
@@ -279,17 +279,15 @@ class SingleStatCtrl extends MetricsPanelCtrl {
data.value = this.series[0].stats[this.panel.valueName];
data.flotpairs = this.series[0].flotpairs;
let decimals = this.panel.decimals;
let scaledDecimals = 0;
const decimalInfo = getDecimalsForValue(data.value, this.panel.decimals);
if (!this.panel.decimals) {
const decimalInfo = getDecimalsForValue(data.value);
decimals = decimalInfo.decimals;
scaledDecimals = decimalInfo.scaledDecimals;
}
data.valueFormatted = formatFunc(data.value, decimals, scaledDecimals, this.dashboard.isTimezoneUtc());
data.valueRounded = kbn.roundValue(data.value, decimals);
data.valueFormatted = formatFunc(
data.value,
decimalInfo.decimals,
decimalInfo.scaledDecimals,
this.dashboard.isTimezoneUtc()
);
data.valueRounded = kbn.roundValue(data.value, decimalInfo.decimals);
}
// Add $__name variable for using in prefix or postfix

View File

@@ -316,6 +316,7 @@ export interface QueryOptions {
hinting?: boolean;
instant?: boolean;
valueWithRefId?: boolean;
maxDataPoints?: number;
}
export interface QueryTransaction {

View File

@@ -102,14 +102,14 @@ $edit-gradient: linear-gradient(180deg, $dark-2 50%, $input-black);
// Links
// -------------------------
$link-color: #e3e3e3;
$link-color-disabled: #e3e3e3;
$link-color: #d8d9da;
$link-color-disabled: #8e8e8e;
$link-hover-color: #ffffff;
$external-link-color: #33b5e5;
// Typography
// -------------------------
$headings-color: #e3e3e3;
$headings-color: #d8d9da;
$abbr-border-color: $gray-2 !default;
$text-muted: $text-color-weak;

View File

@@ -200,7 +200,7 @@ $side-menu-width: 60px;
// dashboard
$dashboard-padding: $space-md;
$panel-padding: 0 $space-md $space-sm $space-md;
$panel-padding: 0 16px 8px 16px;
// tabs
$tabs-padding: 10px 15px 9px;

View File

@@ -76,7 +76,7 @@ $text-color: #52545c;
$text-color-strong: #41444b;
$text-color-weak: #767980;
$text-color-faint: #35373f;
$text-color-emphasis: #dde4ed;
$text-color-emphasis: #41444b;
$text-shadow-faint: none;
@@ -89,8 +89,8 @@ $edit-gradient: linear-gradient(-60deg, $gray-7, #f5f6f9 70%, $gray-7 98%);
// Links
// -------------------------
$link-color: #52545c;
$link-color-disabled: #9ea0a9;
$link-hover-color: #222326;
$link-color-disabled: #acb6bf;
$link-hover-color: #1e2028;
$external-link-color: #5794f2;
// Typography

View File

@@ -153,7 +153,7 @@
.share-modal-info-text {
margin-top: 5px;
strong {
color: $headings-color;
color: $text-color-emphasis;
font-weight: 500;
}
}

View File

@@ -120,6 +120,10 @@
// fix for phantomjs
.body--phantomjs {
.graph-panel {
display: -webkit-box;
}
.graph-panel--legend-right {
.graph-legend {
display: block;

View File

@@ -335,7 +335,6 @@
}
@mixin left-brand-border-gradient() {
border: none;
border-image: linear-gradient(rgba(255, 213, 0, 1) 0%, rgba(255, 68, 0, 1) 99%, rgba(255, 68, 0, 1) 100%);
border-image-slice: 1;
border-style: solid;

View File

@@ -18,7 +18,9 @@ RUN pip install -U awscli crcmod && \
apt-get autoremove -y && \
rm -rf /var/lib/apt/lists/* && \
ln -s /opt/google-cloud-sdk/bin/gsutil /usr/bin/gsutil && \
ln -s /opt/google-cloud-sdk/bin/gcloud /usr/bin/gcloud
ln -s /opt/google-cloud-sdk/bin/gcloud /usr/bin/gcloud && \
mkdir -p /deb-repo /rpm-repo && \
chown circleci:circleci /deb-repo /rpm-repo
COPY --from=0 /go/bin/aptly /usr/local/bin/aptly

View File

@@ -1,6 +1,6 @@
#!/bin/bash
_version="1.2.1"
_version="1.2.2"
_tag="grafana/grafana-ci-deploy:${_version}"
docker build -t $_tag .

View File

@@ -6,8 +6,8 @@ EXTRA_OPTS="$@"
# Right now we hack this in into the publish script.
# Eventually we might want to keep a list of all previous releases somewhere.
_releaseNoteUrl="https://community.grafana.com/t/release-notes-v6-0-x/14010"
_whatsNewUrl="http://docs.grafana.org/guides/whats-new-in-v6-0/"
_releaseNoteUrl="https://community.grafana.com/t/release-notes-v6-1-x/15772"
_whatsNewUrl="http://docs.grafana.org/guides/whats-new-in-v6-1/"
./scripts/build/release_publisher/release_publisher \
--wn ${_whatsNewUrl} \

View File

@@ -14,21 +14,21 @@ const changelogTaskRunner: TaskRunner<ChangelogOptions> = async ({ milestone })
timeout: 10000,
});
if (!/^\d+$/.test(milestone)) {
console.log('Use milestone number not title, find number in milestone url');
return;
}
const res = await client.get('/issues', {
params: {
state: 'closed',
per_page: 100,
labels: 'add to changelog',
milestone: milestone,
},
});
const issues = res.data.filter(item => {
if (!item.milestone) {
console.log('Item missing milestone', item.number);
return false;
}
return item.milestone.title === milestone;
});
const issues = res.data;
const bugs = _.sortBy(
issues.filter(item => {

View File

@@ -27,13 +27,10 @@ const cherryPickRunner: TaskRunner<CherryPickOptions> = async () => {
continue;
}
console.log(item.number + ' closed_at ' + item.closed_at + ' ' + item.html_url);
console.log(`${item.title} (${item.number}) closed_at ${item.closed_at}`);
console.log(`\tURL: ${item.closed_at} ${item.html_url}`);
const issueDetails = await client.get(item.pull_request.url);
const commits = await client.get(issueDetails.data.commits_url);
for (const commit of commits.data) {
console.log(commit.commit.message + ' sha: ' + commit.sha);
}
console.log(`\tMerge sha: ${issueDetails.data.merge_commit_sha}`);
}
};

View File

@@ -1,7 +1,9 @@
module.exports = {
plugins: {
'autoprefixer': {},
'postcss-reporter': {},
'postcss-browser-reporter': {},
}
}
module.exports = () => {
return {
plugins: {
autoprefixer: {},
'postcss-reporter': {},
'postcss-browser-reporter': {},
}
};
};

View File

@@ -19,7 +19,7 @@ module.exports = function(options) {
loader: 'postcss-loader',
options: {
sourceMap: options.sourceMap,
config: { path: __dirname + '/postcss.config.js' },
config: { path: __dirname },
},
},
{

21
vendor/github.com/gobwas/glob/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016 Sergey Kamardin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

525
vendor/github.com/gobwas/glob/compiler/compiler.go generated vendored Normal file
View File

@@ -0,0 +1,525 @@
package compiler
// TODO use constructor with all matchers, and to their structs private
// TODO glue multiple Text nodes (like after QuoteMeta)
import (
"fmt"
"reflect"
"github.com/gobwas/glob/match"
"github.com/gobwas/glob/syntax/ast"
"github.com/gobwas/glob/util/runes"
)
func optimizeMatcher(matcher match.Matcher) match.Matcher {
switch m := matcher.(type) {
case match.Any:
if len(m.Separators) == 0 {
return match.NewSuper()
}
case match.AnyOf:
if len(m.Matchers) == 1 {
return m.Matchers[0]
}
return m
case match.List:
if m.Not == false && len(m.List) == 1 {
return match.NewText(string(m.List))
}
return m
case match.BTree:
m.Left = optimizeMatcher(m.Left)
m.Right = optimizeMatcher(m.Right)
r, ok := m.Value.(match.Text)
if !ok {
return m
}
var (
leftNil = m.Left == nil
rightNil = m.Right == nil
)
if leftNil && rightNil {
return match.NewText(r.Str)
}
_, leftSuper := m.Left.(match.Super)
lp, leftPrefix := m.Left.(match.Prefix)
la, leftAny := m.Left.(match.Any)
_, rightSuper := m.Right.(match.Super)
rs, rightSuffix := m.Right.(match.Suffix)
ra, rightAny := m.Right.(match.Any)
switch {
case leftSuper && rightSuper:
return match.NewContains(r.Str, false)
case leftSuper && rightNil:
return match.NewSuffix(r.Str)
case rightSuper && leftNil:
return match.NewPrefix(r.Str)
case leftNil && rightSuffix:
return match.NewPrefixSuffix(r.Str, rs.Suffix)
case rightNil && leftPrefix:
return match.NewPrefixSuffix(lp.Prefix, r.Str)
case rightNil && leftAny:
return match.NewSuffixAny(r.Str, la.Separators)
case leftNil && rightAny:
return match.NewPrefixAny(r.Str, ra.Separators)
}
return m
}
return matcher
}
func compileMatchers(matchers []match.Matcher) (match.Matcher, error) {
if len(matchers) == 0 {
return nil, fmt.Errorf("compile error: need at least one matcher")
}
if len(matchers) == 1 {
return matchers[0], nil
}
if m := glueMatchers(matchers); m != nil {
return m, nil
}
idx := -1
maxLen := -1
var val match.Matcher
for i, matcher := range matchers {
if l := matcher.Len(); l != -1 && l >= maxLen {
maxLen = l
idx = i
val = matcher
}
}
if val == nil { // not found matcher with static length
r, err := compileMatchers(matchers[1:])
if err != nil {
return nil, err
}
return match.NewBTree(matchers[0], nil, r), nil
}
left := matchers[:idx]
var right []match.Matcher
if len(matchers) > idx+1 {
right = matchers[idx+1:]
}
var l, r match.Matcher
var err error
if len(left) > 0 {
l, err = compileMatchers(left)
if err != nil {
return nil, err
}
}
if len(right) > 0 {
r, err = compileMatchers(right)
if err != nil {
return nil, err
}
}
return match.NewBTree(val, l, r), nil
}
func glueMatchers(matchers []match.Matcher) match.Matcher {
if m := glueMatchersAsEvery(matchers); m != nil {
return m
}
if m := glueMatchersAsRow(matchers); m != nil {
return m
}
return nil
}
func glueMatchersAsRow(matchers []match.Matcher) match.Matcher {
if len(matchers) <= 1 {
return nil
}
var (
c []match.Matcher
l int
)
for _, matcher := range matchers {
if ml := matcher.Len(); ml == -1 {
return nil
} else {
c = append(c, matcher)
l += ml
}
}
return match.NewRow(l, c...)
}
func glueMatchersAsEvery(matchers []match.Matcher) match.Matcher {
if len(matchers) <= 1 {
return nil
}
var (
hasAny bool
hasSuper bool
hasSingle bool
min int
separator []rune
)
for i, matcher := range matchers {
var sep []rune
switch m := matcher.(type) {
case match.Super:
sep = []rune{}
hasSuper = true
case match.Any:
sep = m.Separators
hasAny = true
case match.Single:
sep = m.Separators
hasSingle = true
min++
case match.List:
if !m.Not {
return nil
}
sep = m.List
hasSingle = true
min++
default:
return nil
}
// initialize
if i == 0 {
separator = sep
}
if runes.Equal(sep, separator) {
continue
}
return nil
}
if hasSuper && !hasAny && !hasSingle {
return match.NewSuper()
}
if hasAny && !hasSuper && !hasSingle {
return match.NewAny(separator)
}
if (hasAny || hasSuper) && min > 0 && len(separator) == 0 {
return match.NewMin(min)
}
every := match.NewEveryOf()
if min > 0 {
every.Add(match.NewMin(min))
if !hasAny && !hasSuper {
every.Add(match.NewMax(min))
}
}
if len(separator) > 0 {
every.Add(match.NewContains(string(separator), true))
}
return every
}
func minimizeMatchers(matchers []match.Matcher) []match.Matcher {
var done match.Matcher
var left, right, count int
for l := 0; l < len(matchers); l++ {
for r := len(matchers); r > l; r-- {
if glued := glueMatchers(matchers[l:r]); glued != nil {
var swap bool
if done == nil {
swap = true
} else {
cl, gl := done.Len(), glued.Len()
swap = cl > -1 && gl > -1 && gl > cl
swap = swap || count < r-l
}
if swap {
done = glued
left = l
right = r
count = r - l
}
}
}
}
if done == nil {
return matchers
}
next := append(append([]match.Matcher{}, matchers[:left]...), done)
if right < len(matchers) {
next = append(next, matchers[right:]...)
}
if len(next) == len(matchers) {
return next
}
return minimizeMatchers(next)
}
// minimizeAnyOf tries to apply some heuristics to minimize number of nodes in given tree
func minimizeTree(tree *ast.Node) *ast.Node {
switch tree.Kind {
case ast.KindAnyOf:
return minimizeTreeAnyOf(tree)
default:
return nil
}
}
// minimizeAnyOf tries to find common children of given node of AnyOf pattern
// it searches for common children from left and from right
// if any common children are found then it returns new optimized ast tree
// else it returns nil
func minimizeTreeAnyOf(tree *ast.Node) *ast.Node {
if !areOfSameKind(tree.Children, ast.KindPattern) {
return nil
}
commonLeft, commonRight := commonChildren(tree.Children)
commonLeftCount, commonRightCount := len(commonLeft), len(commonRight)
if commonLeftCount == 0 && commonRightCount == 0 { // there are no common parts
return nil
}
var result []*ast.Node
if commonLeftCount > 0 {
result = append(result, ast.NewNode(ast.KindPattern, nil, commonLeft...))
}
var anyOf []*ast.Node
for _, child := range tree.Children {
reuse := child.Children[commonLeftCount : len(child.Children)-commonRightCount]
var node *ast.Node
if len(reuse) == 0 {
// this pattern is completely reduced by commonLeft and commonRight patterns
// so it become nothing
node = ast.NewNode(ast.KindNothing, nil)
} else {
node = ast.NewNode(ast.KindPattern, nil, reuse...)
}
anyOf = appendIfUnique(anyOf, node)
}
switch {
case len(anyOf) == 1 && anyOf[0].Kind != ast.KindNothing:
result = append(result, anyOf[0])
case len(anyOf) > 1:
result = append(result, ast.NewNode(ast.KindAnyOf, nil, anyOf...))
}
if commonRightCount > 0 {
result = append(result, ast.NewNode(ast.KindPattern, nil, commonRight...))
}
return ast.NewNode(ast.KindPattern, nil, result...)
}
func commonChildren(nodes []*ast.Node) (commonLeft, commonRight []*ast.Node) {
if len(nodes) <= 1 {
return
}
// find node that has least number of children
idx := leastChildren(nodes)
if idx == -1 {
return
}
tree := nodes[idx]
treeLength := len(tree.Children)
// allocate max able size for rightCommon slice
// to get ability insert elements in reverse order (from end to start)
// without sorting
commonRight = make([]*ast.Node, treeLength)
lastRight := treeLength // will use this to get results as commonRight[lastRight:]
var (
breakLeft bool
breakRight bool
commonTotal int
)
for i, j := 0, treeLength-1; commonTotal < treeLength && j >= 0 && !(breakLeft && breakRight); i, j = i+1, j-1 {
treeLeft := tree.Children[i]
treeRight := tree.Children[j]
for k := 0; k < len(nodes) && !(breakLeft && breakRight); k++ {
// skip least children node
if k == idx {
continue
}
restLeft := nodes[k].Children[i]
restRight := nodes[k].Children[j+len(nodes[k].Children)-treeLength]
breakLeft = breakLeft || !treeLeft.Equal(restLeft)
// disable searching for right common parts, if left part is already overlapping
breakRight = breakRight || (!breakLeft && j <= i)
breakRight = breakRight || !treeRight.Equal(restRight)
}
if !breakLeft {
commonTotal++
commonLeft = append(commonLeft, treeLeft)
}
if !breakRight {
commonTotal++
lastRight = j
commonRight[j] = treeRight
}
}
commonRight = commonRight[lastRight:]
return
}
func appendIfUnique(target []*ast.Node, val *ast.Node) []*ast.Node {
for _, n := range target {
if reflect.DeepEqual(n, val) {
return target
}
}
return append(target, val)
}
func areOfSameKind(nodes []*ast.Node, kind ast.Kind) bool {
for _, n := range nodes {
if n.Kind != kind {
return false
}
}
return true
}
func leastChildren(nodes []*ast.Node) int {
min := -1
idx := -1
for i, n := range nodes {
if idx == -1 || (len(n.Children) < min) {
min = len(n.Children)
idx = i
}
}
return idx
}
func compileTreeChildren(tree *ast.Node, sep []rune) ([]match.Matcher, error) {
var matchers []match.Matcher
for _, desc := range tree.Children {
m, err := compile(desc, sep)
if err != nil {
return nil, err
}
matchers = append(matchers, optimizeMatcher(m))
}
return matchers, nil
}
func compile(tree *ast.Node, sep []rune) (m match.Matcher, err error) {
switch tree.Kind {
case ast.KindAnyOf:
// todo this could be faster on pattern_alternatives_combine_lite (see glob_test.go)
if n := minimizeTree(tree); n != nil {
return compile(n, sep)
}
matchers, err := compileTreeChildren(tree, sep)
if err != nil {
return nil, err
}
return match.NewAnyOf(matchers...), nil
case ast.KindPattern:
if len(tree.Children) == 0 {
return match.NewNothing(), nil
}
matchers, err := compileTreeChildren(tree, sep)
if err != nil {
return nil, err
}
m, err = compileMatchers(minimizeMatchers(matchers))
if err != nil {
return nil, err
}
case ast.KindAny:
m = match.NewAny(sep)
case ast.KindSuper:
m = match.NewSuper()
case ast.KindSingle:
m = match.NewSingle(sep)
case ast.KindNothing:
m = match.NewNothing()
case ast.KindList:
l := tree.Value.(ast.List)
m = match.NewList([]rune(l.Chars), l.Not)
case ast.KindRange:
r := tree.Value.(ast.Range)
m = match.NewRange(r.Lo, r.Hi, r.Not)
case ast.KindText:
t := tree.Value.(ast.Text)
m = match.NewText(t.Text)
default:
return nil, fmt.Errorf("could not compile tree: unknown node type")
}
return optimizeMatcher(m), nil
}
func Compile(tree *ast.Node, sep []rune) (match.Matcher, error) {
m, err := compile(tree, sep)
if err != nil {
return nil, err
}
return m, nil
}

80
vendor/github.com/gobwas/glob/glob.go generated vendored Normal file
View File

@@ -0,0 +1,80 @@
package glob
import (
"github.com/gobwas/glob/compiler"
"github.com/gobwas/glob/syntax"
)
// Glob represents compiled glob pattern.
type Glob interface {
Match(string) bool
}
// Compile creates Glob for given pattern and strings (if any present after pattern) as separators.
// The pattern syntax is:
//
// pattern:
// { term }
//
// term:
// `*` matches any sequence of non-separator characters
// `**` matches any sequence of characters
// `?` matches any single non-separator character
// `[` [ `!` ] { character-range } `]`
// character class (must be non-empty)
// `{` pattern-list `}`
// pattern alternatives
// c matches character c (c != `*`, `**`, `?`, `\`, `[`, `{`, `}`)
// `\` c matches character c
//
// character-range:
// c matches character c (c != `\\`, `-`, `]`)
// `\` c matches character c
// lo `-` hi matches character c for lo <= c <= hi
//
// pattern-list:
// pattern { `,` pattern }
// comma-separated (without spaces) patterns
//
func Compile(pattern string, separators ...rune) (Glob, error) {
ast, err := syntax.Parse(pattern)
if err != nil {
return nil, err
}
matcher, err := compiler.Compile(ast, separators)
if err != nil {
return nil, err
}
return matcher, nil
}
// MustCompile is the same as Compile, except that if Compile returns error, this will panic
func MustCompile(pattern string, separators ...rune) Glob {
g, err := Compile(pattern, separators...)
if err != nil {
panic(err)
}
return g
}
// QuoteMeta returns a string that quotes all glob pattern meta characters
// inside the argument text; For example, QuoteMeta(`{foo*}`) returns `\[foo\*\]`.
func QuoteMeta(s string) string {
b := make([]byte, 2*len(s))
// a byte loop is correct because all meta characters are ASCII
j := 0
for i := 0; i < len(s); i++ {
if syntax.Special(s[i]) {
b[j] = '\\'
j++
}
b[j] = s[i]
j++
}
return string(b[0:j])
}

45
vendor/github.com/gobwas/glob/match/any.go generated vendored Normal file
View File

@@ -0,0 +1,45 @@
package match
import (
"fmt"
"github.com/gobwas/glob/util/strings"
)
type Any struct {
Separators []rune
}
func NewAny(s []rune) Any {
return Any{s}
}
func (self Any) Match(s string) bool {
return strings.IndexAnyRunes(s, self.Separators) == -1
}
func (self Any) Index(s string) (int, []int) {
found := strings.IndexAnyRunes(s, self.Separators)
switch found {
case -1:
case 0:
return 0, segments0
default:
s = s[:found]
}
segments := acquireSegments(len(s))
for i := range s {
segments = append(segments, i)
}
segments = append(segments, len(s))
return 0, segments
}
func (self Any) Len() int {
return lenNo
}
func (self Any) String() string {
return fmt.Sprintf("<any:![%s]>", string(self.Separators))
}

82
vendor/github.com/gobwas/glob/match/any_of.go generated vendored Normal file
View File

@@ -0,0 +1,82 @@
package match
import "fmt"
type AnyOf struct {
Matchers Matchers
}
func NewAnyOf(m ...Matcher) AnyOf {
return AnyOf{Matchers(m)}
}
func (self *AnyOf) Add(m Matcher) error {
self.Matchers = append(self.Matchers, m)
return nil
}
func (self AnyOf) Match(s string) bool {
for _, m := range self.Matchers {
if m.Match(s) {
return true
}
}
return false
}
func (self AnyOf) Index(s string) (int, []int) {
index := -1
segments := acquireSegments(len(s))
for _, m := range self.Matchers {
idx, seg := m.Index(s)
if idx == -1 {
continue
}
if index == -1 || idx < index {
index = idx
segments = append(segments[:0], seg...)
continue
}
if idx > index {
continue
}
// here idx == index
segments = appendMerge(segments, seg)
}
if index == -1 {
releaseSegments(segments)
return -1, nil
}
return index, segments
}
func (self AnyOf) Len() (l int) {
l = -1
for _, m := range self.Matchers {
ml := m.Len()
switch {
case l == -1:
l = ml
continue
case ml == -1:
return -1
case l != ml:
return -1
}
}
return
}
func (self AnyOf) String() string {
return fmt.Sprintf("<any_of:[%s]>", self.Matchers)
}

146
vendor/github.com/gobwas/glob/match/btree.go generated vendored Normal file
View File

@@ -0,0 +1,146 @@
package match
import (
"fmt"
"unicode/utf8"
)
type BTree struct {
Value Matcher
Left Matcher
Right Matcher
ValueLengthRunes int
LeftLengthRunes int
RightLengthRunes int
LengthRunes int
}
func NewBTree(Value, Left, Right Matcher) (tree BTree) {
tree.Value = Value
tree.Left = Left
tree.Right = Right
lenOk := true
if tree.ValueLengthRunes = Value.Len(); tree.ValueLengthRunes == -1 {
lenOk = false
}
if Left != nil {
if tree.LeftLengthRunes = Left.Len(); tree.LeftLengthRunes == -1 {
lenOk = false
}
}
if Right != nil {
if tree.RightLengthRunes = Right.Len(); tree.RightLengthRunes == -1 {
lenOk = false
}
}
if lenOk {
tree.LengthRunes = tree.LeftLengthRunes + tree.ValueLengthRunes + tree.RightLengthRunes
} else {
tree.LengthRunes = -1
}
return tree
}
func (self BTree) Len() int {
return self.LengthRunes
}
// todo?
func (self BTree) Index(s string) (int, []int) {
return -1, nil
}
func (self BTree) Match(s string) bool {
inputLen := len(s)
// self.Length, self.RLen and self.LLen are values meaning the length of runes for each part
// here we manipulating byte length for better optimizations
// but these checks still works, cause minLen of 1-rune string is 1 byte.
if self.LengthRunes != -1 && self.LengthRunes > inputLen {
return false
}
// try to cut unnecessary parts
// by knowledge of length of right and left part
var offset, limit int
if self.LeftLengthRunes >= 0 {
offset = self.LeftLengthRunes
}
if self.RightLengthRunes >= 0 {
limit = inputLen - self.RightLengthRunes
} else {
limit = inputLen
}
for offset < limit {
// search for matching part in substring
index, segments := self.Value.Index(s[offset:limit])
if index == -1 {
releaseSegments(segments)
return false
}
l := s[:offset+index]
var left bool
if self.Left != nil {
left = self.Left.Match(l)
} else {
left = l == ""
}
if left {
for i := len(segments) - 1; i >= 0; i-- {
length := segments[i]
var right bool
var r string
// if there is no string for the right branch
if inputLen <= offset+index+length {
r = ""
} else {
r = s[offset+index+length:]
}
if self.Right != nil {
right = self.Right.Match(r)
} else {
right = r == ""
}
if right {
releaseSegments(segments)
return true
}
}
}
_, step := utf8.DecodeRuneInString(s[offset+index:])
offset += index + step
releaseSegments(segments)
}
return false
}
func (self BTree) String() string {
const n string = "<nil>"
var l, r string
if self.Left == nil {
l = n
} else {
l = self.Left.String()
}
if self.Right == nil {
r = n
} else {
r = self.Right.String()
}
return fmt.Sprintf("<btree:[%s<-%s->%s]>", l, self.Value, r)
}

58
vendor/github.com/gobwas/glob/match/contains.go generated vendored Normal file
View File

@@ -0,0 +1,58 @@
package match
import (
"fmt"
"strings"
)
type Contains struct {
Needle string
Not bool
}
func NewContains(needle string, not bool) Contains {
return Contains{needle, not}
}
func (self Contains) Match(s string) bool {
return strings.Contains(s, self.Needle) != self.Not
}
func (self Contains) Index(s string) (int, []int) {
var offset int
idx := strings.Index(s, self.Needle)
if !self.Not {
if idx == -1 {
return -1, nil
}
offset = idx + len(self.Needle)
if len(s) <= offset {
return 0, []int{offset}
}
s = s[offset:]
} else if idx != -1 {
s = s[:idx]
}
segments := acquireSegments(len(s) + 1)
for i := range s {
segments = append(segments, offset+i)
}
return 0, append(segments, offset+len(s))
}
func (self Contains) Len() int {
return lenNo
}
func (self Contains) String() string {
var not string
if self.Not {
not = "!"
}
return fmt.Sprintf("<contains:%s[%s]>", not, self.Needle)
}

99
vendor/github.com/gobwas/glob/match/every_of.go generated vendored Normal file
View File

@@ -0,0 +1,99 @@
package match
import (
"fmt"
)
type EveryOf struct {
Matchers Matchers
}
func NewEveryOf(m ...Matcher) EveryOf {
return EveryOf{Matchers(m)}
}
func (self *EveryOf) Add(m Matcher) error {
self.Matchers = append(self.Matchers, m)
return nil
}
func (self EveryOf) Len() (l int) {
for _, m := range self.Matchers {
if ml := m.Len(); l > 0 {
l += ml
} else {
return -1
}
}
return
}
func (self EveryOf) Index(s string) (int, []int) {
var index int
var offset int
// make `in` with cap as len(s),
// cause it is the maximum size of output segments values
next := acquireSegments(len(s))
current := acquireSegments(len(s))
sub := s
for i, m := range self.Matchers {
idx, seg := m.Index(sub)
if idx == -1 {
releaseSegments(next)
releaseSegments(current)
return -1, nil
}
if i == 0 {
// we use copy here instead of `current = seg`
// cause seg is a slice from reusable buffer `in`
// and it could be overwritten in next iteration
current = append(current, seg...)
} else {
// clear the next
next = next[:0]
delta := index - (idx + offset)
for _, ex := range current {
for _, n := range seg {
if ex+delta == n {
next = append(next, n)
}
}
}
if len(next) == 0 {
releaseSegments(next)
releaseSegments(current)
return -1, nil
}
current = append(current[:0], next...)
}
index = idx + offset
sub = s[index:]
offset += idx
}
releaseSegments(next)
return index, current
}
func (self EveryOf) Match(s string) bool {
for _, m := range self.Matchers {
if !m.Match(s) {
return false
}
}
return true
}
func (self EveryOf) String() string {
return fmt.Sprintf("<every_of:[%s]>", self.Matchers)
}

49
vendor/github.com/gobwas/glob/match/list.go generated vendored Normal file
View File

@@ -0,0 +1,49 @@
package match
import (
"fmt"
"github.com/gobwas/glob/util/runes"
"unicode/utf8"
)
type List struct {
List []rune
Not bool
}
func NewList(list []rune, not bool) List {
return List{list, not}
}
func (self List) Match(s string) bool {
r, w := utf8.DecodeRuneInString(s)
if len(s) > w {
return false
}
inList := runes.IndexRune(self.List, r) != -1
return inList == !self.Not
}
func (self List) Len() int {
return lenOne
}
func (self List) Index(s string) (int, []int) {
for i, r := range s {
if self.Not == (runes.IndexRune(self.List, r) == -1) {
return i, segmentsByRuneLength[utf8.RuneLen(r)]
}
}
return -1, nil
}
func (self List) String() string {
var not string
if self.Not {
not = "!"
}
return fmt.Sprintf("<list:%s[%s]>", not, string(self.List))
}

81
vendor/github.com/gobwas/glob/match/match.go generated vendored Normal file
View File

@@ -0,0 +1,81 @@
package match
// todo common table of rune's length
import (
"fmt"
"strings"
)
const lenOne = 1
const lenZero = 0
const lenNo = -1
type Matcher interface {
Match(string) bool
Index(string) (int, []int)
Len() int
String() string
}
type Matchers []Matcher
func (m Matchers) String() string {
var s []string
for _, matcher := range m {
s = append(s, fmt.Sprint(matcher))
}
return fmt.Sprintf("%s", strings.Join(s, ","))
}
// appendMerge merges and sorts given already SORTED and UNIQUE segments.
func appendMerge(target, sub []int) []int {
lt, ls := len(target), len(sub)
out := make([]int, 0, lt+ls)
for x, y := 0, 0; x < lt || y < ls; {
if x >= lt {
out = append(out, sub[y:]...)
break
}
if y >= ls {
out = append(out, target[x:]...)
break
}
xValue := target[x]
yValue := sub[y]
switch {
case xValue == yValue:
out = append(out, xValue)
x++
y++
case xValue < yValue:
out = append(out, xValue)
x++
case yValue < xValue:
out = append(out, yValue)
y++
}
}
target = append(target[:0], out...)
return target
}
func reverseSegments(input []int) {
l := len(input)
m := l / 2
for i := 0; i < m; i++ {
input[i], input[l-i-1] = input[l-i-1], input[i]
}
}

49
vendor/github.com/gobwas/glob/match/max.go generated vendored Normal file
View File

@@ -0,0 +1,49 @@
package match
import (
"fmt"
"unicode/utf8"
)
type Max struct {
Limit int
}
func NewMax(l int) Max {
return Max{l}
}
func (self Max) Match(s string) bool {
var l int
for range s {
l += 1
if l > self.Limit {
return false
}
}
return true
}
func (self Max) Index(s string) (int, []int) {
segments := acquireSegments(self.Limit + 1)
segments = append(segments, 0)
var count int
for i, r := range s {
count++
if count > self.Limit {
break
}
segments = append(segments, i+utf8.RuneLen(r))
}
return 0, segments
}
func (self Max) Len() int {
return lenNo
}
func (self Max) String() string {
return fmt.Sprintf("<max:%d>", self.Limit)
}

57
vendor/github.com/gobwas/glob/match/min.go generated vendored Normal file
View File

@@ -0,0 +1,57 @@
package match
import (
"fmt"
"unicode/utf8"
)
type Min struct {
Limit int
}
func NewMin(l int) Min {
return Min{l}
}
func (self Min) Match(s string) bool {
var l int
for range s {
l += 1
if l >= self.Limit {
return true
}
}
return false
}
func (self Min) Index(s string) (int, []int) {
var count int
c := len(s) - self.Limit + 1
if c <= 0 {
return -1, nil
}
segments := acquireSegments(c)
for i, r := range s {
count++
if count >= self.Limit {
segments = append(segments, i+utf8.RuneLen(r))
}
}
if len(segments) == 0 {
return -1, nil
}
return 0, segments
}
func (self Min) Len() int {
return lenNo
}
func (self Min) String() string {
return fmt.Sprintf("<min:%d>", self.Limit)
}

27
vendor/github.com/gobwas/glob/match/nothing.go generated vendored Normal file
View File

@@ -0,0 +1,27 @@
package match
import (
"fmt"
)
type Nothing struct{}
func NewNothing() Nothing {
return Nothing{}
}
func (self Nothing) Match(s string) bool {
return len(s) == 0
}
func (self Nothing) Index(s string) (int, []int) {
return 0, segments0
}
func (self Nothing) Len() int {
return lenZero
}
func (self Nothing) String() string {
return fmt.Sprintf("<nothing>")
}

50
vendor/github.com/gobwas/glob/match/prefix.go generated vendored Normal file
View File

@@ -0,0 +1,50 @@
package match
import (
"fmt"
"strings"
"unicode/utf8"
)
type Prefix struct {
Prefix string
}
func NewPrefix(p string) Prefix {
return Prefix{p}
}
func (self Prefix) Index(s string) (int, []int) {
idx := strings.Index(s, self.Prefix)
if idx == -1 {
return -1, nil
}
length := len(self.Prefix)
var sub string
if len(s) > idx+length {
sub = s[idx+length:]
} else {
sub = ""
}
segments := acquireSegments(len(sub) + 1)
segments = append(segments, length)
for i, r := range sub {
segments = append(segments, length+i+utf8.RuneLen(r))
}
return idx, segments
}
func (self Prefix) Len() int {
return lenNo
}
func (self Prefix) Match(s string) bool {
return strings.HasPrefix(s, self.Prefix)
}
func (self Prefix) String() string {
return fmt.Sprintf("<prefix:%s>", self.Prefix)
}

55
vendor/github.com/gobwas/glob/match/prefix_any.go generated vendored Normal file
View File

@@ -0,0 +1,55 @@
package match
import (
"fmt"
"strings"
"unicode/utf8"
sutil "github.com/gobwas/glob/util/strings"
)
type PrefixAny struct {
Prefix string
Separators []rune
}
func NewPrefixAny(s string, sep []rune) PrefixAny {
return PrefixAny{s, sep}
}
func (self PrefixAny) Index(s string) (int, []int) {
idx := strings.Index(s, self.Prefix)
if idx == -1 {
return -1, nil
}
n := len(self.Prefix)
sub := s[idx+n:]
i := sutil.IndexAnyRunes(sub, self.Separators)
if i > -1 {
sub = sub[:i]
}
seg := acquireSegments(len(sub) + 1)
seg = append(seg, n)
for i, r := range sub {
seg = append(seg, n+i+utf8.RuneLen(r))
}
return idx, seg
}
func (self PrefixAny) Len() int {
return lenNo
}
func (self PrefixAny) Match(s string) bool {
if !strings.HasPrefix(s, self.Prefix) {
return false
}
return sutil.IndexAnyRunes(s[len(self.Prefix):], self.Separators) == -1
}
func (self PrefixAny) String() string {
return fmt.Sprintf("<prefix_any:%s![%s]>", self.Prefix, string(self.Separators))
}

62
vendor/github.com/gobwas/glob/match/prefix_suffix.go generated vendored Normal file
View File

@@ -0,0 +1,62 @@
package match
import (
"fmt"
"strings"
)
type PrefixSuffix struct {
Prefix, Suffix string
}
func NewPrefixSuffix(p, s string) PrefixSuffix {
return PrefixSuffix{p, s}
}
func (self PrefixSuffix) Index(s string) (int, []int) {
prefixIdx := strings.Index(s, self.Prefix)
if prefixIdx == -1 {
return -1, nil
}
suffixLen := len(self.Suffix)
if suffixLen <= 0 {
return prefixIdx, []int{len(s) - prefixIdx}
}
if (len(s) - prefixIdx) <= 0 {
return -1, nil
}
segments := acquireSegments(len(s) - prefixIdx)
for sub := s[prefixIdx:]; ; {
suffixIdx := strings.LastIndex(sub, self.Suffix)
if suffixIdx == -1 {
break
}
segments = append(segments, suffixIdx+suffixLen)
sub = sub[:suffixIdx]
}
if len(segments) == 0 {
releaseSegments(segments)
return -1, nil
}
reverseSegments(segments)
return prefixIdx, segments
}
func (self PrefixSuffix) Len() int {
return lenNo
}
func (self PrefixSuffix) Match(s string) bool {
return strings.HasPrefix(s, self.Prefix) && strings.HasSuffix(s, self.Suffix)
}
func (self PrefixSuffix) String() string {
return fmt.Sprintf("<prefix_suffix:[%s,%s]>", self.Prefix, self.Suffix)
}

48
vendor/github.com/gobwas/glob/match/range.go generated vendored Normal file
View File

@@ -0,0 +1,48 @@
package match
import (
"fmt"
"unicode/utf8"
)
type Range struct {
Lo, Hi rune
Not bool
}
func NewRange(lo, hi rune, not bool) Range {
return Range{lo, hi, not}
}
func (self Range) Len() int {
return lenOne
}
func (self Range) Match(s string) bool {
r, w := utf8.DecodeRuneInString(s)
if len(s) > w {
return false
}
inRange := r >= self.Lo && r <= self.Hi
return inRange == !self.Not
}
func (self Range) Index(s string) (int, []int) {
for i, r := range s {
if self.Not != (r >= self.Lo && r <= self.Hi) {
return i, segmentsByRuneLength[utf8.RuneLen(r)]
}
}
return -1, nil
}
func (self Range) String() string {
var not string
if self.Not {
not = "!"
}
return fmt.Sprintf("<range:%s[%s,%s]>", not, string(self.Lo), string(self.Hi))
}

77
vendor/github.com/gobwas/glob/match/row.go generated vendored Normal file
View File

@@ -0,0 +1,77 @@
package match
import (
"fmt"
)
type Row struct {
Matchers Matchers
RunesLength int
Segments []int
}
func NewRow(len int, m ...Matcher) Row {
return Row{
Matchers: Matchers(m),
RunesLength: len,
Segments: []int{len},
}
}
func (self Row) matchAll(s string) bool {
var idx int
for _, m := range self.Matchers {
length := m.Len()
var next, i int
for next = range s[idx:] {
i++
if i == length {
break
}
}
if i < length || !m.Match(s[idx:idx+next+1]) {
return false
}
idx += next + 1
}
return true
}
func (self Row) lenOk(s string) bool {
var i int
for range s {
i++
if i > self.RunesLength {
return false
}
}
return self.RunesLength == i
}
func (self Row) Match(s string) bool {
return self.lenOk(s) && self.matchAll(s)
}
func (self Row) Len() (l int) {
return self.RunesLength
}
func (self Row) Index(s string) (int, []int) {
for i := range s {
if len(s[i:]) < self.RunesLength {
break
}
if self.matchAll(s[i:]) {
return i, self.Segments
}
}
return -1, nil
}
func (self Row) String() string {
return fmt.Sprintf("<row_%d:[%s]>", self.RunesLength, self.Matchers)
}

91
vendor/github.com/gobwas/glob/match/segments.go generated vendored Normal file
View File

@@ -0,0 +1,91 @@
package match
import (
"sync"
)
type SomePool interface {
Get() []int
Put([]int)
}
var segmentsPools [1024]sync.Pool
func toPowerOfTwo(v int) int {
v--
v |= v >> 1
v |= v >> 2
v |= v >> 4
v |= v >> 8
v |= v >> 16
v++
return v
}
const (
cacheFrom = 16
cacheToAndHigher = 1024
cacheFromIndex = 15
cacheToAndHigherIndex = 1023
)
var (
segments0 = []int{0}
segments1 = []int{1}
segments2 = []int{2}
segments3 = []int{3}
segments4 = []int{4}
)
var segmentsByRuneLength [5][]int = [5][]int{
0: segments0,
1: segments1,
2: segments2,
3: segments3,
4: segments4,
}
func init() {
for i := cacheToAndHigher; i >= cacheFrom; i >>= 1 {
func(i int) {
segmentsPools[i-1] = sync.Pool{New: func() interface{} {
return make([]int, 0, i)
}}
}(i)
}
}
func getTableIndex(c int) int {
p := toPowerOfTwo(c)
switch {
case p >= cacheToAndHigher:
return cacheToAndHigherIndex
case p <= cacheFrom:
return cacheFromIndex
default:
return p - 1
}
}
func acquireSegments(c int) []int {
// make []int with less capacity than cacheFrom
// is faster than acquiring it from pool
if c < cacheFrom {
return make([]int, 0, c)
}
return segmentsPools[getTableIndex(c)].Get().([]int)[:0]
}
func releaseSegments(s []int) {
c := cap(s)
// make []int with less capacity than cacheFrom
// is faster than acquiring it from pool
if c < cacheFrom {
return
}
segmentsPools[getTableIndex(c)].Put(s)
}

43
vendor/github.com/gobwas/glob/match/single.go generated vendored Normal file
View File

@@ -0,0 +1,43 @@
package match
import (
"fmt"
"github.com/gobwas/glob/util/runes"
"unicode/utf8"
)
// single represents ?
type Single struct {
Separators []rune
}
func NewSingle(s []rune) Single {
return Single{s}
}
func (self Single) Match(s string) bool {
r, w := utf8.DecodeRuneInString(s)
if len(s) > w {
return false
}
return runes.IndexRune(self.Separators, r) == -1
}
func (self Single) Len() int {
return lenOne
}
func (self Single) Index(s string) (int, []int) {
for i, r := range s {
if runes.IndexRune(self.Separators, r) == -1 {
return i, segmentsByRuneLength[utf8.RuneLen(r)]
}
}
return -1, nil
}
func (self Single) String() string {
return fmt.Sprintf("<single:![%s]>", string(self.Separators))
}

35
vendor/github.com/gobwas/glob/match/suffix.go generated vendored Normal file
View File

@@ -0,0 +1,35 @@
package match
import (
"fmt"
"strings"
)
type Suffix struct {
Suffix string
}
func NewSuffix(s string) Suffix {
return Suffix{s}
}
func (self Suffix) Len() int {
return lenNo
}
func (self Suffix) Match(s string) bool {
return strings.HasSuffix(s, self.Suffix)
}
func (self Suffix) Index(s string) (int, []int) {
idx := strings.Index(s, self.Suffix)
if idx == -1 {
return -1, nil
}
return 0, []int{idx + len(self.Suffix)}
}
func (self Suffix) String() string {
return fmt.Sprintf("<suffix:%s>", self.Suffix)
}

43
vendor/github.com/gobwas/glob/match/suffix_any.go generated vendored Normal file
View File

@@ -0,0 +1,43 @@
package match
import (
"fmt"
"strings"
sutil "github.com/gobwas/glob/util/strings"
)
type SuffixAny struct {
Suffix string
Separators []rune
}
func NewSuffixAny(s string, sep []rune) SuffixAny {
return SuffixAny{s, sep}
}
func (self SuffixAny) Index(s string) (int, []int) {
idx := strings.Index(s, self.Suffix)
if idx == -1 {
return -1, nil
}
i := sutil.LastIndexAnyRunes(s[:idx], self.Separators) + 1
return i, []int{idx + len(self.Suffix) - i}
}
func (self SuffixAny) Len() int {
return lenNo
}
func (self SuffixAny) Match(s string) bool {
if !strings.HasSuffix(s, self.Suffix) {
return false
}
return sutil.IndexAnyRunes(s[:len(s)-len(self.Suffix)], self.Separators) == -1
}
func (self SuffixAny) String() string {
return fmt.Sprintf("<suffix_any:![%s]%s>", string(self.Separators), self.Suffix)
}

33
vendor/github.com/gobwas/glob/match/super.go generated vendored Normal file
View File

@@ -0,0 +1,33 @@
package match
import (
"fmt"
)
type Super struct{}
func NewSuper() Super {
return Super{}
}
func (self Super) Match(s string) bool {
return true
}
func (self Super) Len() int {
return lenNo
}
func (self Super) Index(s string) (int, []int) {
segments := acquireSegments(len(s) + 1)
for i := range s {
segments = append(segments, i)
}
segments = append(segments, len(s))
return 0, segments
}
func (self Super) String() string {
return fmt.Sprintf("<super>")
}

45
vendor/github.com/gobwas/glob/match/text.go generated vendored Normal file
View File

@@ -0,0 +1,45 @@
package match
import (
"fmt"
"strings"
"unicode/utf8"
)
// raw represents raw string to match
type Text struct {
Str string
RunesLength int
BytesLength int
Segments []int
}
func NewText(s string) Text {
return Text{
Str: s,
RunesLength: utf8.RuneCountInString(s),
BytesLength: len(s),
Segments: []int{len(s)},
}
}
func (self Text) Match(s string) bool {
return self.Str == s
}
func (self Text) Len() int {
return self.RunesLength
}
func (self Text) Index(s string) (int, []int) {
index := strings.Index(s, self.Str)
if index == -1 {
return -1, nil
}
return index, self.Segments
}
func (self Text) String() string {
return fmt.Sprintf("<text:`%v`>", self.Str)
}

122
vendor/github.com/gobwas/glob/syntax/ast/ast.go generated vendored Normal file
View File

@@ -0,0 +1,122 @@
package ast
import (
"bytes"
"fmt"
)
type Node struct {
Parent *Node
Children []*Node
Value interface{}
Kind Kind
}
func NewNode(k Kind, v interface{}, ch ...*Node) *Node {
n := &Node{
Kind: k,
Value: v,
}
for _, c := range ch {
Insert(n, c)
}
return n
}
func (a *Node) Equal(b *Node) bool {
if a.Kind != b.Kind {
return false
}
if a.Value != b.Value {
return false
}
if len(a.Children) != len(b.Children) {
return false
}
for i, c := range a.Children {
if !c.Equal(b.Children[i]) {
return false
}
}
return true
}
func (a *Node) String() string {
var buf bytes.Buffer
buf.WriteString(a.Kind.String())
if a.Value != nil {
buf.WriteString(" =")
buf.WriteString(fmt.Sprintf("%v", a.Value))
}
if len(a.Children) > 0 {
buf.WriteString(" [")
for i, c := range a.Children {
if i > 0 {
buf.WriteString(", ")
}
buf.WriteString(c.String())
}
buf.WriteString("]")
}
return buf.String()
}
func Insert(parent *Node, children ...*Node) {
parent.Children = append(parent.Children, children...)
for _, ch := range children {
ch.Parent = parent
}
}
type List struct {
Not bool
Chars string
}
type Range struct {
Not bool
Lo, Hi rune
}
type Text struct {
Text string
}
type Kind int
const (
KindNothing Kind = iota
KindPattern
KindList
KindRange
KindText
KindAny
KindSuper
KindSingle
KindAnyOf
)
func (k Kind) String() string {
switch k {
case KindNothing:
return "Nothing"
case KindPattern:
return "Pattern"
case KindList:
return "List"
case KindRange:
return "Range"
case KindText:
return "Text"
case KindAny:
return "Any"
case KindSuper:
return "Super"
case KindSingle:
return "Single"
case KindAnyOf:
return "AnyOf"
default:
return ""
}
}

157
vendor/github.com/gobwas/glob/syntax/ast/parser.go generated vendored Normal file
View File

@@ -0,0 +1,157 @@
package ast
import (
"errors"
"fmt"
"github.com/gobwas/glob/syntax/lexer"
"unicode/utf8"
)
type Lexer interface {
Next() lexer.Token
}
type parseFn func(*Node, Lexer) (parseFn, *Node, error)
func Parse(lexer Lexer) (*Node, error) {
var parser parseFn
root := NewNode(KindPattern, nil)
var (
tree *Node
err error
)
for parser, tree = parserMain, root; parser != nil; {
parser, tree, err = parser(tree, lexer)
if err != nil {
return nil, err
}
}
return root, nil
}
func parserMain(tree *Node, lex Lexer) (parseFn, *Node, error) {
for {
token := lex.Next()
switch token.Type {
case lexer.EOF:
return nil, tree, nil
case lexer.Error:
return nil, tree, errors.New(token.Raw)
case lexer.Text:
Insert(tree, NewNode(KindText, Text{token.Raw}))
return parserMain, tree, nil
case lexer.Any:
Insert(tree, NewNode(KindAny, nil))
return parserMain, tree, nil
case lexer.Super:
Insert(tree, NewNode(KindSuper, nil))
return parserMain, tree, nil
case lexer.Single:
Insert(tree, NewNode(KindSingle, nil))
return parserMain, tree, nil
case lexer.RangeOpen:
return parserRange, tree, nil
case lexer.TermsOpen:
a := NewNode(KindAnyOf, nil)
Insert(tree, a)
p := NewNode(KindPattern, nil)
Insert(a, p)
return parserMain, p, nil
case lexer.Separator:
p := NewNode(KindPattern, nil)
Insert(tree.Parent, p)
return parserMain, p, nil
case lexer.TermsClose:
return parserMain, tree.Parent.Parent, nil
default:
return nil, tree, fmt.Errorf("unexpected token: %s", token)
}
}
return nil, tree, fmt.Errorf("unknown error")
}
func parserRange(tree *Node, lex Lexer) (parseFn, *Node, error) {
var (
not bool
lo rune
hi rune
chars string
)
for {
token := lex.Next()
switch token.Type {
case lexer.EOF:
return nil, tree, errors.New("unexpected end")
case lexer.Error:
return nil, tree, errors.New(token.Raw)
case lexer.Not:
not = true
case lexer.RangeLo:
r, w := utf8.DecodeRuneInString(token.Raw)
if len(token.Raw) > w {
return nil, tree, fmt.Errorf("unexpected length of lo character")
}
lo = r
case lexer.RangeBetween:
//
case lexer.RangeHi:
r, w := utf8.DecodeRuneInString(token.Raw)
if len(token.Raw) > w {
return nil, tree, fmt.Errorf("unexpected length of lo character")
}
hi = r
if hi < lo {
return nil, tree, fmt.Errorf("hi character '%s' should be greater than lo '%s'", string(hi), string(lo))
}
case lexer.Text:
chars = token.Raw
case lexer.RangeClose:
isRange := lo != 0 && hi != 0
isChars := chars != ""
if isChars == isRange {
return nil, tree, fmt.Errorf("could not parse range")
}
if isRange {
Insert(tree, NewNode(KindRange, Range{
Lo: lo,
Hi: hi,
Not: not,
}))
} else {
Insert(tree, NewNode(KindList, List{
Chars: chars,
Not: not,
}))
}
return parserMain, tree, nil
}
}
}

273
vendor/github.com/gobwas/glob/syntax/lexer/lexer.go generated vendored Normal file
View File

@@ -0,0 +1,273 @@
package lexer
import (
"bytes"
"fmt"
"github.com/gobwas/glob/util/runes"
"unicode/utf8"
)
const (
char_any = '*'
char_comma = ','
char_single = '?'
char_escape = '\\'
char_range_open = '['
char_range_close = ']'
char_terms_open = '{'
char_terms_close = '}'
char_range_not = '!'
char_range_between = '-'
)
var specials = []byte{
char_any,
char_single,
char_escape,
char_range_open,
char_range_close,
char_terms_open,
char_terms_close,
}
func Special(c byte) bool {
return bytes.IndexByte(specials, c) != -1
}
type tokens []Token
func (i *tokens) shift() (ret Token) {
ret = (*i)[0]
copy(*i, (*i)[1:])
*i = (*i)[:len(*i)-1]
return
}
func (i *tokens) push(v Token) {
*i = append(*i, v)
}
func (i *tokens) empty() bool {
return len(*i) == 0
}
var eof rune = 0
type lexer struct {
data string
pos int
err error
tokens tokens
termsLevel int
lastRune rune
lastRuneSize int
hasRune bool
}
func NewLexer(source string) *lexer {
l := &lexer{
data: source,
tokens: tokens(make([]Token, 0, 4)),
}
return l
}
func (l *lexer) Next() Token {
if l.err != nil {
return Token{Error, l.err.Error()}
}
if !l.tokens.empty() {
return l.tokens.shift()
}
l.fetchItem()
return l.Next()
}
func (l *lexer) peek() (r rune, w int) {
if l.pos == len(l.data) {
return eof, 0
}
r, w = utf8.DecodeRuneInString(l.data[l.pos:])
if r == utf8.RuneError {
l.errorf("could not read rune")
r = eof
w = 0
}
return
}
func (l *lexer) read() rune {
if l.hasRune {
l.hasRune = false
l.seek(l.lastRuneSize)
return l.lastRune
}
r, s := l.peek()
l.seek(s)
l.lastRune = r
l.lastRuneSize = s
return r
}
func (l *lexer) seek(w int) {
l.pos += w
}
func (l *lexer) unread() {
if l.hasRune {
l.errorf("could not unread rune")
return
}
l.seek(-l.lastRuneSize)
l.hasRune = true
}
func (l *lexer) errorf(f string, v ...interface{}) {
l.err = fmt.Errorf(f, v...)
}
func (l *lexer) inTerms() bool {
return l.termsLevel > 0
}
func (l *lexer) termsEnter() {
l.termsLevel++
}
func (l *lexer) termsLeave() {
l.termsLevel--
}
var inTextBreakers = []rune{char_single, char_any, char_range_open, char_terms_open}
var inTermsBreakers = append(inTextBreakers, char_terms_close, char_comma)
func (l *lexer) fetchItem() {
r := l.read()
switch {
case r == eof:
l.tokens.push(Token{EOF, ""})
case r == char_terms_open:
l.termsEnter()
l.tokens.push(Token{TermsOpen, string(r)})
case r == char_comma && l.inTerms():
l.tokens.push(Token{Separator, string(r)})
case r == char_terms_close && l.inTerms():
l.tokens.push(Token{TermsClose, string(r)})
l.termsLeave()
case r == char_range_open:
l.tokens.push(Token{RangeOpen, string(r)})
l.fetchRange()
case r == char_single:
l.tokens.push(Token{Single, string(r)})
case r == char_any:
if l.read() == char_any {
l.tokens.push(Token{Super, string(r) + string(r)})
} else {
l.unread()
l.tokens.push(Token{Any, string(r)})
}
default:
l.unread()
var breakers []rune
if l.inTerms() {
breakers = inTermsBreakers
} else {
breakers = inTextBreakers
}
l.fetchText(breakers)
}
}
func (l *lexer) fetchRange() {
var wantHi bool
var wantClose bool
var seenNot bool
for {
r := l.read()
if r == eof {
l.errorf("unexpected end of input")
return
}
if wantClose {
if r != char_range_close {
l.errorf("expected close range character")
} else {
l.tokens.push(Token{RangeClose, string(r)})
}
return
}
if wantHi {
l.tokens.push(Token{RangeHi, string(r)})
wantClose = true
continue
}
if !seenNot && r == char_range_not {
l.tokens.push(Token{Not, string(r)})
seenNot = true
continue
}
if n, w := l.peek(); n == char_range_between {
l.seek(w)
l.tokens.push(Token{RangeLo, string(r)})
l.tokens.push(Token{RangeBetween, string(n)})
wantHi = true
continue
}
l.unread() // unread first peek and fetch as text
l.fetchText([]rune{char_range_close})
wantClose = true
}
}
func (l *lexer) fetchText(breakers []rune) {
var data []rune
var escaped bool
reading:
for {
r := l.read()
if r == eof {
break
}
if !escaped {
if r == char_escape {
escaped = true
continue
}
if runes.IndexRune(breakers, r) != -1 {
l.unread()
break reading
}
}
escaped = false
data = append(data, r)
}
if len(data) > 0 {
l.tokens.push(Token{Text, string(data)})
}
}

88
vendor/github.com/gobwas/glob/syntax/lexer/token.go generated vendored Normal file
View File

@@ -0,0 +1,88 @@
package lexer
import "fmt"
type TokenType int
const (
EOF TokenType = iota
Error
Text
Char
Any
Super
Single
Not
Separator
RangeOpen
RangeClose
RangeLo
RangeHi
RangeBetween
TermsOpen
TermsClose
)
func (tt TokenType) String() string {
switch tt {
case EOF:
return "eof"
case Error:
return "error"
case Text:
return "text"
case Char:
return "char"
case Any:
return "any"
case Super:
return "super"
case Single:
return "single"
case Not:
return "not"
case Separator:
return "separator"
case RangeOpen:
return "range_open"
case RangeClose:
return "range_close"
case RangeLo:
return "range_lo"
case RangeHi:
return "range_hi"
case RangeBetween:
return "range_between"
case TermsOpen:
return "terms_open"
case TermsClose:
return "terms_close"
default:
return "undef"
}
}
type Token struct {
Type TokenType
Raw string
}
func (t Token) String() string {
return fmt.Sprintf("%v<%q>", t.Type, t.Raw)
}

14
vendor/github.com/gobwas/glob/syntax/syntax.go generated vendored Normal file
View File

@@ -0,0 +1,14 @@
package syntax
import (
"github.com/gobwas/glob/syntax/ast"
"github.com/gobwas/glob/syntax/lexer"
)
func Parse(s string) (*ast.Node, error) {
return ast.Parse(lexer.NewLexer(s))
}
func Special(b byte) bool {
return lexer.Special(b)
}

154
vendor/github.com/gobwas/glob/util/runes/runes.go generated vendored Normal file
View File

@@ -0,0 +1,154 @@
package runes
func Index(s, needle []rune) int {
ls, ln := len(s), len(needle)
switch {
case ln == 0:
return 0
case ln == 1:
return IndexRune(s, needle[0])
case ln == ls:
if Equal(s, needle) {
return 0
}
return -1
case ln > ls:
return -1
}
head:
for i := 0; i < ls && ls-i >= ln; i++ {
for y := 0; y < ln; y++ {
if s[i+y] != needle[y] {
continue head
}
}
return i
}
return -1
}
func LastIndex(s, needle []rune) int {
ls, ln := len(s), len(needle)
switch {
case ln == 0:
if ls == 0 {
return 0
}
return ls
case ln == 1:
return IndexLastRune(s, needle[0])
case ln == ls:
if Equal(s, needle) {
return 0
}
return -1
case ln > ls:
return -1
}
head:
for i := ls - 1; i >= 0 && i >= ln; i-- {
for y := ln - 1; y >= 0; y-- {
if s[i-(ln-y-1)] != needle[y] {
continue head
}
}
return i - ln + 1
}
return -1
}
// IndexAny returns the index of the first instance of any Unicode code point
// from chars in s, or -1 if no Unicode code point from chars is present in s.
func IndexAny(s, chars []rune) int {
if len(chars) > 0 {
for i, c := range s {
for _, m := range chars {
if c == m {
return i
}
}
}
}
return -1
}
func Contains(s, needle []rune) bool {
return Index(s, needle) >= 0
}
func Max(s []rune) (max rune) {
for _, r := range s {
if r > max {
max = r
}
}
return
}
func Min(s []rune) rune {
min := rune(-1)
for _, r := range s {
if min == -1 {
min = r
continue
}
if r < min {
min = r
}
}
return min
}
func IndexRune(s []rune, r rune) int {
for i, c := range s {
if c == r {
return i
}
}
return -1
}
func IndexLastRune(s []rune, r rune) int {
for i := len(s) - 1; i >= 0; i-- {
if s[i] == r {
return i
}
}
return -1
}
func Equal(a, b []rune) bool {
if len(a) == len(b) {
for i := 0; i < len(a); i++ {
if a[i] != b[i] {
return false
}
}
return true
}
return false
}
// HasPrefix tests whether the string s begins with prefix.
func HasPrefix(s, prefix []rune) bool {
return len(s) >= len(prefix) && Equal(s[0:len(prefix)], prefix)
}
// HasSuffix tests whether the string s ends with suffix.
func HasSuffix(s, suffix []rune) bool {
return len(s) >= len(suffix) && Equal(s[len(s)-len(suffix):], suffix)
}

39
vendor/github.com/gobwas/glob/util/strings/strings.go generated vendored Normal file
View File

@@ -0,0 +1,39 @@
package strings
import (
"strings"
"unicode/utf8"
)
func IndexAnyRunes(s string, rs []rune) int {
for _, r := range rs {
if i := strings.IndexRune(s, r); i != -1 {
return i
}
}
return -1
}
func LastIndexAnyRunes(s string, rs []rune) int {
for _, r := range rs {
i := -1
if 0 <= r && r < utf8.RuneSelf {
i = strings.LastIndexByte(s, byte(r))
} else {
sub := s
for len(sub) > 0 {
j := strings.IndexRune(s, r)
if j == -1 {
break
}
i = j
sub = sub[i+1:]
}
}
if i != -1 {
return i
}
}
return -1
}