Commit 5c95a012 by Torkel Ödegaard

Merge branch 'master' of github.com:grafana/grafana

parents ab2be340 0f592679
...@@ -2,9 +2,10 @@ ...@@ -2,9 +2,10 @@
### New Features ### New Features
* **Alerting**: Option to disable OK alert notifications [#12330](https://github.com/grafana/grafana/issues/12330) & [#6696](https://github.com/grafana/grafana/issues/6696), thx [@davewat](https://github.com/davewat)
* **Postgres/MySQL/MSSQL**: Adds support for configuration of max open/idle connections and connection max lifetime. Also, panels with multiple SQL queries will now be executed concurrently [#11711](https://github.com/grafana/grafana/issues/11711), thx [@connection-reset](https://github.com/connection-reset) * **Postgres/MySQL/MSSQL**: Adds support for configuration of max open/idle connections and connection max lifetime. Also, panels with multiple SQL queries will now be executed concurrently [#11711](https://github.com/grafana/grafana/issues/11711), thx [@connection-reset](https://github.com/connection-reset)
* **MSSQL**: Add encrypt setting to allow configuration of how data sent between client and server are encrypted [#13629](https://github.com/grafana/grafana/issues/13629), thx [@ramiro](https://github.com/ramiro) * **MSSQL**: Add encrypt setting to allow configuration of how data sent between client and server are encrypted [#13629](https://github.com/grafana/grafana/issues/13629), thx [@ramiro](https://github.com/ramiro)
* **Alerting**: Option to disable OK alert notifications [#12330](https://github.com/grafana/grafana/issues/12330) & [#6696](https://github.com/grafana/grafana/issues/6696), thx [@davewat](https://github.com/davewat) * **MySQL**: Support connecting thru Unix socket for MySQL datasource [#12342](https://github.com/grafana/grafana/issues/12342), thx [@Yukinoshita-Yukino](https://github.com/Yukinoshita-Yukino)
### Minor ### Minor
...@@ -19,6 +20,7 @@ ...@@ -19,6 +20,7 @@
# 5.3.2 (unreleased) # 5.3.2 (unreleased)
* **Postgres**: Fix template variables error [#13692](https://github.com/grafana/grafana/issues/13692), thx [@svenklemm](https://github.com/svenklemm) * **Postgres**: Fix template variables error [#13692](https://github.com/grafana/grafana/issues/13692), thx [@svenklemm](https://github.com/svenklemm)
* **Cloudwatch**: Fix service panic because of race conditions [#13674](https://github.com/grafana/grafana/issues/13674), thx [@mtanda](https://github.com/mtanda)
# 5.3.1 (2018-10-16) # 5.3.1 (2018-10-16)
......
...@@ -69,15 +69,27 @@ bra run ...@@ -69,15 +69,27 @@ bra run
Open grafana in your browser (default: `http://localhost:3000`) and login with admin user (default: `user/pass = admin/admin`). Open grafana in your browser (default: `http://localhost:3000`) and login with admin user (default: `user/pass = admin/admin`).
### Building a docker image (on linux/amd64) ### Building a Docker image
This builds a docker image from your local sources: There are two different ways to build a Grafana docker image. If you're machine is setup for Grafana development and you run linux/amd64 you can build just the image. Otherwise, there is the option to build Grafana completely within Docker.
Run the image you have built using: `docker run --rm -p 3000:3000 grafana/grafana:dev`
#### Building on linux/amd64 (fast)
1. Build the frontend `go run build.go build-frontend` 1. Build the frontend `go run build.go build-frontend`
2. Build the docker image `make build-docker-dev` 2. Build the docker image `make build-docker-dev`
The resulting image will be tagged as `grafana/grafana:dev` The resulting image will be tagged as `grafana/grafana:dev`
#### Building anywhere (slower)
Choose this option to build on platforms other than linux/amd64 and/or not have to setup the Grafana development environment.
1. `make build-docker-full` or `docker build -t grafana/grafana:dev .`
The resulting image will be tagged as `grafana/grafana:dev`
### Dev config ### Dev config
Create a custom.ini in the conf directory to override default configuration options. Create a custom.ini in the conf directory to override default configuration options.
...@@ -113,18 +125,6 @@ GRAFANA_TEST_DB=mysql go test ./pkg/... ...@@ -113,18 +125,6 @@ GRAFANA_TEST_DB=mysql go test ./pkg/...
GRAFANA_TEST_DB=postgres go test ./pkg/... GRAFANA_TEST_DB=postgres go test ./pkg/...
``` ```
## Building custom docker image
You can build a custom image using Docker, which doesn't require installing any dependencies besides docker itself.
```bash
git clone https://github.com/grafana/grafana
cd grafana
docker build -t grafana:dev .
docker run -d --name=grafana -p 3000:3000 grafana:dev
```
Open grafana in your browser (default: `http://localhost:3000`) and login with admin user (default: `user/pass = admin/admin`).
## Contribute ## Contribute
If you have any idea for an improvement or found a bug, do not hesitate to open an issue. If you have any idea for an improvement or found a bug, do not hesitate to open an issue.
......
...@@ -160,6 +160,7 @@ ...@@ -160,6 +160,7 @@
"react-redux": "^5.0.7", "react-redux": "^5.0.7",
"react-select": "2.1.0", "react-select": "2.1.0",
"react-sizeme": "^2.3.6", "react-sizeme": "^2.3.6",
"react-table": "^6.8.6",
"react-transition-group": "^2.2.1", "react-transition-group": "^2.2.1",
"redux": "^4.0.0", "redux": "^4.0.0",
"redux-logger": "^3.0.6", "redux-logger": "^3.0.6",
......
...@@ -185,7 +185,9 @@ func (a *ldapAuther) GetGrafanaUserFor(ctx *m.ReqContext, ldapUser *LdapUserInfo ...@@ -185,7 +185,9 @@ func (a *ldapAuther) GetGrafanaUserFor(ctx *m.ReqContext, ldapUser *LdapUserInfo
if ldapUser.isMemberOf(group.GroupDN) { if ldapUser.isMemberOf(group.GroupDN) {
extUser.OrgRoles[group.OrgId] = group.OrgRole extUser.OrgRoles[group.OrgId] = group.OrgRole
extUser.IsGrafanaAdmin = group.IsGrafanaAdmin if extUser.IsGrafanaAdmin == nil || *extUser.IsGrafanaAdmin == false {
extUser.IsGrafanaAdmin = group.IsGrafanaAdmin
}
} }
} }
......
...@@ -86,9 +86,10 @@ func (e *CloudWatchExecutor) Query(ctx context.Context, dsInfo *models.DataSourc ...@@ -86,9 +86,10 @@ func (e *CloudWatchExecutor) Query(ctx context.Context, dsInfo *models.DataSourc
} }
func (e *CloudWatchExecutor) executeTimeSeriesQuery(ctx context.Context, queryContext *tsdb.TsdbQuery) (*tsdb.Response, error) { func (e *CloudWatchExecutor) executeTimeSeriesQuery(ctx context.Context, queryContext *tsdb.TsdbQuery) (*tsdb.Response, error) {
result := &tsdb.Response{ results := &tsdb.Response{
Results: make(map[string]*tsdb.QueryResult), Results: make(map[string]*tsdb.QueryResult),
} }
resultChan := make(chan *tsdb.QueryResult, len(queryContext.Queries))
eg, ectx := errgroup.WithContext(ctx) eg, ectx := errgroup.WithContext(ctx)
...@@ -102,10 +103,10 @@ func (e *CloudWatchExecutor) executeTimeSeriesQuery(ctx context.Context, queryCo ...@@ -102,10 +103,10 @@ func (e *CloudWatchExecutor) executeTimeSeriesQuery(ctx context.Context, queryCo
RefId := queryContext.Queries[i].RefId RefId := queryContext.Queries[i].RefId
query, err := parseQuery(queryContext.Queries[i].Model) query, err := parseQuery(queryContext.Queries[i].Model)
if err != nil { if err != nil {
result.Results[RefId] = &tsdb.QueryResult{ results.Results[RefId] = &tsdb.QueryResult{
Error: err, Error: err,
} }
return result, nil return results, nil
} }
query.RefId = RefId query.RefId = RefId
...@@ -118,10 +119,10 @@ func (e *CloudWatchExecutor) executeTimeSeriesQuery(ctx context.Context, queryCo ...@@ -118,10 +119,10 @@ func (e *CloudWatchExecutor) executeTimeSeriesQuery(ctx context.Context, queryCo
} }
if query.Id == "" && query.Expression != "" { if query.Id == "" && query.Expression != "" {
result.Results[query.RefId] = &tsdb.QueryResult{ results.Results[query.RefId] = &tsdb.QueryResult{
Error: fmt.Errorf("Invalid query: id should be set if using expression"), Error: fmt.Errorf("Invalid query: id should be set if using expression"),
} }
return result, nil return results, nil
} }
eg.Go(func() error { eg.Go(func() error {
...@@ -130,12 +131,13 @@ func (e *CloudWatchExecutor) executeTimeSeriesQuery(ctx context.Context, queryCo ...@@ -130,12 +131,13 @@ func (e *CloudWatchExecutor) executeTimeSeriesQuery(ctx context.Context, queryCo
return err return err
} }
if err != nil { if err != nil {
result.Results[query.RefId] = &tsdb.QueryResult{ resultChan <- &tsdb.QueryResult{
RefId: query.RefId,
Error: err, Error: err,
} }
return nil return nil
} }
result.Results[queryRes.RefId] = queryRes resultChan <- queryRes
return nil return nil
}) })
} }
...@@ -149,10 +151,10 @@ func (e *CloudWatchExecutor) executeTimeSeriesQuery(ctx context.Context, queryCo ...@@ -149,10 +151,10 @@ func (e *CloudWatchExecutor) executeTimeSeriesQuery(ctx context.Context, queryCo
return err return err
} }
for _, queryRes := range queryResponses { for _, queryRes := range queryResponses {
result.Results[queryRes.RefId] = queryRes
if err != nil { if err != nil {
result.Results[queryRes.RefId].Error = err queryRes.Error = err
} }
resultChan <- queryRes
} }
return nil return nil
}) })
...@@ -162,8 +164,12 @@ func (e *CloudWatchExecutor) executeTimeSeriesQuery(ctx context.Context, queryCo ...@@ -162,8 +164,12 @@ func (e *CloudWatchExecutor) executeTimeSeriesQuery(ctx context.Context, queryCo
if err := eg.Wait(); err != nil { if err := eg.Wait(); err != nil {
return nil, err return nil, err
} }
close(resultChan)
for result := range resultChan {
results.Results[result.RefId] = result
}
return result, nil return results, nil
} }
func (e *CloudWatchExecutor) executeQuery(ctx context.Context, query *CloudWatchQuery, queryContext *tsdb.TsdbQuery) (*tsdb.QueryResult, error) { func (e *CloudWatchExecutor) executeQuery(ctx context.Context, query *CloudWatchQuery, queryContext *tsdb.TsdbQuery) (*tsdb.QueryResult, error) {
......
...@@ -5,6 +5,7 @@ import ( ...@@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"reflect" "reflect"
"strconv" "strconv"
"strings"
"github.com/go-sql-driver/mysql" "github.com/go-sql-driver/mysql"
"github.com/go-xorm/core" "github.com/go-xorm/core"
...@@ -20,10 +21,14 @@ func init() { ...@@ -20,10 +21,14 @@ func init() {
func newMysqlQueryEndpoint(datasource *models.DataSource) (tsdb.TsdbQueryEndpoint, error) { func newMysqlQueryEndpoint(datasource *models.DataSource) (tsdb.TsdbQueryEndpoint, error) {
logger := log.New("tsdb.mysql") logger := log.New("tsdb.mysql")
protocol := "tcp"
if strings.HasPrefix(datasource.Url, "/") {
protocol = "unix"
}
cnnstr := fmt.Sprintf("%s:%s@%s(%s)/%s?collation=utf8mb4_unicode_ci&parseTime=true&loc=UTC&allowNativePasswords=true", cnnstr := fmt.Sprintf("%s:%s@%s(%s)/%s?collation=utf8mb4_unicode_ci&parseTime=true&loc=UTC&allowNativePasswords=true",
datasource.User, datasource.User,
datasource.Password, datasource.Password,
"tcp", protocol,
datasource.Url, datasource.Url,
datasource.Database, datasource.Database,
) )
......
...@@ -644,7 +644,9 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> { ...@@ -644,7 +644,9 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
/> />
)} )}
{supportsTable && showingTable ? ( {supportsTable && showingTable ? (
<Table className="m-t-3" data={tableResult} loading={loading} onClickCell={this.onClickTableCell} /> <div className="panel-container">
<Table data={tableResult} loading={loading} onClickCell={this.onClickTableCell} />
</div>
) : null} ) : null}
{supportsLogs && showingLogs ? <Logs data={logsResult} loading={loading} /> : null} {supportsLogs && showingLogs ? <Logs data={logsResult} loading={loading} /> : null}
</main> </main>
......
import _ from 'lodash';
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import ReactTable from 'react-table';
import TableModel from 'app/core/table_model'; import TableModel from 'app/core/table_model';
const EMPTY_TABLE = new TableModel(); const EMPTY_TABLE = new TableModel();
interface TableProps { interface TableProps {
className?: string;
data: TableModel; data: TableModel;
loading: boolean; loading: boolean;
onClickCell?: (columnKey: string, rowValue: string) => void; onClickCell?: (columnKey: string, rowValue: string) => void;
} }
interface SFCCellProps { function prepareRows(rows, columnNames) {
columnIndex: number; return rows.map(cells => _.zipObject(columnNames, cells));
onClickCell?: (columnKey: string, rowValue: string, columnIndex: number, rowIndex: number, table: TableModel) => void;
rowIndex: number;
table: TableModel;
value: string;
} }
function Cell(props: SFCCellProps) { export default class Table extends PureComponent<TableProps> {
const { columnIndex, rowIndex, table, value, onClickCell } = props; getCellProps = (state, rowInfo, column) => {
const column = table.columns[columnIndex]; return {
if (column && column.filterable && onClickCell) { onClick: () => {
const onClick = event => { const columnKey = column.Header;
event.preventDefault(); const rowValue = rowInfo.row[columnKey];
onClickCell(column.text, value, columnIndex, rowIndex, table); this.props.onClickCell(columnKey, rowValue);
},
}; };
return ( };
<td>
<a className="link" onClick={onClick}>
{value}
</a>
</td>
);
}
return <td>{value}</td>;
}
export default class Table extends PureComponent<TableProps, {}> {
render() { render() {
const { className = '', data, loading, onClickCell } = this.props; const { data, loading } = this.props;
const tableModel = data || EMPTY_TABLE; const tableModel = data || EMPTY_TABLE;
if (!loading && data && data.rows.length === 0) { const columnNames = tableModel.columns.map(({ text }) => text);
return ( const columns = tableModel.columns.map(({ filterable, text }) => ({
<table className={`${className} filter-table`}> Header: text,
<thead> accessor: text,
<tr> show: text !== 'Time',
<th>Table</th> Cell: row => <span className={filterable ? 'link' : ''}>{row.value}</span>,
</tr> }));
</thead> const noDataText = data ? 'The queries returned no data for a table.' : '';
<tbody>
<tr>
<td className="muted">The queries returned no data for a table.</td>
</tr>
</tbody>
</table>
);
}
return ( return (
<table className={`${className} filter-table`}> <ReactTable
<thead> columns={columns}
<tr>{tableModel.columns.map(col => <th key={col.text}>{col.text}</th>)}</tr> data={tableModel.rows}
</thead> getTdProps={this.getCellProps}
<tbody> loading={loading}
{tableModel.rows.map((row, i) => ( minRows={0}
<tr key={i}> noDataText={noDataText}
{row.map((value, j) => ( resolveData={data => prepareRows(data, columnNames)}
<Cell showPagination={data}
key={j} />
columnIndex={j}
rowIndex={i}
value={String(value)}
table={data}
onClickCell={onClickCell}
/>
))}
</tr>
))}
</tbody>
</table>
); );
} }
} }
// vendor // DEPENDENCIES
@import '../../node_modules/react-table/react-table.css';
// VENDOR
@import '../vendor/css/timepicker.css'; @import '../vendor/css/timepicker.css';
@import '../vendor/css/spectrum.css'; @import '../vendor/css/spectrum.css';
@import '../vendor/css/rc-cascader.scss'; @import '../vendor/css/rc-cascader.scss';
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
font-size: $font-size-root; font-size: $font-size-root;
font-family: $font-family-monospace; font-family: $font-family-monospace;
height: auto; height: auto;
word-break: break-word;
} }
.slate-query-field-wrapper { .slate-query-field-wrapper {
......
...@@ -126,7 +126,7 @@ ...@@ -126,7 +126,7 @@
} }
.query-row-tools { .query-row-tools {
width: 6rem; white-space: nowrap;
} }
.query-row-field { .query-row-field {
...@@ -186,3 +186,60 @@ ...@@ -186,3 +186,60 @@
margin: 0.25em 0.5em 0.5em; margin: 0.25em 0.5em 0.5em;
} }
} }
// ReactTable basic overrides (does not include pivot/groups/filters)
// When integrating ReactTable as new panel plugin, move to _panel_table.scss
.ReactTable {
border: none;
// Allow some space for the no-data text
min-height: 120px;
}
.ReactTable .rt-thead.-header {
box-shadow: none;
background: $list-item-bg;
border-top: 2px solid $body-bg;
border-bottom: 2px solid $body-bg;
height: 2em;
}
.ReactTable .rt-thead.-header .rt-th {
text-align: left;
color: $blue;
font-weight: 500;
}
.ReactTable .rt-thead .rt-td,
.ReactTable .rt-thead .rt-th {
padding: 0.45em 0 0.45em 1.1em;
border-right: none;
box-shadow: none;
}
.ReactTable .rt-tbody .rt-td {
padding: 0.45em 0 0.45em 1.1em;
border-bottom: 2px solid $body-bg;
border-right: 2px solid $body-bg;
}
.ReactTable .rt-tbody .rt-td:last-child {
border-right: none;
}
.ReactTable .-pagination .-btn {
color: $blue;
background: $list-item-bg;
}
.ReactTable .-pagination input,
.ReactTable .-pagination select {
color: $input-color;
background-color: $input-bg;
}
.ReactTable .-loading {
background: $input-bg;
}
.ReactTable .-loading.-active {
opacity: 0.8;
}
.ReactTable .-loading > div {
color: $input-color;
}
.ReactTable .rt-tr .rt-td:last-child {
text-align: right;
}
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment