mirror of https://github.com/grafana/grafana.git
Api clients: Update provisioning, playlist clients and fix type errors (#109850)
* Update provisioning types * Update playlist types * Revert * import * lint
This commit is contained in:
parent
525f444407
commit
2a617abd77
|
|
@ -261,43 +261,43 @@ export type ObjectMeta = {
|
|||
Populated by the system. Read-only. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids */
|
||||
uid?: string;
|
||||
};
|
||||
export type PlaylistItem = {
|
||||
/** type of the item. */
|
||||
type: string;
|
||||
/** Value depends on type and describes the playlist item.
|
||||
- dashboard_by_id: The value is an internal numerical identifier set by Grafana. This
|
||||
is not portable as the numerical identifier is non-deterministic between different instances.
|
||||
Will be replaced by dashboard_by_uid in the future. (deprecated)
|
||||
- dashboard_by_tag: The value is a tag which is set on any number of dashboards. All
|
||||
dashboards behind the tag will be added to the playlist.
|
||||
- dashboard_by_uid: The value is the dashboard UID */
|
||||
value: string;
|
||||
};
|
||||
export type PlaylistSpec = {
|
||||
interval: string;
|
||||
items: PlaylistItem[];
|
||||
items: {
|
||||
/** type of the item. */
|
||||
type: 'dashboard_by_tag' | 'dashboard_by_uid' | 'dashboard_by_id';
|
||||
/** Value depends on type and describes the playlist item.
|
||||
- dashboard_by_id: The value is an internal numerical identifier set by Grafana. This
|
||||
is not portable as the numerical identifier is non-deterministic between different instances.
|
||||
Will be replaced by dashboard_by_uid in the future. (deprecated)
|
||||
- dashboard_by_tag: The value is a tag which is set on any number of dashboards. All
|
||||
dashboards behind the tag will be added to the playlist.
|
||||
- dashboard_by_uid: The value is the dashboard UID */
|
||||
value: string;
|
||||
}[];
|
||||
title: string;
|
||||
};
|
||||
export type PlayliststatusOperatorState = {
|
||||
/** descriptiveState is an optional more descriptive state field which has no requirements on format */
|
||||
descriptiveState?: string;
|
||||
/** details contains any extra information that is operator-specific */
|
||||
details?: {
|
||||
[key: string]: object;
|
||||
};
|
||||
/** lastEvaluation is the ResourceVersion last evaluated */
|
||||
lastEvaluation: string;
|
||||
/** state describes the state of the lastEvaluation. It is limited to three possible states for machine evaluation. */
|
||||
state: string;
|
||||
};
|
||||
export type PlaylistStatus = {
|
||||
/** additionalFields is reserved for future use */
|
||||
additionalFields?: {
|
||||
[key: string]: object;
|
||||
[key: string]: any;
|
||||
};
|
||||
/** operatorStates is a map of operator ID to operator state evaluations. Any operator which consumes this kind SHOULD add its state evaluation information to this field. */
|
||||
/** operatorStates is a map of operator ID to operator state evaluations.
|
||||
Any operator which consumes this kind SHOULD add its state evaluation information to this field. */
|
||||
operatorStates?: {
|
||||
[key: string]: PlayliststatusOperatorState;
|
||||
[key: string]: {
|
||||
/** descriptiveState is an optional more descriptive state field which has no requirements on format */
|
||||
descriptiveState?: string;
|
||||
/** details contains any extra information that is operator-specific */
|
||||
details?: {
|
||||
[key: string]: any;
|
||||
};
|
||||
/** lastEvaluation is the ResourceVersion last evaluated */
|
||||
lastEvaluation: string;
|
||||
/** state describes the state of the lastEvaluation.
|
||||
It is limited to three possible states for machine evaluation. */
|
||||
state: 'success' | 'in_progress' | 'failed';
|
||||
};
|
||||
};
|
||||
};
|
||||
export type Playlist = {
|
||||
|
|
@ -305,10 +305,9 @@ export type Playlist = {
|
|||
apiVersion?: string;
|
||||
/** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */
|
||||
kind?: string;
|
||||
metadata: ObjectMeta;
|
||||
/** Spec is the spec of the Playlist */
|
||||
spec: PlaylistSpec;
|
||||
status: PlaylistStatus;
|
||||
metadata?: ObjectMeta;
|
||||
spec?: PlaylistSpec;
|
||||
status?: PlaylistStatus;
|
||||
};
|
||||
export type ListMeta = {
|
||||
/** continue may be set if the user set a limit on the number of items returned, and indicates that the server has more data available. The value is opaque and may be used to issue another request to the endpoint that served this list to retrieve the next set of available objects. Continuing a consistent list may not be possible if the server configuration has changed or more than a few minutes have passed. The resourceVersion field returned when using this continue value will be identical to the value in the first response, unless you have received this token from an error message. */
|
||||
|
|
|
|||
|
|
@ -17,17 +17,20 @@ export const playlistAPIv0alpha1 = generatedAPI.enhanceEndpoints({
|
|||
},
|
||||
createPlaylist: (endpointDefinition) => {
|
||||
const originalQuery = endpointDefinition.query;
|
||||
if (originalQuery) {
|
||||
endpointDefinition.query = (requestOptions) => {
|
||||
if (!requestOptions.playlist.metadata.name && !requestOptions.playlist.metadata.generateName) {
|
||||
const login = contextSrv.user.login;
|
||||
// GenerateName lets the apiserver create a new uid for the name
|
||||
// The passed in value is the suggested prefix
|
||||
requestOptions.playlist.metadata.generateName = login ? login.slice(0, 2) : 'g';
|
||||
}
|
||||
return originalQuery(requestOptions);
|
||||
};
|
||||
if (!originalQuery) {
|
||||
return;
|
||||
}
|
||||
|
||||
endpointDefinition.query = (requestOptions) => {
|
||||
const metadata = requestOptions.playlist.metadata;
|
||||
if (metadata && !metadata.name && !metadata.generateName) {
|
||||
// GenerateName lets the apiserver create a new uid for the name
|
||||
// The passed in value is the suggested prefix
|
||||
metadata.generateName = contextSrv.user.login?.slice(0, 2) || 'g';
|
||||
}
|
||||
return originalQuery(requestOptions);
|
||||
};
|
||||
|
||||
endpointDefinition.onQueryStarted = async (_, { queryFulfilled, dispatch }) => {
|
||||
try {
|
||||
await queryFulfilled;
|
||||
|
|
@ -61,7 +64,7 @@ export const playlistAPIv0alpha1 = generatedAPI.enhanceEndpoints({
|
|||
});
|
||||
|
||||
/** @deprecated -- this migrates playlists saved with internal ids to uid */
|
||||
async function migrateInternalIDs(playlist: PlaylistSpec) {
|
||||
async function migrateInternalIDs(playlist?: PlaylistSpec) {
|
||||
if (playlist?.items) {
|
||||
for (const item of playlist.items) {
|
||||
if (item.type === 'dashboard_by_id') {
|
||||
|
|
@ -84,4 +87,4 @@ export const {
|
|||
} = playlistAPIv0alpha1;
|
||||
|
||||
// eslint-disable-next-line no-barrel-files/no-barrel-files
|
||||
export type { Playlist } from './endpoints.gen';
|
||||
export type { Playlist, PlaylistSpec } from './endpoints.gen';
|
||||
|
|
|
|||
|
|
@ -1013,7 +1013,7 @@ export type RepositorySpec = {
|
|||
- `"local"` */
|
||||
type: 'bitbucket' | 'git' | 'github' | 'gitlab' | 'local';
|
||||
/** UI driven Workflow that allow changes to the contends of the repository. The order is relevant for defining the precedence of the workflows. When empty, the repository does not support any edits (eg, readonly) */
|
||||
workflows: RepoWorkflows;
|
||||
workflows: ('branch' | 'write')[];
|
||||
};
|
||||
export type HealthStatus = {
|
||||
/** When the health was checked last time */
|
||||
|
|
@ -1258,7 +1258,6 @@ export type WebhookResponse = {
|
|||
/** Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */
|
||||
kind?: string;
|
||||
};
|
||||
export type RepoWorkflows = ('branch' | 'write')[]
|
||||
export type RepositoryView = {
|
||||
/** For git, this is the target branch */
|
||||
branch?: string;
|
||||
|
|
@ -1282,7 +1281,7 @@ export type RepositoryView = {
|
|||
- `"local"` */
|
||||
type: 'bitbucket' | 'git' | 'github' | 'gitlab' | 'local';
|
||||
/** The supported workflows */
|
||||
workflows: RepoWorkflows;
|
||||
workflows: ('branch' | 'write')[];
|
||||
};
|
||||
export type RepositoryViewList = {
|
||||
/** APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ const PlaylistCardComponent = ({ playlist, setStartPlaylist, setPlaylistToDelete
|
|||
return (
|
||||
<Card>
|
||||
<Card.Heading>
|
||||
{playlist.spec.title}
|
||||
{playlist.spec?.title}
|
||||
<ModalsController key="button-share">
|
||||
{({ showModal, hideModal }) => (
|
||||
<DashNavButton
|
||||
|
|
@ -31,7 +31,7 @@ const PlaylistCardComponent = ({ playlist, setStartPlaylist, setPlaylistToDelete
|
|||
iconSize="lg"
|
||||
onClick={() => {
|
||||
showModal(ShareModal, {
|
||||
playlistUid: playlist.metadata.name ?? '',
|
||||
playlistUid: playlist.metadata?.name ?? '',
|
||||
onDismiss: hideModal,
|
||||
});
|
||||
}}
|
||||
|
|
@ -45,7 +45,7 @@ const PlaylistCardComponent = ({ playlist, setStartPlaylist, setPlaylistToDelete
|
|||
</Button>
|
||||
{contextSrv.isEditor && (
|
||||
<>
|
||||
<LinkButton key="edit" variant="secondary" href={`/playlists/edit/${playlist.metadata.name}`} icon="cog">
|
||||
<LinkButton key="edit" variant="secondary" href={`/playlists/edit/${playlist.metadata?.name}`} icon="cog">
|
||||
<Trans i18nKey="playlist-page.card.edit">Edit playlist</Trans>
|
||||
</LinkButton>
|
||||
<Button
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ export const PlaylistEditPage = () => {
|
|||
|
||||
const onSubmit = async (playlist: Playlist) => {
|
||||
replacePlaylist({
|
||||
name: playlist.metadata.name ?? '',
|
||||
name: playlist.metadata?.name ?? '',
|
||||
playlist,
|
||||
});
|
||||
locationService.push('/playlists');
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import { Form } from 'app/core/components/Form/Form';
|
|||
import { DashboardPicker } from 'app/core/components/Select/DashboardPicker';
|
||||
import { TagFilter } from 'app/core/components/TagFilter/TagFilter';
|
||||
|
||||
import { Playlist } from '../../api/clients/playlist/v0alpha1';
|
||||
import { Playlist, PlaylistSpec } from '../../api/clients/playlist/v0alpha1';
|
||||
import { getGrafanaSearcher } from '../search/service/searcher';
|
||||
|
||||
import { PlaylistTable } from './PlaylistTable';
|
||||
|
|
@ -21,7 +21,7 @@ interface Props {
|
|||
|
||||
export const PlaylistForm = ({ onSubmit, playlist }: Props) => {
|
||||
const [saving, setSaving] = useState(false);
|
||||
const { title: name, interval, items: propItems } = playlist.spec;
|
||||
const { title: name, interval, items: propItems } = playlist.spec || {};
|
||||
const tagOptions = useMemo(() => {
|
||||
return () => getGrafanaSearcher().tags({ kind: ['dashboard'] });
|
||||
}, []);
|
||||
|
|
@ -34,13 +34,15 @@ export const PlaylistForm = ({ onSubmit, playlist }: Props) => {
|
|||
...playlist,
|
||||
spec: {
|
||||
...specUpdates,
|
||||
interval: specUpdates?.interval ?? '5m',
|
||||
title: specUpdates?.title ?? '',
|
||||
items,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Form onSubmit={doSubmit} validateOn={'onBlur'}>
|
||||
<Form<PlaylistSpec> onSubmit={doSubmit} validateOn={'onBlur'}>
|
||||
{({ register, errors }) => {
|
||||
const isDisabled = items.length === 0 || Object.keys(errors).length > 0;
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ export const PlaylistPage = () => {
|
|||
return;
|
||||
}
|
||||
deletePlaylist({
|
||||
name: playlistToDelete.metadata.name ?? '',
|
||||
name: playlistToDelete.metadata?.name ?? '',
|
||||
}).finally(() => {
|
||||
setPlaylistToDelete(undefined);
|
||||
});
|
||||
|
|
@ -84,10 +84,10 @@ export const PlaylistPage = () => {
|
|||
)}
|
||||
{playlistToDelete && (
|
||||
<ConfirmModal
|
||||
title={playlistToDelete.spec.title}
|
||||
title={playlistToDelete.spec?.title ?? ''}
|
||||
confirmText={t('playlist-page.delete-modal.confirm-text', 'Delete')}
|
||||
body={t('playlist-page.delete-modal.body', 'Are you sure you want to delete {{name}} playlist?', {
|
||||
name: playlistToDelete.spec.title,
|
||||
name: playlistToDelete.spec?.title,
|
||||
})}
|
||||
onConfirm={onDeletePlaylist}
|
||||
isOpen={Boolean(playlistToDelete)}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ const PlaylistPageListComponent = ({ playlists, setStartPlaylist, setPlaylistToD
|
|||
return (
|
||||
<ul className={styles.list}>
|
||||
{playlists.map((playlist) => (
|
||||
<li className={styles.listItem} key={playlist.metadata.name}>
|
||||
<li className={styles.listItem} key={playlist.metadata?.name}>
|
||||
<PlaylistCard
|
||||
playlist={playlist}
|
||||
setStartPlaylist={setStartPlaylist}
|
||||
|
|
|
|||
|
|
@ -103,14 +103,14 @@ export class PlaylistSrv extends StateManagerBase<PlaylistSrvState> {
|
|||
this.locationListenerUnsub = locationService.getHistory().listen(this.locationUpdated);
|
||||
const urls: string[] = [];
|
||||
|
||||
if (!playlist.spec.items?.length) {
|
||||
if (!playlist.spec?.items?.length) {
|
||||
// alert
|
||||
return;
|
||||
}
|
||||
|
||||
this.interval = rangeUtil.intervalToMs(playlist.spec.interval);
|
||||
this.interval = rangeUtil.intervalToMs(playlist.spec?.interval);
|
||||
|
||||
const items = await loadDashboards(playlist.spec.items);
|
||||
const items = await loadDashboards(playlist.spec?.items);
|
||||
for (const item of items) {
|
||||
if (item.dashboards) {
|
||||
for (const dash of item.dashboards) {
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ export const StartModal = ({ playlist, onDismiss }: Props) => {
|
|||
params['_dash.hideLinks'] = true;
|
||||
}
|
||||
|
||||
locationService.push(urlUtil.renderUrl(`/playlists/play/${playlist.metadata.name}`, params));
|
||||
locationService.push(urlUtil.renderUrl(`/playlists/play/${playlist.metadata?.name}`, params));
|
||||
reportInteraction('grafana_kiosk_mode', {
|
||||
action: 'start_playlist',
|
||||
mode: mode,
|
||||
|
|
@ -110,7 +110,7 @@ export const StartModal = ({ playlist, onDismiss }: Props) => {
|
|||
</FieldSet>
|
||||
<Modal.ButtonRow>
|
||||
<Button variant="primary" onClick={onStart}>
|
||||
<Trans i18nKey="playlist.start-modal.button-start" values={{ title: playlist.spec.title }}>
|
||||
<Trans i18nKey="playlist.start-modal.button-start" values={{ title: playlist.spec?.title }}>
|
||||
Start {'{{title}}'}
|
||||
</Trans>
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { Playlist } from '../../api/clients/playlist/v0alpha1';
|
||||
import { PlaylistSpec } from '../../api/clients/playlist/v0alpha1';
|
||||
import { DashboardQueryResult } from '../search/service/types';
|
||||
export type PlaylistMode = boolean;
|
||||
|
||||
type PlaylistItem = Playlist['spec']['items'][number];
|
||||
type PlaylistItem = PlaylistSpec['items'][number];
|
||||
|
||||
export interface PlaylistItemUI extends PlaylistItem {
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -88,5 +88,5 @@ export function searchPlaylists(playlists: Playlist[], query?: string): Playlist
|
|||
return playlists;
|
||||
}
|
||||
query = query.toLowerCase();
|
||||
return playlists.filter((v) => v.spec.title.toLowerCase().includes(query!));
|
||||
return playlists.filter((v) => v.spec?.title.toLowerCase().includes(query!));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import {
|
|||
|
||||
// Repository type definition - extracted from API client
|
||||
export type RepositoryType = RepositorySpec['type'];
|
||||
export type RepoWorkflows = RepositorySpec['workflows'];
|
||||
|
||||
// Field configuration interface
|
||||
export interface RepositoryFieldData {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import { t } from '@grafana/i18n';
|
||||
import { RepositoryView, RepoWorkflows } from 'app/api/clients/provisioning/v0alpha1';
|
||||
import { RepositoryView } from 'app/api/clients/provisioning/v0alpha1';
|
||||
|
||||
import { RepoWorkflows } from '../types';
|
||||
|
||||
export function getIsReadOnlyWorkflows(workflows?: RepoWorkflows): boolean {
|
||||
// Repository is consider read-only if it has no workflows defined (workflows are required for write operations)
|
||||
// Repository is considered read-only if it has no workflows defined (workflows are required for write operations)
|
||||
return workflows?.length === 0;
|
||||
}
|
||||
|
||||
|
|
@ -14,7 +16,7 @@ export function getIsReadOnlyRepo(repository: RepositoryView | undefined): boole
|
|||
return getIsReadOnlyWorkflows(repository.workflows);
|
||||
}
|
||||
|
||||
// Right now we only support local file provisioning message and git provisioned. This can be extend in the future as needed.
|
||||
// Right now we only support local file provisioning message and git provisioned. This can be extended in the future as needed.
|
||||
export const getReadOnlyTooltipText = ({ isLocal = false }) => {
|
||||
return isLocal
|
||||
? t(
|
||||
|
|
|
|||
Loading…
Reference in New Issue