Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

upcoming: [M3-7874] - Linode Create Refactor - Marketplace App Sections #10520

Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Upcoming Features
---

Linode Create Refactor - Marketplace App Sections ([#10520](https://github.com/linode/manager/pull/10520))
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,8 @@ import {
} from 'support/intercepts/feature-flags';
import { makeFeatureFlagData } from 'support/util/feature-flags';
import { mapStackScriptLabelToOCA } from 'src/features/OneClickApps/utils';
import { baseApps } from 'src/features/StackScripts/stackScriptUtils';
import { stackScriptFactory } from 'src/factories/stackscripts';
import { oneClickApps } from 'src/features/OneClickApps/oneClickApps';
import { oneClickApps } from 'src/features/OneClickApps/oneClickAppsv2';

import type { StackScript } from '@linode/api-v4';
import type { OCA } from '@src/features/OneClickApps/types';
Expand All @@ -42,7 +41,7 @@ describe('OneClick Apps (OCA)', () => {
const stackScripts: StackScript[] = xhr.response?.body.data ?? [];

const trimmedApps: StackScript[] = filterOneClickApps({
baseApps,
baseAppIds: Object.keys(oneClickApps).map(Number),
newApps: {},
queryResults: stackScripts,
});
Expand All @@ -65,7 +64,7 @@ describe('OneClick Apps (OCA)', () => {
// This is only true for the apps defined in `oneClickApps.ts`
expect(
mapStackScriptLabelToOCA({
oneClickApps,
oneClickApps: Object.values(oneClickApps),
stackScriptLabel: decodedLabel,
})
).to.not.be.undefined;
Expand All @@ -82,7 +81,7 @@ describe('OneClick Apps (OCA)', () => {
stackScriptCandidate.should('exist').click();

const app: OCA | undefined = mapStackScriptLabelToOCA({
oneClickApps,
oneClickApps: Object.values(oneClickApps),
stackScriptLabel: candidateApp.label,
});

Expand Down
1 change: 0 additions & 1 deletion packages/manager/src/MainContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,6 @@ export const MainContent = () => {
// the followed comment is for later use, the showCloudPulse will be removed and isACLPEnabled will be used
// const { isACLPEnabled } = useIsACLPEnabled();


/**
* this is the case where the user has successfully completed signup
* but needs a manual review from Customer Support. In this case,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ export const PrimaryNav = (props: PrimaryNavProps) => {

const allowMarketplacePrefetch =
!oneClickApps && !oneClickAppsLoading && !oneClickAppsError;

const showCloudPulse = Boolean(flags.aclp?.enabled);
// the followed comment is for later use, the showCloudPulse will be removed and isACLPEnabled will be used
// const { isACLPEnabled } = useIsACLPEnabled();
Expand Down
4 changes: 2 additions & 2 deletions packages/manager/src/containers/withMarketplaceApps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { StackScript } from '@linode/api-v4';
import React from 'react';
import { useLocation } from 'react-router-dom';

import { baseApps } from 'src/features/StackScripts/stackScriptUtils';
import { oneClickApps } from 'src/features/OneClickApps/oneClickAppsv2';
import { useFlags } from 'src/hooks/useFlags';
import { useMarketplaceAppsQuery } from 'src/queries/stackscripts';
import { getQueryParamFromQueryString } from 'src/utilities/queryParams';
Expand Down Expand Up @@ -34,7 +34,7 @@ export const withMarketplaceApps = <Props>(
const { data, error, isLoading } = useMarketplaceAppsQuery(enabled);

const newApps = flags.oneClickApps || [];
const allowedApps = Object.keys({ ...baseApps, ...newApps });
const allowedApps = Object.keys({ ...oneClickApps, ...newApps });

const filteredApps = (data ?? []).filter((script) => {
return (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import userEvent from '@testing-library/user-event';
import React from 'react';

import { stackScriptFactory } from 'src/factories';
import { renderWithTheme } from 'src/utilities/testHelpers';

import { AppSection } from './AppSection';

describe('AppSection', () => {
it('should render a title', () => {
const { getByText } = renderWithTheme(
<AppSection
onOpenDetailsDrawer={vi.fn()}
onSelect={vi.fn()}
selectedStackscriptId={undefined}
stackscripts={[]}
title="Test title"
/>
);

expect(getByText('Test title')).toBeVisible();
});

it('should render apps', () => {
const app = stackScriptFactory.build({
id: 0,
label: 'Linode Marketplace App',
});

const { getByText } = renderWithTheme(
<AppSection
onOpenDetailsDrawer={vi.fn()}
onSelect={vi.fn()}
selectedStackscriptId={undefined}
stackscripts={[app]}
title="Test title"
/>
);

expect(getByText('Linode Marketplace App')).toBeVisible();
});

it('should call `onOpenDetailsDrawer` when the details button is clicked for an app', async () => {
const app = stackScriptFactory.build({ id: 0 });
const onOpenDetailsDrawer = vi.fn();

const { getByLabelText } = renderWithTheme(
<AppSection
onOpenDetailsDrawer={onOpenDetailsDrawer}
onSelect={vi.fn()}
selectedStackscriptId={undefined}
stackscripts={[app]}
title="Test title"
/>
);

await userEvent.click(getByLabelText(`Info for "${app.label}"`));

expect(onOpenDetailsDrawer).toHaveBeenCalledWith(app.id);
});

it('should call `onSelect` when an app is clicked', async () => {
const app = stackScriptFactory.build({ id: 0 });
const onSelect = vi.fn();

const { getByText } = renderWithTheme(
<AppSection
onOpenDetailsDrawer={vi.fn()}
onSelect={onSelect}
selectedStackscriptId={undefined}
stackscripts={[app]}
title="Test title"
/>
);

await userEvent.click(getByText(app.label));

expect(onSelect).toHaveBeenCalledWith(app);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import Grid from '@mui/material/Unstable_Grid2';
import React from 'react';

import { Divider } from 'src/components/Divider';
import { Stack } from 'src/components/Stack';
import { Typography } from 'src/components/Typography';
import { oneClickApps } from 'src/features/OneClickApps/oneClickAppsv2';

import { AppSelectionCard } from './AppSelectionCard';

import type { StackScript } from '@linode/api-v4';

interface Props {
onOpenDetailsDrawer: (stackscriptId: number) => void;
onSelect: (stackscript: StackScript) => void;
selectedStackscriptId: null | number | undefined;
stackscripts: StackScript[];
title: string;
}

export const AppSection = (props: Props) => {
const {
onOpenDetailsDrawer,
onSelect,
selectedStackscriptId,
stackscripts,
title,
} = props;

return (
<Stack>
<Typography variant="h2">{title}</Typography>
<Divider spacingBottom={16} spacingTop={16} />
<Grid container spacing={2}>
{stackscripts?.map((stackscript) => (
<AppSelectionCard
checked={stackscript.id === selectedStackscriptId}
iconUrl={`/assets/${oneClickApps[stackscript.id].logo_url}`}
key={stackscript.id}
label={stackscript.label}
onOpenDetailsDrawer={() => onOpenDetailsDrawer(stackscript.id)}
onSelect={() => onSelect(stackscript)}
/>
))}
</Grid>
</Stack>
);
};
Original file line number Diff line number Diff line change
@@ -1,24 +1,16 @@
import Grid from '@mui/material/Unstable_Grid2';
import React from 'react';
import { useController, useFormContext } from 'react-hook-form';

import { Autocomplete } from 'src/components/Autocomplete/Autocomplete';
import { Box } from 'src/components/Box';
import { CircularProgress } from 'src/components/CircularProgress';
import { DebouncedSearchTextField } from 'src/components/DebouncedSearchTextField';
import { ErrorState } from 'src/components/ErrorState/ErrorState';
import { Paper } from 'src/components/Paper';
import { Stack } from 'src/components/Stack';
import { Typography } from 'src/components/Typography';
import { oneClickApps } from 'src/features/OneClickApps/oneClickAppsv2';
import { useMarketplaceAppsQuery } from 'src/queries/stackscripts';

import { getDefaultUDFData } from '../StackScripts/UserDefinedFields/utilities';
import { AppSelectionCard } from './AppSelectionCard';
import { AppsList } from './AppsList';
import { categoryOptions } from './utilities';

import type { LinodeCreateFormValues } from '../../utilities';

interface Props {
/**
* Opens the Marketplace App details drawer for the given app
Expand All @@ -28,60 +20,8 @@ interface Props {

export const AppSelect = (props: Props) => {
const { onOpenDetailsDrawer } = props;
const { setValue } = useFormContext<LinodeCreateFormValues>();
const { field } = useController<LinodeCreateFormValues, 'stackscript_id'>({
name: 'stackscript_id',
});

const { data: stackscripts, error, isLoading } = useMarketplaceAppsQuery(
true
);

const renderContent = () => {
if (isLoading) {
return (
<Box
alignItems="center"
display="flex"
height="100%"
justifyContent="center"
width="100%"
>
<CircularProgress />
</Box>
);
}

if (error) {
return <ErrorState errorText={error?.[0].reason} />;
}

return (
<Grid container spacing={2}>
{stackscripts?.map((stackscript) => {
if (!oneClickApps[stackscript.id]) {
return null;
}
return (
<AppSelectionCard
onSelect={() => {
setValue(
'stackscript_data',
getDefaultUDFData(stackscript.user_defined_fields)
);
field.onChange(stackscript.id);
}}
checked={field.value === stackscript.id}
iconUrl={`/assets/${oneClickApps[stackscript.id].logo_url}`}
key={stackscript.id}
label={stackscript.label}
onOpenDetailsDrawer={() => onOpenDetailsDrawer(stackscript.id)}
/>
);
})}
</Grid>
);
};
const { isLoading } = useMarketplaceAppsQuery(true);

return (
<Paper>
Expand Down Expand Up @@ -111,7 +51,7 @@ export const AppSelect = (props: Props) => {
/>
</Stack>
<Box height="500px" sx={{ overflowX: 'hidden', overflowY: 'auto' }}>
{renderContent()}
<AppsList onOpenDetailsDrawer={onOpenDetailsDrawer} />
</Box>
</Stack>
</Paper>
Expand Down
Loading
Loading