Skip to content

Commit

Permalink
Send server owner (nebari-dev#533)
Browse files Browse the repository at this point in the history
* added app creators name to the POST request when starting app

* added index.js

* updated logic to add owner to post request when card clicked

---------

Co-authored-by: Kilian <[email protected]>
  • Loading branch information
kildre and Kilian authored Nov 20, 2024
1 parent a4dee87 commit 444a639
Show file tree
Hide file tree
Showing 7 changed files with 228 additions and 29 deletions.
44 changes: 22 additions & 22 deletions jhub_apps/static/js/index.js

Large diffs are not rendered by default.

104 changes: 104 additions & 0 deletions ui/src/components/app-card/app-card.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,7 @@ describe('AppCard', () => {
pending: false,
stopped: false,
status: 'false',
full_name: 'test/test-app-1',
}}
/>
</QueryClientProvider>
Expand Down Expand Up @@ -701,4 +702,107 @@ describe('AppCard', () => {
expect(editMenuItem).toHaveAttribute('aria-disabled', 'true');
expect(deleteMenuItem).toHaveAttribute('aria-disabled', 'true');
});
test('redirects to app URL when app is running', async () => {
const mockHref = vi.spyOn(window, 'location', 'get').mockReturnValue({
href: '',
assign: vi.fn(),
ancestorOrigins: document.location.ancestorOrigins,
hash: '',
host: '',
hostname: '',
origin: '',
pathname: '',
port: '',
protocol: '',
search: '',
reload: function (): void {
throw new Error('Function not implemented.');
},
// eslint-disable-next-line @typescript-eslint/no-unused-vars
replace: function (_url: string | URL): void {
throw new Error('Function not implemented.');
},
});

const { getByText } = render(
<RecoilRoot>
<QueryClientProvider client={queryClient}>
<AppCard
id="1"
title="Test App"
username="Developer"
framework="Some Framework"
url="/some-url"
serverStatus="Running" // App is running
/>
</QueryClientProvider>
</RecoilRoot>,
);

const card = getByText('Test App').closest('a'); // Select the card
expect(card).toBeInTheDocument();

await act(async () => {
card?.click();
});

expect(window.location.href).toBe('/some-url'); // Verify redirection
mockHref.mockRestore();
});

test('sets currentApp state correctly on card click', async () => {
const { getByText } = render(
<RecoilRoot>
<QueryClientProvider client={queryClient}>
<AppCard
id="1"
title="Test App"
username="Developer"
framework="Some Framework"
url="/some-url"
serverStatus="Ready"
app={{
id: '1',
name: 'Test App',
framework: 'Some Framework',
description: 'Test App 1',
url: '/user/test/test-app-1/',
thumbnail: '',
username: 'test',
ready: true,
public: false,
shared: false,
last_activity: new Date(),
pending: false,
stopped: false,
status: 'false',
full_name: 'test/test-app-1',
}}
/>
</QueryClientProvider>
</RecoilRoot>,
);

const card = getByText('Test App').closest('a');
expect(card).toBeInTheDocument();

await act(async () => {
card?.click();
});

// Define `defaultApp` and verify that `currentApp` is updated
const defaultApp = {
id: '1',
name: 'Test App',
full_name: 'test/test-app-1',
};

expect(defaultApp).toEqual(
expect.objectContaining({
id: '1',
name: 'Test App',
full_name: 'test/test-app-1',
}),
);
});
});
12 changes: 9 additions & 3 deletions ui/src/components/app-card/app-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -215,8 +215,13 @@ export const AppCard = ({
<Link
href={url}
onClick={(e) => {
if (app && serverStatus === 'Ready') {
e.preventDefault();
e.preventDefault();

if (serverStatus === 'Running') {
// Redirect to the app's URL if it is already running
window.location.href = url;
} else if (serverStatus === 'Ready') {
// Set the current app and open the Start modal
setCurrentApp({
id,
name: title,
Expand All @@ -227,8 +232,9 @@ export const AppCard = ({
shared: app?.shared || isShared || false,
last_activity: new Date(app?.last_activity || ''),
status: 'Ready',
full_name: app?.full_name || '', // Ensure full_name is set
});
setIsStartNotRunningOpen(true);
setIsStartNotRunningOpen(true); // Open the Start modal
}
}}
>
Expand Down
42 changes: 42 additions & 0 deletions ui/src/pages/home/home.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -818,4 +818,46 @@ describe('Home', () => {
expect(snackbar).toBeInTheDocument();
});
});
test('should include creator name as query parameter when starting app', async () => {
const creatorName = 'userx';
const appId = 'test-app-1';

mock.onPost(`/server/${appId}`).reply((config) => {
expect(config.params.owner).toBe(creatorName);
return [200];
});

const { baseElement } = render(
<RecoilRoot
initializeState={({ set }) => {
set(isStartOpen, true);
set(defaultApp, {
id: appId,
full_name: `${creatorName}/my-panel-app-git-86d9635`,
name: 'Test App',
framework: 'JupyterLab',
url: 'https://example.com',
ready: true,
public: false,
shared: false,
last_activity: new Date(),
status: 'Ready',
});
}}
>
<QueryClientProvider client={queryClient}>
<BrowserRouter>
<Home />
</BrowserRouter>
</QueryClientProvider>
</RecoilRoot>,
);

const startBtn = baseElement.querySelector(
'#start-btn',
) as HTMLButtonElement;
await act(async () => {
startBtn.click();
});
});
});
53 changes: 49 additions & 4 deletions ui/src/pages/home/home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,17 @@ export const Home = (): React.ReactElement => {
'success',
); // Set severity

const handleStartRequest = async ({ id }: AppQueryPostProps) => {
const response = await axios.post(`/server/${id}`);
const handleStartRequest = async ({ id, full_name }: AppQueryPostProps) => {
// Extract the creator's name from the full_name
const creatorName = full_name?.split('/')[0]; // Extract 'userx' from 'userx/my-panel-app-git-86d9635'
const requestConfig = {
method: 'post',
url: `/server/${id}`,
params: { owner: creatorName || '' },
};

const response = await axios(requestConfig);

return response;
};

Expand Down Expand Up @@ -156,9 +165,9 @@ export const Home = (): React.ReactElement => {
): error is { response: { status: number } } => {
return typeof error === 'object' && error !== null && 'response' in error;
};

const handleStart = async () => {
const appId = currentApp?.id || '';
const fullName = currentApp?.full_name || '';
setSubmitting(true);

// Close the modal immediately when the Start button is clicked
Expand All @@ -178,7 +187,7 @@ export const Home = (): React.ReactElement => {
}

startQuery(
{ id: appId },
{ id: appId, full_name: fullName },
{
onSuccess: async () => {
setSubmitting(false);
Expand Down Expand Up @@ -273,6 +282,42 @@ export const Home = (): React.ReactElement => {
setSubmitting(false);
return;
}
// Extract the creator's name from the full_name
const creatorName = currentApp.full_name?.split('/')[0];

// Ensure the owner parameter is included in the request
try {
const response = await axios.post(`/server/${currentApp.id}`, {
params: { owner: creatorName || '' },
});

if (response.status === 200) {
setSnackbarMessage('App started successfully');
setSnackbarSeverity('success');
setSnackbarOpen(true);
}
} catch (error) {
if (isErrorWithResponse(error)) {
const status = error.response?.status;
if (status === 403) {
setSnackbarMessage(
"You don't have permission to start this app. Please ask the owner to start it.",
);
} else if (status === 404) {
setSnackbarMessage('App not found (404).');
} else if (status === 500) {
setSnackbarMessage('Internal server error (500).');
} else {
setSnackbarMessage('An unknown server error occurred.');
}
} else {
setSnackbarMessage('An unknown error occurred.');
}
setSnackbarSeverity('error');
setSnackbarOpen(true);
} finally {
setSubmitting(false);
}

window.location.assign(getSpawnUrl(currentUser, currentApp));
};
Expand Down
1 change: 1 addition & 0 deletions ui/src/types/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export interface AppQueryUpdateProps {

export interface AppQueryPostProps {
id: string;
full_name?: string;
}

export interface AppQueryDeleteProps {
Expand Down
1 change: 1 addition & 0 deletions ui/src/types/jupyterhub.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export interface JhApp {
pending?: boolean;
stopped?: boolean;
status: string;
full_name?: string;
}

export interface JhServiceApp {
Expand Down

0 comments on commit 444a639

Please sign in to comment.