mirror of
https://github.com/grafana/grafana.git
synced 2025-09-18 17:32:58 +08:00
Implement API server client in Scopes (#85266)
This commit is contained in:
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
@ -403,7 +403,6 @@ playwright.config.ts @grafana/plugins-platform-frontend
|
|||||||
/public/app/features/dashboard/ @grafana/dashboards-squad
|
/public/app/features/dashboard/ @grafana/dashboards-squad
|
||||||
/public/app/features/dashboard/components/TransformationsEditor/ @grafana/dataviz-squad
|
/public/app/features/dashboard/components/TransformationsEditor/ @grafana/dataviz-squad
|
||||||
/public/app/features/dashboard-scene/ @grafana/dashboards-squad
|
/public/app/features/dashboard-scene/ @grafana/dashboards-squad
|
||||||
/public/app/features/scopes/ @grafana/dashboards-squad
|
|
||||||
/public/app/features/datasources/ @grafana/plugins-platform-frontend @mikkancso
|
/public/app/features/datasources/ @grafana/plugins-platform-frontend @mikkancso
|
||||||
/public/app/features/dimensions/ @grafana/dataviz-squad
|
/public/app/features/dimensions/ @grafana/dataviz-squad
|
||||||
/public/app/features/dataframe-import/ @grafana/dataviz-squad
|
/public/app/features/dataframe-import/ @grafana/dataviz-squad
|
||||||
|
@ -1,20 +1,26 @@
|
|||||||
export interface ScopeDashboard {
|
export interface ScopeDashboardBindingSpec {
|
||||||
uid: string;
|
dashboard: string;
|
||||||
title: string;
|
scope: string;
|
||||||
url: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ScopeFilter {
|
export interface ScopeSpecFilter {
|
||||||
key: string;
|
key: string;
|
||||||
value: string;
|
value: string;
|
||||||
operator: string;
|
operator: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Scope {
|
export interface ScopeSpec {
|
||||||
uid: string;
|
|
||||||
title: string;
|
title: string;
|
||||||
type: string;
|
type: string;
|
||||||
description: string;
|
description: string;
|
||||||
category: string;
|
category: string;
|
||||||
filters: ScopeFilter[];
|
filters: ScopeSpecFilter[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Use Resource from apiserver when we export the types
|
||||||
|
export interface Scope {
|
||||||
|
metadata: {
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
spec: ScopeSpec;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Scope } from '@grafana/data';
|
import { ScopeSpec } from '@grafana/data';
|
||||||
import * as common from '@grafana/schema';
|
import * as common from '@grafana/schema';
|
||||||
|
|
||||||
export enum QueryEditorMode {
|
export enum QueryEditorMode {
|
||||||
@ -42,5 +42,5 @@ export interface Prometheus extends common.DataQuery {
|
|||||||
* Returns a Range vector, comprised of a set of time series containing a range of data points over time for each time series
|
* Returns a Range vector, comprised of a set of time series containing a range of data points over time for each time series
|
||||||
*/
|
*/
|
||||||
range?: boolean;
|
range?: boolean;
|
||||||
scope?: Scope;
|
scope?: ScopeSpec;
|
||||||
}
|
}
|
||||||
|
@ -369,7 +369,7 @@ export class PrometheusDatasource
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (config.featureToggles.promQLScope) {
|
if (config.featureToggles.promQLScope) {
|
||||||
processedTarget.scope = request.scope;
|
processedTarget.scope = request.scope?.spec;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (target.instant && target.range) {
|
if (target.instant && target.range) {
|
||||||
|
@ -2,6 +2,7 @@ import { config, getBackendSrv } from '@grafana/runtime';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
ListOptions,
|
ListOptions,
|
||||||
|
ListOptionsFieldSelector,
|
||||||
ListOptionsLabelSelector,
|
ListOptionsLabelSelector,
|
||||||
MetaStatus,
|
MetaStatus,
|
||||||
Resource,
|
Resource,
|
||||||
@ -33,9 +34,10 @@ export class ScopedResourceServer<T = object, K = string> implements ResourceSer
|
|||||||
return getBackendSrv().get<Resource<T, K>>(`${this.url}/${name}`);
|
return getBackendSrv().get<Resource<T, K>>(`${this.url}/${name}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async list(opts?: ListOptions<T> | undefined): Promise<ResourceList<T, K>> {
|
public async list(opts?: ListOptions | undefined): Promise<ResourceList<T, K>> {
|
||||||
const finalOpts = opts || {};
|
const finalOpts = opts || {};
|
||||||
finalOpts.labelSelector = this.parseLabelSelector(finalOpts?.labelSelector);
|
finalOpts.labelSelector = this.parseListOptionsSelector(finalOpts?.labelSelector);
|
||||||
|
finalOpts.fieldSelector = this.parseListOptionsSelector(finalOpts?.fieldSelector);
|
||||||
|
|
||||||
return getBackendSrv().get<ResourceList<T, K>>(this.url, opts);
|
return getBackendSrv().get<ResourceList<T, K>>(this.url, opts);
|
||||||
}
|
}
|
||||||
@ -48,12 +50,14 @@ export class ScopedResourceServer<T = object, K = string> implements ResourceSer
|
|||||||
return getBackendSrv().delete<MetaStatus>(`${this.url}/${name}`);
|
return getBackendSrv().delete<MetaStatus>(`${this.url}/${name}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
private parseLabelSelector<T>(labelSelector: ListOptionsLabelSelector<T> | undefined): string | undefined {
|
private parseListOptionsSelector(
|
||||||
if (!Array.isArray(labelSelector)) {
|
selector: ListOptionsLabelSelector | ListOptionsFieldSelector | undefined
|
||||||
return labelSelector;
|
): string | undefined {
|
||||||
|
if (!Array.isArray(selector)) {
|
||||||
|
return selector;
|
||||||
}
|
}
|
||||||
|
|
||||||
return labelSelector
|
return selector
|
||||||
.map((label) => {
|
.map((label) => {
|
||||||
const key = String(label.key);
|
const key = String(label.key);
|
||||||
const operator = label.operator;
|
const operator = label.operator;
|
||||||
|
@ -78,32 +78,44 @@ export interface ResourceList<T, K = string> extends TypeMeta {
|
|||||||
items: Array<Resource<T, K>>;
|
items: Array<Resource<T, K>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ListOptionsLabelSelector<T = {}> =
|
export type ListOptionsLabelSelector =
|
||||||
| string
|
| string
|
||||||
| Array<
|
| Array<
|
||||||
| {
|
| {
|
||||||
key: keyof T;
|
key: string;
|
||||||
operator: '=' | '!=';
|
operator: '=' | '!=';
|
||||||
value: string;
|
value: string;
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
key: keyof T;
|
key: string;
|
||||||
operator: 'in' | 'notin';
|
operator: 'in' | 'notin';
|
||||||
value: string[];
|
value: string[];
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
key: keyof T;
|
key: string;
|
||||||
operator: '' | '!';
|
operator: '' | '!';
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export interface ListOptions<T = {}> {
|
export type ListOptionsFieldSelector =
|
||||||
|
| string
|
||||||
|
| Array<{
|
||||||
|
key: string;
|
||||||
|
operator: '=' | '!=';
|
||||||
|
value: string;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export interface ListOptions {
|
||||||
// continue the list at a given batch
|
// continue the list at a given batch
|
||||||
continue?: string;
|
continue?: string;
|
||||||
|
|
||||||
// Query by labels
|
// Query by labels
|
||||||
// https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors
|
// https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors
|
||||||
labelSelector?: ListOptionsLabelSelector<T>;
|
labelSelector?: ListOptionsLabelSelector;
|
||||||
|
|
||||||
|
// Query by fields
|
||||||
|
// https://kubernetes.io/docs/concepts/overview/working-with-objects/field-selectors/
|
||||||
|
fieldSelector?: ListOptionsFieldSelector;
|
||||||
|
|
||||||
// Limit the response count
|
// Limit the response count
|
||||||
limit?: number;
|
limit?: number;
|
||||||
@ -129,7 +141,7 @@ export interface MetaStatus {
|
|||||||
export interface ResourceServer<T = object, K = string> {
|
export interface ResourceServer<T = object, K = string> {
|
||||||
create(obj: ResourceForCreate<T, K>): Promise<void>;
|
create(obj: ResourceForCreate<T, K>): Promise<void>;
|
||||||
get(name: string): Promise<Resource<T, K>>;
|
get(name: string): Promise<Resource<T, K>>;
|
||||||
list(opts?: ListOptions<T>): Promise<ResourceList<T, K>>;
|
list(opts?: ListOptions): Promise<ResourceList<T, K>>;
|
||||||
update(obj: ResourceForCreate<T, K>): Promise<Resource<T, K>>;
|
update(obj: ResourceForCreate<T, K>): Promise<Resource<T, K>>;
|
||||||
delete(name: string): Promise<MetaStatus>;
|
delete(name: string): Promise<MetaStatus>;
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,19 @@ import { css } from '@emotion/css';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
import { AppEvents, GrafanaTheme2, ScopeDashboard } from '@grafana/data';
|
import { AppEvents, GrafanaTheme2, ScopeDashboardBindingSpec } from '@grafana/data';
|
||||||
import { config, getAppEvents, getBackendSrv, locationService } from '@grafana/runtime';
|
import { getAppEvents, getBackendSrv, locationService } from '@grafana/runtime';
|
||||||
import { SceneComponentProps, SceneObjectBase, SceneObjectState } from '@grafana/scenes';
|
import { SceneComponentProps, SceneObjectBase, SceneObjectState } from '@grafana/scenes';
|
||||||
import { CustomScrollbar, Icon, Input, useStyles2 } from '@grafana/ui';
|
import { CustomScrollbar, Icon, Input, useStyles2 } from '@grafana/ui';
|
||||||
|
|
||||||
|
import { ScopedResourceServer } from '../../apiserver/server';
|
||||||
|
|
||||||
|
export interface ScopeDashboard {
|
||||||
|
uid: string;
|
||||||
|
title: string;
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ScopesDashboardsSceneState extends SceneObjectState {
|
export interface ScopesDashboardsSceneState extends SceneObjectState {
|
||||||
dashboards: ScopeDashboard[];
|
dashboards: ScopeDashboard[];
|
||||||
filteredDashboards: ScopeDashboard[];
|
filteredDashboards: ScopeDashboard[];
|
||||||
@ -17,8 +25,11 @@ export interface ScopesDashboardsSceneState extends SceneObjectState {
|
|||||||
export class ScopesDashboardsScene extends SceneObjectBase<ScopesDashboardsSceneState> {
|
export class ScopesDashboardsScene extends SceneObjectBase<ScopesDashboardsSceneState> {
|
||||||
static Component = ScopesDashboardsSceneRenderer;
|
static Component = ScopesDashboardsSceneRenderer;
|
||||||
|
|
||||||
private _url =
|
private server = new ScopedResourceServer<ScopeDashboardBindingSpec, 'ScopeDashboardBinding'>({
|
||||||
config.bootData.settings.listDashboardScopesEndpoint || '/apis/scope.grafana.app/v0alpha1/scopedashboards';
|
group: 'scope.grafana.app',
|
||||||
|
version: 'v0alpha1',
|
||||||
|
resource: 'scopedashboardbindings',
|
||||||
|
});
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super({
|
super({
|
||||||
@ -57,11 +68,17 @@ export class ScopesDashboardsScene extends SceneObjectBase<ScopesDashboardsScene
|
|||||||
|
|
||||||
private async fetchDashboardsUids(scope: string): Promise<string[]> {
|
private async fetchDashboardsUids(scope: string): Promise<string[]> {
|
||||||
try {
|
try {
|
||||||
const response = await getBackendSrv().get<{
|
const response = await this.server.list({
|
||||||
items: Array<{ spec: { dashboards: null | string[]; scope: string } }>;
|
fieldSelector: [
|
||||||
}>(this._url, { scope });
|
{
|
||||||
|
key: 'spec.scope',
|
||||||
|
operator: '=',
|
||||||
|
value: scope,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
return response.items.find((item) => !!item.spec.dashboards && item.spec.scope === scope)?.spec.dashboards ?? [];
|
return response.items.map((item) => item.spec.dashboard).filter((dashboardUid) => !!dashboardUid) ?? [];
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { AppEvents, Scope, SelectableValue } from '@grafana/data';
|
import { AppEvents, Scope, ScopeSpec, SelectableValue } from '@grafana/data';
|
||||||
import { config, getAppEvents, getBackendSrv } from '@grafana/runtime';
|
import { getAppEvents } from '@grafana/runtime';
|
||||||
import {
|
import {
|
||||||
SceneComponentProps,
|
SceneComponentProps,
|
||||||
SceneObjectBase,
|
SceneObjectBase,
|
||||||
@ -11,6 +11,8 @@ import {
|
|||||||
} from '@grafana/scenes';
|
} from '@grafana/scenes';
|
||||||
import { Select } from '@grafana/ui';
|
import { Select } from '@grafana/ui';
|
||||||
|
|
||||||
|
import { ScopedResourceServer } from '../../apiserver/server';
|
||||||
|
|
||||||
export interface ScopesFiltersSceneState extends SceneObjectState {
|
export interface ScopesFiltersSceneState extends SceneObjectState {
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
pendingValue: string | undefined;
|
pendingValue: string | undefined;
|
||||||
@ -23,7 +25,11 @@ export class ScopesFiltersScene extends SceneObjectBase<ScopesFiltersSceneState>
|
|||||||
|
|
||||||
protected _urlSync = new SceneObjectUrlSyncConfig(this, { keys: ['scope'] });
|
protected _urlSync = new SceneObjectUrlSyncConfig(this, { keys: ['scope'] });
|
||||||
|
|
||||||
private _url = config.bootData.settings.listScopesEndpoint || '/apis/scope.grafana.app/v0alpha1/scopes';
|
private server = new ScopedResourceServer<ScopeSpec, 'Scope'>({
|
||||||
|
group: 'scope.grafana.app',
|
||||||
|
version: 'v0alpha1',
|
||||||
|
resource: 'scopes',
|
||||||
|
});
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super({
|
super({
|
||||||
@ -44,7 +50,7 @@ export class ScopesFiltersScene extends SceneObjectBase<ScopesFiltersSceneState>
|
|||||||
}
|
}
|
||||||
|
|
||||||
public getSelectedScope(): Scope | undefined {
|
public getSelectedScope(): Scope | undefined {
|
||||||
return this.state.scopes.find((scope) => scope.uid === this.state.value);
|
return this.state.scopes.find((scope) => scope.metadata.name === this.state.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public setScope(newScope: string | undefined) {
|
public setScope(newScope: string | undefined) {
|
||||||
@ -52,7 +58,7 @@ export class ScopesFiltersScene extends SceneObjectBase<ScopesFiltersSceneState>
|
|||||||
return this.setState({ pendingValue: newScope });
|
return this.setState({ pendingValue: newScope });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.state.scopes.find((scope) => scope.uid === newScope)) {
|
if (!this.state.scopes.find((scope) => scope.metadata.name === newScope)) {
|
||||||
newScope = undefined;
|
newScope = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,16 +69,9 @@ export class ScopesFiltersScene extends SceneObjectBase<ScopesFiltersSceneState>
|
|||||||
this.setState({ isLoading: true });
|
this.setState({ isLoading: true });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await getBackendSrv().get<{
|
const response = await this.server.list();
|
||||||
items: Array<{ metadata: { uid: string }; spec: Omit<Scope, 'uid'> }>;
|
|
||||||
}>(this._url);
|
|
||||||
|
|
||||||
this.setScopesAfterFetch(
|
this.setScopesAfterFetch(response.items);
|
||||||
response.items.map(({ metadata: { uid }, spec }) => ({
|
|
||||||
uid,
|
|
||||||
...spec,
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
getAppEvents().publish({
|
getAppEvents().publish({
|
||||||
type: AppEvents.alertError.name,
|
type: AppEvents.alertError.name,
|
||||||
@ -88,7 +87,7 @@ export class ScopesFiltersScene extends SceneObjectBase<ScopesFiltersSceneState>
|
|||||||
private setScopesAfterFetch(scopes: Scope[]) {
|
private setScopesAfterFetch(scopes: Scope[]) {
|
||||||
let value = this.state.pendingValue ?? this.state.value;
|
let value = this.state.pendingValue ?? this.state.value;
|
||||||
|
|
||||||
if (!scopes.find((scope) => scope.uid === value)) {
|
if (!scopes.find((scope) => scope.metadata.name === value)) {
|
||||||
value = undefined;
|
value = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,9 +100,9 @@ export function ScopesFiltersSceneRenderer({ model }: SceneComponentProps<Scopes
|
|||||||
const parentState = model.parent!.useState();
|
const parentState = model.parent!.useState();
|
||||||
const isViewing = 'isViewing' in parentState ? !!parentState.isViewing : false;
|
const isViewing = 'isViewing' in parentState ? !!parentState.isViewing : false;
|
||||||
|
|
||||||
const options: Array<SelectableValue<string>> = scopes.map(({ uid, title, category }) => ({
|
const options: Array<SelectableValue<string>> = scopes.map(({ metadata: { name }, spec: { title, category } }) => ({
|
||||||
label: title,
|
label: title,
|
||||||
value: uid,
|
value: name,
|
||||||
description: category,
|
description: category,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { waitFor } from '@testing-library/react';
|
import { waitFor } from '@testing-library/react';
|
||||||
|
|
||||||
|
import { Scope } from '@grafana/data';
|
||||||
import { config } from '@grafana/runtime';
|
import { config } from '@grafana/runtime';
|
||||||
import {
|
import {
|
||||||
behaviors,
|
behaviors,
|
||||||
@ -13,7 +14,7 @@ import {
|
|||||||
|
|
||||||
import { DashboardControls } from './DashboardControls';
|
import { DashboardControls } from './DashboardControls';
|
||||||
import { DashboardScene } from './DashboardScene';
|
import { DashboardScene } from './DashboardScene';
|
||||||
import { ScopesDashboardsScene } from './ScopesDashboardsScene';
|
import { ScopeDashboard, ScopesDashboardsScene } from './ScopesDashboardsScene';
|
||||||
import { ScopesFiltersScene } from './ScopesFiltersScene';
|
import { ScopesFiltersScene } from './ScopesFiltersScene';
|
||||||
import { ScopesScene } from './ScopesScene';
|
import { ScopesScene } from './ScopesScene';
|
||||||
|
|
||||||
@ -35,9 +36,17 @@ const dashboardsMocks = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const scopesMocks = {
|
const scopesMocks: Record<
|
||||||
|
string,
|
||||||
|
Scope & {
|
||||||
|
dashboards: ScopeDashboard[];
|
||||||
|
}
|
||||||
|
> = {
|
||||||
scope1: {
|
scope1: {
|
||||||
uid: 'scope1',
|
metadata: {
|
||||||
|
name: 'scope1',
|
||||||
|
},
|
||||||
|
spec: {
|
||||||
title: 'Scope 1',
|
title: 'Scope 1',
|
||||||
type: 'Type 1',
|
type: 'Type 1',
|
||||||
description: 'Description 1',
|
description: 'Description 1',
|
||||||
@ -46,24 +55,33 @@ const scopesMocks = {
|
|||||||
{ key: 'a-key', operator: '=', value: 'a-value' },
|
{ key: 'a-key', operator: '=', value: 'a-value' },
|
||||||
{ key: 'b-key', operator: '!=', value: 'b-value' },
|
{ key: 'b-key', operator: '!=', value: 'b-value' },
|
||||||
],
|
],
|
||||||
|
},
|
||||||
dashboards: [dashboardsMocks.dashboard1, dashboardsMocks.dashboard2, dashboardsMocks.dashboard3],
|
dashboards: [dashboardsMocks.dashboard1, dashboardsMocks.dashboard2, dashboardsMocks.dashboard3],
|
||||||
},
|
},
|
||||||
scope2: {
|
scope2: {
|
||||||
uid: 'scope2',
|
metadata: {
|
||||||
|
name: 'scope2',
|
||||||
|
},
|
||||||
|
spec: {
|
||||||
title: 'Scope 2',
|
title: 'Scope 2',
|
||||||
type: 'Type 2',
|
type: 'Type 2',
|
||||||
description: 'Description 2',
|
description: 'Description 2',
|
||||||
category: 'Category 2',
|
category: 'Category 2',
|
||||||
filters: [{ key: 'c-key', operator: '!=', value: 'c-value' }],
|
filters: [{ key: 'c-key', operator: '!=', value: 'c-value' }],
|
||||||
|
},
|
||||||
dashboards: [dashboardsMocks.dashboard3],
|
dashboards: [dashboardsMocks.dashboard3],
|
||||||
},
|
},
|
||||||
scope3: {
|
scope3: {
|
||||||
uid: 'scope3',
|
metadata: {
|
||||||
|
name: 'scope3',
|
||||||
|
},
|
||||||
|
spec: {
|
||||||
title: 'Scope 3',
|
title: 'Scope 3',
|
||||||
type: 'Type 1',
|
type: 'Type 1',
|
||||||
description: 'Description 3',
|
description: 'Description 3',
|
||||||
category: 'Category 1',
|
category: 'Category 1',
|
||||||
filters: [{ key: 'd-key', operator: '=', value: 'd-value' }],
|
filters: [{ key: 'd-key', operator: '=', value: 'd-value' }],
|
||||||
|
},
|
||||||
dashboards: [dashboardsMocks.dashboard1, dashboardsMocks.dashboard2],
|
dashboards: [dashboardsMocks.dashboard1, dashboardsMocks.dashboard2],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -73,29 +91,27 @@ jest.mock('@grafana/runtime', () => ({
|
|||||||
...jest.requireActual('@grafana/runtime'),
|
...jest.requireActual('@grafana/runtime'),
|
||||||
getBackendSrv: () => ({
|
getBackendSrv: () => ({
|
||||||
get: jest.fn().mockImplementation((url: string) => {
|
get: jest.fn().mockImplementation((url: string) => {
|
||||||
if (url === '/apis/scope.grafana.app/v0alpha1/scopes') {
|
if (url.startsWith('/apis/scope.grafana.app/v0alpha1/namespaces/default/scopes')) {
|
||||||
return {
|
return {
|
||||||
items: Object.values(scopesMocks).map((scope) => ({
|
items: Object.values(scopesMocks).map(({ dashboards: _dashboards, ...scope }) => scope),
|
||||||
metadata: { uid: scope.uid },
|
};
|
||||||
spec: {
|
}
|
||||||
title: scope.title,
|
|
||||||
type: scope.type,
|
if (url.startsWith('/apis/scope.grafana.app/v0alpha1/namespaces/default/scopedashboardbindings')) {
|
||||||
description: scope.description,
|
const search = new URLSearchParams(url.split('?').pop() || '');
|
||||||
category: scope.category,
|
const scope = search.get('fieldSelector')?.replace('spec.scope=', '') ?? '';
|
||||||
filters: scope.filters,
|
|
||||||
},
|
if (scope in scopesMocks) {
|
||||||
|
return {
|
||||||
|
items: scopesMocks[scope].dashboards.map(({ uid }) => ({
|
||||||
|
scope,
|
||||||
|
dashboard: uid,
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (url === '/apis/scope.grafana.app/v0alpha1/scopedashboards') {
|
|
||||||
return {
|
return {
|
||||||
items: Object.values(scopesMocks).map((scope) => ({
|
items: [],
|
||||||
spec: {
|
|
||||||
dashboards: scope.dashboards.map((dashboard) => dashboard.uid),
|
|
||||||
scope: scope.uid,
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -179,14 +195,14 @@ describe('ScopesScene', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('Fetches dashboards list', () => {
|
it('Fetches dashboards list', () => {
|
||||||
filtersScene.setScope(scopesMocks.scope1.uid);
|
filtersScene.setScope(scopesMocks.scope1.metadata.name);
|
||||||
|
|
||||||
waitFor(() => {
|
waitFor(() => {
|
||||||
expect(fetchDashboardsSpy).toHaveBeenCalled();
|
expect(fetchDashboardsSpy).toHaveBeenCalled();
|
||||||
expect(dashboardsScene.state.dashboards).toEqual(scopesMocks.scope1.dashboards);
|
expect(dashboardsScene.state.dashboards).toEqual(scopesMocks.scope1.dashboards);
|
||||||
});
|
});
|
||||||
|
|
||||||
filtersScene.setScope(scopesMocks.scope2.uid);
|
filtersScene.setScope(scopesMocks.scope2.metadata.name);
|
||||||
|
|
||||||
waitFor(() => {
|
waitFor(() => {
|
||||||
expect(fetchDashboardsSpy).toHaveBeenCalled();
|
expect(fetchDashboardsSpy).toHaveBeenCalled();
|
||||||
@ -197,7 +213,7 @@ describe('ScopesScene', () => {
|
|||||||
it('Enriches data requests', () => {
|
it('Enriches data requests', () => {
|
||||||
const { dashboards: _dashboards, ...scope1 } = scopesMocks.scope1;
|
const { dashboards: _dashboards, ...scope1 } = scopesMocks.scope1;
|
||||||
|
|
||||||
filtersScene.setScope(scope1.uid);
|
filtersScene.setScope(scope1.metadata.name);
|
||||||
|
|
||||||
const queryRunner = sceneGraph.findObject(dashboardScene, (o) => o.state.key === 'data-query-runner')!;
|
const queryRunner = sceneGraph.findObject(dashboardScene, (o) => o.state.key === 'data-query-runner')!;
|
||||||
|
|
||||||
|
@ -1,32 +0,0 @@
|
|||||||
import { Scope, ScopeDashboard } from '@grafana/data';
|
|
||||||
|
|
||||||
import { ScopedResourceServer } from '../apiserver/server';
|
|
||||||
import { ResourceServer } from '../apiserver/types';
|
|
||||||
|
|
||||||
// config.bootData.settings.listDashboardScopesEndpoint || '/apis/scope.grafana.app/v0alpha1/scopedashboards';
|
|
||||||
// config.bootData.settings.listScopesEndpoint || '/apis/scope.grafana.app/v0alpha1/scopes';
|
|
||||||
|
|
||||||
interface ScopeServers {
|
|
||||||
scopes: ResourceServer<Scope>;
|
|
||||||
dashboards: ResourceServer<ScopeDashboard>;
|
|
||||||
}
|
|
||||||
|
|
||||||
let instance: ScopeServers | undefined = undefined;
|
|
||||||
|
|
||||||
export function getScopeServers() {
|
|
||||||
if (!instance) {
|
|
||||||
instance = {
|
|
||||||
scopes: new ScopedResourceServer<Scope>({
|
|
||||||
group: 'scope.grafana.app',
|
|
||||||
version: 'v0alpha1',
|
|
||||||
resource: 'scopes',
|
|
||||||
}),
|
|
||||||
dashboards: new ScopedResourceServer<ScopeDashboard>({
|
|
||||||
group: 'scope.grafana.app',
|
|
||||||
version: 'v0alpha1',
|
|
||||||
resource: 'scopedashboards',
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return instance;
|
|
||||||
}
|
|
@ -1,4 +1,4 @@
|
|||||||
import { Scope } from '@grafana/data';
|
import { ScopeSpec } from '@grafana/data';
|
||||||
import * as common from '@grafana/schema';
|
import * as common from '@grafana/schema';
|
||||||
|
|
||||||
export enum QueryEditorMode {
|
export enum QueryEditorMode {
|
||||||
@ -45,5 +45,5 @@ export interface Prometheus extends common.DataQuery {
|
|||||||
/**
|
/**
|
||||||
* A scope object that will be used by Prometheus
|
* A scope object that will be used by Prometheus
|
||||||
*/
|
*/
|
||||||
scope?: Scope;
|
scope?: ScopeSpec;
|
||||||
}
|
}
|
||||||
|
@ -369,7 +369,7 @@ export class PrometheusDatasource
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (config.featureToggles.promQLScope) {
|
if (config.featureToggles.promQLScope) {
|
||||||
processedTarget.scope = request.scope;
|
processedTarget.scope = request.scope?.spec;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (target.instant && target.range) {
|
if (target.instant && target.range) {
|
||||||
|
Reference in New Issue
Block a user