POC/Provisioning: Import resources from repository (#97105)

* Update endpoints

* Add import component

* Cleanup

* Add import from repository

* Rename

* Move import to the list view
This commit is contained in:
Alex Khomenko authored and GitHub committed 2024-11-27 19:54:56 +01:00
1 parent a990f67b51
commit 2129bdf596
4 files changed
+95 -17

No files matched your search

@@ -14,6 +14,7 @@ import {
TextLink,
ControlledCollapse,
FieldSet,
Stack,
} from '@grafana/ui';
import { FormPrompt } from 'app/core/components/FormPrompt/FormPrompt';
import { FolderPicker } from 'app/core/components/Select/FolderPicker';
@@ -65,7 +66,7 @@ export function ConfigForm({ data }: ConfigFormProps) {
} = useForm<RepositoryFormData>({ defaultValues: getDefaultValues(data?.spec) });
const [tokenConfigured, setTokenConfigured] = useState(Boolean(data?.metadata?.name));
const navigate = useNavigate();
const watchType = watch('type');
const type = watch('type');
useEffect(() => {
if (request.isSuccess) {
@@ -115,7 +116,7 @@ export function ConfigForm({ data }: ConfigFormProps) {
>
<Input {...register('title', { required: 'This field is required.' })} placeholder={'My config'} />
</Field>
{watchType === 'github' && (
{type === 'github' && (
<>
<ControlledCollapse collapsible label="Access Token Permissions" isOpen>
<p>
@@ -173,13 +174,13 @@ export function ConfigForm({ data }: ConfigFormProps) {
</>
)}
{watchType === 'local' && (
{type === 'local' && (
<Field label={'Local path'} error={errors?.path?.message} invalid={!!errors?.path}>
<Input {...register('path', { required: 'This field is required.' })} placeholder={'/path/to/repo'} />
</Field>
)}
{watchType === 's3' && (
{type === 's3' && (
<>
<Field label={'S3 bucket'} error={errors?.bucket?.message} invalid={!!errors?.bucket}>
<Input {...register('bucket', { required: 'This field is required.' })} placeholder={'bucket-name'} />
@@ -209,7 +210,9 @@ export function ConfigForm({ data }: ConfigFormProps) {
<Switch {...register('editing.delete')} id={'editing.delete'} />
</Field>
</FieldSet>
<Button type={'submit'}>Save</Button>
<Stack gap={2}>
<Button type={'submit'}>Save</Button>
</Stack>
</form>
);
}
@@ -0,0 +1,72 @@
import { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom-v5-compat';
import { useAsync } from 'react-use';
import { AppEvents } from '@grafana/data';
import { config, getAppEvents, getBackendSrv } from '@grafana/runtime';
import { Button, ConfirmModal } from '@grafana/ui';
import { Loader } from '../plugins/admin/components/Loader';
import { useCreateRepositoryImportMutation, useListRepositoryQuery } from './api';
interface Props {
name?: string;
folder?: string;
}
export function ImportFromRepository({ name, folder }: Props) {
const query = useListRepositoryQuery();
const [importResource, importQuery] = useCreateRepositoryImportMutation();
const [isModalOpen, setIsModalOpen] = useState(false);
const navigate = useNavigate();
// TODO generate endpoints for this
const { value } = useAsync(async () => {
return getBackendSrv().get(`/apis/folder.grafana.app/v0alpha1/namespaces/${config.namespace}/folders/${folder}`);
});
useEffect(() => {
const appEvents = getAppEvents();
if (importQuery.isSuccess) {
appEvents.publish({
type: AppEvents.alertSuccess.name,
payload: ['Resources imported'],
});
// TODO the URL will be different based on the type of resource imported
navigate(`/dashboards${folder ? `/f/${folder}` : ''}`);
} else if (importQuery.isError) {
appEvents.publish({
type: AppEvents.alertError.name,
payload: ['Error importing resources', importQuery.error],
});
}
}, [folder, importQuery.error, importQuery.isError, importQuery.isSuccess, navigate]);
const onClick = () => {
if (!name) {
return;
}
importResource({ name, ref: 'main' });
};
if (query.isLoading) {
return <Loader />;
}
return (
<>
<Button variant={'secondary'} onClick={() => setIsModalOpen(true)} disabled={importQuery.isLoading || !name}>
{importQuery.isLoading ? 'Importing...' : 'Import from repository'}
</Button>
<ConfirmModal
isOpen={isModalOpen}
title={'Import resources from repository'}
body={`This will pull all resources from the repository into your instance into the "${value?.spec?.title}" folder. Existing dashboards with the same UID will be overwritten. Proceed?`}
confirmText={'Import'}
onConfirm={onClick}
onDismiss={() => setIsModalOpen(false)}
/>
</>
);
}
@@ -7,6 +7,7 @@ import { Page } from 'app/core/components/Page/Page';
import { Resource } from '../apiserver/types';
import { DeleteRepositoryButton } from './DeleteRepositoryButton';
import { ImportFromRepository } from './ImportFromRepository';
import { RepositorySpec } from './api/types';
import { NEW_URL, PROVISIONING_URL } from './constants';
import { useRepositoryList } from './hooks';
@@ -63,6 +64,7 @@ function RepositoryListPageContent({ items }: { items?: Array<Resource<Repositor
<LinkButton variant="secondary" href={`${PROVISIONING_URL}/${item.metadata.name}/edit`}>
Edit
</LinkButton>
<ImportFromRepository name={item?.metadata.name} folder={item.spec.folder} />
<DeleteRepositoryButton name={item.metadata.name} />
</Stack>
</Card>
@@ -20,17 +20,17 @@ const injectedRtkApi = api.injectEndpoints({
query: () => ({ url: BASE_PATH }),
providesTags: ['RepositoryList'],
}),
getRepository: build.query<RepositoryResource, RequestArg>({
query: ({ name }) => ({ url: `${BASE_PATH}/${name}` }),
}),
createRepository: build.mutation<void, RepositoryForCreate>({
query: (resource) => ({
query: (body) => ({
url: BASE_PATH,
method: 'POST',
body: resource,
body,
}),
invalidatesTags: ['RepositoryList'],
}),
getRepository: build.query<RepositoryResource, RequestArg>({
query: ({ name }) => ({ url: `${BASE_PATH}/${name}` }),
}),
updateRepository: build.mutation<void, UpdateRequestArg>({
query: ({ name, body }) => ({
url: `${BASE_PATH}/${name}`,
@@ -54,11 +54,6 @@ const injectedRtkApi = api.injectEndpoints({
}),
invalidatesTags: ['RepositoryList'],
}),
getRepositoryExport: build.query<ResourceWrapper, RequestArg>({
query: ({ name }) => ({
url: `${BASE_PATH}/${name}/export`,
}),
}),
createRepositoryExport: build.mutation<ResourceWrapper, RequestArg>({
query: ({ name }) => ({
url: `${BASE_PATH}/${name}/export`,
@@ -94,7 +89,6 @@ const injectedRtkApi = api.injectEndpoints({
query: ({ name, path, ref, message }) => ({
url: `${BASE_PATH}/${name}/files/${path}`,
method: 'DELETE',
params: { ref, message },
}),
}),
getRepositoryHello: build.query<HelloWorld, { name: string; whom?: string }>({
@@ -103,6 +97,13 @@ const injectedRtkApi = api.injectEndpoints({
params: { whom },
}),
}),
createRepositoryImport: build.mutation<ResourceWrapper, { name: string; ref: string }>({
query: ({ name, ref }) => ({
url: `${BASE_PATH}/${name}/import`,
method: 'POST',
params: { ref },
}),
}),
getRepositoryStatus: build.query<RepositoryResource, RequestArg>({
query: ({ name }) => ({
url: `${BASE_PATH}/${name}/status`,
@@ -147,13 +148,13 @@ export const {
useUpdateRepositoryMutation,
useDeleteRepositoryMutation,
usePatchRepositoryMutation,
useGetRepositoryExportQuery,
useCreateRepositoryExportMutation,
useGetRepositoryFilesQuery,
useUpdateRepositoryFilesMutation,
useCreateRepositoryFilesMutation,
useDeleteRepositoryFilesMutation,
useGetRepositoryHelloQuery,
useCreateRepositoryImportMutation,
useGetRepositoryStatusQuery,
useUpdateRepositoryStatusMutation,
usePatchRepositoryStatusMutation,