mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
add isPublic to dashboard (#48012)
adds toggle to make a dashboard public * config struct for public dashboard config * api endpoints for public dashboard configuration * ui for toggling public dashboard on and off * load public dashboard config on share modal Co-authored-by: Owen Smallwood <owen.smallwood@grafana.com> Co-authored-by: Torkel Ödegaard <torkel@grafana.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Modal, ModalTabsHeader, TabContent } from '@grafana/ui';
|
||||
import { config } from 'app/core/config';
|
||||
import { contextSrv } from 'app/core/core';
|
||||
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
|
||||
import { isPanelModelLibraryPanel } from 'app/features/library-panels/guard';
|
||||
@@ -9,6 +10,7 @@ import { ShareEmbed } from './ShareEmbed';
|
||||
import { ShareExport } from './ShareExport';
|
||||
import { ShareLibraryPanel } from './ShareLibraryPanel';
|
||||
import { ShareLink } from './ShareLink';
|
||||
import { SharePublicDashboard } from './SharePublicDashboard';
|
||||
import { ShareSnapshot } from './ShareSnapshot';
|
||||
import { ShareModalTabModel } from './types';
|
||||
|
||||
@@ -52,6 +54,10 @@ function getTabs(props: Props) {
|
||||
tabs.push(...customDashboardTabs);
|
||||
}
|
||||
|
||||
if (Boolean(config.featureToggles['publicDashboards'])) {
|
||||
tabs.push({ label: 'Public Dashboard', value: 'share', component: SharePublicDashboard });
|
||||
}
|
||||
|
||||
return tabs;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
|
||||
import config from 'app/core/config';
|
||||
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
|
||||
|
||||
import { ShareModal } from './ShareModal';
|
||||
|
||||
jest.mock('app/core/core', () => {
|
||||
return {
|
||||
contextSrv: {
|
||||
hasPermission: () => true,
|
||||
},
|
||||
appEvents: {
|
||||
subscribe: () => {
|
||||
return {
|
||||
unsubscribe: () => {},
|
||||
};
|
||||
},
|
||||
emit: () => {},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
describe('SharePublic', () => {
|
||||
let originalBootData: any;
|
||||
|
||||
beforeAll(() => {
|
||||
originalBootData = config.bootData;
|
||||
config.appUrl = 'http://dashboards.grafana.com/';
|
||||
|
||||
config.bootData = {
|
||||
user: {
|
||||
orgId: 1,
|
||||
},
|
||||
} as any;
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
config.bootData = originalBootData;
|
||||
});
|
||||
|
||||
it('does not render share panel when public dashboards feature is disabled', () => {
|
||||
const mockDashboard = new DashboardModel({
|
||||
uid: 'mockDashboardUid',
|
||||
});
|
||||
const mockPanel = new PanelModel({
|
||||
id: 'mockPanelId',
|
||||
});
|
||||
|
||||
render(<ShareModal panel={mockPanel} dashboard={mockDashboard} onDismiss={() => {}} />);
|
||||
|
||||
expect(screen.getByRole('tablist')).toHaveTextContent('Link');
|
||||
expect(screen.getByRole('tablist')).not.toHaveTextContent('Public Dashboard');
|
||||
});
|
||||
|
||||
it('renders share panel when public dashboards feature is enabled', async () => {
|
||||
config.featureToggles.publicDashboards = true;
|
||||
const mockDashboard = new DashboardModel({
|
||||
uid: 'mockDashboardUid',
|
||||
});
|
||||
const mockPanel = new PanelModel({
|
||||
id: 'mockPanelId',
|
||||
});
|
||||
|
||||
render(<ShareModal panel={mockPanel} dashboard={mockDashboard} onDismiss={() => {}} />);
|
||||
|
||||
await waitFor(() => screen.getByText('Link'));
|
||||
expect(screen.getByRole('tablist')).toHaveTextContent('Link');
|
||||
expect(screen.getByRole('tablist')).toHaveTextContent('Public Dashboard');
|
||||
|
||||
fireEvent.click(screen.getByText('Public Dashboard'));
|
||||
|
||||
await waitFor(() => screen.getByText('Enabled'));
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,69 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
|
||||
import { Button, Field, Switch } from '@grafana/ui';
|
||||
import { notifyApp } from 'app/core/actions';
|
||||
import { createErrorNotification, createSuccessNotification } from 'app/core/copy/appNotification';
|
||||
import { dispatch } from 'app/store/store';
|
||||
|
||||
import {
|
||||
dashboardCanBePublic,
|
||||
getPublicDashboardConfig,
|
||||
savePublicDashboardConfig,
|
||||
PublicDashboardConfig,
|
||||
} from './SharePublicDashboardUtils';
|
||||
import { ShareModalTabProps } from './types';
|
||||
|
||||
interface Props extends ShareModalTabProps {}
|
||||
|
||||
// 1. write test for dashboardCanBePublic
|
||||
// 2. figure out how to disable the switch
|
||||
|
||||
export const SharePublicDashboard = (props: Props) => {
|
||||
const [publicDashboardConfig, setPublicDashboardConfig] = useState<PublicDashboardConfig>({ isPublic: false });
|
||||
const dashboardUid = props.dashboard.uid;
|
||||
|
||||
useEffect(() => {
|
||||
getPublicDashboardConfig(dashboardUid)
|
||||
.then((pdc: PublicDashboardConfig) => {
|
||||
setPublicDashboardConfig(pdc);
|
||||
})
|
||||
.catch(() => {
|
||||
dispatch(notifyApp(createErrorNotification('Failed to retrieve public dashboard config')));
|
||||
});
|
||||
}, [dashboardUid]);
|
||||
|
||||
const onSavePublicConfig = () => {
|
||||
// verify dashboard can be public
|
||||
if (!dashboardCanBePublic(props.dashboard)) {
|
||||
dispatch(notifyApp(createErrorNotification('This dashboard cannot be made public')));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
savePublicDashboardConfig(props.dashboard.uid, publicDashboardConfig);
|
||||
dispatch(notifyApp(createSuccessNotification('Dashboard sharing configuration saved')));
|
||||
} catch (err) {
|
||||
console.error('Error while making dashboard public', err);
|
||||
dispatch(notifyApp(createErrorNotification('Error making dashboard public')));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<p className="share-modal-info-text">Public Dashboard Configuration</p>
|
||||
<Field label="Enabled" description="Configures whether current dashboard can be available publicly">
|
||||
<Switch
|
||||
id="share-current-time-range"
|
||||
disabled={!dashboardCanBePublic(props.dashboard)}
|
||||
value={publicDashboardConfig?.isPublic}
|
||||
onChange={() =>
|
||||
setPublicDashboardConfig((state) => {
|
||||
return { ...state, isPublic: !state.isPublic };
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Field>
|
||||
<Button onClick={onSavePublicConfig}>Save Sharing Configuration</Button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
import { DashboardModel } from 'app/features/dashboard/state';
|
||||
|
||||
import { dashboardCanBePublic } from './SharePublicDashboardUtils';
|
||||
|
||||
describe('dashboardCanBePublic', () => {
|
||||
it('can be public with no template variables', () => {
|
||||
//@ts-ignore
|
||||
const dashboard: DashboardModel = { templating: { list: [] } };
|
||||
expect(dashboardCanBePublic(dashboard)).toBe(true);
|
||||
});
|
||||
|
||||
it('cannot be public with template variables', () => {
|
||||
//@ts-ignore
|
||||
const dashboard: DashboardModel = { templating: { list: [{}] } };
|
||||
expect(dashboardCanBePublic(dashboard)).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,21 @@
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { DashboardModel } from 'app/features/dashboard/state';
|
||||
|
||||
export interface PublicDashboardConfig {
|
||||
isPublic: boolean;
|
||||
}
|
||||
|
||||
export const dashboardCanBePublic = (dashboard: DashboardModel): boolean => {
|
||||
return dashboard?.templating?.list.length === 0;
|
||||
};
|
||||
|
||||
export const getPublicDashboardConfig = async (dashboardUid: string) => {
|
||||
const url = `/api/dashboards/uid/${dashboardUid}/public-config`;
|
||||
return getBackendSrv().get(url);
|
||||
};
|
||||
|
||||
export const savePublicDashboardConfig = async (dashboardUid: string, conf: PublicDashboardConfig) => {
|
||||
const payload = { isPublic: conf.isPublic };
|
||||
const url = `/api/dashboards/uid/${dashboardUid}/public-config`;
|
||||
return getBackendSrv().post(url, payload);
|
||||
};
|
||||
Reference in New Issue
Block a user