POC/Provisioning: Fix saving in nested folders (#100193)
* Remove saveProvisioned flag * Get repo data from resource * Refactor * get repository data for nested folder * Remove unused hook * Sort configs alphabetically * Codeformat
This commit is contained in:
1 parent
f5725e1b1e
commit
a7deb70096
6 files changed
+46
-47
No files matched your search
@@ -1,10 +1,9 @@
|
||||
import { SceneComponentProps, SceneObjectBase, SceneObjectState, SceneObjectRef } from '@grafana/scenes';
|
||||
import { Drawer, Tab, TabsBar } from '@grafana/ui';
|
||||
import { useUrlParams } from 'app/core/navigation/hooks';
|
||||
import { AnnoKeyRepoName } from 'app/features/apiserver/types';
|
||||
import { SaveDashboardDiff } from 'app/features/dashboard/components/SaveDashboard/SaveDashboardDiff';
|
||||
|
||||
import { useFolderRepository } from '../../provisioning/hooks';
|
||||
import { useGetResourceRepository } from '../../provisioning/hooks';
|
||||
import { DashboardScene } from '../scene/DashboardScene';
|
||||
|
||||
import { SaveDashboardAsForm } from './SaveDashboardAsForm';
|
||||
@@ -20,7 +19,6 @@ interface SaveDashboardDrawerState extends SceneObjectState {
|
||||
saveRefresh?: boolean;
|
||||
saveAsCopy?: boolean;
|
||||
onSaveSuccess?: () => void;
|
||||
saveProvisioned?: boolean;
|
||||
}
|
||||
|
||||
export class SaveDashboardDrawer extends SceneObjectBase<SaveDashboardDrawerState> {
|
||||
@@ -47,22 +45,21 @@ export class SaveDashboardDrawer extends SceneObjectBase<SaveDashboardDrawerStat
|
||||
};
|
||||
|
||||
static Component = ({ model }: SceneComponentProps<SaveDashboardDrawer>) => {
|
||||
const { saveProvisioned, showDiff, saveAsCopy, saveTimeRange, saveVariables, saveRefresh } = model.useState();
|
||||
const { showDiff, saveAsCopy, saveTimeRange, saveVariables, saveRefresh } = model.useState();
|
||||
|
||||
const changeInfo = model.state.dashboardRef
|
||||
.resolve()
|
||||
.getDashboardChanges(saveTimeRange, saveVariables, saveRefresh);
|
||||
|
||||
const { changedSaveModel, initialSaveModel, diffs, diffCount, hasFolderChanges } = changeInfo;
|
||||
const changesCount = diffCount + (hasFolderChanges ? 1 : 0);
|
||||
const dashboard = model.state.dashboardRef.resolve();
|
||||
const { meta } = dashboard.useState();
|
||||
const { provisioned: isProvisioned, folderTitle } = meta;
|
||||
const [params] = useUrlParams();
|
||||
const folderUid = params.get('folderUid');
|
||||
const folderRepository = useFolderRepository(folderUid ?? undefined);
|
||||
// Provisioned dashboards have k8s metadata annotations
|
||||
const isProvisionedNG = saveProvisioned || meta.k8s?.annotations?.[AnnoKeyRepoName] || Boolean(folderRepository);
|
||||
const folderUid = params.get('folderUid') || undefined;
|
||||
|
||||
const folderRepository = useGetResourceRepository({ folderUid });
|
||||
const isProvisionedNG = dashboard.isProvisioned() || Boolean(folderRepository);
|
||||
|
||||
const tabs = (
|
||||
<TabsBar>
|
||||
|
||||
@@ -41,7 +41,6 @@ export interface Props {
|
||||
}
|
||||
|
||||
export function SaveProvisionedDashboard({ drawer, changeInfo, dashboard }: Props) {
|
||||
const { saveProvisioned } = drawer.useState();
|
||||
const { meta, title: defaultTitle, description: defaultDescription } = dashboard.useState();
|
||||
const prURL = usePullRequestParam();
|
||||
|
||||
@@ -51,7 +50,7 @@ export function SaveProvisionedDashboard({ drawer, changeInfo, dashboard }: Prop
|
||||
isGitHub,
|
||||
repositoryConfig,
|
||||
} = useDefaultValues({ meta, defaultTitle, defaultDescription });
|
||||
const [action, request] = useCreateOrUpdateRepositoryFile(saveProvisioned || isNew ? undefined : defaultValues.path);
|
||||
const [action, request] = useCreateOrUpdateRepositoryFile(isNew ? undefined : defaultValues.path);
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
@@ -207,7 +206,7 @@ export function SaveProvisionedDashboard({ drawer, changeInfo, dashboard }: Prop
|
||||
</Alert>
|
||||
)}
|
||||
<Stack gap={2}>
|
||||
<Button variant="primary" type="submit" disabled={(request.isLoading || !isDirty) && !saveProvisioned}>
|
||||
<Button variant="primary" type="submit" disabled={request.isLoading || !isDirty}>
|
||||
{request.isLoading ? 'Saving...' : 'Save'}
|
||||
</Button>
|
||||
<Button variant="secondary" onClick={drawer.onClose} fill="outline">
|
||||
@@ -218,6 +217,7 @@ export function SaveProvisionedDashboard({ drawer, changeInfo, dashboard }: Prop
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
const BranchValidationError = () => {
|
||||
return (
|
||||
<>
|
||||
@@ -231,6 +231,7 @@ const BranchValidationError = () => {
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
async function validateDashboardName(title: string, formValues: FormData) {
|
||||
if (title === formValues.folder.title?.trim()) {
|
||||
return 'Dashboard name cannot be the same as folder name';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { AnnoKeyRepoName, AnnoKeyRepoPath } from 'app/features/apiserver/types';
|
||||
import { useFolderRepository } from 'app/features/provisioning/hooks';
|
||||
import { useGetResourceRepository } from 'app/features/provisioning/hooks';
|
||||
import { DashboardMeta } from 'app/types';
|
||||
|
||||
import { getDefaultWorkflow } from './defaults';
|
||||
@@ -21,20 +21,19 @@ function generatePath(timestamp: number, pathFromAnnotation?: string, slug?: str
|
||||
}
|
||||
|
||||
export function useDefaultValues({ meta, defaultTitle, defaultDescription }: UseDefaultValuesParams) {
|
||||
// ???? why is this getting the repository from the folder, not the file???
|
||||
// Should we only get it from the folder when new?
|
||||
const folderRepository = useFolderRepository(meta.folderUid);
|
||||
const timestamp = Date.now();
|
||||
const annotations = meta.k8s?.annotations;
|
||||
const annoName = annotations?.[AnnoKeyRepoName];
|
||||
const annoPath = annotations?.[AnnoKeyRepoPath];
|
||||
const repositoryConfig = folderRepository?.spec;
|
||||
const repositoryName = folderRepository?.metadata?.name ?? '';
|
||||
// Get config by resource name or folder UID for new resources
|
||||
const repositoryConfig = useGetResourceRepository({ name: annoName, folderUid: meta.folderUid });
|
||||
const repository = repositoryConfig?.spec;
|
||||
const timestamp = Date.now();
|
||||
|
||||
return {
|
||||
values: {
|
||||
ref: `dashboard/${timestamp}`,
|
||||
path: generatePath(timestamp, annoPath, meta.slug),
|
||||
repo: annotations?.[AnnoKeyRepoName] ?? repositoryName,
|
||||
repo: annoName || repositoryConfig?.metadata?.name || '',
|
||||
comment: '',
|
||||
folder: {
|
||||
uid: meta.folderUid,
|
||||
@@ -42,10 +41,10 @@ export function useDefaultValues({ meta, defaultTitle, defaultDescription }: Use
|
||||
},
|
||||
title: defaultTitle,
|
||||
description: defaultDescription ?? '',
|
||||
workflow: getDefaultWorkflow(repositoryConfig),
|
||||
workflow: getDefaultWorkflow(repository),
|
||||
},
|
||||
isNew: !annoPath,
|
||||
repositoryConfig,
|
||||
isGitHub: repositoryConfig?.type === 'github',
|
||||
isNew: !annoName,
|
||||
repositoryConfig: repository,
|
||||
isGitHub: repository?.type === 'github',
|
||||
};
|
||||
}
|
||||
@@ -395,15 +395,7 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
|
||||
return true;
|
||||
};
|
||||
|
||||
public openSaveDrawer({
|
||||
saveAsCopy,
|
||||
saveProvisioned,
|
||||
onSaveSuccess,
|
||||
}: {
|
||||
saveAsCopy?: boolean;
|
||||
saveProvisioned?: boolean;
|
||||
onSaveSuccess?: () => void;
|
||||
}) {
|
||||
public openSaveDrawer({ saveAsCopy, onSaveSuccess }: { saveAsCopy?: boolean; onSaveSuccess?: () => void }) {
|
||||
if (!this.state.isEditing) {
|
||||
return;
|
||||
}
|
||||
@@ -413,7 +405,6 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
|
||||
dashboardRef: this.getRef(),
|
||||
saveAsCopy,
|
||||
onSaveSuccess,
|
||||
saveProvisioned,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -608,7 +608,7 @@ export function ToolbarActions({ dashboard }: Props) {
|
||||
return (
|
||||
<Button
|
||||
onClick={() => {
|
||||
dashboard.openSaveDrawer({ saveProvisioned: isProvisionedNG });
|
||||
dashboard.openSaveDrawer({});
|
||||
}}
|
||||
className={styles.buttonWithExtraMargin}
|
||||
tooltip={t('dashboard.toolbar.save-dashboard.tooltip', 'Save changes')}
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
import { skipToken } from '@reduxjs/toolkit/query/react';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { useUrlParams } from 'app/core/navigation/hooks';
|
||||
|
||||
import { useGetFolderQuery } from '../browse-dashboards/api/browseDashboardsAPI';
|
||||
|
||||
import {
|
||||
ListRepositoryArg,
|
||||
ReplaceRepositoryFilesWithPathArg,
|
||||
Repository,
|
||||
RepositorySpec,
|
||||
useCreateRepositoryFilesWithPathMutation,
|
||||
useCreateRepositoryMutation,
|
||||
useListRepositoryQuery,
|
||||
useReplaceRepositoryFilesWithPathMutation,
|
||||
RepositorySpec,
|
||||
ReplaceRepositoryFilesWithPathArg,
|
||||
useReplaceRepositoryMutation,
|
||||
ListRepositoryArg,
|
||||
} from './api';
|
||||
|
||||
export function useCreateOrUpdateRepository(name?: string) {
|
||||
@@ -51,12 +54,15 @@ export function useCreateOrUpdateRepository(name?: string) {
|
||||
return [updateOrCreate, name ? updateRequest : createRequest] as const;
|
||||
}
|
||||
|
||||
// Sort repositories by resourceVersion to show the last modified
|
||||
// Sort repositories alphabetically by title
|
||||
export function useRepositoryList(options: ListRepositoryArg = {}): [Repository[] | undefined, boolean] {
|
||||
const query = useListRepositoryQuery(options);
|
||||
const collator = new Intl.Collator(undefined, { numeric: true });
|
||||
|
||||
const sortedItems = query.data?.items?.slice().sort((a, b) => {
|
||||
return Number(b.metadata?.resourceVersion) - Number(a.metadata?.resourceVersion);
|
||||
const titleA = a.spec?.title ?? '';
|
||||
const titleB = b.spec?.title ?? '';
|
||||
return collator.compare(titleA, titleB);
|
||||
});
|
||||
|
||||
return [sortedItems, query.isLoading];
|
||||
@@ -88,16 +94,21 @@ export const usePullRequestParam = () => {
|
||||
return decodeURIComponent(prParam);
|
||||
};
|
||||
|
||||
export const useFolderRepository = (folderUid?: string) => {
|
||||
interface GetResourceRepositoryArgs {
|
||||
name?: string;
|
||||
folderUid?: string;
|
||||
}
|
||||
|
||||
export const useGetResourceRepository = ({ name, folderUid }: GetResourceRepositoryArgs) => {
|
||||
const [items, isLoading] = useRepositoryList();
|
||||
// Get the folder data from API to get repository data for nested folders
|
||||
const folderQuery = useGetFolderQuery(name || !folderUid ? skipToken : folderUid);
|
||||
|
||||
if (!folderUid) {
|
||||
const repoName = name || folderQuery.data?.repository?.name;
|
||||
|
||||
if (!items?.length || isLoading || !repoName) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!items?.length || isLoading || !folderUid) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return items.find((repo) => repo.metadata?.name === folderUid);
|
||||
return items.find((repo) => repo.metadata?.name === repoName);
|
||||
};
|
||||
Reference in new issue
Block a user