Skip to content

Commit

Permalink
feat: Statically Cache Marketplace Apps (#9732)
Browse files Browse the repository at this point in the history
* initial POC

* use `placeholderData` insted of `initalData`

* clean up and approve

* revert regions so unit tests don't fail

* fix flags import

* add changeset

---------

Co-authored-by: Banks Nussman <banks@nussman.us>
  • Loading branch information
bnussman-akamai and bnussman authored Oct 3, 2023
1 parent ebec3e3 commit a617735
Show file tree
Hide file tree
Showing 7 changed files with 100 additions and 37 deletions.
5 changes: 5 additions & 0 deletions packages/manager/.changeset/pr-9732-added-1696340382725.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Added
---

Statically Cache Marketplace Apps ([#9732](https://github.com/linode/manager/pull/9732))
32 changes: 28 additions & 4 deletions packages/manager/scripts/prebuild.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,39 @@ const cachedRequests = [
endpoint: 'linode/kernels',
filename: 'kernels.json',
},
{
endpoint: 'linode/stackscripts',
filename: 'marketplace.json',
filter: [
{
'+and': [
{
'+or': [
{ username: 'linode-stackscripts' },
{ username: 'linode' },
],
},
{
label: {
'+contains': 'One-Click',
},
},
],
},
{ '+order_by': 'ordinal' },
],
},
];

async function handleRequest(endpoint, filename) {
const response = await fetch(API_ROOT + endpoint + '?page_size=500');
async function handleRequest(endpoint, filename, filter) {
const response = await fetch(API_ROOT + endpoint + '?page_size=500', {
headers: filter ? { 'x-filter': JSON.stringify(filter) } : {},
});
const data = await response.json();

if (data.data.pages > 1) {
throw new Error(
'Results over 100 will not be retrieved or cached. Aborting.'
`Request ${endpoint} has many pages but we only support caching 1 page.`
);
}

Expand All @@ -51,7 +75,7 @@ async function prebuild() {
console.log('Caching common requests');

const requests = cachedRequests.map((request) =>
handleRequest(request.endpoint, request.filename)
handleRequest(request.endpoint, request.filename, request.filter)
);

try {
Expand Down
2 changes: 1 addition & 1 deletion packages/manager/src/cachedData/kernels.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions packages/manager/src/cachedData/marketplace.json

Large diffs are not rendered by default.

54 changes: 54 additions & 0 deletions packages/manager/src/containers/withMarketplaceApps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { StackScript } from '@linode/api-v4';
import React from 'react';
import { useLocation } from 'react-router-dom';

import { baseApps } from 'src/features/StackScripts/stackScriptUtils';
import { useFlags } from 'src/hooks/useFlags';
import { useStackScriptsOCA } from 'src/queries/stackscripts';
import { getQueryParamFromQueryString } from 'src/utilities/queryParams';

const trimOneClickFromLabel = (script: StackScript) => {
return {
...script,
label: script.label.replace('One-Click', ''),
};
};

export interface WithMarketplaceAppsProps {
appInstances: StackScript[] | undefined;
appInstancesError: string | undefined;
appInstancesLoading: boolean;
}

export const withMarketplaceApps = <Props>(
Component: React.ComponentType<Props & WithMarketplaceAppsProps>
) => (props: Props) => {
const location = useLocation();
const flags = useFlags();

const type = getQueryParamFromQueryString(location.search, 'type');

// Only enable the query when the user is on the Marketplace page
const enabled = type === 'One-Click';

const { data, error, isLoading } = useStackScriptsOCA(enabled);

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

const filteredApps = (data ?? []).filter((script) => {
return (
!script.label.match(/helpers/i) && allowedApps.includes(String(script.id))
);
});
const trimmedApps = filteredApps.map((stackscript) =>
trimOneClickFromLabel(stackscript)
);

return React.createElement(Component, {
...props,
appInstances: trimmedApps,
appInstancesError: error?.[0]?.reason,
appInstancesLoading: isLoading,
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Agreements, signAgreement } from '@linode/api-v4/lib/account';
import { Image } from '@linode/api-v4/lib/images';
import { Region } from '@linode/api-v4/lib/regions';
import { convertYupToLinodeErrors } from '@linode/api-v4/lib/request';
import { StackScript, UserDefinedField } from '@linode/api-v4/lib/stackscripts';
import { UserDefinedField } from '@linode/api-v4/lib/stackscripts';
import { APIError } from '@linode/api-v4/lib/types';
import { vpcsValidateIP } from '@linode/validation';
import { CreateLinodeSchema } from '@linode/validation/lib/linodes.schema';
Expand Down Expand Up @@ -45,13 +45,11 @@ import { resetEventsPolling } from 'src/eventsPolling';
import withAgreements, {
AgreementsProps,
} from 'src/features/Account/Agreements/withAgreements';
import { baseApps } from 'src/features/StackScripts/stackScriptUtils';
import {
queryKey as accountAgreementsQueryKey,
reportAgreementSigningError,
} from 'src/queries/accountAgreements';
import { simpleMutationHandlers } from 'src/queries/base';
import { getAllOCAsRequest } from 'src/queries/stackscripts';
import { vpcQueryKey } from 'src/queries/vpcs';
import { CreateTypes } from 'src/store/linodeCreate/linodeCreate.actions';
import { MapState } from 'src/store/types';
Expand All @@ -70,7 +68,7 @@ import { validatePassword } from 'src/utilities/validatePassword';
import LinodeCreate from './LinodeCreate';
import { deriveDefaultLabel } from './deriveDefaultLabel';
import { HandleSubmit, Info, LinodeCreateValidation, TypeInfo } from './types';
import { filterOneClickApps, getRegionIDFromLinodeID } from './utilities';
import { getRegionIDFromLinodeID } from './utilities';

import type {
CreateLinodeRequest,
Expand All @@ -79,13 +77,14 @@ import type {
LinodeTypeClass,
PriceObject,
} from '@linode/api-v4/lib/linodes';
import {
WithMarketplaceAppsProps,
withMarketplaceApps,
} from 'src/containers/withMarketplaceApps';

const DEFAULT_IMAGE = 'linode/debian11';

interface State {
appInstances?: StackScript[];
appInstancesError?: string;
appInstancesLoading: boolean;
assignPublicIPv4Address: boolean;
attachedVLANLabel: null | string;
authorized_users: string[];
Expand Down Expand Up @@ -133,10 +132,10 @@ type CombinedProps = WithSnackbarProps &
WithProfileProps &
AgreementsProps &
WithQueryClientProps &
WithMarketplaceAppsProps &
WithAccountSettingsProps;

const defaultState: State = {
appInstancesLoading: false,
assignPublicIPv4Address: false,
attachedVLANLabel: '',
authorized_users: [],
Expand Down Expand Up @@ -200,33 +199,10 @@ const isNonDefaultImageType = (prevType: string, type: string) => {
class LinodeCreateContainer extends React.PureComponent<CombinedProps, State> {
componentDidMount() {
// Allowed apps include the base set of original apps + anything LD tells us to show
const newApps = this.props.flags.oneClickApps || [];
if (nonImageCreateTypes.includes(this.props.createType)) {
// If we're navigating directly to e.g. the clone page, don't select an image by default
this.setState({ selectedImageID: undefined });
}
this.setState({ appInstancesLoading: true });

this.props.queryClient
.fetchQuery('stackscripts-oca-all', () => getAllOCAsRequest())
.then((res: StackScript[]) => {
const trimmedApps = filterOneClickApps({
baseApps,
newApps,
queryResults: res,
});

this.setState({
appInstances: trimmedApps,
appInstancesLoading: false,
});
})
.catch((_) => {
this.setState({
appInstancesError: 'There was an error loading Marketplace Apps.',
appInstancesLoading: false,
});
});
}

componentDidUpdate(prevProps: CombinedProps) {
Expand Down Expand Up @@ -945,7 +921,8 @@ export default recompose<CombinedProps, {}>(
withProfile,
withAgreements,
withQueryClient,
withAccountSettings
withAccountSettings,
withMarketplaceApps
)(LinodeCreateContainer);

const actionsAndLabels = {
Expand Down
2 changes: 2 additions & 0 deletions packages/manager/src/queries/stackscripts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { StackScript } from '@linode/api-v4/lib/stackscripts';
import { APIError, Params } from '@linode/api-v4/lib/types';
import { useQuery } from 'react-query';

import { data as placeholderData } from 'src/cachedData/marketplace.json';
import { getOneClickApps } from 'src/features/StackScripts/stackScriptUtils';
import { getAll } from 'src/utilities/getAll';

Expand All @@ -15,6 +16,7 @@ export const useStackScriptsOCA = (enabled: boolean, params: Params = {}) => {
() => getAllOCAsRequest(params),
{
enabled,
placeholderData,
...queryPresets.oneTimeFetch,
}
);
Expand Down

0 comments on commit a617735

Please sign in to comment.