mirror of
https://github.com/portainer/portainer.git
synced 2025-07-19 13:29:41 +02:00
chore(helm): Convert helm details view to react (#476)
This commit is contained in:
parent
8e6d0e7d42
commit
52bb06eb7b
9 changed files with 287 additions and 118 deletions
|
@ -0,0 +1,119 @@
|
|||
import { render, screen } from '@testing-library/react';
|
||||
import { HttpResponse } from 'msw';
|
||||
|
||||
import { withTestQueryProvider } from '@/react/test-utils/withTestQuery';
|
||||
import { server, http } from '@/setup-tests/server';
|
||||
import { withTestRouter } from '@/react/test-utils/withRouter';
|
||||
import { UserViewModel } from '@/portainer/models/user';
|
||||
import { withUserProvider } from '@/react/test-utils/withUserProvider';
|
||||
|
||||
import { HelmApplicationView } from './HelmApplicationView';
|
||||
|
||||
// Mock the necessary hooks and dependencies
|
||||
const mockUseCurrentStateAndParams = vi.fn();
|
||||
const mockUseEnvironmentId = vi.fn();
|
||||
|
||||
vi.mock('@uirouter/react', async (importOriginal: () => Promise<object>) => ({
|
||||
...(await importOriginal()),
|
||||
useCurrentStateAndParams: () => mockUseCurrentStateAndParams(),
|
||||
}));
|
||||
|
||||
vi.mock('@/react/hooks/useEnvironmentId', () => ({
|
||||
useEnvironmentId: () => mockUseEnvironmentId(),
|
||||
}));
|
||||
|
||||
function renderComponent() {
|
||||
const user = new UserViewModel({ Username: 'user' });
|
||||
const Wrapped = withTestQueryProvider(
|
||||
withUserProvider(withTestRouter(HelmApplicationView), user)
|
||||
);
|
||||
return render(<Wrapped />);
|
||||
}
|
||||
|
||||
describe('HelmApplicationView', () => {
|
||||
beforeEach(() => {
|
||||
// Set up default mock values
|
||||
mockUseEnvironmentId.mockReturnValue(3);
|
||||
mockUseCurrentStateAndParams.mockReturnValue({
|
||||
params: {
|
||||
name: 'test-release',
|
||||
namespace: 'default',
|
||||
},
|
||||
});
|
||||
|
||||
// Set up default mock API responses
|
||||
server.use(
|
||||
http.get('/api/endpoints/3/kubernetes/helm', () =>
|
||||
HttpResponse.json([
|
||||
{
|
||||
name: 'test-release',
|
||||
chart: 'test-chart-1.0.0',
|
||||
app_version: '1.0.0',
|
||||
},
|
||||
])
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
it('should display helm release details when data is loaded', async () => {
|
||||
renderComponent();
|
||||
|
||||
// Check for the page header
|
||||
expect(await screen.findByText('Helm details')).toBeInTheDocument();
|
||||
|
||||
// Check for the release details
|
||||
expect(screen.getByText('Release')).toBeInTheDocument();
|
||||
|
||||
// Check for the table content
|
||||
expect(screen.getByText('Name')).toBeInTheDocument();
|
||||
expect(screen.getByText('Chart')).toBeInTheDocument();
|
||||
expect(screen.getByText('App version')).toBeInTheDocument();
|
||||
|
||||
// Check for the actual values
|
||||
expect(screen.getByTestId('k8sAppDetail-appName')).toHaveTextContent(
|
||||
'test-release'
|
||||
);
|
||||
expect(screen.getByText('test-chart-1.0.0')).toBeInTheDocument();
|
||||
expect(screen.getByText('1.0.0')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should display error message when API request fails', async () => {
|
||||
// Mock API failure
|
||||
server.use(
|
||||
http.get('/api/endpoints/3/kubernetes/helm', () => HttpResponse.error())
|
||||
);
|
||||
|
||||
// Mock console.error to prevent test output pollution
|
||||
vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||
|
||||
renderComponent();
|
||||
|
||||
// Wait for the error message to appear
|
||||
expect(
|
||||
await screen.findByText('Failed to load Helm application details')
|
||||
).toBeInTheDocument();
|
||||
|
||||
// Restore console.error
|
||||
vi.spyOn(console, 'error').mockRestore();
|
||||
});
|
||||
|
||||
it('should display error message when release is not found', async () => {
|
||||
// Mock empty response (no releases found)
|
||||
server.use(
|
||||
http.get('/api/endpoints/3/kubernetes/helm', () => HttpResponse.json([]))
|
||||
);
|
||||
|
||||
// Mock console.error to prevent test output pollution
|
||||
vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||
|
||||
renderComponent();
|
||||
|
||||
// Wait for the error message to appear
|
||||
expect(
|
||||
await screen.findByText('Failed to load Helm application details')
|
||||
).toBeInTheDocument();
|
||||
|
||||
// Restore console.error
|
||||
vi.spyOn(console, 'error').mockRestore();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,79 @@
|
|||
import { useCurrentStateAndParams } from '@uirouter/react';
|
||||
|
||||
import { PageHeader } from '@/react/components/PageHeader';
|
||||
import { Widget, WidgetBody, WidgetTitle } from '@/react/components/Widget';
|
||||
import helm from '@/assets/ico/vendor/helm.svg?c';
|
||||
import { useEnvironmentId } from '@/react/hooks/useEnvironmentId';
|
||||
|
||||
import { ViewLoading } from '@@/ViewLoading';
|
||||
import { Alert } from '@@/Alert';
|
||||
|
||||
import { useHelmRelease } from './queries/useHelmRelease';
|
||||
|
||||
export function HelmApplicationView() {
|
||||
const { params } = useCurrentStateAndParams();
|
||||
const environmentId = useEnvironmentId();
|
||||
|
||||
const name = params.name as string;
|
||||
const namespace = params.namespace as string;
|
||||
|
||||
const {
|
||||
data: release,
|
||||
isLoading,
|
||||
error,
|
||||
} = useHelmRelease(environmentId, name, namespace);
|
||||
|
||||
if (isLoading) {
|
||||
return <ViewLoading />;
|
||||
}
|
||||
|
||||
if (error || !release) {
|
||||
return (
|
||||
<Alert color="error" title="Failed to load Helm application details" />
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHeader
|
||||
title="Helm details"
|
||||
breadcrumbs={[
|
||||
{ label: 'Applications', link: 'kubernetes.applications' },
|
||||
name,
|
||||
]}
|
||||
reload
|
||||
/>
|
||||
|
||||
<div className="row">
|
||||
<div className="col-sm-12">
|
||||
<Widget>
|
||||
<WidgetTitle icon={helm} title="Release" />
|
||||
<WidgetBody>
|
||||
<table className="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className="!border-none w-40">Name</td>
|
||||
<td
|
||||
className="!border-none min-w-[140px]"
|
||||
data-cy="k8sAppDetail-appName"
|
||||
>
|
||||
{release.name}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="!border-t">Chart</td>
|
||||
<td className="!border-t">{release.chart}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>App version</td>
|
||||
<td>{release.app_version}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</WidgetBody>
|
||||
</Widget>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
1
app/react/kubernetes/helm/HelmApplicationView/index.ts
Normal file
1
app/react/kubernetes/helm/HelmApplicationView/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export { HelmApplicationView } from './HelmApplicationView';
|
|
@ -0,0 +1,83 @@
|
|||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { EnvironmentId } from '@/react/portainer/environments/types';
|
||||
import { withGlobalError } from '@/react-tools/react-query';
|
||||
import PortainerError from 'Portainer/error';
|
||||
import axios, { parseAxiosError } from '@/portainer/services/axios';
|
||||
|
||||
interface HelmRelease {
|
||||
name: string;
|
||||
chart: string;
|
||||
app_version: string;
|
||||
}
|
||||
/**
|
||||
* List all helm releases based on passed in options
|
||||
* @param environmentId - Environment ID
|
||||
* @param options - Options for filtering releases
|
||||
* @returns List of helm releases
|
||||
*/
|
||||
export async function listReleases(
|
||||
environmentId: EnvironmentId,
|
||||
options: {
|
||||
namespace?: string;
|
||||
filter?: string;
|
||||
selector?: string;
|
||||
output?: string;
|
||||
} = {}
|
||||
): Promise<HelmRelease[]> {
|
||||
try {
|
||||
const { namespace, filter, selector, output } = options;
|
||||
const url = `endpoints/${environmentId}/kubernetes/helm`;
|
||||
const { data } = await axios.get<HelmRelease[]>(url, {
|
||||
params: { namespace, filter, selector, output },
|
||||
});
|
||||
return data;
|
||||
} catch (e) {
|
||||
throw parseAxiosError(e as Error, 'Unable to retrieve release list');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* React hook to fetch a specific Helm release
|
||||
*/
|
||||
export function useHelmRelease(
|
||||
environmentId: EnvironmentId,
|
||||
name: string,
|
||||
namespace: string
|
||||
) {
|
||||
return useQuery(
|
||||
[environmentId, 'helm', namespace, name],
|
||||
() => getHelmRelease(environmentId, name, namespace),
|
||||
{
|
||||
enabled: !!environmentId,
|
||||
...withGlobalError('Unable to retrieve helm application details'),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific Helm release
|
||||
*/
|
||||
async function getHelmRelease(
|
||||
environmentId: EnvironmentId,
|
||||
name: string,
|
||||
namespace: string
|
||||
): Promise<HelmRelease> {
|
||||
try {
|
||||
const releases = await listReleases(environmentId, {
|
||||
filter: `^${name}$`,
|
||||
namespace,
|
||||
});
|
||||
|
||||
if (releases.length > 0) {
|
||||
return releases[0];
|
||||
}
|
||||
|
||||
throw new PortainerError(`Release ${name} not found`);
|
||||
} catch (err) {
|
||||
throw new PortainerError(
|
||||
'Unable to retrieve helm application details',
|
||||
err as Error
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue