Merge branch 'main' into ifrost/rich-history-migration/frontend-storage-refactor
# Conflicts: # public/app/features/explore/Wrapper.tsx
This commit is contained in:
+5
-5
@@ -1094,7 +1094,7 @@ type: docker
|
||||
---
|
||||
depends_on: []
|
||||
kind: pipeline
|
||||
name: oss-build-publish-e2e-release
|
||||
name: oss-build-e2e-publish-release
|
||||
node:
|
||||
type: no-parallel
|
||||
platform:
|
||||
@@ -1599,7 +1599,7 @@ volumes:
|
||||
medium: memory
|
||||
---
|
||||
depends_on:
|
||||
- oss-build-publish-e2e-release
|
||||
- oss-build-e2e-publish-release
|
||||
- oss-test-release
|
||||
- oss-integration-tests-release
|
||||
kind: pipeline
|
||||
@@ -2858,7 +2858,7 @@ volumes:
|
||||
---
|
||||
depends_on: []
|
||||
kind: pipeline
|
||||
name: oss-build-publish-e2e-release-branch
|
||||
name: oss-build-e2e-publish-release-branch
|
||||
node:
|
||||
type: no-parallel
|
||||
platform:
|
||||
@@ -3305,7 +3305,7 @@ volumes:
|
||||
medium: memory
|
||||
---
|
||||
depends_on:
|
||||
- oss-build-publish-e2e-release-branch
|
||||
- oss-build-e2e-publish-release-branch
|
||||
- oss-test-release-branch
|
||||
- oss-integration-tests-release-branch
|
||||
kind: pipeline
|
||||
@@ -4191,6 +4191,6 @@ kind: secret
|
||||
name: gcp_upload_artifacts_key
|
||||
---
|
||||
kind: signature
|
||||
hmac: 4d1a5696bf1e510fb51a021c07e240c50cb913724ce08ed52cce037ff02dd8de
|
||||
hmac: f26fc6de1d7ec3cf5608b70c851c8cf2b998e07abad8c52714e72ad072492387
|
||||
|
||||
...
|
||||
|
||||
Vendored
+1
@@ -8,6 +8,7 @@
|
||||
"mode": "auto",
|
||||
"program": "${workspaceFolder}/pkg/cmd/grafana-server/",
|
||||
"env": {},
|
||||
"cwd": "${workspaceFolder}",
|
||||
"args": ["--homepath", "${workspaceFolder}", "--packaging", "dev"]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,3 +1,23 @@
|
||||
<!-- 8.4.0-beta1 START -->
|
||||
|
||||
# 8.4.0-beta1 (2022-02-02)
|
||||
|
||||
### Features and enhancements
|
||||
|
||||
- **Alerting:** Support WeCom as a contact point type. [#40975](https://github.com/grafana/grafana/pull/40975), [@smallpath](https://github.com/smallpath)
|
||||
- **Alerting:** UI for mute timings. [#41578](https://github.com/grafana/grafana/pull/41578), [@nathanrodman](https://github.com/nathanrodman)
|
||||
- **Alerting:** add settings for peer reconnection in HA mode. [#42300](https://github.com/grafana/grafana/pull/42300), [@JohnnyQQQQ](https://github.com/JohnnyQQQQ)
|
||||
- **Auth:** implement auto_sign_up for auth.jwt. [#37040](https://github.com/grafana/grafana/pull/37040), [@Roguelazer](https://github.com/Roguelazer)
|
||||
- **Dashboard:** Add Show unknown variables toggle to dashboard settings. [#41854](https://github.com/grafana/grafana/pull/41854), [@hugohaggmark](https://github.com/hugohaggmark)
|
||||
- **Instrumentation:** Logger migration from log15 to gokit/log. [#41636](https://github.com/grafana/grafana/pull/41636), [@ying-jeanne](https://github.com/ying-jeanne)
|
||||
- **MSSQL:** Change regex to validate Provider connection string. [#40248](https://github.com/grafana/grafana/pull/40248), [@ianselmi](https://github.com/ianselmi)
|
||||
- **MSSQL:** Configuration of certificate verification for TLS connection. [#31865](https://github.com/grafana/grafana/pull/31865), [@mortenaa](https://github.com/mortenaa)
|
||||
- **Middleware:** Don't require HTTPS for HSTS headers to be emitted. [#35147](https://github.com/grafana/grafana/pull/35147), [@alexmv](https://github.com/alexmv)
|
||||
- **Navigation:** Implement Keyboard Navigation. [#41618](https://github.com/grafana/grafana/pull/41618), [@axelavargas](https://github.com/axelavargas)
|
||||
- **News:** Reload feed when changing the time range or refreshing. [#42217](https://github.com/grafana/grafana/pull/42217), [@ashharrison90](https://github.com/ashharrison90)
|
||||
- **UI/Plot:** Implement keyboard controls for plot cursor. [#42244](https://github.com/grafana/grafana/pull/42244), [@kaydelaney](https://github.com/kaydelaney)
|
||||
|
||||
<!-- 8.4.0-beta1 END -->
|
||||
<!-- 8.3.4 START -->
|
||||
|
||||
# 8.3.4 (2022-01-17)
|
||||
|
||||
@@ -15,10 +15,11 @@ Grafana 8.0 introduced new and improved alerting that centralizes alerting infor
|
||||
|
||||
Grafana alerting is enabled by default for new OSS installations. For older installations, it is still an [opt-in]({{< relref "./unified-alerting/opt-in.md" >}}) feature.
|
||||
|
||||
| Release | Cloud | Enterprise | OSS |
|
||||
| ----------- | ------------- | ---------- | -------------------------------- |
|
||||
| Grafana 8.2 | On by default | Opt-in | Opt-in |
|
||||
| Grafana 8.3 | On by default | Opt-in | On by default for new installs\* |
|
||||
| Release | Cloud | Enterprise | OSS |
|
||||
| ------------------------ | ------------- | ------------- | -------------------------------- |
|
||||
| Grafana 8.2 | On by default | Opt-in | Opt-in |
|
||||
| Grafana 8.3 | On by default | Opt-in | On by default for new installs\* |
|
||||
| Grafana 9.0 (unreleased) | On by default | On by default | On by default |
|
||||
|
||||
> **Note:** New installs include existing installs which do not have any alerts configured.
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ weight = 114
|
||||
|
||||
Grafana alerting is enabled by default for new OSS installations. For older installations, it is still an [opt-in]({{< relref "../unified-alerting/opt-in.md" >}}) feature.
|
||||
|
||||
> **Note**: Legacy dashboard alerts is deprecated and will be removed in a future release. We encourage you to migrate to [Grafana alerting]({{< relref "../unified-alerting/_index.md" >}}) for all existing installations.
|
||||
> **Note**: Legacy dashboard alerts are deprecated and will be removed in Grafana 9. We encourage you to migrate to [Grafana alerting]({{< relref "../unified-alerting/_index.md" >}}) for all existing installations.
|
||||
|
||||
Legacy dashboard alerts have two main components:
|
||||
|
||||
|
||||
@@ -501,9 +501,3 @@ The org id of the datasource where the query data will be written.
|
||||
|
||||
If all `default_remote_write_*` properties are set, this information will be populated at startup. If a remote write target has
|
||||
already been configured, nothing will happen.
|
||||
|
||||
## [feature_highlights]
|
||||
|
||||
### enabled
|
||||
|
||||
Whether the feature highlights feature is enabled
|
||||
|
||||
@@ -8,6 +8,7 @@ weight = 10000
|
||||
Here you can find detailed release notes that list everything that is included in every release as well as notices
|
||||
about deprecations, breaking changes as well as changes that relate to plugin development.
|
||||
|
||||
- [Release notes for 8.4.0-beta1]({{< relref "release-notes-8-4-0-beta1" >}})
|
||||
- [Release notes for 8.3.4]({{< relref "release-notes-8-3-4" >}})
|
||||
- [Release notes for 8.3.3]({{< relref "release-notes-8-3-3" >}})
|
||||
- [Release notes for 8.3.2]({{< relref "release-notes-8-3-2" >}})
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
+++
|
||||
title = "Release notes for Grafana 8.4.0-beta1"
|
||||
hide_menu = true
|
||||
+++
|
||||
|
||||
<!-- Auto generated by update changelog github action -->
|
||||
|
||||
# Release notes for Grafana 8.4.0-beta1
|
||||
|
||||
### Features and enhancements
|
||||
|
||||
- **Alerting:** Support WeCom as a contact point type. [#40975](https://github.com/grafana/grafana/pull/40975), [@smallpath](https://github.com/smallpath)
|
||||
- **Alerting:** UI for mute timings. [#41578](https://github.com/grafana/grafana/pull/41578), [@nathanrodman](https://github.com/nathanrodman)
|
||||
- **Alerting:** add settings for peer reconnection in HA mode. [#42300](https://github.com/grafana/grafana/pull/42300), [@JohnnyQQQQ](https://github.com/JohnnyQQQQ)
|
||||
- **Auth:** implement auto_sign_up for auth.jwt. [#37040](https://github.com/grafana/grafana/pull/37040), [@Roguelazer](https://github.com/Roguelazer)
|
||||
- **Dashboard:** Add Show unknown variables toggle to dashboard settings. [#41854](https://github.com/grafana/grafana/pull/41854), [@hugohaggmark](https://github.com/hugohaggmark)
|
||||
- **Instrumentation:** Logger migration from log15 to gokit/log. [#41636](https://github.com/grafana/grafana/pull/41636), [@ying-jeanne](https://github.com/ying-jeanne)
|
||||
- **MSSQL:** Change regex to validate Provider connection string. [#40248](https://github.com/grafana/grafana/pull/40248), [@ianselmi](https://github.com/ianselmi)
|
||||
- **MSSQL:** Configuration of certificate verification for TLS connection. [#31865](https://github.com/grafana/grafana/pull/31865), [@mortenaa](https://github.com/mortenaa)
|
||||
- **Middleware:** Don't require HTTPS for HSTS headers to be emitted. [#35147](https://github.com/grafana/grafana/pull/35147), [@alexmv](https://github.com/alexmv)
|
||||
- **Navigation:** Implement Keyboard Navigation. [#41618](https://github.com/grafana/grafana/pull/41618), [@axelavargas](https://github.com/axelavargas)
|
||||
- **News:** Reload feed when changing the time range or refreshing. [#42217](https://github.com/grafana/grafana/pull/42217), [@ashharrison90](https://github.com/ashharrison90)
|
||||
- **UI/Plot:** Implement keyboard controls for plot cursor. [#42244](https://github.com/grafana/grafana/pull/42244), [@kaydelaney](https://github.com/kaydelaney)
|
||||
@@ -3,23 +3,23 @@ import { e2e } from '@grafana/e2e';
|
||||
const PAGE_UNDER_TEST = 'kVi2Gex7z/test-variable-output';
|
||||
|
||||
describe('Variables - Constant', () => {
|
||||
it('can add a new text box variable', () => {
|
||||
it('can add a new constant variable', () => {
|
||||
e2e.flows.login('admin', 'admin');
|
||||
e2e.flows.openDashboard({ uid: `${PAGE_UNDER_TEST}?orgId=1&editview=templating` });
|
||||
|
||||
// Create a new "Constant" variable
|
||||
e2e.components.CallToActionCard.buttonV2('Add variable').click();
|
||||
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalTypeSelect().type('Constant{enter}');
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalNameInput().clear().type('VariableUnderTest').blur();
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalLabelInput().type('Variable under test').blur();
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.ConstantVariable.constantOptionsQueryInput().type('pesto').blur();
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalTypeSelectV2().type('Constant{enter}');
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalNameInputV2().clear().type('VariableUnderTest').blur();
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalLabelInputV2().type('Variable under test').blur();
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.ConstantVariable.constantOptionsQueryInputV2().type('pesto').blur();
|
||||
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.previewOfValuesOption().eq(0).should('have.text', 'pesto');
|
||||
|
||||
// Navigate back to the homepage and change the selected variable value
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.submitButton().click();
|
||||
e2e.components.BackButton.backArrow().should('be.visible').click({ force: true });
|
||||
e2e.components.BackButton.backArrow().click({ force: true });
|
||||
e2e.components.RefreshPicker.runButtonV2().click();
|
||||
|
||||
// Assert it was rendered
|
||||
|
||||
@@ -3,9 +3,9 @@ import { e2e } from '@grafana/e2e';
|
||||
const PAGE_UNDER_TEST = 'kVi2Gex7z/test-variable-output';
|
||||
|
||||
function fillInCustomVariable(name: string, label: string, value: string) {
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalTypeSelect().type('Custom{enter}');
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalNameInput().clear().type(name).blur();
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalLabelInput().type(label).blur();
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalTypeSelectV2().type('Custom{enter}');
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalNameInputV2().clear().type(name).blur();
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalLabelInputV2().type(label).blur();
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.CustomVariable.customValueInput().type(value).blur();
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ describe('Variables - Custom', () => {
|
||||
|
||||
// Create a new "Custom" variable
|
||||
e2e.components.CallToActionCard.buttonV2('Add variable').click();
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalTypeSelect().type('Custom{enter}');
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalTypeSelectV2().type('Custom{enter}');
|
||||
|
||||
// Set it's name, label, and content
|
||||
fillInCustomVariable('VariableUnderTest', 'Variable under test', 'One : 1,Two : 2, Three : 3');
|
||||
@@ -50,7 +50,7 @@ describe('Variables - Custom', () => {
|
||||
|
||||
// Navigate back to the homepage and change the selected variable value
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.submitButton().click();
|
||||
e2e.components.BackButton.backArrow().should('be.visible').click({ force: true });
|
||||
e2e.components.BackButton.backArrow().click({ force: true });
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownValueLinkTexts('One').click();
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownOptionTexts('Two').click();
|
||||
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import { e2e } from '@grafana/e2e';
|
||||
|
||||
const PAGE_UNDER_TEST = 'kVi2Gex7z/test-variable-output';
|
||||
|
||||
describe('Variables - Datasource', () => {
|
||||
it('can add a new datasource variable', () => {
|
||||
e2e.flows.login('admin', 'admin');
|
||||
e2e.flows.openDashboard({ uid: `${PAGE_UNDER_TEST}?orgId=1&editview=templating` });
|
||||
|
||||
// Create a new "Datasource" variable
|
||||
e2e.components.CallToActionCard.buttonV2('Add variable').click();
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalTypeSelectV2().type('Data source{enter}');
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalNameInputV2().clear().type('VariableUnderTest').blur();
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalLabelInputV2().type('Variable under test').blur();
|
||||
|
||||
// If this is failing, but sure to check there are Prometheus datasources named "gdev-prometheus" and "gdev-slow-prometheus"
|
||||
// Or, just update is to match some gdev datasources to test with :)
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.DatasourceVariable.datasourceSelect().type('Prometheus{enter}');
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.previewOfValuesOption()
|
||||
.eq(0)
|
||||
.should('have.text', 'gdev-prometheus');
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.previewOfValuesOption()
|
||||
.eq(1)
|
||||
.should('have.text', 'gdev-slow-prometheus');
|
||||
|
||||
// Navigate back to the homepage and change the selected variable value
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.submitButton().click();
|
||||
e2e.components.BackButton.backArrow().click({ force: true });
|
||||
e2e.components.RefreshPicker.runButtonV2().click();
|
||||
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownValueLinkTexts('gdev-prometheus').click();
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownOptionTexts('gdev-slow-prometheus').click();
|
||||
|
||||
// Assert it was rendered
|
||||
e2e().get('.markdown-html').should('include.text', 'VariableUnderTest: gdev-slow-prometheus');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
import { e2e } from '@grafana/e2e';
|
||||
|
||||
const PAGE_UNDER_TEST = 'kVi2Gex7z/test-variable-output';
|
||||
|
||||
function assertPreviewValues(expectedValues: string[]) {
|
||||
for (const expected of expectedValues) {
|
||||
const index = expectedValues.indexOf(expected);
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.previewOfValuesOption().eq(index).should('have.text', expected);
|
||||
}
|
||||
}
|
||||
|
||||
describe('Variables - Interval', () => {
|
||||
it('can add a new interval variable', () => {
|
||||
e2e.flows.login('admin', 'admin');
|
||||
e2e.flows.openDashboard({ uid: `${PAGE_UNDER_TEST}?orgId=1&editview=templating` });
|
||||
|
||||
// Create a new "Interval" variable
|
||||
e2e.components.CallToActionCard.buttonV2('Add variable').click();
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalTypeSelectV2().type('Interval{enter}');
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalNameInputV2().clear().type('VariableUnderTest').blur();
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalLabelInputV2().type('Variable under test').blur();
|
||||
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.IntervalVariable.intervalsValueInput()
|
||||
.clear()
|
||||
.type('10s,10m,60m,90m,1h30m')
|
||||
.blur();
|
||||
|
||||
assertPreviewValues(['10s', '10m', '60m', '90m', '1h30m']);
|
||||
|
||||
// Navigate back to the homepage and change the selected variable value
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.submitButton().click();
|
||||
e2e.components.BackButton.backArrow().click({ force: true });
|
||||
e2e.components.RefreshPicker.runButtonV2().click();
|
||||
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownValueLinkTexts('10s').click();
|
||||
e2e.pages.Dashboard.SubMenu.submenuItemValueDropDownOptionTexts('1h30m').click();
|
||||
|
||||
// Assert it was rendered
|
||||
e2e().get('.markdown-html').should('include.text', 'VariableUnderTest: 1h30m');
|
||||
});
|
||||
});
|
||||
@@ -9,18 +9,18 @@ describe('Variables - Query - Add variable', () => {
|
||||
|
||||
e2e.pages.Dashboard.Settings.Variables.List.newButton().should('be.visible').click();
|
||||
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalNameInput()
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalNameInputV2()
|
||||
.should('be.visible')
|
||||
.within((input) => {
|
||||
expect(input.attr('placeholder')).equals('name');
|
||||
expect(input.val()).equals('query0');
|
||||
});
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalTypeSelect()
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalTypeSelectV2()
|
||||
.should('be.visible')
|
||||
.within((select) => {
|
||||
e2e.components.Select.singleValue().should('have.text', 'Query');
|
||||
});
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalLabelInput()
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalLabelInputV2()
|
||||
.should('be.visible')
|
||||
.within((input) => {
|
||||
expect(input.attr('placeholder')).equals('optional display name');
|
||||
@@ -33,7 +33,7 @@ describe('Variables - Query - Add variable', () => {
|
||||
expect(input.attr('placeholder')).equals('descriptive text');
|
||||
expect(input.val()).equals('');
|
||||
});
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalHideSelect()
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalHideSelectV2()
|
||||
.should('be.visible')
|
||||
.within((select) => {
|
||||
e2e.components.Select.singleValue().should('have.text', '');
|
||||
@@ -45,19 +45,19 @@ describe('Variables - Query - Add variable', () => {
|
||||
e2e.components.Select.singleValue().should('have.text', 'gdev-testdata');
|
||||
});
|
||||
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsRefreshSelect()
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsRefreshSelectV2()
|
||||
.should('be.visible')
|
||||
.within((select) => {
|
||||
e2e.components.Select.singleValue().should('have.text', 'On dashboard load');
|
||||
});
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsRegExInput()
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsRegExInputV2()
|
||||
.should('be.visible')
|
||||
.within((input) => {
|
||||
const placeholder = '/.*-(?<text>.*)-(?<value>.*)-.*/';
|
||||
expect(input.attr('placeholder')).equals(placeholder);
|
||||
expect(input.val()).equals('');
|
||||
});
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsSortSelect()
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsSortSelectV2()
|
||||
.should('be.visible')
|
||||
.within((select) => {
|
||||
e2e.components.Select.singleValue().should('have.text', 'Disabled');
|
||||
@@ -66,7 +66,7 @@ describe('Variables - Query - Add variable', () => {
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsIncludeAllSwitch().should('not.be.checked');
|
||||
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.previewOfValuesOption().should('not.exist');
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsCustomAllInput().should('not.exist');
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsCustomAllInputV2().should('not.exist');
|
||||
});
|
||||
|
||||
it('adding a single value query variable', () => {
|
||||
@@ -75,7 +75,7 @@ describe('Variables - Query - Add variable', () => {
|
||||
|
||||
e2e.pages.Dashboard.Settings.Variables.List.newButton().should('be.visible').click();
|
||||
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalLabelInput()
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalLabelInputV2()
|
||||
.should('be.visible')
|
||||
.clear()
|
||||
.type('a label');
|
||||
@@ -89,7 +89,7 @@ describe('Variables - Query - Add variable', () => {
|
||||
.type('*')
|
||||
.blur();
|
||||
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsRegExInput()
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsRegExInputV2()
|
||||
.should('be.visible')
|
||||
.type('/.*C.*/')
|
||||
.blur();
|
||||
@@ -125,7 +125,7 @@ describe('Variables - Query - Add variable', () => {
|
||||
|
||||
e2e.pages.Dashboard.Settings.Variables.List.newButton().should('be.visible').click();
|
||||
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalLabelInput()
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalLabelInputV2()
|
||||
.should('be.visible')
|
||||
.clear()
|
||||
.type('a label');
|
||||
@@ -139,7 +139,7 @@ describe('Variables - Query - Add variable', () => {
|
||||
.type('*')
|
||||
.blur();
|
||||
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsRegExInput()
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsRegExInputV2()
|
||||
.should('be.visible')
|
||||
.type('/.*C.*/')
|
||||
.blur();
|
||||
@@ -152,7 +152,7 @@ describe('Variables - Query - Add variable', () => {
|
||||
.click({ force: true })
|
||||
.should('be.checked');
|
||||
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsCustomAllInput().within((input) => {
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsCustomAllInputV2().within((input) => {
|
||||
expect(input.attr('placeholder')).equals('blank = auto');
|
||||
expect(input.val()).equals('');
|
||||
});
|
||||
|
||||
@@ -7,19 +7,19 @@ describe('Variables - Text box', () => {
|
||||
e2e.flows.login('admin', 'admin');
|
||||
e2e.flows.openDashboard({ uid: `${PAGE_UNDER_TEST}?orgId=1&editview=templating` });
|
||||
|
||||
// Create a new "Custom" variable
|
||||
// Create a new "text box" variable
|
||||
e2e.components.CallToActionCard.buttonV2('Add variable').click();
|
||||
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalTypeSelect().type('Text box{enter}');
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalNameInput().clear().type('VariableUnderTest').blur();
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalLabelInput().type('Variable under test').blur();
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.TextBoxVariable.textBoxOptionsQueryInput().type('cat-dog').blur();
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalTypeSelectV2().type('Text box{enter}');
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalNameInputV2().clear().type('VariableUnderTest').blur();
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalLabelInputV2().type('Variable under test').blur();
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.TextBoxVariable.textBoxOptionsQueryInputV2().type('cat-dog').blur();
|
||||
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.previewOfValuesOption().eq(0).should('have.text', 'cat-dog');
|
||||
|
||||
// Navigate back to the homepage and change the selected variable value
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.submitButton().click();
|
||||
e2e.components.BackButton.backArrow().should('be.visible').click({ force: true });
|
||||
e2e.components.BackButton.backArrow().click({ force: true });
|
||||
e2e().get('#VariableUnderTest').clear().type('dog-cat').blur();
|
||||
|
||||
// Assert it was rendered
|
||||
|
||||
@@ -201,7 +201,7 @@ function validateVariable(value: string) {
|
||||
|
||||
e2e.pages.Dashboard.Settings.Variables.List.tableRowNameFields('text').should('be.visible').click();
|
||||
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.TextBoxVariable.textBoxOptionsQueryInput()
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.TextBoxVariable.textBoxOptionsQueryInputV2()
|
||||
.should('be.visible')
|
||||
.should('have.value', value);
|
||||
}
|
||||
@@ -234,7 +234,7 @@ function changeQueryInput() {
|
||||
|
||||
e2e.pages.Dashboard.Settings.Variables.List.tableRowNameFields('text').should('be.visible').click();
|
||||
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.TextBoxVariable.textBoxOptionsQueryInput()
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.TextBoxVariable.textBoxOptionsQueryInputV2()
|
||||
.should('be.visible')
|
||||
.clear()
|
||||
.type('changed value')
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
<!-- The template body needs to be wrapped in div to prevent Premailer from adding </tr></td> tags which breaks the HTML structure -->
|
||||
<div>
|
||||
|
||||
[[Subject .Subject "[[.Title]]"]]
|
||||
|
||||
[[ define "alert" ]]
|
||||
<tr>
|
||||
<td colspan="2" class="value">
|
||||
<span class="value-heading">Value:</span> <span class="value-value">[[ .ValueString ]]</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" class="value">
|
||||
<span class="value-heading">Value:</span> <span class="value-value">[[ .ValueString ]]</span>
|
||||
</td>
|
||||
</tr>
|
||||
[[ if gt (len .Annotations.SortedPairs) 0 ]]
|
||||
<tr>
|
||||
<td colspan="2" class="annotations">
|
||||
@@ -187,62 +190,63 @@
|
||||
<tr>
|
||||
<td class="twelve">
|
||||
<table>
|
||||
[[ if gt (len .Alerts.Firing) 0 ]]
|
||||
<tr>
|
||||
<td colspan="2" class="section-heading">
|
||||
Firing: [[ .Alerts.Firing | len ]] alert[[ if gt (len .Alerts.Firing) 1 ]]s[[ end ]][[ if gt (len .GroupLabels.SortedPairs) 1 ]] for
|
||||
[[ range .GroupLabels.SortedPairs ]]
|
||||
[[ .Name ]]=[[ .Value ]]
|
||||
[[ end ]][[ end ]]
|
||||
</td>
|
||||
</tr>
|
||||
[[ range .Alerts.Firing ]]
|
||||
[[ if gt (len .Alerts.Firing) 0 ]]
|
||||
<tr>
|
||||
<td colspan="2" class="section-heading">
|
||||
Firing: [[ .Alerts.Firing | len ]] alert[[ if gt (len .Alerts.Firing) 1 ]]s[[ end ]][[ if gt (len .GroupLabels.SortedPairs) 1 ]] for
|
||||
[[ range .GroupLabels.SortedPairs ]]
|
||||
[[ .Name ]]=[[ .Value ]]
|
||||
[[ end ]][[ end ]]
|
||||
</td>
|
||||
</tr>
|
||||
[[ range .Alerts.Firing ]]
|
||||
<tr>
|
||||
<td
|
||||
class="status-tag status-firing"
|
||||
width="68"
|
||||
>
|
||||
Firing
|
||||
</td>
|
||||
<td class="alert-label">
|
||||
[[ .Labels.alertname ]]
|
||||
</td>
|
||||
</tr>
|
||||
[[ template "alert" . ]]
|
||||
[[ end ]]
|
||||
[[ end ]]
|
||||
[[ if gt (len .Alerts.Resolved) 0 ]]
|
||||
<tr>
|
||||
<td colspan="2" class="section-heading">
|
||||
Resolved: [[ .Alerts.Resolved | len ]] alert[[ if gt (len .Alerts.Resolved) 1 ]]s[[ end ]][[ if gt (len .GroupLabels.SortedPairs) 1 ]] for
|
||||
[[ range .GroupLabels.SortedPairs ]]
|
||||
[[ .Name ]]=[[ .Value ]]
|
||||
[[ end ]][[ end ]]
|
||||
</td>
|
||||
</tr>
|
||||
[[ range .Alerts.Resolved ]]
|
||||
<tr>
|
||||
<td
|
||||
class="status-tag status-resolved"
|
||||
width="68"
|
||||
>
|
||||
Resolved
|
||||
</td>
|
||||
<td class="alert-label">
|
||||
[[ .Labels.alertname ]]
|
||||
</td>
|
||||
</tr>
|
||||
[[ template "alert" . ]]
|
||||
[[ end ]]
|
||||
[[ end ]]
|
||||
<tr>
|
||||
<td
|
||||
class="status-tag status-firing"
|
||||
width="68"
|
||||
>
|
||||
Firing
|
||||
</td>
|
||||
<td class="alert-label">
|
||||
[[ .Labels.alertname ]]
|
||||
<td colspan="2">
|
||||
<a href="[[ .AlertPageUrl ]]" class="button">Go to alerts page</a>
|
||||
</td>
|
||||
</tr>
|
||||
[[ template "alert" . ]]
|
||||
[[ end ]]
|
||||
[[ end ]]
|
||||
[[ if gt (len .Alerts.Resolved) 0 ]]
|
||||
<tr>
|
||||
<td colspan="2" class="section-heading">
|
||||
Resolved: [[ .Alerts.Resolved | len ]] alert[[ if gt (len .Alerts.Resolved) 1 ]]s[[ end ]][[ if gt (len .GroupLabels.SortedPairs) 1 ]] for
|
||||
[[ range .GroupLabels.SortedPairs ]]
|
||||
[[ .Name ]]=[[ .Value ]]
|
||||
[[ end ]][[ end ]]
|
||||
</td>
|
||||
</tr>
|
||||
[[ range .Alerts.Resolved ]]
|
||||
<tr>
|
||||
<td
|
||||
class="status-tag status-resolved"
|
||||
width="68"
|
||||
>
|
||||
Resolved
|
||||
</td>
|
||||
<td class="alert-label">
|
||||
[[ .Labels.alertname ]]
|
||||
</td>
|
||||
</tr>
|
||||
[[ template "alert" . ]]
|
||||
[[ end ]]
|
||||
[[ end ]]
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<a href="[[ .AlertPageUrl ]]" class="button">Go to alerts page</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
[[ end ]]
|
||||
|
||||
</div>
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"stable": "8.3.4",
|
||||
"testing": "8.3.4"
|
||||
"testing": "8.4.0-beta1"
|
||||
}
|
||||
|
||||
+1
-1
@@ -2,5 +2,5 @@
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true,
|
||||
"packages": ["packages/*"],
|
||||
"version": "8.4.0-pre"
|
||||
"version": "8.5.0-pre"
|
||||
}
|
||||
|
||||
+2
-2
@@ -3,7 +3,7 @@
|
||||
"license": "AGPL-3.0-only",
|
||||
"private": true,
|
||||
"name": "grafana",
|
||||
"version": "8.4.0-pre",
|
||||
"version": "8.5.0-pre",
|
||||
"repository": "github:grafana/grafana",
|
||||
"scripts": {
|
||||
"api-tests": "jest --notify --watch --config=devenv/e2e-api-tests/jest.js",
|
||||
@@ -35,7 +35,7 @@
|
||||
"packages:typecheck": "lerna run typecheck",
|
||||
"packages:clean": "lerna run clean",
|
||||
"precommit": "yarn run lint-staged",
|
||||
"prettier:check": "prettier --list-different \"**/*.{scss,md,mdx}\"",
|
||||
"prettier:check": "prettier --check --list-different=false --loglevel=warn \"**/*.{scss,md,mdx}\"",
|
||||
"prettier:write": "prettier --list-different \"**/*.{scss,md,mdx}\" --write",
|
||||
"start": "yarn themes:generate && yarn dev --watch",
|
||||
"start:noTsCheck": "yarn start --env noTsCheck=1",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/data",
|
||||
"version": "8.4.0-pre",
|
||||
"version": "8.5.0-pre",
|
||||
"description": "Grafana Data Library",
|
||||
"keywords": [
|
||||
"typescript"
|
||||
@@ -22,7 +22,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@braintree/sanitize-url": "5.0.2",
|
||||
"@grafana/schema": "8.4.0-pre",
|
||||
"@grafana/schema": "8.5.0-pre",
|
||||
"@types/d3-interpolate": "^1.4.0",
|
||||
"d3-interpolate": "1.4.0",
|
||||
"date-fns": "2.28.0",
|
||||
|
||||
@@ -36,4 +36,5 @@ export interface FeatureToggles {
|
||||
showFeatureFlagsInUI?: boolean;
|
||||
disable_http_request_histogram?: boolean;
|
||||
validatedQueries?: boolean;
|
||||
featureHighlights?: boolean;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/e2e-selectors",
|
||||
"version": "8.4.0-pre",
|
||||
"version": "8.5.0-pre",
|
||||
"description": "Grafana End-to-End Test Selectors Library",
|
||||
"keywords": [
|
||||
"cli",
|
||||
|
||||
@@ -110,20 +110,28 @@ export const Pages = {
|
||||
*/
|
||||
modeLabelEdit: 'Variable editor Header mode Edit',
|
||||
generalNameInput: 'Variable editor Form Name field',
|
||||
generalNameInputV2: 'data-testid Variable editor Form Name field',
|
||||
generalTypeSelect: 'Variable editor Form Type select',
|
||||
generalTypeSelectV2: 'data-testid Variable editor Form Type select',
|
||||
generalLabelInput: 'Variable editor Form Label field',
|
||||
generalLabelInputV2: 'data-testid Variable editor Form Label field',
|
||||
generalHideSelect: 'Variable editor Form Hide select',
|
||||
generalHideSelectV2: 'data-testid Variable editor Form Hide select',
|
||||
selectionOptionsMultiSwitch: 'Variable editor Form Multi switch',
|
||||
selectionOptionsIncludeAllSwitch: 'Variable editor Form IncludeAll switch',
|
||||
selectionOptionsCustomAllInput: 'Variable editor Form IncludeAll field',
|
||||
selectionOptionsCustomAllInputV2: 'data-testid Variable editor Form IncludeAll field',
|
||||
previewOfValuesOption: 'Variable editor Preview of Values option',
|
||||
submitButton: 'Variable editor Submit button',
|
||||
},
|
||||
QueryVariable: {
|
||||
queryOptionsDataSourceSelect: Components.DataSourcePicker.container,
|
||||
queryOptionsRefreshSelect: 'Variable editor Form Query Refresh select',
|
||||
queryOptionsRefreshSelectV2: 'data-testid Variable editor Form Query Refresh select',
|
||||
queryOptionsRegExInput: 'Variable editor Form Query RegEx field',
|
||||
queryOptionsRegExInputV2: 'data-testid Variable editor Form Query RegEx field',
|
||||
queryOptionsSortSelect: 'Variable editor Form Query Sort select',
|
||||
queryOptionsSortSelectV2: 'data-testid Variable editor Form Query Sort select',
|
||||
queryOptionsQueryInput: 'Variable editor Form Default Variable Query Editor textarea',
|
||||
valueGroupsTagsEnabledSwitch: 'Variable editor Form Query UseTags switch',
|
||||
valueGroupsTagsTagsQueryInput: 'Variable editor Form Query TagsQuery field',
|
||||
@@ -131,13 +139,21 @@ export const Pages = {
|
||||
},
|
||||
ConstantVariable: {
|
||||
constantOptionsQueryInput: 'Variable editor Form Constant Query field',
|
||||
constantOptionsQueryInputV2: 'data-testid Variable editor Form Constant Query field',
|
||||
},
|
||||
DatasourceVariable: {
|
||||
datasourceSelect: 'data-testid datasource variable datasource type',
|
||||
},
|
||||
TextBoxVariable: {
|
||||
textBoxOptionsQueryInput: 'Variable editor Form TextBox Query field',
|
||||
textBoxOptionsQueryInputV2: 'data-testid Variable editor Form TextBox Query field',
|
||||
},
|
||||
CustomVariable: {
|
||||
customValueInput: 'data-testid custom-variable-input',
|
||||
},
|
||||
IntervalVariable: {
|
||||
intervalsValueInput: 'data-testid interval variable intervals input',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/e2e",
|
||||
"version": "8.4.0-pre",
|
||||
"version": "8.5.0-pre",
|
||||
"description": "Grafana End-to-End Test Library",
|
||||
"keywords": [
|
||||
"cli",
|
||||
@@ -48,7 +48,7 @@
|
||||
"@babel/core": "7.16.7",
|
||||
"@babel/preset-env": "7.16.7",
|
||||
"@cypress/webpack-preprocessor": "5.11.0",
|
||||
"@grafana/e2e-selectors": "8.4.0-pre",
|
||||
"@grafana/e2e-selectors": "8.5.0-pre",
|
||||
"@grafana/tsconfig": "^1.0.0-rc1",
|
||||
"@mochajs/json-file-reporter": "^1.2.0",
|
||||
"babel-loader": "8.2.3",
|
||||
|
||||
@@ -162,7 +162,7 @@ const addVariable = (config: PartialAddVariableConfig, isFirst: boolean): AddVar
|
||||
|
||||
// This field is key to many reactive changes
|
||||
if (type !== VARIABLE_TYPE_QUERY) {
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalTypeSelect()
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalTypeSelectV2()
|
||||
.should('be.visible')
|
||||
.within(() => {
|
||||
e2e.components.Select.singleValue().should('have.text', 'Query').click();
|
||||
@@ -171,10 +171,10 @@ const addVariable = (config: PartialAddVariableConfig, isFirst: boolean): AddVar
|
||||
}
|
||||
|
||||
if (label) {
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalLabelInput().type(label);
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalLabelInputV2().type(label);
|
||||
}
|
||||
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalNameInput().clear().type(name);
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.General.generalNameInputV2().clear().type(name);
|
||||
|
||||
if (
|
||||
dataSource &&
|
||||
@@ -188,7 +188,7 @@ const addVariable = (config: PartialAddVariableConfig, isFirst: boolean): AddVar
|
||||
}
|
||||
|
||||
if (constantValue && type === VARIABLE_TYPE_CONSTANT) {
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.ConstantVariable.constantOptionsQueryInput().type(constantValue);
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.ConstantVariable.constantOptionsQueryInputV2().type(constantValue);
|
||||
}
|
||||
|
||||
if (type === VARIABLE_TYPE_QUERY) {
|
||||
@@ -197,7 +197,7 @@ const addVariable = (config: PartialAddVariableConfig, isFirst: boolean): AddVar
|
||||
}
|
||||
|
||||
if (regex) {
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsRegExInput().type(regex);
|
||||
e2e.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsRegExInputV2().type(regex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/runtime",
|
||||
"version": "8.4.0-pre",
|
||||
"version": "8.5.0-pre",
|
||||
"description": "Grafana Runtime Library",
|
||||
"keywords": [
|
||||
"grafana",
|
||||
@@ -22,9 +22,9 @@
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@grafana/data": "8.4.0-pre",
|
||||
"@grafana/e2e-selectors": "8.4.0-pre",
|
||||
"@grafana/ui": "8.4.0-pre",
|
||||
"@grafana/data": "8.5.0-pre",
|
||||
"@grafana/e2e-selectors": "8.5.0-pre",
|
||||
"@grafana/ui": "8.5.0-pre",
|
||||
"@sentry/browser": "6.17.2",
|
||||
"history": "4.10.1",
|
||||
"lodash": "4.17.21",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/schema",
|
||||
"version": "8.4.0-pre",
|
||||
"version": "8.5.0-pre",
|
||||
"description": "Grafana Schema Library",
|
||||
"keywords": [
|
||||
"typescript"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/toolkit",
|
||||
"version": "8.4.0-pre",
|
||||
"version": "8.5.0-pre",
|
||||
"description": "Grafana Toolkit",
|
||||
"keywords": [
|
||||
"grafana",
|
||||
@@ -28,10 +28,10 @@
|
||||
"dependencies": {
|
||||
"@babel/core": "7.13.14",
|
||||
"@babel/preset-env": "7.13.12",
|
||||
"@grafana/data": "8.4.0-pre",
|
||||
"@grafana/data": "8.5.0-pre",
|
||||
"@grafana/eslint-config": "2.5.2",
|
||||
"@grafana/tsconfig": "^1.0.0-rc1",
|
||||
"@grafana/ui": "8.4.0-pre",
|
||||
"@grafana/ui": "8.5.0-pre",
|
||||
"@jest/core": "26.6.3",
|
||||
"@rushstack/eslint-patch": "1.0.6",
|
||||
"@types/command-exists": "^1.2.0",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Grafana Labs",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@grafana/ui",
|
||||
"version": "8.4.0-pre",
|
||||
"version": "8.5.0-pre",
|
||||
"description": "Grafana Components Library",
|
||||
"keywords": [
|
||||
"grafana",
|
||||
@@ -33,9 +33,9 @@
|
||||
"@emotion/css": "11.7.1",
|
||||
"@emotion/react": "11.7.1",
|
||||
"@grafana/aws-sdk": "0.0.31",
|
||||
"@grafana/data": "8.4.0-pre",
|
||||
"@grafana/e2e-selectors": "8.4.0-pre",
|
||||
"@grafana/schema": "8.4.0-pre",
|
||||
"@grafana/data": "8.5.0-pre",
|
||||
"@grafana/e2e-selectors": "8.5.0-pre",
|
||||
"@grafana/schema": "8.5.0-pre",
|
||||
"@grafana/slate-react": "0.22.10-grafana",
|
||||
"@monaco-editor/react": "4.3.1",
|
||||
"@popperjs/core": "2.11.2",
|
||||
|
||||
+40
-8
@@ -18,6 +18,7 @@ import {
|
||||
import { Field } from '../../Forms/Field';
|
||||
import { getInputStyles, Input } from '../../Input/Input';
|
||||
import { Icon } from '../../Icon/Icon';
|
||||
import { Tooltip } from '../../Tooltip/Tooltip';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@@ -115,14 +116,13 @@ export function RelativeTimeRangePicker(props: RelativeTimeRangePickerProps): Re
|
||||
</CustomScrollbar>
|
||||
<div className={styles.rightSide}>
|
||||
<div className={styles.title}>
|
||||
<TimePickerTitle>Specify time range</TimePickerTitle>
|
||||
<div className={styles.description}>
|
||||
Specify a relative time range, for more information see{' '}
|
||||
<a href="https://grafana.com/docs/grafana/latest/dashboards/time-range-controls/">
|
||||
docs <Icon name="external-link-alt" />
|
||||
</a>
|
||||
.
|
||||
</div>
|
||||
<TimePickerTitle>
|
||||
<Tooltip content={<TooltipContent />} placement="bottom" theme="info">
|
||||
<div>
|
||||
Specify time range <Icon name="info-circle" />
|
||||
</div>
|
||||
</Tooltip>
|
||||
</TimePickerTitle>
|
||||
</div>
|
||||
<Field label="From" invalid={!from.validation.isValid} error={from.validation.errorMessage}>
|
||||
<Input
|
||||
@@ -152,6 +152,38 @@ export function RelativeTimeRangePicker(props: RelativeTimeRangePickerProps): Re
|
||||
);
|
||||
}
|
||||
|
||||
const TooltipContent = () => {
|
||||
const styles = useStyles2(toolTipStyles);
|
||||
return (
|
||||
<>
|
||||
<div className={styles.supported}>
|
||||
Supported formats: <code className={styles.tooltip}>now-[digit]s/m/h/d/w</code>
|
||||
</div>
|
||||
<div>Example: to select a time range from 10 minutes ago to now</div>
|
||||
<code className={styles.tooltip}>From: now-10m To: now</code>
|
||||
<div className={styles.link}>
|
||||
For more information see{' '}
|
||||
<a href="https://grafana.com/docs/grafana/latest/dashboards/time-range-controls/">
|
||||
docs <Icon name="external-link-alt" />
|
||||
</a>
|
||||
.
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const toolTipStyles = (theme: GrafanaTheme2) => ({
|
||||
supported: css`
|
||||
margin-bottom: ${theme.spacing(1)};
|
||||
`,
|
||||
tooltip: css`
|
||||
margin: 0;
|
||||
`,
|
||||
link: css`
|
||||
margin-top: ${theme.spacing(1)};
|
||||
`,
|
||||
});
|
||||
|
||||
const getStyles = (fromError?: string, toError?: string) => (theme: GrafanaTheme2) => {
|
||||
const inputStyles = getInputStyles({ theme, invalid: false });
|
||||
const bodyMinimumHeight = 250;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@jaegertracing/jaeger-ui-components",
|
||||
"version": "8.4.0-pre",
|
||||
"version": "8.5.0-pre",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -27,8 +27,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@emotion/css": "11.7.1",
|
||||
"@grafana/data": "8.4.0-pre",
|
||||
"@grafana/ui": "8.4.0-pre",
|
||||
"@grafana/data": "8.5.0-pre",
|
||||
"@grafana/ui": "8.5.0-pre",
|
||||
"chance": "^1.0.10",
|
||||
"classnames": "^2.2.5",
|
||||
"combokeys": "^3.0.0",
|
||||
|
||||
+10
-10
@@ -13,7 +13,6 @@ import (
|
||||
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
acmiddleware "github.com/grafana/grafana/pkg/services/accesscontrol/middleware"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
sa "github.com/grafana/grafana/pkg/services/serviceaccounts/manager"
|
||||
)
|
||||
|
||||
var plog = log.New("api")
|
||||
@@ -58,8 +57,9 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
r.Get("/org/users", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionOrgUsersRead)), hs.Index)
|
||||
r.Get("/org/users/new", reqOrgAdmin, hs.Index)
|
||||
r.Get("/org/users/invite", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionUsersCreate)), hs.Index)
|
||||
r.Get("/org/teams", reqCanAccessTeams, hs.Index)
|
||||
r.Get("/org/teams/*", reqCanAccessTeams, hs.Index)
|
||||
r.Get("/org/teams", authorize(reqCanAccessTeams, ac.EvalPermission(ac.ActionTeamsRead)), hs.Index)
|
||||
r.Get("/org/teams/edit/*", authorize(reqCanAccessTeams, teamsEditAccessEvaluator), hs.Index)
|
||||
r.Get("/org/teams/new", authorize(reqCanAccessTeams, ac.EvalPermission(ac.ActionTeamsCreate)), hs.Index)
|
||||
r.Get("/org/serviceaccounts", middleware.ReqOrgAdmin, hs.Index)
|
||||
r.Get("/org/serviceaccounts/:serviceAccountId", middleware.ReqOrgAdmin, hs.Index)
|
||||
r.Get("/org/apikeys/", reqOrgAdmin, hs.Index)
|
||||
@@ -127,8 +127,8 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
r.Get("/user/password/send-reset-email", reqNotSignedIn, hs.Index)
|
||||
r.Get("/user/password/reset", hs.Index)
|
||||
|
||||
r.Post("/api/user/password/send-reset-email", routing.Wrap(SendResetPasswordEmail))
|
||||
r.Post("/api/user/password/reset", routing.Wrap(ResetPassword))
|
||||
r.Post("/api/user/password/send-reset-email", routing.Wrap(hs.SendResetPasswordEmail))
|
||||
r.Post("/api/user/password/reset", routing.Wrap(hs.ResetPassword))
|
||||
|
||||
// dashboard snapshots
|
||||
r.Get("/dashboard/snapshot/*", reqNoAuth, hs.Index)
|
||||
@@ -256,10 +256,10 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
|
||||
// auth api keys
|
||||
apiRoute.Group("/auth/keys", func(keysRoute routing.RouteRegister) {
|
||||
keysRoute.Get("/", authorize(reqOrgAdmin, sa.ActionApikeyListEv), routing.Wrap(GetAPIKeys))
|
||||
keysRoute.Post("/", authorize(reqOrgAdmin, sa.ActionApikeyAddEv), quota("api_key"), routing.Wrap(hs.AddAPIKey))
|
||||
keysRoute.Post("/additional", authorize(reqOrgAdmin, sa.ActionApikeyAddAdditionalEv), quota("api_key"), routing.Wrap(hs.AdditionalAPIKey))
|
||||
keysRoute.Delete("/:id", authorize(reqOrgAdmin, sa.ActionApikeyRemoveEv), routing.Wrap(DeleteAPIKey))
|
||||
keysRoute.Get("/", routing.Wrap(GetAPIKeys))
|
||||
keysRoute.Post("/", quota("api_key"), routing.Wrap(hs.AddAPIKey))
|
||||
keysRoute.Post("/additional", quota("api_key"), routing.Wrap(hs.AdditionalAPIKey))
|
||||
keysRoute.Delete("/:id", routing.Wrap(DeleteAPIKey))
|
||||
}, reqOrgAdmin)
|
||||
|
||||
// Preferences
|
||||
@@ -373,7 +373,7 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
|
||||
// Search
|
||||
apiRoute.Get("/search/sorting", routing.Wrap(hs.ListSortOptions))
|
||||
apiRoute.Get("/search/", routing.Wrap(Search))
|
||||
apiRoute.Get("/search/", routing.Wrap(hs.Search))
|
||||
|
||||
// metrics
|
||||
apiRoute.Post("/tsdb/query", authorize(reqSignedIn, ac.EvalPermission(ActionDatasourcesQuery)), routing.Wrap(hs.QueryMetrics))
|
||||
|
||||
@@ -311,9 +311,10 @@ func setupSimpleHTTPServer(features *featuremgmt.FeatureManager) *HTTPServer {
|
||||
cfg.IsFeatureToggleEnabled = features.IsEnabled
|
||||
|
||||
return &HTTPServer{
|
||||
Cfg: cfg,
|
||||
Features: features,
|
||||
Bus: bus.GetBus(),
|
||||
Cfg: cfg,
|
||||
Features: features,
|
||||
Bus: bus.GetBus(),
|
||||
AccessControl: accesscontrolmock.New().WithDisabled(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ func logSentryEventScenario(t *testing.T, desc string, event frontendlogging.Fro
|
||||
}))
|
||||
|
||||
origHandler := frontendLogger.GetLogger()
|
||||
frontendLogger.AddLogger(newfrontendLogger, "info", map[string]level.Option{})
|
||||
frontendLogger.SetLogger(level.NewFilter(newfrontendLogger, level.AllowInfo()))
|
||||
sourceMapReads := []SourceMapReadRecord{}
|
||||
|
||||
t.Cleanup(func() {
|
||||
|
||||
@@ -273,9 +273,6 @@ func (hs *HTTPServer) getFrontendSettingsMap(c *models.ReqContext) (map[string]i
|
||||
"enabled": hs.Cfg.SectionWithEnvOverrides("recorded_queries").Key("enabled").MustBool(true),
|
||||
},
|
||||
"unifiedAlertingEnabled": hs.Cfg.UnifiedAlerting.Enabled,
|
||||
"featureHighlights": map[string]bool{
|
||||
"enabled": hs.SettingsProvider.Section("feature_highlights").KeyValue("enabled").MustBool(false),
|
||||
},
|
||||
}
|
||||
|
||||
if hs.Cfg.GeomapDefaultBaseLayerConfig != nil {
|
||||
|
||||
@@ -44,8 +44,8 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/live"
|
||||
"github.com/grafana/grafana/pkg/services/live/pushhttp"
|
||||
"github.com/grafana/grafana/pkg/services/login"
|
||||
"github.com/grafana/grafana/pkg/services/login/authinfoservice"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert"
|
||||
"github.com/grafana/grafana/pkg/services/notifications"
|
||||
"github.com/grafana/grafana/pkg/services/provisioning"
|
||||
"github.com/grafana/grafana/pkg/services/query"
|
||||
"github.com/grafana/grafana/pkg/services/queryhistory"
|
||||
@@ -127,8 +127,9 @@ type HTTPServer struct {
|
||||
teamGuardian teamguardian.TeamGuardian
|
||||
queryDataService *query.Service
|
||||
serviceAccountsService serviceaccounts.Service
|
||||
authInfoService authinfoservice.Service
|
||||
authInfoService login.AuthInfoService
|
||||
TeamPermissionsService *resourcepermissions.Service
|
||||
NotificationService *notifications.NotificationService
|
||||
}
|
||||
|
||||
type ServerOptions struct {
|
||||
@@ -155,7 +156,8 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi
|
||||
pluginsUpdateChecker *updatechecker.PluginsService, searchUsersService searchusers.Service,
|
||||
dataSourcesService *datasources.Service, secretsService secrets.Service, queryDataService *query.Service,
|
||||
ldapGroups ldap.Groups, teamGuardian teamguardian.TeamGuardian, serviceaccountsService serviceaccounts.Service,
|
||||
authInfoService authinfoservice.Service, resourcePermissionServices *resourceservices.ResourceServices) (*HTTPServer, error) {
|
||||
authInfoService login.AuthInfoService, resourcePermissionServices *resourceservices.ResourceServices,
|
||||
notificationService *notifications.NotificationService) (*HTTPServer, error) {
|
||||
web.Env = cfg.Env
|
||||
m := web.New()
|
||||
|
||||
@@ -215,6 +217,7 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi
|
||||
serviceAccountsService: serviceaccountsService,
|
||||
authInfoService: authInfoService,
|
||||
TeamPermissionsService: resourcePermissionServices.GetTeamService(),
|
||||
NotificationService: notificationService,
|
||||
}
|
||||
if hs.Listener != nil {
|
||||
hs.log.Debug("Using provided listener")
|
||||
|
||||
+4
-5
@@ -7,7 +7,6 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/dtos"
|
||||
"github.com/grafana/grafana/pkg/api/navlinks"
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
@@ -148,7 +147,7 @@ func enableServiceAccount(hs *HTTPServer, c *models.ReqContext) bool {
|
||||
hs.Features.IsEnabled(featuremgmt.FlagServiceAccounts)
|
||||
}
|
||||
|
||||
func enableTeams(hs *HTTPServer, c *models.ReqContext) bool {
|
||||
func (hs *HTTPServer) ReqCanAdminTeams(c *models.ReqContext) bool {
|
||||
return c.OrgRole == models.ROLE_ADMIN || (hs.Cfg.EditorsCanAdmin && c.OrgRole == models.ROLE_EDITOR)
|
||||
}
|
||||
|
||||
@@ -263,7 +262,7 @@ func (hs *HTTPServer) getNavTree(c *models.ReqContext, hasEditPerm bool) ([]*dto
|
||||
})
|
||||
}
|
||||
|
||||
if enableTeams(hs, c) {
|
||||
if hasAccess(hs.ReqCanAdminTeams, teamsAccessEvaluator) {
|
||||
configNodes = append(configNodes, &dtos.NavLink{
|
||||
Text: "Teams",
|
||||
Id: "teams",
|
||||
@@ -540,7 +539,7 @@ func (hs *HTTPServer) buildAdminNavLinks(c *models.ReqContext) []*dtos.NavLink {
|
||||
|
||||
func (hs *HTTPServer) setIndexViewData(c *models.ReqContext) (*dtos.IndexViewData, error) {
|
||||
hasEditPermissionInFoldersQuery := models.HasEditPermissionInFoldersQuery{SignedInUser: c.SignedInUser}
|
||||
if err := bus.Dispatch(c.Req.Context(), &hasEditPermissionInFoldersQuery); err != nil {
|
||||
if err := hs.SQLStore.HasEditPermissionInFolders(c.Req.Context(), &hasEditPermissionInFoldersQuery); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hasEditPerm := hasEditPermissionInFoldersQuery.Result
|
||||
@@ -553,7 +552,7 @@ func (hs *HTTPServer) setIndexViewData(c *models.ReqContext) (*dtos.IndexViewDat
|
||||
settings["dateFormats"] = hs.Cfg.DateFormats
|
||||
|
||||
prefsQuery := models.GetPreferencesWithDefaultsQuery{User: c.SignedInUser}
|
||||
if err := bus.Dispatch(c.Req.Context(), &prefsQuery); err != nil {
|
||||
if err := hs.SQLStore.GetPreferencesWithDefaults(c.Req.Context(), &prefsQuery); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
prefs := prefsQuery.Result
|
||||
|
||||
+13
-7
@@ -1,19 +1,19 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/dtos"
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
"github.com/grafana/grafana/pkg/web"
|
||||
)
|
||||
|
||||
func SendResetPasswordEmail(c *models.ReqContext) response.Response {
|
||||
func (hs *HTTPServer) SendResetPasswordEmail(c *models.ReqContext) response.Response {
|
||||
form := dtos.SendResetPasswordEmailForm{}
|
||||
if err := web.Bind(c.Req, &form); err != nil {
|
||||
return response.Error(http.StatusBadRequest, "bad request data", err)
|
||||
@@ -27,27 +27,33 @@ func SendResetPasswordEmail(c *models.ReqContext) response.Response {
|
||||
|
||||
userQuery := models.GetUserByLoginQuery{LoginOrEmail: form.UserOrEmail}
|
||||
|
||||
if err := bus.Dispatch(c.Req.Context(), &userQuery); err != nil {
|
||||
if err := hs.SQLStore.GetUserByLogin(c.Req.Context(), &userQuery); err != nil {
|
||||
c.Logger.Info("Requested password reset for user that was not found", "user", userQuery.LoginOrEmail)
|
||||
return response.Error(200, "Email sent", err)
|
||||
}
|
||||
|
||||
emailCmd := models.SendResetPasswordEmailCommand{User: userQuery.Result}
|
||||
if err := bus.Dispatch(c.Req.Context(), &emailCmd); err != nil {
|
||||
if err := hs.NotificationService.SendResetPasswordEmail(c.Req.Context(), &emailCmd); err != nil {
|
||||
return response.Error(500, "Failed to send email", err)
|
||||
}
|
||||
|
||||
return response.Success("Email sent")
|
||||
}
|
||||
|
||||
func ResetPassword(c *models.ReqContext) response.Response {
|
||||
func (hs *HTTPServer) ResetPassword(c *models.ReqContext) response.Response {
|
||||
form := dtos.ResetUserPasswordForm{}
|
||||
if err := web.Bind(c.Req, &form); err != nil {
|
||||
return response.Error(http.StatusBadRequest, "bad request data", err)
|
||||
}
|
||||
query := models.ValidateResetPasswordCodeQuery{Code: form.Code}
|
||||
|
||||
if err := bus.Dispatch(c.Req.Context(), &query); err != nil {
|
||||
getUserByLogin := func(ctx context.Context, login string) (*models.User, error) {
|
||||
userQuery := models.GetUserByLoginQuery{LoginOrEmail: login}
|
||||
err := hs.SQLStore.GetUserByLogin(ctx, &userQuery)
|
||||
return userQuery.Result, err
|
||||
}
|
||||
|
||||
if err := hs.NotificationService.ValidateResetPasswordCode(c.Req.Context(), &query, getUserByLogin); err != nil {
|
||||
if errors.Is(err, models.ErrInvalidEmailCode) {
|
||||
return response.Error(400, "Invalid or expired reset password code", nil)
|
||||
}
|
||||
@@ -66,7 +72,7 @@ func ResetPassword(c *models.ReqContext) response.Response {
|
||||
return response.Error(500, "Failed to encode password", err)
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(c.Req.Context(), &cmd); err != nil {
|
||||
if err := hs.SQLStore.ChangeUserPassword(c.Req.Context(), &cmd); err != nil {
|
||||
return response.Error(500, "Failed to change user password", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -298,3 +298,22 @@ var orgsCreateAccessEvaluator = accesscontrol.EvalAll(
|
||||
accesscontrol.EvalPermission(ActionOrgsRead),
|
||||
accesscontrol.EvalPermission(ActionOrgsCreate),
|
||||
)
|
||||
|
||||
// teamsAccessEvaluator is used to protect the "Configuration > Teams" page access
|
||||
var teamsAccessEvaluator = accesscontrol.EvalAll(
|
||||
accesscontrol.EvalPermission(accesscontrol.ActionTeamsRead),
|
||||
accesscontrol.EvalAny(
|
||||
accesscontrol.EvalPermission(accesscontrol.ActionTeamsCreate),
|
||||
accesscontrol.EvalPermission(accesscontrol.ActionTeamsWrite),
|
||||
accesscontrol.EvalPermission(accesscontrol.ActionTeamsPermissionsWrite),
|
||||
),
|
||||
)
|
||||
|
||||
// teamsEditAccessEvaluator is used to protect the "Configuration > Teams > edit" page access
|
||||
var teamsEditAccessEvaluator = accesscontrol.EvalAll(
|
||||
accesscontrol.EvalPermission(accesscontrol.ActionTeamsRead),
|
||||
accesscontrol.EvalAny(
|
||||
accesscontrol.EvalPermission(accesscontrol.ActionTeamsWrite),
|
||||
accesscontrol.EvalPermission(accesscontrol.ActionTeamsPermissionsWrite),
|
||||
),
|
||||
)
|
||||
|
||||
+2
-3
@@ -7,13 +7,12 @@ import (
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/infra/metrics"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/search"
|
||||
)
|
||||
|
||||
func Search(c *models.ReqContext) response.Response {
|
||||
func (hs *HTTPServer) Search(c *models.ReqContext) response.Response {
|
||||
query := c.Query("query")
|
||||
tags := c.QueryStrings("tag")
|
||||
starred := c.Query("starred")
|
||||
@@ -62,7 +61,7 @@ func Search(c *models.ReqContext) response.Response {
|
||||
Sort: sort,
|
||||
}
|
||||
|
||||
err := bus.Dispatch(c.Req.Context(), &searchQuery)
|
||||
err := hs.SearchService.SearchHandler(c.Req.Context(), &searchQuery)
|
||||
if err != nil {
|
||||
return response.Error(500, "Search failed", err)
|
||||
}
|
||||
|
||||
@@ -2,12 +2,14 @@ package api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/dtos"
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
"github.com/grafana/grafana/pkg/web"
|
||||
@@ -103,6 +105,20 @@ func (hs *HTTPServer) DeleteTeamByID(c *models.ReqContext) response.Response {
|
||||
return response.Success("Team deleted")
|
||||
}
|
||||
|
||||
func (hs *HTTPServer) getTeamsAccessControlMetadata(c *models.ReqContext, teamIDs map[string]bool) (map[string]accesscontrol.Metadata, error) {
|
||||
if hs.AccessControl.IsDisabled() || !c.QueryBool("accesscontrol") {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
userPermissions, err := hs.AccessControl.GetUserPermissions(c.Req.Context(), c.SignedInUser)
|
||||
if err != nil || len(userPermissions) == 0 {
|
||||
hs.log.Warn("could not fetch accesscontrol metadata for teams", "error", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return accesscontrol.GetResourcesMetadata(c.Req.Context(), userPermissions, "teams", teamIDs), nil
|
||||
}
|
||||
|
||||
// GET /api/teams/search
|
||||
func (hs *HTTPServer) SearchTeams(c *models.ReqContext) response.Response {
|
||||
perPage := c.QueryInt("perpage")
|
||||
@@ -134,8 +150,17 @@ func (hs *HTTPServer) SearchTeams(c *models.ReqContext) response.Response {
|
||||
return response.Error(500, "Failed to search Teams", err)
|
||||
}
|
||||
|
||||
teamIDs := map[string]bool{}
|
||||
for _, team := range query.Result.Teams {
|
||||
team.AvatarUrl = dtos.GetGravatarUrlWithDefault(team.Email, team.Name)
|
||||
teamIDs[strconv.FormatInt(team.Id, 10)] = true
|
||||
}
|
||||
|
||||
metadata, err := hs.getTeamsAccessControlMetadata(c, teamIDs)
|
||||
if err == nil && len(metadata) != 0 {
|
||||
for _, team := range query.Result.Teams {
|
||||
team.AccessControl = metadata[strconv.FormatInt(team.Id, 10)]
|
||||
}
|
||||
}
|
||||
|
||||
query.Result.Page = page
|
||||
@@ -144,6 +169,23 @@ func (hs *HTTPServer) SearchTeams(c *models.ReqContext) response.Response {
|
||||
return response.JSON(200, query.Result)
|
||||
}
|
||||
|
||||
func (hs *HTTPServer) getTeamAccessControlMetadata(c *models.ReqContext, teamID int64) (accesscontrol.Metadata, error) {
|
||||
if hs.AccessControl.IsDisabled() || !c.QueryBool("accesscontrol") {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
userPermissions, err := hs.AccessControl.GetUserPermissions(c.Req.Context(), c.SignedInUser)
|
||||
if err != nil || len(userPermissions) == 0 {
|
||||
hs.log.Warn("could not fetch accesscontrol metadata", "team", teamID, "error", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
key := fmt.Sprintf("%d", teamID)
|
||||
teamIDs := map[string]bool{key: true}
|
||||
|
||||
return accesscontrol.GetResourcesMetadata(c.Req.Context(), userPermissions, "teams", teamIDs)[key], nil
|
||||
}
|
||||
|
||||
// GET /api/teams/:teamId
|
||||
func (hs *HTTPServer) GetTeamByID(c *models.ReqContext) response.Response {
|
||||
teamId, err := strconv.ParseInt(web.Params(c.Req)[":teamId"], 10, 64)
|
||||
@@ -165,6 +207,9 @@ func (hs *HTTPServer) GetTeamByID(c *models.ReqContext) response.Response {
|
||||
return response.Error(500, "Failed to get Team", err)
|
||||
}
|
||||
|
||||
metadata, _ := hs.getTeamAccessControlMetadata(c, query.Result.Id)
|
||||
query.Result.AccessControl = metadata
|
||||
|
||||
query.Result.AvatarUrl = dtos.GetGravatarUrlWithDefault(query.Result.Email, query.Result.Name)
|
||||
return response.JSON(200, &query.Result)
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/api/dtos"
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/resourcepermissions"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
@@ -152,7 +153,7 @@ func (hs *HTTPServer) RemoveTeamMember(c *models.ReqContext) response.Response {
|
||||
}
|
||||
|
||||
teamIDString := strconv.FormatInt(teamId, 10)
|
||||
if _, err := hs.TeamPermissionsService.SetUserPermission(c.Req.Context(), orgId, userId, teamIDString, ""); err != nil {
|
||||
if _, err := hs.TeamPermissionsService.SetUserPermission(c.Req.Context(), orgId, accesscontrol.User{ID: userId}, teamIDString, ""); err != nil {
|
||||
if errors.Is(err, models.ErrTeamNotFound) {
|
||||
return response.Error(404, "Team not found", nil)
|
||||
}
|
||||
@@ -171,7 +172,7 @@ func (hs *HTTPServer) RemoveTeamMember(c *models.ReqContext) response.Response {
|
||||
// Stubbable by tests.
|
||||
var addOrUpdateTeamMember = func(ctx context.Context, resourcePermissionService *resourcepermissions.Service, userID, orgID, teamID int64, permission string) error {
|
||||
teamIDString := strconv.FormatInt(teamID, 10)
|
||||
if _, err := resourcePermissionService.SetUserPermission(ctx, orgID, userID, teamIDString, permission); err != nil {
|
||||
if _, err := resourcePermissionService.SetUserPermission(ctx, orgID, accesscontrol.User{ID: userID}, teamIDString, permission); err != nil {
|
||||
return fmt.Errorf("failed setting permissions for user %d in team %d: %w", userID, teamID, err)
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/dtos"
|
||||
"github.com/grafana/grafana/pkg/services/login/authinfoservice"
|
||||
authinfostore "github.com/grafana/grafana/pkg/services/login/authinfoservice/database"
|
||||
"github.com/grafana/grafana/pkg/services/searchusers/filters"
|
||||
"github.com/grafana/grafana/pkg/services/secrets/database"
|
||||
secretsManager "github.com/grafana/grafana/pkg/services/secrets/manager"
|
||||
@@ -46,7 +47,8 @@ func TestUserAPIEndpoint_userLoggedIn(t *testing.T) {
|
||||
loggedInUserScenario(t, "When calling GET on", "api/users/1", "api/users/:id", func(sc *scenarioContext) {
|
||||
fakeNow := time.Date(2019, 2, 11, 17, 30, 40, 0, time.UTC)
|
||||
secretsService := secretsManager.SetupTestService(t, database.ProvideSecretsStore(sqlStore))
|
||||
srv := authinfoservice.ProvideAuthInfoService(bus.New(), sqlStore, &authinfoservice.OSSUserProtectionImpl{}, secretsService)
|
||||
authInfoStore := authinfostore.ProvideAuthInfoStore(sqlStore, bus.New(), secretsService)
|
||||
srv := authinfoservice.ProvideAuthInfoService(&authinfoservice.OSSUserProtectionImpl{}, authInfoStore)
|
||||
hs.authInfoService = srv
|
||||
|
||||
createUserCmd := models.CreateUserCommand{
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package log
|
||||
|
||||
import gokitlog "github.com/go-kit/log"
|
||||
|
||||
type compositeLogger struct {
|
||||
loggers []gokitlog.Logger
|
||||
}
|
||||
|
||||
func newCompositeLogger(loggers ...gokitlog.Logger) *compositeLogger {
|
||||
if len(loggers) == 0 {
|
||||
loggers = []gokitlog.Logger{}
|
||||
}
|
||||
|
||||
return &compositeLogger{loggers: loggers}
|
||||
}
|
||||
|
||||
func (l *compositeLogger) Log(keyvals ...interface{}) error {
|
||||
for _, logger := range l.loggers {
|
||||
if err := logger.Log(keyvals...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -12,7 +12,7 @@ const (
|
||||
|
||||
type Logger interface {
|
||||
// New returns a new Logger that has this logger's context plus the given context
|
||||
New(ctx ...interface{}) MultiLoggers
|
||||
New(ctx ...interface{}) *ConcreteLogger
|
||||
|
||||
Log(keyvals ...interface{}) error
|
||||
|
||||
|
||||
+224
-115
@@ -7,9 +7,12 @@ package log
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
gokitlog "github.com/go-kit/log"
|
||||
@@ -25,8 +28,7 @@ import (
|
||||
|
||||
var loggersToClose []DisposableHandler
|
||||
var loggersToReload []ReloadableHandler
|
||||
var filters map[string]level.Option
|
||||
var Root MultiLoggers
|
||||
var root *logManager
|
||||
|
||||
const (
|
||||
// top 7 calls in the stack are within logger
|
||||
@@ -37,11 +39,212 @@ const (
|
||||
func init() {
|
||||
loggersToClose = make([]DisposableHandler, 0)
|
||||
loggersToReload = make([]ReloadableHandler, 0)
|
||||
filters = map[string]level.Option{}
|
||||
|
||||
// Use console by default
|
||||
format := getLogFormat("console")
|
||||
Root.AddLogger(format(os.Stderr), "info", filters)
|
||||
logger := level.NewFilter(format(os.Stderr), level.AllowInfo())
|
||||
root = newManager(logger)
|
||||
}
|
||||
|
||||
// logManager manage loggers
|
||||
type logManager struct {
|
||||
*ConcreteLogger
|
||||
loggersByName map[string]*ConcreteLogger
|
||||
logFilters []LogWithFilters
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
|
||||
func newManager(logger gokitlog.Logger) *logManager {
|
||||
return &logManager{
|
||||
ConcreteLogger: newConcreteLogger(logger),
|
||||
loggersByName: map[string]*ConcreteLogger{},
|
||||
}
|
||||
}
|
||||
|
||||
func (lm *logManager) initialize(loggers []LogWithFilters) {
|
||||
lm.mutex.Lock()
|
||||
defer lm.mutex.Unlock()
|
||||
|
||||
defaultLoggers := make([]gokitlog.Logger, len(loggers))
|
||||
for index, logger := range loggers {
|
||||
defaultLoggers[index] = level.NewFilter(logger.val, logger.maxLevel)
|
||||
}
|
||||
|
||||
lm.ConcreteLogger.SetLogger(&compositeLogger{loggers: defaultLoggers})
|
||||
lm.logFilters = loggers
|
||||
|
||||
loggersByName := []string{}
|
||||
for k := range lm.loggersByName {
|
||||
loggersByName = append(loggersByName, k)
|
||||
}
|
||||
sort.Strings(loggersByName)
|
||||
|
||||
for _, name := range loggersByName {
|
||||
ctxLoggers := make([]gokitlog.Logger, len(loggers))
|
||||
|
||||
for index, logger := range loggers {
|
||||
if filterLevel, exists := logger.filters[name]; !exists {
|
||||
ctxLoggers[index] = level.NewFilter(logger.val, logger.maxLevel)
|
||||
} else {
|
||||
ctxLoggers[index] = level.NewFilter(logger.val, filterLevel)
|
||||
}
|
||||
}
|
||||
|
||||
lm.loggersByName[name].SetLogger(&compositeLogger{loggers: ctxLoggers})
|
||||
}
|
||||
}
|
||||
|
||||
func (lm *logManager) SetLogger(logger gokitlog.Logger) {
|
||||
lm.ConcreteLogger.SetLogger(logger)
|
||||
}
|
||||
|
||||
func (lm *logManager) GetLogger() gokitlog.Logger {
|
||||
return lm.ConcreteLogger.GetLogger()
|
||||
}
|
||||
|
||||
func (lm *logManager) Log(args ...interface{}) error {
|
||||
lm.mutex.RLock()
|
||||
defer lm.mutex.RUnlock()
|
||||
if err := lm.ConcreteLogger.Log(args...); err != nil {
|
||||
log.Println("Logging error", "error", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (lm *logManager) New(ctx ...interface{}) *ConcreteLogger {
|
||||
lm.mutex.Lock()
|
||||
defer lm.mutex.Unlock()
|
||||
if len(ctx) == 0 {
|
||||
return lm.ConcreteLogger
|
||||
}
|
||||
|
||||
loggerName, ok := ctx[0].(string)
|
||||
if !ok {
|
||||
return lm.ConcreteLogger
|
||||
}
|
||||
|
||||
if logger, exists := lm.loggersByName[loggerName]; exists {
|
||||
return logger
|
||||
}
|
||||
|
||||
ctx = append([]interface{}{"logger"}, ctx...)
|
||||
|
||||
if len(lm.logFilters) == 0 {
|
||||
ctxLogger := newConcreteLogger(lm.logger, ctx...)
|
||||
lm.loggersByName[loggerName] = ctxLogger
|
||||
return ctxLogger
|
||||
}
|
||||
|
||||
compositeLogger := newCompositeLogger()
|
||||
for _, logWithFilter := range lm.logFilters {
|
||||
filterLevel, ok := logWithFilter.filters[loggerName]
|
||||
if ok {
|
||||
logWithFilter.val = level.NewFilter(logWithFilter.val, filterLevel)
|
||||
} else {
|
||||
logWithFilter.val = level.NewFilter(logWithFilter.val, logWithFilter.maxLevel)
|
||||
}
|
||||
|
||||
compositeLogger.loggers = append(compositeLogger.loggers, logWithFilter.val)
|
||||
}
|
||||
|
||||
ctxLogger := newConcreteLogger(compositeLogger, ctx...)
|
||||
lm.loggersByName[loggerName] = ctxLogger
|
||||
return ctxLogger
|
||||
}
|
||||
|
||||
type ConcreteLogger struct {
|
||||
ctx []interface{}
|
||||
logger gokitlog.Logger
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
|
||||
func newConcreteLogger(logger gokitlog.Logger, ctx ...interface{}) *ConcreteLogger {
|
||||
if len(ctx) == 0 {
|
||||
ctx = []interface{}{}
|
||||
} else {
|
||||
logger = gokitlog.With(logger, ctx...)
|
||||
}
|
||||
|
||||
return &ConcreteLogger{
|
||||
ctx: ctx,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
func (cl *ConcreteLogger) SetLogger(logger gokitlog.Logger) {
|
||||
cl.mutex.Lock()
|
||||
cl.logger = gokitlog.With(logger, cl.ctx...)
|
||||
cl.mutex.Unlock()
|
||||
}
|
||||
|
||||
func (cl *ConcreteLogger) GetLogger() gokitlog.Logger {
|
||||
cl.mutex.Lock()
|
||||
defer cl.mutex.Unlock()
|
||||
return cl.logger
|
||||
}
|
||||
|
||||
func (cl *ConcreteLogger) Warn(msg string, args ...interface{}) {
|
||||
_ = cl.log(msg, level.WarnValue(), args...)
|
||||
}
|
||||
|
||||
func (cl *ConcreteLogger) Debug(msg string, args ...interface{}) {
|
||||
// args = append([]interface{}{level.Key(), level.DebugValue(), "msg", msg}, args...)
|
||||
_ = cl.log(msg, level.DebugValue(), args...)
|
||||
}
|
||||
|
||||
func (cl *ConcreteLogger) Error(msg string, args ...interface{}) {
|
||||
_ = cl.log(msg, level.ErrorValue(), args...)
|
||||
}
|
||||
|
||||
func (cl *ConcreteLogger) Info(msg string, args ...interface{}) {
|
||||
_ = cl.log(msg, level.InfoValue(), args...)
|
||||
}
|
||||
|
||||
func (cl *ConcreteLogger) log(msg string, logLevel level.Value, args ...interface{}) error {
|
||||
cl.mutex.RLock()
|
||||
logger := gokitlog.With(cl.logger, "t", gokitlog.TimestampFormat(time.Now, "2006-01-02T15:04:05.99-0700"))
|
||||
cl.mutex.RUnlock()
|
||||
|
||||
args = append([]interface{}{level.Key(), logLevel, "msg", msg}, args...)
|
||||
|
||||
return logger.Log(args...)
|
||||
}
|
||||
|
||||
func (cl *ConcreteLogger) Log(keyvals ...interface{}) error {
|
||||
cl.mutex.RLock()
|
||||
defer cl.mutex.RUnlock()
|
||||
return cl.logger.Log(keyvals...)
|
||||
}
|
||||
|
||||
func (cl *ConcreteLogger) New(ctx ...interface{}) *ConcreteLogger {
|
||||
if len(ctx) == 0 {
|
||||
root.New()
|
||||
}
|
||||
|
||||
keyvals := []interface{}{}
|
||||
|
||||
if len(cl.ctx)%2 == 1 {
|
||||
cl.ctx = append(cl.ctx, nil)
|
||||
}
|
||||
|
||||
for i := 0; i < len(cl.ctx); i += 2 {
|
||||
k, v := cl.ctx[i], cl.ctx[i+1]
|
||||
|
||||
if k == "logger" {
|
||||
continue
|
||||
}
|
||||
|
||||
keyvals = append(keyvals, k, v)
|
||||
}
|
||||
|
||||
keyvals = append(keyvals, ctx...)
|
||||
|
||||
return root.New(keyvals...)
|
||||
}
|
||||
|
||||
func New(ctx ...interface{}) *ConcreteLogger {
|
||||
return root.New(ctx...)
|
||||
}
|
||||
|
||||
type LogWithFilters struct {
|
||||
@@ -50,111 +253,23 @@ type LogWithFilters struct {
|
||||
maxLevel level.Option
|
||||
}
|
||||
|
||||
type MultiLoggers struct {
|
||||
loggers []LogWithFilters
|
||||
}
|
||||
|
||||
func (ml *MultiLoggers) AddLogger(val gokitlog.Logger, levelName string, filters map[string]level.Option) {
|
||||
logger := LogWithFilters{val: val, filters: filters, maxLevel: getLogLevelFromString(levelName)}
|
||||
ml.loggers = append(ml.loggers, logger)
|
||||
}
|
||||
|
||||
func (ml *MultiLoggers) SetLogger(des MultiLoggers) {
|
||||
ml.loggers = des.loggers
|
||||
}
|
||||
|
||||
func (ml *MultiLoggers) GetLogger() MultiLoggers {
|
||||
return *ml
|
||||
}
|
||||
|
||||
func (ml MultiLoggers) Warn(msg string, args ...interface{}) {
|
||||
args = append([]interface{}{level.Key(), level.WarnValue(), "msg", msg}, args...)
|
||||
err := ml.Log(args...)
|
||||
if err != nil {
|
||||
_ = level.Error(Root).Log("Logging error", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (ml MultiLoggers) Debug(msg string, args ...interface{}) {
|
||||
args = append([]interface{}{level.Key(), level.DebugValue(), "msg", msg}, args...)
|
||||
err := ml.Log(args...)
|
||||
if err != nil {
|
||||
_ = level.Error(Root).Log("Logging error", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (ml MultiLoggers) Error(msg string, args ...interface{}) {
|
||||
args = append([]interface{}{level.Key(), level.ErrorValue(), "msg", msg}, args...)
|
||||
err := ml.Log(args...)
|
||||
if err != nil {
|
||||
_ = level.Error(Root).Log("Logging error", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (ml MultiLoggers) Info(msg string, args ...interface{}) {
|
||||
args = append([]interface{}{level.Key(), level.InfoValue(), "msg", msg}, args...)
|
||||
err := ml.Log(args...)
|
||||
if err != nil {
|
||||
_ = level.Error(Root).Log("Logging error", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (ml MultiLoggers) Log(keyvals ...interface{}) error {
|
||||
for _, multilogger := range ml.loggers {
|
||||
multilogger.val = gokitlog.With(multilogger.val, "t", gokitlog.TimestampFormat(time.Now, "2006-01-02T15:04:05.99-0700"))
|
||||
if err := multilogger.val.Log(keyvals...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// New creates a new logger from the existing one with additional context
|
||||
func (ml MultiLoggers) New(ctx ...interface{}) MultiLoggers {
|
||||
return with(ml, gokitlog.With, ctx)
|
||||
}
|
||||
|
||||
// New creates MultiLoggers with the provided context and caller that is added as a suffix.
|
||||
// The first element of the context must be the logger name
|
||||
func New(ctx ...interface{}) MultiLoggers {
|
||||
func with(ctxLogger *ConcreteLogger, withFunc func(gokitlog.Logger, ...interface{}) gokitlog.Logger, ctx []interface{}) *ConcreteLogger {
|
||||
if len(ctx) == 0 {
|
||||
return Root
|
||||
return ctxLogger
|
||||
}
|
||||
var newloger MultiLoggers
|
||||
ctx = append([]interface{}{"logger"}, ctx...)
|
||||
for _, logWithFilter := range Root.loggers {
|
||||
logWithFilter.val = gokitlog.With(logWithFilter.val, ctx...)
|
||||
v, ok := logWithFilter.filters[ctx[0].(string)]
|
||||
if ok {
|
||||
logWithFilter.val = level.NewFilter(logWithFilter.val, v)
|
||||
} else {
|
||||
logWithFilter.val = level.NewFilter(logWithFilter.val, logWithFilter.maxLevel)
|
||||
}
|
||||
newloger.loggers = append(newloger.loggers, logWithFilter)
|
||||
}
|
||||
return newloger
|
||||
}
|
||||
|
||||
func with(loggers MultiLoggers, withFunc func(gokitlog.Logger, ...interface{}) gokitlog.Logger, ctx []interface{}) MultiLoggers {
|
||||
if len(ctx) == 0 {
|
||||
return loggers
|
||||
}
|
||||
var newloger MultiLoggers
|
||||
for _, l := range loggers.loggers {
|
||||
l.val = withFunc(l.val, ctx...)
|
||||
newloger.loggers = append(newloger.loggers, l)
|
||||
}
|
||||
return newloger
|
||||
ctxLogger.logger = withFunc(ctxLogger.logger, ctx...)
|
||||
return ctxLogger
|
||||
}
|
||||
|
||||
// WithPrefix adds context that will be added to the log message
|
||||
func WithPrefix(loggers MultiLoggers, ctx ...interface{}) MultiLoggers {
|
||||
return with(loggers, gokitlog.WithPrefix, ctx)
|
||||
func WithPrefix(ctxLogger *ConcreteLogger, ctx ...interface{}) *ConcreteLogger {
|
||||
return with(ctxLogger, gokitlog.WithPrefix, ctx)
|
||||
}
|
||||
|
||||
// WithSuffix adds context that will be appended at the end of the log message
|
||||
func WithSuffix(loggers MultiLoggers, ctx ...interface{}) MultiLoggers {
|
||||
return with(loggers, gokitlog.WithSuffix, ctx)
|
||||
func WithSuffix(ctxLogger *ConcreteLogger, ctx ...interface{}) *ConcreteLogger {
|
||||
return with(ctxLogger, gokitlog.WithSuffix, ctx)
|
||||
}
|
||||
|
||||
var logLevels = map[string]level.Option{
|
||||
@@ -177,7 +292,7 @@ func getLogLevelFromString(levelName string) level.Option {
|
||||
loglevel, ok := logLevels[levelName]
|
||||
|
||||
if !ok {
|
||||
_ = level.Error(Root).Log("Unknown log level", "level", levelName)
|
||||
_ = level.Error(root).Log("Unknown log level", "level", levelName)
|
||||
return level.AllowError()
|
||||
}
|
||||
|
||||
@@ -282,7 +397,7 @@ func ReadLoggingConfig(modes []string, logsPath string, cfg *ini.File) error {
|
||||
mode = strings.TrimSpace(mode)
|
||||
sec, err := cfg.GetSection("log." + mode)
|
||||
if err != nil {
|
||||
_ = level.Error(Root).Log("Unknown log mode", "mode", mode)
|
||||
_ = level.Error(root).Log("Unknown log mode", "mode", mode)
|
||||
return errutil.Wrapf(err, "failed to get config section log.%s", mode)
|
||||
}
|
||||
|
||||
@@ -301,8 +416,8 @@ func ReadLoggingConfig(modes []string, logsPath string, cfg *ini.File) error {
|
||||
fileName := sec.Key("file_name").MustString(filepath.Join(logsPath, "grafana.log"))
|
||||
dpath := filepath.Dir(fileName)
|
||||
if err := os.MkdirAll(dpath, os.ModePerm); err != nil {
|
||||
_ = level.Error(Root).Log("Failed to create directory", "dpath", dpath, "err", err)
|
||||
return errutil.Wrapf(err, "failed to create log directory %q", dpath)
|
||||
_ = level.Error(root).Log("Failed to create directory", "dpath", dpath, "err", err)
|
||||
continue
|
||||
}
|
||||
fileHandler := NewFileWriter()
|
||||
fileHandler.Filename = fileName
|
||||
@@ -313,8 +428,8 @@ func ReadLoggingConfig(modes []string, logsPath string, cfg *ini.File) error {
|
||||
fileHandler.Daily = sec.Key("daily_rotate").MustBool(true)
|
||||
fileHandler.Maxdays = sec.Key("max_days").MustInt64(7)
|
||||
if err := fileHandler.Init(); err != nil {
|
||||
_ = level.Error(Root).Log("Failed to initialize file handler", "dpath", dpath, "err", err)
|
||||
return errutil.Wrapf(err, "failed to initialize file handler")
|
||||
_ = level.Error(root).Log("Failed to initialize file handler", "dpath", dpath, "err", err)
|
||||
continue
|
||||
}
|
||||
|
||||
loggersToClose = append(loggersToClose, fileHandler)
|
||||
@@ -336,20 +451,14 @@ func ReadLoggingConfig(modes []string, logsPath string, cfg *ini.File) error {
|
||||
}
|
||||
}
|
||||
|
||||
// copy joined default + mode filters into filters
|
||||
for key, value := range modeFilters {
|
||||
if _, exist := filters[key]; !exist {
|
||||
filters[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
handler.filters = modeFilters
|
||||
handler.maxLevel = leveloption
|
||||
// handler = LogFilterHandler(leveloption, modeFilters, handler)
|
||||
configLoggers = append(configLoggers, handler)
|
||||
}
|
||||
|
||||
if len(configLoggers) > 0 {
|
||||
Root.loggers = configLoggers
|
||||
root.initialize(configLoggers)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ func NewSyslog(sec *ini.Section, format Formatedlogger) *SysLogHandler {
|
||||
handler.Tag = sec.Key("tag").MustString("")
|
||||
|
||||
if err := handler.Init(); err != nil {
|
||||
_ = level.Error(Root).Log("Failed to init syslog log handler", "error", err)
|
||||
_ = level.Error(root).Log("Failed to init syslog log handler", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
handler.logger = gokitsyslog.NewSyslogLogger(handler.syslog, format, gokitsyslog.PrioritySelectorOption(selector))
|
||||
|
||||
@@ -25,7 +25,7 @@ type UsageStats struct {
|
||||
kvStore *kvstore.NamespacedKVStore
|
||||
RouteRegister routing.RouteRegister
|
||||
|
||||
log log.MultiLoggers
|
||||
log log.Logger
|
||||
|
||||
oauthProviders map[string]bool
|
||||
externalMetrics []usagestats.MetricsFunc
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
|
||||
func newLogger(name string, lev string) log.Logger {
|
||||
logger := log.New(name)
|
||||
logger.AddLogger(logger, lev, map[string]level.Option{})
|
||||
logger.SetLogger(level.NewFilter(logger.GetLogger(), level.AllowInfo()))
|
||||
return logger
|
||||
}
|
||||
|
||||
|
||||
@@ -107,7 +107,7 @@ func Recovery(cfg *setting.Cfg) web.Handler {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
var panicLogger log.Logger
|
||||
panicLogger = log.Root
|
||||
panicLogger = log.New("recovery")
|
||||
// try to get request logger
|
||||
ctx := contexthandler.FromContext(c.Req.Context())
|
||||
if ctx != nil {
|
||||
|
||||
+8
-7
@@ -77,13 +77,14 @@ type SearchTeamsQuery struct {
|
||||
}
|
||||
|
||||
type TeamDTO struct {
|
||||
Id int64 `json:"id"`
|
||||
OrgId int64 `json:"orgId"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
AvatarUrl string `json:"avatarUrl"`
|
||||
MemberCount int64 `json:"memberCount"`
|
||||
Permission PermissionType `json:"permission"`
|
||||
Id int64 `json:"id"`
|
||||
OrgId int64 `json:"orgId"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
AvatarUrl string `json:"avatarUrl"`
|
||||
MemberCount int64 `json:"memberCount"`
|
||||
Permission PermissionType `json:"permission"`
|
||||
AccessControl map[string]bool `json:"accessControl"`
|
||||
}
|
||||
|
||||
type SearchTeamQueryResult struct {
|
||||
|
||||
@@ -212,11 +212,11 @@ func (*testLicensingService) FeatureEnabled(feature string) bool {
|
||||
}
|
||||
|
||||
type fakeLogger struct {
|
||||
log.MultiLoggers
|
||||
*log.ConcreteLogger
|
||||
}
|
||||
|
||||
func (f fakeLogger) New(_ ...interface{}) log.MultiLoggers {
|
||||
return log.MultiLoggers{}
|
||||
func (f fakeLogger) New(_ ...interface{}) *log.ConcreteLogger {
|
||||
return &log.ConcreteLogger{}
|
||||
}
|
||||
|
||||
func (f fakeLogger) Warn(_ string, _ ...interface{}) {
|
||||
|
||||
@@ -1128,8 +1128,8 @@ type fakeLogger struct {
|
||||
log.Logger
|
||||
}
|
||||
|
||||
func (fl fakeLogger) New(_ ...interface{}) log.MultiLoggers {
|
||||
return log.MultiLoggers{}
|
||||
func (fl fakeLogger) New(_ ...interface{}) *log.ConcreteLogger {
|
||||
return &log.ConcreteLogger{}
|
||||
}
|
||||
|
||||
func (fl fakeLogger) Info(_ string, _ ...interface{}) {
|
||||
|
||||
+5
-1
@@ -45,6 +45,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/live/pushhttp"
|
||||
"github.com/grafana/grafana/pkg/services/login"
|
||||
"github.com/grafana/grafana/pkg/services/login/authinfoservice"
|
||||
authinfodatabase "github.com/grafana/grafana/pkg/services/login/authinfoservice/database"
|
||||
"github.com/grafana/grafana/pkg/services/login/loginservice"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert"
|
||||
ngmetrics "github.com/grafana/grafana/pkg/services/ngalert/metrics"
|
||||
@@ -144,6 +145,8 @@ var wireBasicSet = wire.NewSet(
|
||||
wire.Bind(new(login.Service), new(*loginservice.Implementation)),
|
||||
authinfoservice.ProvideAuthInfoService,
|
||||
wire.Bind(new(login.AuthInfoService), new(*authinfoservice.Implementation)),
|
||||
authinfodatabase.ProvideAuthInfoStore,
|
||||
wire.Bind(new(login.Store), new(*authinfodatabase.AuthInfoStore)),
|
||||
datasourceproxy.ProvideService,
|
||||
search.ProvideService,
|
||||
live.ProvideService,
|
||||
@@ -190,7 +193,6 @@ var wireBasicSet = wire.NewSet(
|
||||
wire.Bind(new(teamguardian.Store), new(*teamguardianDatabase.TeamGuardianStoreImpl)),
|
||||
teamguardianManager.ProvideService,
|
||||
wire.Bind(new(teamguardian.TeamGuardian), new(*teamguardianManager.Service)),
|
||||
wire.Bind(new(authinfoservice.Service), new(*authinfoservice.Implementation)),
|
||||
featuremgmt.ProvideManagerService,
|
||||
featuremgmt.ProvideToggles,
|
||||
resourceservices.ProvideResourceServices,
|
||||
@@ -202,6 +204,7 @@ var wireBasicSet = wire.NewSet(
|
||||
var wireSet = wire.NewSet(
|
||||
wireBasicSet,
|
||||
sqlstore.ProvideService,
|
||||
wire.Bind(new(alerting.AlertStore), new(*sqlstore.SQLStore)),
|
||||
ngmetrics.ProvideService,
|
||||
wire.Bind(new(notifications.Service), new(*notifications.NotificationService)),
|
||||
wire.Bind(new(notifications.WebhookSender), new(*notifications.NotificationService)),
|
||||
@@ -214,6 +217,7 @@ var wireTestSet = wire.NewSet(
|
||||
ProvideTestEnv,
|
||||
sqlstore.ProvideServiceForTests,
|
||||
ngmetrics.ProvideServiceForTest,
|
||||
wire.Bind(new(alerting.AlertStore), new(*sqlstore.SQLStore)),
|
||||
|
||||
notifications.MockNotificationService,
|
||||
wire.Bind(new(notifications.Service), new(*notifications.NotificationServiceMock)),
|
||||
|
||||
@@ -37,13 +37,18 @@ type ResourcePermissionsService interface {
|
||||
// GetPermissions returns all permissions for given resourceID
|
||||
GetPermissions(ctx context.Context, orgID int64, resourceID string) ([]ResourcePermission, error)
|
||||
// SetUserPermission sets permission on resource for a user
|
||||
SetUserPermission(ctx context.Context, orgID, userID int64, resourceID, permission string) (*ResourcePermission, error)
|
||||
SetUserPermission(ctx context.Context, orgID int64, user User, resourceID, permission string) (*ResourcePermission, error)
|
||||
// SetTeamPermission sets permission on resource for a team
|
||||
SetTeamPermission(ctx context.Context, orgID, teamID int64, resourceID, permission string) (*ResourcePermission, error)
|
||||
// SetBuiltInRolePermission sets permission on resource for a built-in role (Admin, Editor, Viewer)
|
||||
SetBuiltInRolePermission(ctx context.Context, orgID int64, builtInRole string, resourceID string, permission string) (*ResourcePermission, error)
|
||||
}
|
||||
|
||||
type User struct {
|
||||
ID int64
|
||||
IsExternal bool
|
||||
}
|
||||
|
||||
// Metadata contains user accesses for a given resource
|
||||
// Ex: map[string]bool{"create":true, "delete": true}
|
||||
type Metadata map[string]bool
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
@@ -81,7 +80,7 @@ func TestAccessControlStore_GetUserPermissions(t *testing.T) {
|
||||
user, team := createUserAndTeam(t, sql, tt.orgID)
|
||||
|
||||
for _, id := range tt.userPermissions {
|
||||
_, err := store.SetUserResourcePermission(context.Background(), tt.orgID, user.Id, accesscontrol.SetResourcePermissionCommand{
|
||||
_, err := store.SetUserResourcePermission(context.Background(), tt.orgID, accesscontrol.User{ID: user.Id}, accesscontrol.SetResourcePermissionCommand{
|
||||
Actions: []string{"dashboards:write"},
|
||||
Resource: "dashboards",
|
||||
ResourceID: id,
|
||||
|
||||
@@ -34,20 +34,20 @@ func (p *flatResourcePermission) Managed() bool {
|
||||
}
|
||||
|
||||
func (s *AccessControlStore) SetUserResourcePermission(
|
||||
ctx context.Context, orgID, userID int64,
|
||||
ctx context.Context, orgID int64, user accesscontrol.User,
|
||||
cmd accesscontrol.SetResourcePermissionCommand,
|
||||
hook types.UserResourceHookFunc,
|
||||
) (*accesscontrol.ResourcePermission, error) {
|
||||
if userID == 0 {
|
||||
if user.ID == 0 {
|
||||
return nil, models.ErrUserNotFound
|
||||
}
|
||||
|
||||
var err error
|
||||
var permission *accesscontrol.ResourcePermission
|
||||
err = s.sql.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
permission, err = s.setResourcePermission(sess, orgID, managedUserRoleName(userID), s.userAdder(sess, orgID, userID), cmd)
|
||||
permission, err = s.setResourcePermission(sess, orgID, managedUserRoleName(user.ID), s.userAdder(sess, orgID, user.ID), cmd)
|
||||
if err == nil && hook != nil {
|
||||
return hook(sess, orgID, userID, cmd.ResourceID, cmd.Permission)
|
||||
return hook(sess, orgID, user, cmd.ResourceID, cmd.Permission)
|
||||
}
|
||||
|
||||
return err
|
||||
|
||||
@@ -93,7 +93,7 @@ func GenerateDatasourcePermissions(b *testing.B, db *sqlstore.SQLStore, ac *Acce
|
||||
_, err := ac.SetUserResourcePermission(
|
||||
context.Background(),
|
||||
accesscontrol.GlobalOrgID,
|
||||
userIds[i],
|
||||
accesscontrol.User{ID: userIds[i]},
|
||||
accesscontrol.SetResourcePermissionCommand{
|
||||
Actions: []string{dsAction},
|
||||
Resource: dsResource,
|
||||
|
||||
@@ -70,11 +70,11 @@ func TestAccessControlStore_SetUserResourcePermission(t *testing.T) {
|
||||
store, _ := setupTestEnv(t)
|
||||
|
||||
for _, s := range test.seeds {
|
||||
_, err := store.SetUserResourcePermission(context.Background(), test.orgID, test.userID, s, nil)
|
||||
_, err := store.SetUserResourcePermission(context.Background(), test.orgID, accesscontrol.User{ID: test.userID}, s, nil)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
added, err := store.SetUserResourcePermission(context.Background(), test.userID, test.userID, accesscontrol.SetResourcePermissionCommand{
|
||||
added, err := store.SetUserResourcePermission(context.Background(), test.userID, accesscontrol.User{ID: test.userID}, accesscontrol.SetResourcePermissionCommand{
|
||||
Actions: test.actions,
|
||||
Resource: test.resource,
|
||||
ResourceID: test.resourceID,
|
||||
@@ -352,7 +352,7 @@ func seedResourcePermissions(t *testing.T, store *AccessControlStore, sql *sqlst
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = store.SetUserResourcePermission(context.Background(), 1, u.Id, accesscontrol.SetResourcePermissionCommand{
|
||||
_, err = store.SetUserResourcePermission(context.Background(), 1, accesscontrol.User{ID: u.Id}, accesscontrol.SetResourcePermissionCommand{
|
||||
Actions: actions,
|
||||
Resource: resource,
|
||||
ResourceID: resourceID,
|
||||
|
||||
@@ -131,7 +131,7 @@ func (a *api) setUserPermission(c *models.ReqContext) response.Response {
|
||||
return response.Error(http.StatusBadRequest, "bad request data", err)
|
||||
}
|
||||
|
||||
_, err = a.service.SetUserPermission(c.Req.Context(), c.OrgId, userID, resourceID, cmd.Permission)
|
||||
_, err = a.service.SetUserPermission(c.Req.Context(), c.OrgId, accesscontrol.User{ID: userID}, resourceID, cmd.Permission)
|
||||
if err != nil {
|
||||
return response.Error(http.StatusBadRequest, "failed to set user permission", err)
|
||||
}
|
||||
|
||||
@@ -160,7 +160,7 @@ func TestApi_getPermissions(t *testing.T) {
|
||||
// seed user 1 with "View" permission on dashboard 1
|
||||
u, err := sql.CreateUser(context.Background(), models.CreateUserCommand{Login: "test", OrgId: 1})
|
||||
require.NoError(t, err)
|
||||
_, err = service.SetUserPermission(context.Background(), u.OrgId, u.Id, tt.resourceID, "View")
|
||||
_, err = service.SetUserPermission(context.Background(), u.OrgId, accesscontrol.User{ID: u.Id}, tt.resourceID, "View")
|
||||
require.NoError(t, err)
|
||||
|
||||
// seed built in role Admin with "Edit" permission on dashboard 1
|
||||
|
||||
@@ -3,6 +3,7 @@ package resourcepermissions
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
)
|
||||
|
||||
@@ -28,7 +29,7 @@ type Options struct {
|
||||
// RoleGroup is the group name for the generated fixed roles
|
||||
RoleGroup string
|
||||
// OnSetUser if configured will be called each time a permission is set for a user
|
||||
OnSetUser func(session *sqlstore.DBSession, orgID, userID int64, resourceID, permission string) error
|
||||
OnSetUser func(session *sqlstore.DBSession, orgID int64, user accesscontrol.User, resourceID, permission string) error
|
||||
// OnSetTeam if configured will be called each time a permission is set for a team
|
||||
OnSetTeam func(session *sqlstore.DBSession, orgID, teamID int64, resourceID, permission string) error
|
||||
// OnSetBuiltInRole if configured will be called each time a permission is set for a built-in role
|
||||
|
||||
@@ -16,7 +16,8 @@ import (
|
||||
type Store interface {
|
||||
// SetUserResourcePermission sets permission for managed user role on a resource
|
||||
SetUserResourcePermission(
|
||||
ctx context.Context, orgID, userID int64,
|
||||
ctx context.Context, orgID int64,
|
||||
user accesscontrol.User,
|
||||
cmd accesscontrol.SetResourcePermissionCommand,
|
||||
hook types.UserResourceHookFunc,
|
||||
) (*accesscontrol.ResourcePermission, error)
|
||||
@@ -100,7 +101,7 @@ func (s *Service) GetPermissions(ctx context.Context, orgID int64, resourceID st
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Service) SetUserPermission(ctx context.Context, orgID, userID int64, resourceID, permission string) (*accesscontrol.ResourcePermission, error) {
|
||||
func (s *Service) SetUserPermission(ctx context.Context, orgID int64, user accesscontrol.User, resourceID, permission string) (*accesscontrol.ResourcePermission, error) {
|
||||
if !s.options.Assignments.Users {
|
||||
return nil, ErrInvalidAssignment
|
||||
}
|
||||
@@ -114,11 +115,11 @@ func (s *Service) SetUserPermission(ctx context.Context, orgID, userID int64, re
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := s.validateUser(ctx, orgID, userID); err != nil {
|
||||
if err := s.validateUser(ctx, orgID, user.ID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.store.SetUserResourcePermission(ctx, orgID, userID, accesscontrol.SetResourcePermissionCommand{
|
||||
return s.store.SetUserResourcePermission(ctx, orgID, user, accesscontrol.SetResourcePermissionCommand{
|
||||
Actions: actions,
|
||||
Permission: permission,
|
||||
ResourceID: resourceID,
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package resourcepermissions
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/stretchr/testify/mock"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
)
|
||||
|
||||
type MockService struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *MockService) GetPermissions(ctx context.Context, orgID int64, resourceID string) ([]accesscontrol.ResourcePermission, error) {
|
||||
mockedArgs := m.Called(ctx, orgID, resourceID)
|
||||
return mockedArgs.Get(0).([]accesscontrol.ResourcePermission), mockedArgs.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockService) SetUserPermission(ctx context.Context, orgID int64, user accesscontrol.User, resourceID, permission string) (*accesscontrol.ResourcePermission, error) {
|
||||
mockedArgs := m.Called(ctx, orgID, user, resourceID, permission)
|
||||
return mockedArgs.Get(0).(*accesscontrol.ResourcePermission), mockedArgs.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockService) SetTeamPermission(ctx context.Context, orgID, teamID int64, resourceID, permission string) (*accesscontrol.ResourcePermission, error) {
|
||||
mockedArgs := m.Called(ctx, orgID, teamID, resourceID, permission)
|
||||
return mockedArgs.Get(0).(*accesscontrol.ResourcePermission), mockedArgs.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockService) SetBuiltInRolePermission(ctx context.Context, orgID int64, builtInRole, resourceID, permission string) (*accesscontrol.ResourcePermission, error) {
|
||||
mockedArgs := m.Called(ctx, orgID, builtInRole, resourceID, permission)
|
||||
return mockedArgs.Get(0).(*accesscontrol.ResourcePermission), mockedArgs.Error(1)
|
||||
}
|
||||
@@ -46,13 +46,13 @@ func TestService_SetUserPermission(t *testing.T) {
|
||||
|
||||
var hookCalled bool
|
||||
if tt.callHook {
|
||||
service.options.OnSetUser = func(session *sqlstore.DBSession, orgID, userID int64, resourceID, permission string) error {
|
||||
service.options.OnSetUser = func(session *sqlstore.DBSession, orgID int64, user accesscontrol.User, resourceID, permission string) error {
|
||||
hookCalled = true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
_, err = service.SetUserPermission(context.Background(), user.OrgId, user.Id, "1", "")
|
||||
_, err = service.SetUserPermission(context.Background(), user.OrgId, accesscontrol.User{ID: user.Id}, "1", "")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tt.callHook, hookCalled)
|
||||
})
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
package types
|
||||
|
||||
import "github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
import (
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
)
|
||||
|
||||
type UserResourceHookFunc func(session *sqlstore.DBSession, orgID, userID int64, resourceID, permission string) error
|
||||
type UserResourceHookFunc func(session *sqlstore.DBSession, orgID int64, user accesscontrol.User, resourceID, permission string) error
|
||||
type TeamResourceHookFunc func(session *sqlstore.DBSession, orgID, teamID int64, resourceID, permission string) error
|
||||
type BuiltinResourceHookFunc func(session *sqlstore.DBSession, orgID int64, builtInRole, resourceID, permission string) error
|
||||
|
||||
type User struct {
|
||||
ID int64
|
||||
IsExternal bool
|
||||
}
|
||||
|
||||
@@ -77,20 +77,20 @@ func ProvideTeamPermissions(router routing.RouteRegister, sql *sqlstore.SQLStore
|
||||
ReaderRoleName: "Team permission reader",
|
||||
WriterRoleName: "Team permission writer",
|
||||
RoleGroup: "Teams",
|
||||
OnSetUser: func(session *sqlstore.DBSession, orgID, userID int64, resourceID, permission string) error {
|
||||
OnSetUser: func(session *sqlstore.DBSession, orgID int64, user accesscontrol.User, resourceID, permission string) error {
|
||||
teamId, err := strconv.ParseInt(resourceID, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch permission {
|
||||
case "Member":
|
||||
return sqlstore.AddOrUpdateTeamMemberHook(session, userID, orgID, teamId, false, 0)
|
||||
return sqlstore.AddOrUpdateTeamMemberHook(session, user.ID, orgID, teamId, user.IsExternal, 0)
|
||||
case "Admin":
|
||||
return sqlstore.AddOrUpdateTeamMemberHook(session, userID, orgID, teamId, false, models.PERMISSION_ADMIN)
|
||||
return sqlstore.AddOrUpdateTeamMemberHook(session, user.ID, orgID, teamId, user.IsExternal, models.PERMISSION_ADMIN)
|
||||
case "":
|
||||
return sqlstore.RemoveTeamMemberHook(session, &models.RemoveTeamMemberCommand{
|
||||
OrgId: orgID,
|
||||
UserId: userID,
|
||||
UserId: user.ID,
|
||||
TeamId: teamId,
|
||||
})
|
||||
default:
|
||||
|
||||
@@ -28,7 +28,7 @@ type UsageStatsQuerier interface {
|
||||
// configured in Grafana.
|
||||
func (e *AlertEngine) QueryUsageStats(ctx context.Context) (*UsageStats, error) {
|
||||
cmd := &models.GetAllAlertsQuery{}
|
||||
err := e.Bus.Dispatch(ctx, cmd)
|
||||
err := e.sqlStore.GetAllAlertQueryHandler(ctx, cmd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -63,7 +63,7 @@ func (e *AlertEngine) mapRulesToUsageStats(ctx context.Context, rules []*models.
|
||||
result := map[string]int{}
|
||||
for k, v := range typeCount {
|
||||
query := &models.GetDataSourceQuery{Id: k}
|
||||
err := e.Bus.Dispatch(ctx, query)
|
||||
err := e.sqlStore.GetDataSource(ctx, query)
|
||||
if err != nil {
|
||||
return map[string]int{}, nil
|
||||
}
|
||||
|
||||
@@ -7,18 +7,18 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAlertingUsageStats(t *testing.T) {
|
||||
store := &AlertStoreMock{}
|
||||
ae := &AlertEngine{
|
||||
Bus: bus.New(),
|
||||
sqlStore: store,
|
||||
}
|
||||
|
||||
ae.Bus.AddHandler(func(ctx context.Context, query *models.GetAllAlertsQuery) error {
|
||||
store.getAllAlerts = func(ctx context.Context, query *models.GetAllAlertsQuery) error {
|
||||
var createFake = func(file string) *simplejson.Json {
|
||||
// Ignore gosec warning G304 since it's a test
|
||||
// nolint:gosec
|
||||
@@ -37,9 +37,9 @@ func TestAlertingUsageStats(t *testing.T) {
|
||||
{Id: 3, Settings: createFake("testdata/settings/empty.json")},
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
ae.Bus.AddHandler(func(ctx context.Context, query *models.GetDataSourceQuery) error {
|
||||
store.getDataSource = func(ctx context.Context, query *models.GetDataSourceQuery) error {
|
||||
ds := map[int64]*models.DataSource{
|
||||
1: {Type: "influxdb"},
|
||||
2: {Type: "graphite"},
|
||||
@@ -54,7 +54,7 @@ func TestAlertingUsageStats(t *testing.T) {
|
||||
|
||||
query.Result = r
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
result, err := ae.QueryUsageStats(context.Background())
|
||||
require.NoError(t, err, "getAlertingUsage should not return error")
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
gocontext "context"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/components/null"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
@@ -140,7 +139,7 @@ func (c *QueryCondition) executeQuery(context *alerting.EvalContext, timeRange l
|
||||
OrgId: context.Rule.OrgID,
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(context.Ctx, getDsInfo); err != nil {
|
||||
if err := context.Store.GetDataSource(context.Ctx, getDsInfo); err != nil {
|
||||
return nil, fmt.Errorf("could not find datasource: %w", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -4,10 +4,10 @@ import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/alerting"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/mockstore"
|
||||
"github.com/grafana/grafana/pkg/services/validations"
|
||||
"github.com/grafana/grafana/pkg/tsdb/intervalv2"
|
||||
"github.com/grafana/grafana/pkg/tsdb/legacydata"
|
||||
@@ -137,16 +137,15 @@ func (rh fakeIntervalTestReqHandler) HandleRequest(ctx context.Context, dsInfo *
|
||||
//nolint: staticcheck // legacydata.DataResponse deprecated
|
||||
func applyScenario(t *testing.T, timeRange string, dataSourceJsonData *simplejson.Json, queryModel string, verifier func(query legacydata.DataSubQuery)) {
|
||||
t.Run("desc", func(t *testing.T) {
|
||||
bus.AddHandler("test", func(ctx context.Context, query *models.GetDataSourceQuery) error {
|
||||
query.Result = &models.DataSource{Id: 1, Type: "graphite", JsonData: dataSourceJsonData}
|
||||
return nil
|
||||
})
|
||||
store := mockstore.NewSQLStoreMock()
|
||||
store.ExpectedDatasource = &models.DataSource{Id: 1, Type: "graphite", JsonData: dataSourceJsonData}
|
||||
|
||||
ctx := &queryIntervalTestContext{}
|
||||
ctx.result = &alerting.EvalContext{
|
||||
Ctx: context.Background(),
|
||||
Rule: &alerting.Rule{},
|
||||
RequestValidator: &validations.OSSPluginRequestValidator{},
|
||||
Store: store,
|
||||
}
|
||||
|
||||
jsonModel, err := simplejson.NewJson([]byte(`{
|
||||
|
||||
@@ -6,13 +6,13 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/mockstore"
|
||||
"github.com/grafana/grafana/pkg/services/validations"
|
||||
"github.com/grafana/grafana/pkg/tsdb/legacydata"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/components/null"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
@@ -35,10 +35,8 @@ func newTimeSeriesPointsFromArgs(values ...float64) legacydata.DataTimeSeriesPoi
|
||||
func TestQueryCondition(t *testing.T) {
|
||||
setup := func() *queryConditionTestContext {
|
||||
ctx := &queryConditionTestContext{}
|
||||
bus.AddHandler("test", func(ctx context.Context, query *models.GetDataSourceQuery) error {
|
||||
query.Result = &models.DataSource{Id: 1, Type: "graphite"}
|
||||
return nil
|
||||
})
|
||||
store := mockstore.NewSQLStoreMock()
|
||||
store.ExpectedDatasource = &models.DataSource{Id: 1, Type: "graphite"}
|
||||
|
||||
ctx.reducer = `{"type":"avg"}`
|
||||
ctx.evaluator = `{"type":"gt","params":[100]}`
|
||||
@@ -46,6 +44,7 @@ func TestQueryCondition(t *testing.T) {
|
||||
Ctx: context.Background(),
|
||||
Rule: &alerting.Rule{},
|
||||
RequestValidator: &validations.OSSPluginRequestValidator{},
|
||||
Store: store,
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
|
||||
@@ -16,11 +16,25 @@ import (
|
||||
"github.com/grafana/grafana/pkg/infra/usagestats"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/encryption"
|
||||
"github.com/grafana/grafana/pkg/services/notifications"
|
||||
"github.com/grafana/grafana/pkg/services/rendering"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/tsdb/legacydata"
|
||||
)
|
||||
|
||||
// AlertStore is a subset of SQLStore API to satisfy the needs of the alerting service.
|
||||
// A subset is needed to make it easier to mock during the tests.
|
||||
type AlertStore interface {
|
||||
GetAllAlertQueryHandler(context.Context, *models.GetAllAlertsQuery) error
|
||||
GetDataSource(context.Context, *models.GetDataSourceQuery) error
|
||||
GetDashboardUIDById(context.Context, *models.GetDashboardRefByIdQuery) error
|
||||
SetAlertNotificationStateToCompleteCommand(context.Context, *models.SetAlertNotificationStateToCompleteCommand) error
|
||||
SetAlertNotificationStateToPendingCommand(context.Context, *models.SetAlertNotificationStateToPendingCommand) error
|
||||
GetAlertNotificationsWithUidToSend(context.Context, *models.GetAlertNotificationsWithUidToSendQuery) error
|
||||
GetOrCreateAlertNotificationState(context.Context, *models.GetOrCreateNotificationStateQuery) error
|
||||
SetAlertState(context.Context, *models.SetAlertStateCommand) error
|
||||
}
|
||||
|
||||
// AlertEngine is the background process that
|
||||
// schedules alert evaluations and makes sure notifications
|
||||
// are sent.
|
||||
@@ -40,6 +54,7 @@ type AlertEngine struct {
|
||||
resultHandler resultHandler
|
||||
usageStatsService usagestats.Service
|
||||
tracer tracing.Tracer
|
||||
sqlStore AlertStore
|
||||
}
|
||||
|
||||
// IsDisabled returns true if the alerting service is disabled for this instance.
|
||||
@@ -50,7 +65,7 @@ func (e *AlertEngine) IsDisabled() bool {
|
||||
// ProvideAlertEngine returns a new AlertEngine.
|
||||
func ProvideAlertEngine(renderer rendering.Service, bus bus.Bus, requestValidator models.PluginRequestValidator,
|
||||
dataService legacydata.RequestHandler, usageStatsService usagestats.Service, encryptionService encryption.Internal,
|
||||
cfg *setting.Cfg, tracer tracing.Tracer) *AlertEngine {
|
||||
notificationService *notifications.NotificationService, tracer tracing.Tracer, sqlStore AlertStore, cfg *setting.Cfg) *AlertEngine {
|
||||
e := &AlertEngine{
|
||||
Cfg: cfg,
|
||||
RenderService: renderer,
|
||||
@@ -59,14 +74,15 @@ func ProvideAlertEngine(renderer rendering.Service, bus bus.Bus, requestValidato
|
||||
DataService: dataService,
|
||||
usageStatsService: usageStatsService,
|
||||
tracer: tracer,
|
||||
sqlStore: sqlStore,
|
||||
}
|
||||
e.ticker = NewTicker(time.Now(), time.Second*0, clock.New(), 1)
|
||||
e.execQueue = make(chan *Job, 1000)
|
||||
e.scheduler = newScheduler()
|
||||
e.evalHandler = NewEvalHandler(e.DataService)
|
||||
e.ruleReader = newRuleReader()
|
||||
e.ruleReader = newRuleReader(sqlStore)
|
||||
e.log = log.New("alerting.engine")
|
||||
e.resultHandler = newResultHandler(e.RenderService, encryptionService.GetDecryptedValue)
|
||||
e.resultHandler = newResultHandler(e.RenderService, sqlStore, notificationService, encryptionService.GetDecryptedValue)
|
||||
|
||||
e.registerUsageMetrics()
|
||||
|
||||
@@ -179,7 +195,7 @@ func (e *AlertEngine) processJob(attemptID int, attemptChan chan int, cancelChan
|
||||
alertCtx, cancelFn := context.WithTimeout(context.Background(), setting.AlertingEvaluationTimeout)
|
||||
cancelChan <- cancelFn
|
||||
alertCtx, span := e.tracer.Start(alertCtx, "alert execution")
|
||||
evalContext := NewEvalContext(alertCtx, job.Rule, e.RequestValidator)
|
||||
evalContext := NewEvalContext(alertCtx, job.Rule, e.RequestValidator, e.sqlStore)
|
||||
evalContext.Ctx = alertCtx
|
||||
|
||||
go func() {
|
||||
|
||||
@@ -24,7 +24,7 @@ func TestEngineTimeouts(t *testing.T) {
|
||||
usMock := &usagestats.UsageStatsMock{T: t}
|
||||
tracer, err := tracing.InitializeTracerForTest()
|
||||
require.NoError(t, err)
|
||||
engine := ProvideAlertEngine(nil, nil, nil, nil, usMock, ossencryption.ProvideService(), setting.NewCfg(), tracer)
|
||||
engine := ProvideAlertEngine(nil, nil, nil, nil, usMock, ossencryption.ProvideService(), nil, tracer, nil, setting.NewCfg())
|
||||
setting.AlertingNotificationTimeout = 30 * time.Second
|
||||
setting.AlertingMaxAttempts = 3
|
||||
engine.resultHandler = &FakeResultHandler{}
|
||||
|
||||
@@ -43,12 +43,66 @@ func (handler *FakeResultHandler) handle(evalContext *EvalContext) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// A mock implementation of the AlertStore interface, allowing to override certain methods individually
|
||||
type AlertStoreMock struct {
|
||||
getAllAlerts func(context.Context, *models.GetAllAlertsQuery) error
|
||||
getDataSource func(context.Context, *models.GetDataSourceQuery) error
|
||||
getAlertNotificationsWithUidToSend func(ctx context.Context, query *models.GetAlertNotificationsWithUidToSendQuery) error
|
||||
getOrCreateNotificationState func(ctx context.Context, query *models.GetOrCreateNotificationStateQuery) error
|
||||
}
|
||||
|
||||
func (a *AlertStoreMock) GetDataSource(c context.Context, cmd *models.GetDataSourceQuery) error {
|
||||
if a.getDataSource != nil {
|
||||
return a.getDataSource(c, cmd)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *AlertStoreMock) GetAllAlertQueryHandler(c context.Context, cmd *models.GetAllAlertsQuery) error {
|
||||
if a.getAllAlerts != nil {
|
||||
return a.getAllAlerts(c, cmd)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *AlertStoreMock) GetAlertNotificationsWithUidToSend(c context.Context, cmd *models.GetAlertNotificationsWithUidToSendQuery) error {
|
||||
if a.getAlertNotificationsWithUidToSend != nil {
|
||||
return a.getAlertNotificationsWithUidToSend(c, cmd)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *AlertStoreMock) GetOrCreateAlertNotificationState(c context.Context, cmd *models.GetOrCreateNotificationStateQuery) error {
|
||||
if a.getOrCreateNotificationState != nil {
|
||||
return a.getOrCreateNotificationState(c, cmd)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *AlertStoreMock) GetDashboardUIDById(_ context.Context, _ *models.GetDashboardRefByIdQuery) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *AlertStoreMock) SetAlertNotificationStateToCompleteCommand(_ context.Context, _ *models.SetAlertNotificationStateToCompleteCommand) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *AlertStoreMock) SetAlertNotificationStateToPendingCommand(_ context.Context, _ *models.SetAlertNotificationStateToPendingCommand) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *AlertStoreMock) SetAlertState(_ context.Context, _ *models.SetAlertStateCommand) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestEngineProcessJob(t *testing.T) {
|
||||
bus := bus.New()
|
||||
usMock := &usagestats.UsageStatsMock{T: t}
|
||||
tracer, err := tracing.InitializeTracerForTest()
|
||||
require.NoError(t, err)
|
||||
engine := ProvideAlertEngine(nil, bus, nil, nil, usMock, ossencryption.ProvideService(), setting.NewCfg(), tracer)
|
||||
|
||||
store := &AlertStoreMock{}
|
||||
engine := ProvideAlertEngine(nil, bus, nil, nil, usMock, ossencryption.ProvideService(), nil, tracer, store, setting.NewCfg())
|
||||
setting.AlertingEvaluationTimeout = 30 * time.Second
|
||||
setting.AlertingNotificationTimeout = 30 * time.Second
|
||||
setting.AlertingMaxAttempts = 3
|
||||
@@ -56,19 +110,19 @@ func TestEngineProcessJob(t *testing.T) {
|
||||
job := &Job{running: true, Rule: &Rule{}}
|
||||
|
||||
t.Run("Should register usage metrics func", func(t *testing.T) {
|
||||
bus.AddHandler(func(ctx context.Context, q *models.GetAllAlertsQuery) error {
|
||||
store.getAllAlerts = func(ctx context.Context, q *models.GetAllAlertsQuery) error {
|
||||
settings, err := simplejson.NewJson([]byte(`{"conditions": [{"query": { "datasourceId": 1}}]}`))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
q.Result = []*models.Alert{{Settings: settings}}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
bus.AddHandler(func(ctx context.Context, q *models.GetDataSourceQuery) error {
|
||||
store.getDataSource = func(ctx context.Context, q *models.GetDataSourceQuery) error {
|
||||
q.Result = &models.DataSource{Id: 1, Type: models.DS_PROMETHEUS}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
report, err := usMock.GetUsageReport(context.Background())
|
||||
require.Nil(t, err)
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
@@ -36,10 +35,12 @@ type EvalContext struct {
|
||||
RequestValidator models.PluginRequestValidator
|
||||
|
||||
Ctx context.Context
|
||||
|
||||
Store AlertStore
|
||||
}
|
||||
|
||||
// NewEvalContext is the EvalContext constructor.
|
||||
func NewEvalContext(alertCtx context.Context, rule *Rule, requestValidator models.PluginRequestValidator) *EvalContext {
|
||||
func NewEvalContext(alertCtx context.Context, rule *Rule, requestValidator models.PluginRequestValidator, sqlStore AlertStore) *EvalContext {
|
||||
return &EvalContext{
|
||||
Ctx: alertCtx,
|
||||
StartTime: time.Now(),
|
||||
@@ -49,6 +50,7 @@ func NewEvalContext(alertCtx context.Context, rule *Rule, requestValidator model
|
||||
Log: log.New("alerting.evalContext"),
|
||||
PrevAlertState: rule.State,
|
||||
RequestValidator: requestValidator,
|
||||
Store: sqlStore,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,7 +110,7 @@ func (c *EvalContext) GetDashboardUID() (*models.DashboardRef, error) {
|
||||
}
|
||||
|
||||
uidQuery := &models.GetDashboardRefByIdQuery{Id: c.Rule.DashboardID}
|
||||
if err := bus.Dispatch(c.Ctx, uidQuery); err != nil {
|
||||
if err := c.Store.GetDashboardUIDById(c.Ctx, uidQuery); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
)
|
||||
|
||||
func TestStateIsUpdatedWhenNeeded(t *testing.T) {
|
||||
ctx := NewEvalContext(context.Background(), &Rule{Conditions: []Condition{&conditionStub{firing: true}}}, &validations.OSSPluginRequestValidator{})
|
||||
ctx := NewEvalContext(context.Background(), &Rule{Conditions: []Condition{&conditionStub{firing: true}}}, &validations.OSSPluginRequestValidator{}, nil)
|
||||
|
||||
t.Run("ok -> alerting", func(t *testing.T) {
|
||||
ctx.PrevAlertState = models.AlertStateOK
|
||||
@@ -200,7 +200,7 @@ func TestGetStateFromEvalContext(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, tc := range tcs {
|
||||
evalContext := NewEvalContext(context.Background(), &Rule{Conditions: []Condition{&conditionStub{firing: true}}}, &validations.OSSPluginRequestValidator{})
|
||||
evalContext := NewEvalContext(context.Background(), &Rule{Conditions: []Condition{&conditionStub{firing: true}}}, &validations.OSSPluginRequestValidator{}, nil)
|
||||
|
||||
tc.applyFn(evalContext)
|
||||
newState := evalContext.GetNewState()
|
||||
|
||||
@@ -29,7 +29,7 @@ func TestAlertingEvaluationHandler(t *testing.T) {
|
||||
Conditions: []Condition{&conditionStub{
|
||||
firing: true,
|
||||
}},
|
||||
}, &validations.OSSPluginRequestValidator{})
|
||||
}, &validations.OSSPluginRequestValidator{}, nil)
|
||||
|
||||
handler.Eval(context)
|
||||
require.Equal(t, true, context.Firing)
|
||||
@@ -39,7 +39,7 @@ func TestAlertingEvaluationHandler(t *testing.T) {
|
||||
t.Run("Show return triggered with single passing condition2", func(t *testing.T) {
|
||||
context := NewEvalContext(context.Background(), &Rule{
|
||||
Conditions: []Condition{&conditionStub{firing: true, operator: "and"}},
|
||||
}, &validations.OSSPluginRequestValidator{})
|
||||
}, &validations.OSSPluginRequestValidator{}, nil)
|
||||
|
||||
handler.Eval(context)
|
||||
require.Equal(t, true, context.Firing)
|
||||
@@ -52,7 +52,7 @@ func TestAlertingEvaluationHandler(t *testing.T) {
|
||||
&conditionStub{firing: true, operator: "and", matches: []*EvalMatch{{}, {}}},
|
||||
&conditionStub{firing: false, operator: "and"},
|
||||
},
|
||||
}, &validations.OSSPluginRequestValidator{})
|
||||
}, &validations.OSSPluginRequestValidator{}, nil)
|
||||
|
||||
handler.Eval(context)
|
||||
require.Equal(t, false, context.Firing)
|
||||
@@ -65,7 +65,7 @@ func TestAlertingEvaluationHandler(t *testing.T) {
|
||||
&conditionStub{firing: true, operator: "and"},
|
||||
&conditionStub{firing: false, operator: "or"},
|
||||
},
|
||||
}, &validations.OSSPluginRequestValidator{})
|
||||
}, &validations.OSSPluginRequestValidator{}, nil)
|
||||
|
||||
handler.Eval(context)
|
||||
require.Equal(t, true, context.Firing)
|
||||
@@ -78,7 +78,7 @@ func TestAlertingEvaluationHandler(t *testing.T) {
|
||||
&conditionStub{firing: true, operator: "and"},
|
||||
&conditionStub{firing: false, operator: "and"},
|
||||
},
|
||||
}, &validations.OSSPluginRequestValidator{})
|
||||
}, &validations.OSSPluginRequestValidator{}, nil)
|
||||
|
||||
handler.Eval(context)
|
||||
require.Equal(t, false, context.Firing)
|
||||
@@ -92,7 +92,7 @@ func TestAlertingEvaluationHandler(t *testing.T) {
|
||||
&conditionStub{firing: true, operator: "and"},
|
||||
&conditionStub{firing: false, operator: "or"},
|
||||
},
|
||||
}, &validations.OSSPluginRequestValidator{})
|
||||
}, &validations.OSSPluginRequestValidator{}, nil)
|
||||
|
||||
handler.Eval(context)
|
||||
require.Equal(t, true, context.Firing)
|
||||
@@ -106,7 +106,7 @@ func TestAlertingEvaluationHandler(t *testing.T) {
|
||||
&conditionStub{firing: false, operator: "and"},
|
||||
&conditionStub{firing: false, operator: "or"},
|
||||
},
|
||||
}, &validations.OSSPluginRequestValidator{})
|
||||
}, &validations.OSSPluginRequestValidator{}, nil)
|
||||
|
||||
handler.Eval(context)
|
||||
require.Equal(t, false, context.Firing)
|
||||
@@ -120,7 +120,7 @@ func TestAlertingEvaluationHandler(t *testing.T) {
|
||||
&conditionStub{firing: false, operator: "and"},
|
||||
&conditionStub{firing: true, operator: "and"},
|
||||
},
|
||||
}, &validations.OSSPluginRequestValidator{})
|
||||
}, &validations.OSSPluginRequestValidator{}, nil)
|
||||
|
||||
handler.Eval(context)
|
||||
require.Equal(t, false, context.Firing)
|
||||
@@ -134,7 +134,7 @@ func TestAlertingEvaluationHandler(t *testing.T) {
|
||||
&conditionStub{firing: false, operator: "or"},
|
||||
&conditionStub{firing: true, operator: "or"},
|
||||
},
|
||||
}, &validations.OSSPluginRequestValidator{})
|
||||
}, &validations.OSSPluginRequestValidator{}, nil)
|
||||
|
||||
handler.Eval(context)
|
||||
require.Equal(t, true, context.Firing)
|
||||
@@ -148,7 +148,7 @@ func TestAlertingEvaluationHandler(t *testing.T) {
|
||||
&conditionStub{firing: false, operator: "or"},
|
||||
&conditionStub{firing: false, operator: "or"},
|
||||
},
|
||||
}, &validations.OSSPluginRequestValidator{})
|
||||
}, &validations.OSSPluginRequestValidator{}, nil)
|
||||
|
||||
handler.Eval(context)
|
||||
require.Equal(t, false, context.Firing)
|
||||
@@ -163,7 +163,7 @@ func TestAlertingEvaluationHandler(t *testing.T) {
|
||||
&conditionStub{operator: "or", noData: false},
|
||||
&conditionStub{operator: "or", noData: false},
|
||||
},
|
||||
}, &validations.OSSPluginRequestValidator{})
|
||||
}, &validations.OSSPluginRequestValidator{}, nil)
|
||||
|
||||
handler.Eval(context)
|
||||
require.False(t, context.NoDataFound)
|
||||
@@ -174,7 +174,7 @@ func TestAlertingEvaluationHandler(t *testing.T) {
|
||||
Conditions: []Condition{
|
||||
&conditionStub{operator: "and", noData: true},
|
||||
},
|
||||
}, &validations.OSSPluginRequestValidator{})
|
||||
}, &validations.OSSPluginRequestValidator{}, nil)
|
||||
|
||||
handler.Eval(context)
|
||||
require.Equal(t, false, context.Firing)
|
||||
@@ -187,7 +187,7 @@ func TestAlertingEvaluationHandler(t *testing.T) {
|
||||
&conditionStub{operator: "and", noData: true},
|
||||
&conditionStub{operator: "and", noData: false},
|
||||
},
|
||||
}, &validations.OSSPluginRequestValidator{})
|
||||
}, &validations.OSSPluginRequestValidator{}, nil)
|
||||
|
||||
handler.Eval(context)
|
||||
require.True(t, context.NoDataFound)
|
||||
@@ -199,7 +199,7 @@ func TestAlertingEvaluationHandler(t *testing.T) {
|
||||
&conditionStub{operator: "or", noData: true},
|
||||
&conditionStub{operator: "or", noData: false},
|
||||
},
|
||||
}, &validations.OSSPluginRequestValidator{})
|
||||
}, &validations.OSSPluginRequestValidator{}, nil)
|
||||
|
||||
handler.Eval(context)
|
||||
require.True(t, context.NoDataFound)
|
||||
|
||||
@@ -6,11 +6,11 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/components/imguploader"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/infra/metrics"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/notifications"
|
||||
"github.com/grafana/grafana/pkg/services/rendering"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
@@ -83,18 +83,22 @@ type ShowWhen struct {
|
||||
Is string `json:"is"`
|
||||
}
|
||||
|
||||
func newNotificationService(renderService rendering.Service, decryptFn GetDecryptedValueFn) *notificationService {
|
||||
func newNotificationService(renderService rendering.Service, sqlStore AlertStore, notificationSvc *notifications.NotificationService, decryptFn GetDecryptedValueFn) *notificationService {
|
||||
return ¬ificationService{
|
||||
log: log.New("alerting.notifier"),
|
||||
renderService: renderService,
|
||||
decryptFn: decryptFn,
|
||||
log: log.New("alerting.notifier"),
|
||||
renderService: renderService,
|
||||
sqlStore: sqlStore,
|
||||
notificationService: notificationSvc,
|
||||
decryptFn: decryptFn,
|
||||
}
|
||||
}
|
||||
|
||||
type notificationService struct {
|
||||
log log.Logger
|
||||
renderService rendering.Service
|
||||
decryptFn GetDecryptedValueFn
|
||||
log log.Logger
|
||||
renderService rendering.Service
|
||||
sqlStore AlertStore
|
||||
notificationService *notifications.NotificationService
|
||||
decryptFn GetDecryptedValueFn
|
||||
}
|
||||
|
||||
func (n *notificationService) SendIfNeeded(evalCtx *EvalContext) error {
|
||||
@@ -152,7 +156,7 @@ func (n *notificationService) sendAndMarkAsComplete(evalContext *EvalContext, no
|
||||
Version: notifierState.state.Version,
|
||||
}
|
||||
|
||||
return bus.Dispatch(evalContext.Ctx, cmd)
|
||||
return n.sqlStore.SetAlertNotificationStateToCompleteCommand(evalContext.Ctx, cmd)
|
||||
}
|
||||
|
||||
func (n *notificationService) sendNotification(evalContext *EvalContext, notifierState *notifierState) error {
|
||||
@@ -163,7 +167,7 @@ func (n *notificationService) sendNotification(evalContext *EvalContext, notifie
|
||||
AlertRuleStateUpdatedVersion: evalContext.Rule.StateChanges,
|
||||
}
|
||||
|
||||
err := bus.Dispatch(evalContext.Ctx, setPendingCmd)
|
||||
err := n.sqlStore.SetAlertNotificationStateToPendingCommand(evalContext.Ctx, setPendingCmd)
|
||||
if err != nil {
|
||||
if errors.Is(err, models.ErrAlertNotificationStateVersionConflict) {
|
||||
return nil
|
||||
@@ -251,13 +255,13 @@ func (n *notificationService) renderAndUploadImage(evalCtx *EvalContext, timeout
|
||||
func (n *notificationService) getNeededNotifiers(orgID int64, notificationUids []string, evalContext *EvalContext) (notifierStateSlice, error) {
|
||||
query := &models.GetAlertNotificationsWithUidToSendQuery{OrgId: orgID, Uids: notificationUids}
|
||||
|
||||
if err := bus.Dispatch(evalContext.Ctx, query); err != nil {
|
||||
if err := n.sqlStore.GetAlertNotificationsWithUidToSend(evalContext.Ctx, query); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result notifierStateSlice
|
||||
for _, notification := range query.Result {
|
||||
not, err := InitNotifier(notification, n.decryptFn)
|
||||
not, err := InitNotifier(notification, n.decryptFn, n.notificationService)
|
||||
if err != nil {
|
||||
n.log.Error("Could not create notifier", "notifier", notification.Uid, "error", err)
|
||||
continue
|
||||
@@ -269,7 +273,7 @@ func (n *notificationService) getNeededNotifiers(orgID int64, notificationUids [
|
||||
OrgId: evalContext.Rule.OrgID,
|
||||
}
|
||||
|
||||
err = bus.Dispatch(evalContext.Ctx, query)
|
||||
err = n.sqlStore.GetOrCreateAlertNotificationState(evalContext.Ctx, query)
|
||||
if err != nil {
|
||||
n.log.Error("Could not get notification state.", "notifier", notification.Id, "error", err)
|
||||
continue
|
||||
@@ -287,13 +291,13 @@ func (n *notificationService) getNeededNotifiers(orgID int64, notificationUids [
|
||||
}
|
||||
|
||||
// InitNotifier instantiate a new notifier based on the model.
|
||||
func InitNotifier(model *models.AlertNotification, fn GetDecryptedValueFn) (Notifier, error) {
|
||||
func InitNotifier(model *models.AlertNotification, fn GetDecryptedValueFn, notificationService *notifications.NotificationService) (Notifier, error) {
|
||||
notifierPlugin, found := notifierFactories[model.Type]
|
||||
if !found {
|
||||
return nil, fmt.Errorf("unsupported notification type %q", model.Type)
|
||||
}
|
||||
|
||||
return notifierPlugin.Factory(model, fn)
|
||||
return notifierPlugin.Factory(model, fn, notificationService)
|
||||
}
|
||||
|
||||
// GetDecryptedValueFn is a function that returns the decrypted value of
|
||||
@@ -301,7 +305,7 @@ func InitNotifier(model *models.AlertNotification, fn GetDecryptedValueFn) (Noti
|
||||
type GetDecryptedValueFn func(ctx context.Context, sjd map[string][]byte, key string, fallback string, secret string) string
|
||||
|
||||
// NotifierFactory is a signature for creating notifiers.
|
||||
type NotifierFactory func(*models.AlertNotification, GetDecryptedValueFn) (Notifier, error)
|
||||
type NotifierFactory func(*models.AlertNotification, GetDecryptedValueFn, notifications.Service) (Notifier, error)
|
||||
|
||||
var notifierFactories = make(map[string]*NotifierPlugin)
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/notifications"
|
||||
"github.com/grafana/grafana/pkg/services/validations"
|
||||
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
@@ -21,17 +22,18 @@ import (
|
||||
|
||||
func TestNotificationService(t *testing.T) {
|
||||
testRule := &Rule{Name: "Test", Message: "Something is bad"}
|
||||
evalCtx := NewEvalContext(context.Background(), testRule, &validations.OSSPluginRequestValidator{})
|
||||
store := &AlertStoreMock{}
|
||||
evalCtx := NewEvalContext(context.Background(), testRule, &validations.OSSPluginRequestValidator{}, store)
|
||||
|
||||
testRuleTemplated := &Rule{Name: "Test latency ${quantile}", Message: "Something is bad on instance ${instance}"}
|
||||
evalCtxWithMatch := NewEvalContext(context.Background(), testRuleTemplated, &validations.OSSPluginRequestValidator{})
|
||||
evalCtxWithMatch := NewEvalContext(context.Background(), testRuleTemplated, &validations.OSSPluginRequestValidator{}, store)
|
||||
evalCtxWithMatch.EvalMatches = []*EvalMatch{{
|
||||
Tags: map[string]string{
|
||||
"instance": "localhost:3000",
|
||||
"quantile": "0.99",
|
||||
},
|
||||
}}
|
||||
evalCtxWithoutMatch := NewEvalContext(context.Background(), testRuleTemplated, &validations.OSSPluginRequestValidator{})
|
||||
evalCtxWithoutMatch := NewEvalContext(context.Background(), testRuleTemplated, &validations.OSSPluginRequestValidator{}, store)
|
||||
|
||||
notificationServiceScenario(t, "Given alert rule with upload image enabled should render and upload image and send notification",
|
||||
evalCtx, true, func(sc *scenarioContext) {
|
||||
@@ -177,7 +179,9 @@ func notificationServiceScenario(t *testing.T, name string, evalCtx *EvalContext
|
||||
|
||||
evalCtx.dashboardRef = &models.DashboardRef{Uid: "db-uid"}
|
||||
|
||||
bus.AddHandler("test", func(ctx context.Context, query *models.GetAlertNotificationsWithUidToSendQuery) error {
|
||||
store := evalCtx.Store.(*AlertStoreMock)
|
||||
|
||||
store.getAlertNotificationsWithUidToSend = func(ctx context.Context, query *models.GetAlertNotificationsWithUidToSendQuery) error {
|
||||
query.Result = []*models.AlertNotification{
|
||||
{
|
||||
Id: 1,
|
||||
@@ -188,9 +192,9 @@ func notificationServiceScenario(t *testing.T, name string, evalCtx *EvalContext
|
||||
},
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
bus.AddHandler("test", func(ctx context.Context, query *models.GetOrCreateNotificationStateQuery) error {
|
||||
store.getOrCreateNotificationState = func(ctx context.Context, query *models.GetOrCreateNotificationStateQuery) error {
|
||||
query.Result = &models.AlertNotificationState{
|
||||
AlertId: evalCtx.Rule.ID,
|
||||
AlertRuleStateUpdatedVersion: 1,
|
||||
@@ -199,7 +203,7 @@ func notificationServiceScenario(t *testing.T, name string, evalCtx *EvalContext
|
||||
State: models.AlertNotificationStateUnknown,
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
bus.AddHandler("test", func(ctx context.Context, cmd *models.SetAlertNotificationStateToPendingCommand) error {
|
||||
return nil
|
||||
@@ -263,7 +267,7 @@ func notificationServiceScenario(t *testing.T, name string, evalCtx *EvalContext
|
||||
},
|
||||
}
|
||||
|
||||
scenarioCtx.notificationService = newNotificationService(renderService, nil)
|
||||
scenarioCtx.notificationService = newNotificationService(renderService, store, nil, nil)
|
||||
fn(scenarioCtx)
|
||||
})
|
||||
}
|
||||
@@ -279,7 +283,7 @@ type testNotifier struct {
|
||||
Frequency time.Duration
|
||||
}
|
||||
|
||||
func newTestNotifier(model *models.AlertNotification, _ GetDecryptedValueFn) (Notifier, error) {
|
||||
func newTestNotifier(model *models.AlertNotification, _ GetDecryptedValueFn, ns notifications.Service) (Notifier, error) {
|
||||
uploadImage := true
|
||||
value, exist := model.Settings.CheckGet("uploadImage")
|
||||
if exist {
|
||||
|
||||
@@ -7,11 +7,11 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/alerting"
|
||||
"github.com/grafana/grafana/pkg/services/notifications"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
@@ -50,7 +50,7 @@ func init() {
|
||||
}
|
||||
|
||||
// NewAlertmanagerNotifier returns a new Alertmanager notifier
|
||||
func NewAlertmanagerNotifier(model *models.AlertNotification, fn alerting.GetDecryptedValueFn) (alerting.Notifier, error) {
|
||||
func NewAlertmanagerNotifier(model *models.AlertNotification, fn alerting.GetDecryptedValueFn, ns notifications.Service) (alerting.Notifier, error) {
|
||||
urlString := model.Settings.Get("url").MustString()
|
||||
if urlString == "" {
|
||||
return nil, alerting.ValidationError{Reason: "Could not find url property in settings"}
|
||||
@@ -67,7 +67,7 @@ func NewAlertmanagerNotifier(model *models.AlertNotification, fn alerting.GetDec
|
||||
basicAuthPassword := fn(context.Background(), model.SecureSettings, "basicAuthPassword", model.Settings.Get("basicAuthPassword").MustString(), setting.SecretKey)
|
||||
|
||||
return &AlertmanagerNotifier{
|
||||
NotifierBase: NewNotifierBase(model),
|
||||
NotifierBase: NewNotifierBase(model, ns),
|
||||
URL: url,
|
||||
BasicAuthUser: basicAuthUser,
|
||||
BasicAuthPassword: basicAuthPassword,
|
||||
@@ -183,7 +183,7 @@ func (am *AlertmanagerNotifier) Notify(evalContext *alerting.EvalContext) error
|
||||
Body: string(body),
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(evalContext.Ctx, cmd); err != nil {
|
||||
if err := am.NotificationService.SendWebhookSync(evalContext.Ctx, cmd); err != nil {
|
||||
am.log.Error("Failed to send alertmanager", "error", err, "alertmanager", am.Name, "url", url)
|
||||
errCnt++
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ func TestWhenAlertManagerShouldNotify(t *testing.T) {
|
||||
am := &AlertmanagerNotifier{log: log.New("test.logger")}
|
||||
evalContext := alerting.NewEvalContext(context.Background(), &alerting.Rule{
|
||||
State: tc.prevState,
|
||||
}, &validations.OSSPluginRequestValidator{})
|
||||
}, &validations.OSSPluginRequestValidator{}, nil)
|
||||
|
||||
evalContext.Rule.State = tc.newState
|
||||
|
||||
@@ -92,7 +92,7 @@ func TestAlertmanagerNotifier(t *testing.T) {
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
_, err := NewAlertmanagerNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
_, err := NewAlertmanagerNotifier(model, ossencryption.ProvideService().GetDecryptedValue, nil)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
@@ -106,7 +106,7 @@ func TestAlertmanagerNotifier(t *testing.T) {
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
not, err := NewAlertmanagerNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
not, err := NewAlertmanagerNotifier(model, ossencryption.ProvideService().GetDecryptedValue, nil)
|
||||
alertmanagerNotifier := not.(*AlertmanagerNotifier)
|
||||
|
||||
require.NoError(t, err)
|
||||
@@ -125,7 +125,7 @@ func TestAlertmanagerNotifier(t *testing.T) {
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
not, err := NewAlertmanagerNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
not, err := NewAlertmanagerNotifier(model, ossencryption.ProvideService().GetDecryptedValue, nil)
|
||||
alertmanagerNotifier := not.(*AlertmanagerNotifier)
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/alerting"
|
||||
"github.com/grafana/grafana/pkg/services/notifications"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -24,11 +25,13 @@ type NotifierBase struct {
|
||||
DisableResolveMessage bool
|
||||
Frequency time.Duration
|
||||
|
||||
NotificationService notifications.Service
|
||||
|
||||
log log.Logger
|
||||
}
|
||||
|
||||
// NewNotifierBase returns a new `NotifierBase`.
|
||||
func NewNotifierBase(model *models.AlertNotification) NotifierBase {
|
||||
func NewNotifierBase(model *models.AlertNotification, notificationService notifications.Service) NotifierBase {
|
||||
uploadImage := true
|
||||
if value, exists := model.Settings.CheckGet("uploadImage"); exists {
|
||||
uploadImage = value.MustBool()
|
||||
@@ -43,6 +46,7 @@ func NewNotifierBase(model *models.AlertNotification) NotifierBase {
|
||||
SendReminder: model.SendReminder,
|
||||
DisableResolveMessage: model.DisableResolveMessage,
|
||||
Frequency: model.Frequency,
|
||||
NotificationService: notificationService,
|
||||
log: log.New("alerting.notifier." + model.Name),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,7 +170,7 @@ func TestShouldSendAlertNotification(t *testing.T) {
|
||||
for _, tc := range tcs {
|
||||
evalContext := alerting.NewEvalContext(context.Background(), &alerting.Rule{
|
||||
State: tc.prevState,
|
||||
}, &validations.OSSPluginRequestValidator{})
|
||||
}, &validations.OSSPluginRequestValidator{}, nil)
|
||||
|
||||
if tc.state == nil {
|
||||
tc.state = &models.AlertNotificationState{}
|
||||
@@ -197,24 +197,24 @@ func TestBaseNotifier(t *testing.T) {
|
||||
t.Run("can parse false value", func(t *testing.T) {
|
||||
bJSON.Set("uploadImage", false)
|
||||
|
||||
base := NewNotifierBase(model)
|
||||
base := NewNotifierBase(model, nil)
|
||||
require.False(t, base.UploadImage)
|
||||
})
|
||||
|
||||
t.Run("can parse true value", func(t *testing.T) {
|
||||
bJSON.Set("uploadImage", true)
|
||||
|
||||
base := NewNotifierBase(model)
|
||||
base := NewNotifierBase(model, nil)
|
||||
require.True(t, base.UploadImage)
|
||||
})
|
||||
|
||||
t.Run("default value should be true for backwards compatibility", func(t *testing.T) {
|
||||
base := NewNotifierBase(model)
|
||||
base := NewNotifierBase(model, nil)
|
||||
require.True(t, base.UploadImage)
|
||||
})
|
||||
|
||||
t.Run("default value should be false for backwards compatibility", func(t *testing.T) {
|
||||
base := NewNotifierBase(model)
|
||||
base := NewNotifierBase(model, nil)
|
||||
require.False(t, base.DisableResolveMessage)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -5,10 +5,10 @@ import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/alerting"
|
||||
"github.com/grafana/grafana/pkg/services/notifications"
|
||||
)
|
||||
|
||||
const defaultDingdingMsgType = "link"
|
||||
@@ -47,7 +47,7 @@ func init() {
|
||||
})
|
||||
}
|
||||
|
||||
func newDingDingNotifier(model *models.AlertNotification, _ alerting.GetDecryptedValueFn) (alerting.Notifier, error) {
|
||||
func newDingDingNotifier(model *models.AlertNotification, _ alerting.GetDecryptedValueFn, ns notifications.Service) (alerting.Notifier, error) {
|
||||
url := model.Settings.Get("url").MustString()
|
||||
if url == "" {
|
||||
return nil, alerting.ValidationError{Reason: "Could not find url property in settings"}
|
||||
@@ -56,7 +56,7 @@ func newDingDingNotifier(model *models.AlertNotification, _ alerting.GetDecrypte
|
||||
msgType := model.Settings.Get("msgType").MustString(defaultDingdingMsgType)
|
||||
|
||||
return &DingDingNotifier{
|
||||
NotifierBase: NewNotifierBase(model),
|
||||
NotifierBase: NewNotifierBase(model, ns),
|
||||
MsgType: msgType,
|
||||
URL: url,
|
||||
log: log.New("alerting.notifier.dingding"),
|
||||
@@ -91,7 +91,7 @@ func (dd *DingDingNotifier) Notify(evalContext *alerting.EvalContext) error {
|
||||
Body: string(body),
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(evalContext.Ctx, cmd); err != nil {
|
||||
if err := dd.NotificationService.SendWebhookSync(evalContext.Ctx, cmd); err != nil {
|
||||
dd.log.Error("Failed to send DingDing", "error", err, "dingding", dd.Name)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ func TestDingDingNotifier(t *testing.T) {
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
_, err := newDingDingNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
_, err := newDingDingNotifier(model, ossencryption.ProvideService().GetDecryptedValue, nil)
|
||||
require.Error(t, err)
|
||||
})
|
||||
t.Run("settings should trigger incident", func(t *testing.T) {
|
||||
@@ -37,7 +37,7 @@ func TestDingDingNotifier(t *testing.T) {
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
not, err := newDingDingNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
not, err := newDingDingNotifier(model, ossencryption.ProvideService().GetDecryptedValue, nil)
|
||||
notifier := not.(*DingDingNotifier)
|
||||
|
||||
require.Nil(t, err)
|
||||
@@ -50,7 +50,7 @@ func TestDingDingNotifier(t *testing.T) {
|
||||
&alerting.Rule{
|
||||
State: models.AlertStateAlerting,
|
||||
Message: `{host="localhost"}`,
|
||||
}, &validations.OSSPluginRequestValidator{})
|
||||
}, &validations.OSSPluginRequestValidator{}, nil)
|
||||
_, err = notifier.genBody(evalContext, "")
|
||||
require.Nil(t, err)
|
||||
})
|
||||
|
||||
@@ -9,11 +9,11 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/alerting"
|
||||
"github.com/grafana/grafana/pkg/services/notifications"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
@@ -57,7 +57,7 @@ func init() {
|
||||
})
|
||||
}
|
||||
|
||||
func newDiscordNotifier(model *models.AlertNotification, _ alerting.GetDecryptedValueFn) (alerting.Notifier, error) {
|
||||
func newDiscordNotifier(model *models.AlertNotification, _ alerting.GetDecryptedValueFn, ns notifications.Service) (alerting.Notifier, error) {
|
||||
avatar := model.Settings.Get("avatar_url").MustString()
|
||||
content := model.Settings.Get("content").MustString()
|
||||
url := model.Settings.Get("url").MustString()
|
||||
@@ -67,7 +67,7 @@ func newDiscordNotifier(model *models.AlertNotification, _ alerting.GetDecrypted
|
||||
useDiscordUsername := model.Settings.Get("use_discord_username").MustBool(false)
|
||||
|
||||
return &DiscordNotifier{
|
||||
NotifierBase: NewNotifierBase(model),
|
||||
NotifierBase: NewNotifierBase(model, ns),
|
||||
Content: content,
|
||||
AvatarURL: avatar,
|
||||
WebhookURL: url,
|
||||
@@ -177,7 +177,7 @@ func (dn *DiscordNotifier) Notify(evalContext *alerting.EvalContext) error {
|
||||
}
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(evalContext.Ctx, cmd); err != nil {
|
||||
if err := dn.NotificationService.SendWebhookSync(evalContext.Ctx, cmd); err != nil {
|
||||
dn.log.Error("Failed to send notification to Discord", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ func TestDiscordNotifier(t *testing.T) {
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
_, err := newDiscordNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
_, err := newDiscordNotifier(model, ossencryption.ProvideService().GetDecryptedValue, nil)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
@@ -41,7 +41,7 @@ func TestDiscordNotifier(t *testing.T) {
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
not, err := newDiscordNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
not, err := newDiscordNotifier(model, ossencryption.ProvideService().GetDecryptedValue, nil)
|
||||
discordNotifier := not.(*DiscordNotifier)
|
||||
|
||||
require.Nil(t, err)
|
||||
|
||||
@@ -3,12 +3,12 @@ package notifiers
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/alerting"
|
||||
"github.com/grafana/grafana/pkg/services/notifications"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
@@ -48,7 +48,7 @@ type EmailNotifier struct {
|
||||
|
||||
// NewEmailNotifier is the constructor function
|
||||
// for the EmailNotifier.
|
||||
func NewEmailNotifier(model *models.AlertNotification, _ alerting.GetDecryptedValueFn) (alerting.Notifier, error) {
|
||||
func NewEmailNotifier(model *models.AlertNotification, _ alerting.GetDecryptedValueFn, ns notifications.Service) (alerting.Notifier, error) {
|
||||
addressesString := model.Settings.Get("addresses").MustString()
|
||||
singleEmail := model.Settings.Get("singleEmail").MustBool(false)
|
||||
|
||||
@@ -60,7 +60,7 @@ func NewEmailNotifier(model *models.AlertNotification, _ alerting.GetDecryptedVa
|
||||
addresses := util.SplitEmails(addressesString)
|
||||
|
||||
return &EmailNotifier{
|
||||
NotifierBase: NewNotifierBase(model),
|
||||
NotifierBase: NewNotifierBase(model, ns),
|
||||
Addresses: addresses,
|
||||
SingleEmail: singleEmail,
|
||||
log: log.New("alerting.notifier.email"),
|
||||
@@ -117,7 +117,7 @@ func (en *EmailNotifier) Notify(evalContext *alerting.EvalContext) error {
|
||||
}
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(evalContext.Ctx, cmd); err != nil {
|
||||
if err := en.NotificationService.SendEmailCommandHandlerSync(evalContext.Ctx, cmd); err != nil {
|
||||
en.log.Error("Failed to send alert notification email", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ func TestEmailNotifier(t *testing.T) {
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
_, err := NewEmailNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
_, err := NewEmailNotifier(model, ossencryption.ProvideService().GetDecryptedValue, nil)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
@@ -39,7 +39,7 @@ func TestEmailNotifier(t *testing.T) {
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
not, err := NewEmailNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
not, err := NewEmailNotifier(model, ossencryption.ProvideService().GetDecryptedValue, nil)
|
||||
emailNotifier := not.(*EmailNotifier)
|
||||
|
||||
require.Nil(t, err)
|
||||
@@ -63,7 +63,7 @@ func TestEmailNotifier(t *testing.T) {
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
not, err := NewEmailNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
not, err := NewEmailNotifier(model, ossencryption.ProvideService().GetDecryptedValue, nil)
|
||||
emailNotifier := not.(*EmailNotifier)
|
||||
|
||||
require.Nil(t, err)
|
||||
|
||||
@@ -5,10 +5,10 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/alerting"
|
||||
"github.com/grafana/grafana/pkg/services/notifications"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
@@ -32,14 +32,14 @@ func init() {
|
||||
})
|
||||
}
|
||||
|
||||
func newGoogleChatNotifier(model *models.AlertNotification, _ alerting.GetDecryptedValueFn) (alerting.Notifier, error) {
|
||||
func newGoogleChatNotifier(model *models.AlertNotification, _ alerting.GetDecryptedValueFn, ns notifications.Service) (alerting.Notifier, error) {
|
||||
url := model.Settings.Get("url").MustString()
|
||||
if url == "" {
|
||||
return nil, alerting.ValidationError{Reason: "Could not find url property in settings"}
|
||||
}
|
||||
|
||||
return &GoogleChatNotifier{
|
||||
NotifierBase: NewNotifierBase(model),
|
||||
NotifierBase: NewNotifierBase(model, ns),
|
||||
URL: url,
|
||||
log: log.New("alerting.notifier.googlechat"),
|
||||
}, nil
|
||||
@@ -220,7 +220,7 @@ func (gcn *GoogleChatNotifier) Notify(evalContext *alerting.EvalContext) error {
|
||||
Body: string(body),
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(evalContext.Ctx, cmd); err != nil {
|
||||
if err := gcn.NotificationService.SendWebhookSync(evalContext.Ctx, cmd); err != nil {
|
||||
gcn.log.Error("Failed to send Google Hangouts Chat alert", "error", err, "webhook", gcn.Name)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ func TestGoogleChatNotifier(t *testing.T) {
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
_, err := newGoogleChatNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
_, err := newGoogleChatNotifier(model, ossencryption.ProvideService().GetDecryptedValue, nil)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
@@ -39,7 +39,7 @@ func TestGoogleChatNotifier(t *testing.T) {
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
not, err := newGoogleChatNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
not, err := newGoogleChatNotifier(model, ossencryption.ProvideService().GetDecryptedValue, nil)
|
||||
webhookNotifier := not.(*GoogleChatNotifier)
|
||||
|
||||
require.Nil(t, err)
|
||||
|
||||
@@ -7,10 +7,10 @@ import (
|
||||
|
||||
"fmt"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/alerting"
|
||||
"github.com/grafana/grafana/pkg/services/notifications"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -53,7 +53,7 @@ const (
|
||||
|
||||
// NewHipChatNotifier is the constructor functions
|
||||
// for the HipChatNotifier
|
||||
func NewHipChatNotifier(model *models.AlertNotification, _ alerting.GetDecryptedValueFn) (alerting.Notifier, error) {
|
||||
func NewHipChatNotifier(model *models.AlertNotification, _ alerting.GetDecryptedValueFn, ns notifications.Service) (alerting.Notifier, error) {
|
||||
url := model.Settings.Get("url").MustString()
|
||||
if strings.HasSuffix(url, "/") {
|
||||
url = url[:len(url)-1]
|
||||
@@ -66,7 +66,7 @@ func NewHipChatNotifier(model *models.AlertNotification, _ alerting.GetDecrypted
|
||||
roomID := model.Settings.Get("roomid").MustString()
|
||||
|
||||
return &HipChatNotifier{
|
||||
NotifierBase: NewNotifierBase(model),
|
||||
NotifierBase: NewNotifierBase(model, ns),
|
||||
URL: url,
|
||||
APIKey: apikey,
|
||||
RoomID: roomID,
|
||||
@@ -177,7 +177,7 @@ func (hc *HipChatNotifier) Notify(evalContext *alerting.EvalContext) error {
|
||||
hc.log.Info("Request payload", "json", string(data))
|
||||
cmd := &models.SendWebhookSync{Url: hipURL, Body: string(data)}
|
||||
|
||||
if err := bus.Dispatch(evalContext.Ctx, cmd); err != nil {
|
||||
if err := hc.NotificationService.SendWebhookSync(evalContext.Ctx, cmd); err != nil {
|
||||
hc.log.Error("Failed to send hipchat notification", "error", err, "webhook", hc.Name)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ func TestHipChatNotifier(t *testing.T) {
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
_, err := NewHipChatNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
_, err := NewHipChatNotifier(model, ossencryption.ProvideService().GetDecryptedValue, nil)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
@@ -39,7 +39,7 @@ func TestHipChatNotifier(t *testing.T) {
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
not, err := NewHipChatNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
not, err := NewHipChatNotifier(model, ossencryption.ProvideService().GetDecryptedValue, nil)
|
||||
hipchatNotifier := not.(*HipChatNotifier)
|
||||
|
||||
require.Nil(t, err)
|
||||
@@ -65,7 +65,7 @@ func TestHipChatNotifier(t *testing.T) {
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
not, err := NewHipChatNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
not, err := NewHipChatNotifier(model, ossencryption.ProvideService().GetDecryptedValue, nil)
|
||||
hipchatNotifier := not.(*HipChatNotifier)
|
||||
|
||||
require.Nil(t, err)
|
||||
|
||||
@@ -5,11 +5,11 @@ import (
|
||||
|
||||
"fmt"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/alerting"
|
||||
"github.com/grafana/grafana/pkg/services/notifications"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -41,7 +41,7 @@ func init() {
|
||||
}
|
||||
|
||||
// NewKafkaNotifier is the constructor function for the Kafka notifier.
|
||||
func NewKafkaNotifier(model *models.AlertNotification, _ alerting.GetDecryptedValueFn) (alerting.Notifier, error) {
|
||||
func NewKafkaNotifier(model *models.AlertNotification, _ alerting.GetDecryptedValueFn, ns notifications.Service) (alerting.Notifier, error) {
|
||||
endpoint := model.Settings.Get("kafkaRestProxy").MustString()
|
||||
if endpoint == "" {
|
||||
return nil, alerting.ValidationError{Reason: "Could not find kafka rest proxy endpoint property in settings"}
|
||||
@@ -52,7 +52,7 @@ func NewKafkaNotifier(model *models.AlertNotification, _ alerting.GetDecryptedVa
|
||||
}
|
||||
|
||||
return &KafkaNotifier{
|
||||
NotifierBase: NewNotifierBase(model),
|
||||
NotifierBase: NewNotifierBase(model, ns),
|
||||
Endpoint: endpoint,
|
||||
Topic: topic,
|
||||
log: log.New("alerting.notifier.kafka"),
|
||||
@@ -124,7 +124,7 @@ func (kn *KafkaNotifier) Notify(evalContext *alerting.EvalContext) error {
|
||||
},
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(evalContext.Ctx, cmd); err != nil {
|
||||
if err := kn.NotificationService.SendWebhookSync(evalContext.Ctx, cmd); err != nil {
|
||||
kn.log.Error("Failed to send notification to Kafka", "error", err, "body", string(body))
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ func TestKafkaNotifier(t *testing.T) {
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
_, err := NewKafkaNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
_, err := NewKafkaNotifier(model, ossencryption.ProvideService().GetDecryptedValue, nil)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
@@ -40,7 +40,7 @@ func TestKafkaNotifier(t *testing.T) {
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
not, err := NewKafkaNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
not, err := NewKafkaNotifier(model, ossencryption.ProvideService().GetDecryptedValue, nil)
|
||||
kafkaNotifier := not.(*KafkaNotifier)
|
||||
|
||||
require.Nil(t, err)
|
||||
|
||||
@@ -5,10 +5,10 @@ import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/alerting"
|
||||
"github.com/grafana/grafana/pkg/services/notifications"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
@@ -37,14 +37,14 @@ const (
|
||||
)
|
||||
|
||||
// NewLINENotifier is the constructor for the LINE notifier
|
||||
func NewLINENotifier(model *models.AlertNotification, fn alerting.GetDecryptedValueFn) (alerting.Notifier, error) {
|
||||
func NewLINENotifier(model *models.AlertNotification, fn alerting.GetDecryptedValueFn, ns notifications.Service) (alerting.Notifier, error) {
|
||||
token := fn(context.Background(), model.SecureSettings, "token", model.Settings.Get("token").MustString(), setting.SecretKey)
|
||||
if token == "" {
|
||||
return nil, alerting.ValidationError{Reason: "Could not find token in settings"}
|
||||
}
|
||||
|
||||
return &LineNotifier{
|
||||
NotifierBase: NewNotifierBase(model),
|
||||
NotifierBase: NewNotifierBase(model, ns),
|
||||
Token: token,
|
||||
log: log.New("alerting.notifier.line"),
|
||||
}, nil
|
||||
@@ -92,7 +92,7 @@ func (ln *LineNotifier) createAlert(evalContext *alerting.EvalContext) error {
|
||||
Body: form.Encode(),
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(evalContext.Ctx, cmd); err != nil {
|
||||
if err := ln.NotificationService.SendWebhookSync(evalContext.Ctx, cmd); err != nil {
|
||||
ln.log.Error("Failed to send notification to LINE", "error", err, "body", body)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ func TestLineNotifier(t *testing.T) {
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
_, err := NewLINENotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
_, err := NewLINENotifier(model, ossencryption.ProvideService().GetDecryptedValue, nil)
|
||||
require.Error(t, err)
|
||||
})
|
||||
t.Run("settings should trigger incident", func(t *testing.T) {
|
||||
@@ -36,7 +36,7 @@ func TestLineNotifier(t *testing.T) {
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
not, err := NewLINENotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
not, err := NewLINENotifier(model, ossencryption.ProvideService().GetDecryptedValue, nil)
|
||||
lineNotifier := not.(*LineNotifier)
|
||||
|
||||
require.Nil(t, err)
|
||||
|
||||
@@ -5,11 +5,11 @@ import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/alerting"
|
||||
"github.com/grafana/grafana/pkg/services/notifications"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
@@ -84,7 +84,7 @@ const (
|
||||
)
|
||||
|
||||
// NewOpsGenieNotifier is the constructor for OpsGenie.
|
||||
func NewOpsGenieNotifier(model *models.AlertNotification, fn alerting.GetDecryptedValueFn) (alerting.Notifier, error) {
|
||||
func NewOpsGenieNotifier(model *models.AlertNotification, fn alerting.GetDecryptedValueFn, ns notifications.Service) (alerting.Notifier, error) {
|
||||
autoClose := model.Settings.Get("autoClose").MustBool(true)
|
||||
overridePriority := model.Settings.Get("overridePriority").MustBool(true)
|
||||
apiKey := fn(context.Background(), model.SecureSettings, "apiKey", model.Settings.Get("apiKey").MustString(), setting.SecretKey)
|
||||
@@ -104,7 +104,7 @@ func NewOpsGenieNotifier(model *models.AlertNotification, fn alerting.GetDecrypt
|
||||
}
|
||||
|
||||
return &OpsGenieNotifier{
|
||||
NotifierBase: NewNotifierBase(model),
|
||||
NotifierBase: NewNotifierBase(model, ns),
|
||||
APIKey: apiKey,
|
||||
APIUrl: apiURL,
|
||||
AutoClose: autoClose,
|
||||
@@ -205,7 +205,7 @@ func (on *OpsGenieNotifier) createAlert(evalContext *alerting.EvalContext) error
|
||||
},
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(evalContext.Ctx, cmd); err != nil {
|
||||
if err := on.NotificationService.SendWebhookSync(evalContext.Ctx, cmd); err != nil {
|
||||
on.log.Error("Failed to send notification to OpsGenie", "error", err, "body", string(body))
|
||||
}
|
||||
|
||||
@@ -229,7 +229,7 @@ func (on *OpsGenieNotifier) closeAlert(evalContext *alerting.EvalContext) error
|
||||
},
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(evalContext.Ctx, cmd); err != nil {
|
||||
if err := on.NotificationService.SendWebhookSync(evalContext.Ctx, cmd); err != nil {
|
||||
on.log.Error("Failed to send notification to OpsGenie", "error", err, "body", string(body))
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -6,11 +6,11 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/alerting"
|
||||
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
|
||||
"github.com/grafana/grafana/pkg/services/notifications"
|
||||
"github.com/grafana/grafana/pkg/services/validations"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -28,7 +28,7 @@ func TestOpsGenieNotifier(t *testing.T) {
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
_, err := NewOpsGenieNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
_, err := NewOpsGenieNotifier(model, ossencryption.ProvideService().GetDecryptedValue, nil)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
@@ -45,7 +45,7 @@ func TestOpsGenieNotifier(t *testing.T) {
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
not, err := NewOpsGenieNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
not, err := NewOpsGenieNotifier(model, ossencryption.ProvideService().GetDecryptedValue, nil)
|
||||
opsgenieNotifier := not.(*OpsGenieNotifier)
|
||||
|
||||
require.Nil(t, err)
|
||||
@@ -69,7 +69,7 @@ func TestOpsGenieNotifier(t *testing.T) {
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
_, err := NewOpsGenieNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
|
||||
_, err := NewOpsGenieNotifier(model, ossencryption.ProvideService().GetDecryptedValue, nil)
|
||||
require.Error(t, err)
|
||||
require.Equal(t, reflect.TypeOf(err), reflect.TypeOf(alerting.ValidationError{}))
|
||||
require.True(t, strings.HasSuffix(err.Error(), "Invalid value for sendTagsAs: \"not_a_valid_value\""))
|
||||
@@ -92,7 +92,8 @@ func TestOpsGenieNotifier(t *testing.T) {
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
notifier, notifierErr := NewOpsGenieNotifier(model, ossencryption.ProvideService().GetDecryptedValue) // unhandled error
|
||||
notificationService := notifications.MockNotificationService()
|
||||
notifier, notifierErr := NewOpsGenieNotifier(model, ossencryption.ProvideService().GetDecryptedValue, notificationService) // unhandled error
|
||||
|
||||
opsgenieNotifier := notifier.(*OpsGenieNotifier)
|
||||
|
||||
@@ -102,22 +103,20 @@ func TestOpsGenieNotifier(t *testing.T) {
|
||||
Message: "someMessage",
|
||||
State: models.AlertStateAlerting,
|
||||
AlertRuleTags: tagPairs,
|
||||
}, &validations.OSSPluginRequestValidator{})
|
||||
}, &validations.OSSPluginRequestValidator{}, nil)
|
||||
evalContext.IsTestRun = true
|
||||
|
||||
tags := make([]string, 0)
|
||||
details := make(map[string]interface{})
|
||||
bus.AddHandler("alerting", func(ctx context.Context, cmd *models.SendWebhookSync) error {
|
||||
bodyJSON, err := simplejson.NewJson([]byte(cmd.Body))
|
||||
if err == nil {
|
||||
tags = bodyJSON.Get("tags").MustStringArray([]string{})
|
||||
details = bodyJSON.Get("details").MustMap(map[string]interface{}{})
|
||||
}
|
||||
return err
|
||||
})
|
||||
|
||||
alertErr := opsgenieNotifier.createAlert(evalContext)
|
||||
|
||||
bodyJSON, err := simplejson.NewJson([]byte(notificationService.Webhook.Body))
|
||||
if err == nil {
|
||||
tags = bodyJSON.Get("tags").MustStringArray([]string{})
|
||||
details = bodyJSON.Get("details").MustMap(map[string]interface{}{})
|
||||
}
|
||||
|
||||
require.Nil(t, notifierErr)
|
||||
require.Nil(t, alertErr)
|
||||
require.Equal(t, tags, []string{"keyOnly", "aKey:aValue"})
|
||||
@@ -142,7 +141,8 @@ func TestOpsGenieNotifier(t *testing.T) {
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
notifier, notifierErr := NewOpsGenieNotifier(model, ossencryption.ProvideService().GetDecryptedValue) // unhandled error
|
||||
notificationService := notifications.MockNotificationService()
|
||||
notifier, notifierErr := NewOpsGenieNotifier(model, ossencryption.ProvideService().GetDecryptedValue, notificationService) // unhandled error
|
||||
|
||||
opsgenieNotifier := notifier.(*OpsGenieNotifier)
|
||||
|
||||
@@ -152,22 +152,20 @@ func TestOpsGenieNotifier(t *testing.T) {
|
||||
Message: "someMessage",
|
||||
State: models.AlertStateAlerting,
|
||||
AlertRuleTags: tagPairs,
|
||||
}, nil)
|
||||
}, nil, nil)
|
||||
evalContext.IsTestRun = true
|
||||
|
||||
tags := make([]string, 0)
|
||||
details := make(map[string]interface{})
|
||||
bus.AddHandler("alerting", func(ctx context.Context, cmd *models.SendWebhookSync) error {
|
||||
bodyJSON, err := simplejson.NewJson([]byte(cmd.Body))
|
||||
if err == nil {
|
||||
tags = bodyJSON.Get("tags").MustStringArray([]string{})
|
||||
details = bodyJSON.Get("details").MustMap(map[string]interface{}{})
|
||||
}
|
||||
return err
|
||||
})
|
||||
|
||||
alertErr := opsgenieNotifier.createAlert(evalContext)
|
||||
|
||||
bodyJSON, err := simplejson.NewJson([]byte(notificationService.Webhook.Body))
|
||||
if err == nil {
|
||||
tags = bodyJSON.Get("tags").MustStringArray([]string{})
|
||||
details = bodyJSON.Get("details").MustMap(map[string]interface{}{})
|
||||
}
|
||||
|
||||
require.Nil(t, notifierErr)
|
||||
require.Nil(t, alertErr)
|
||||
require.Equal(t, tags, []string{})
|
||||
@@ -192,7 +190,8 @@ func TestOpsGenieNotifier(t *testing.T) {
|
||||
Settings: settingsJSON,
|
||||
}
|
||||
|
||||
notifier, notifierErr := NewOpsGenieNotifier(model, ossencryption.ProvideService().GetDecryptedValue) // unhandled error
|
||||
notificationService := notifications.MockNotificationService()
|
||||
notifier, notifierErr := NewOpsGenieNotifier(model, ossencryption.ProvideService().GetDecryptedValue, notificationService) // unhandled error
|
||||
|
||||
opsgenieNotifier := notifier.(*OpsGenieNotifier)
|
||||
|
||||
@@ -202,22 +201,20 @@ func TestOpsGenieNotifier(t *testing.T) {
|
||||
Message: "someMessage",
|
||||
State: models.AlertStateAlerting,
|
||||
AlertRuleTags: tagPairs,
|
||||
}, nil)
|
||||
}, nil, nil)
|
||||
evalContext.IsTestRun = true
|
||||
|
||||
tags := make([]string, 0)
|
||||
details := make(map[string]interface{})
|
||||
bus.AddHandler("alerting", func(ctx context.Context, cmd *models.SendWebhookSync) error {
|
||||
bodyJSON, err := simplejson.NewJson([]byte(cmd.Body))
|
||||
if err == nil {
|
||||
tags = bodyJSON.Get("tags").MustStringArray([]string{})
|
||||
details = bodyJSON.Get("details").MustMap(map[string]interface{}{})
|
||||
}
|
||||
return err
|
||||
})
|
||||
|
||||
alertErr := opsgenieNotifier.createAlert(evalContext)
|
||||
|
||||
bodyJSON, err := simplejson.NewJson([]byte(notificationService.Webhook.Body))
|
||||
if err == nil {
|
||||
tags = bodyJSON.Get("tags").MustStringArray([]string{})
|
||||
details = bodyJSON.Get("details").MustMap(map[string]interface{}{})
|
||||
}
|
||||
|
||||
require.Nil(t, notifierErr)
|
||||
require.Nil(t, alertErr)
|
||||
require.Equal(t, tags, []string{"keyOnly", "aKey:aValue"})
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user