Skip to content

Commit

Permalink
refactor: [M3-8843] - StackScript Landing page (#11215)
Browse files Browse the repository at this point in the history
* initial refactoring

* more progress

* a little but of clean up

* a little bit more progress

* a few fixes

* react queryify more

* more progress

* parity with old ui

* properly handle error state

* more progress

* fix typecheck

* get tests passing

* add some testing

* small fixes

* unrelated bug fix

* unrelated bug fix

* get closer to parity with old code

* re-add span to fix cypress tests

* use correct `Typography` import

* fix up e2e tests

* fix type errors

* Added changeset: Refactor StackScripts landing page

* Apply suggestions from code review

Co-authored-by: Mariah Jacobs <114685994+mjac0bs@users.noreply.github.com>

* improve restricted user support

* sort imports

* update cypress test after removing crazy copy

---------

Co-authored-by: Banks Nussman <banks@nussman.us>
Co-authored-by: Mariah Jacobs <114685994+mjac0bs@users.noreply.github.com>
  • Loading branch information
3 people authored Feb 5, 2025
1 parent 86d7d8a commit c19c8ce
Show file tree
Hide file tree
Showing 30 changed files with 857 additions and 983 deletions.
5 changes: 5 additions & 0 deletions packages/manager/.changeset/pr-11215-changed-1738603808282.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Changed
---

Refactor StackScripts landing page ([#11215](https://github.com/linode/manager/pull/11215))
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import type { StackScript } from '@linode/api-v4';
import { Profile, getImages, getProfile } from '@linode/api-v4';
import { Profile, getProfile } from '@linode/api-v4';

import { stackScriptFactory } from 'src/factories';
import { isLinodeKubeImageId } from 'src/store/image/image.helpers';
import { formatDate } from 'src/utilities/formatDate';

import { authenticate } from 'support/api/authentication';
Expand All @@ -15,12 +14,9 @@ import {
} from 'support/intercepts/stackscripts';
import { ui } from 'support/ui';
import { cleanUp } from 'support/util/cleanup';
import { depaginate } from 'support/util/paginate';
import { randomLabel, randomString } from 'support/util/random';
import { chooseRegion } from 'support/util/regions';

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

const mockStackScripts: StackScript[] = [
stackScriptFactory.build({
id: 443929,
Expand Down Expand Up @@ -106,7 +102,10 @@ describe('Community Stackscripts integration tests', () => {
cy.visitWithLogin('/stackscripts/community');
cy.wait('@getStackScripts');

cy.get('[data-qa-stackscript-empty-msg="true"]').should('not.exist');
// Confirm that empty state is not shown.
cy.get('[data-qa-placeholder-container="resources-section"]').should(
'not.exist'
);
cy.findByText('Automate deployment scripts').should('not.exist');

cy.defer(getProfile, 'getting profile').then((profile: Profile) => {
Expand Down Expand Up @@ -138,7 +137,7 @@ describe('Community Stackscripts integration tests', () => {

// Search the corresponding community stack script
mockGetStackScripts([stackScript]).as('getFilteredStackScripts');
cy.get('[id="search-by-label,-username,-or-description"]')
cy.findByPlaceholderText('Search by Label, Username, or Description')
.click()
.type(`${stackScript.label}{enter}`);
cy.wait('@getFilteredStackScripts');
Expand Down Expand Up @@ -194,69 +193,39 @@ describe('Community Stackscripts integration tests', () => {
interceptGetStackScripts().as('getStackScripts');

// Fetch all public Images to later use while filtering StackScripts.
cy.defer(() =>
depaginate((page) => getImages({ page }, { is_public: true }))
).then((publicImages: Image[]) => {
cy.visitWithLogin('/stackscripts/community');
cy.wait('@getStackScripts');

// Confirm that empty state is not shown.
cy.get('[data-qa-stackscript-empty-msg="true"]').should('not.exist');
cy.findByText('Automate deployment scripts').should('not.exist');

// Confirm that scrolling to the bottom of the StackScripts list causes
// pagination to occur automatically. Perform this check 3 times.
for (let i = 0; i < 3; i += 1) {
cy.findByLabelText('List of StackScripts')
.should('be.visible')
.within(() => {
// Scroll to the bottom of the StackScripts list, confirm Cloud fetches StackScripts,
// then confirm that list updates with the new StackScripts shown.
cy.get('tr').last().scrollIntoView();
cy.wait('@getStackScripts').then((xhr) => {
const stackScripts = xhr.response?.body['data'] as
| StackScript[]
| undefined;

if (!stackScripts) {
throw new Error(
'Unexpected response received when fetching StackScripts'
);
}

// Cloud Manager hides certain StackScripts from the landing page (although they can
// still be found via search). It does this if either condition is met:
//
// - The StackScript is only compatible with deprecated Images
// - The StackScript is only compatible with LKE Images
//
// As a consequence, we can't use the API response directly to assert
// that content is shown in the list. We need to apply identical filters
// to the response first, then assert the content using that data.
const filteredStackScripts = stackScripts.filter(
(stackScript: StackScript) => {
const hasNonDeprecatedImages = stackScript.images.some(
(stackScriptImage) => {
return !!publicImages.find(
(publicImage) => publicImage.id === stackScriptImage
);
}
);

const usesKubeImage = stackScript.images.some(
(stackScriptImage) => isLinodeKubeImageId(stackScriptImage)
);
return hasNonDeprecatedImages && !usesKubeImage;
}
);
cy.visitWithLogin('/stackscripts/community');
cy.wait('@getStackScripts');

cy.contains(
`${filteredStackScripts[0].username} / ${filteredStackScripts[0].label}`
).should('be.visible');
});
});
}
});
// Confirm that empty state is not shown.
cy.get('[data-qa-placeholder-container="resources-section"]').should(
'not.exist'
);
cy.findByText('Automate deployment scripts').should('not.exist');

// Confirm that scrolling to the bottom of the StackScripts list causes
// pagination to occur automatically. Perform this check 3 times.
for (let i = 0; i < 3; i += 1) {
cy.findByLabelText('List of StackScripts')
.should('be.visible')
.within(() => {
// Scroll to the bottom of the StackScripts list, confirm Cloud fetches StackScripts,
// then confirm that list updates with the new StackScripts shown.
cy.get('tr').last().scrollIntoView();
cy.wait('@getStackScripts').then((xhr) => {
const stackScripts = xhr.response?.body['data'] as
| StackScript[]
| undefined;

if (!stackScripts) {
throw new Error(
'Unexpected response received when fetching StackScripts'
);
}

cy.contains(`${stackScripts[0].username} / ${stackScripts[0].label}`).should('be.visible');
});
});
}
});

/*
Expand All @@ -271,13 +240,16 @@ describe('Community Stackscripts integration tests', () => {
cy.visitWithLogin('/stackscripts/community');
cy.wait('@getStackScripts');

cy.get('[data-qa-stackscript-empty-msg="true"]').should('not.exist');
// Confirm that empty state is not shown.
cy.get('[data-qa-placeholder-container="resources-section"]').should(
'not.exist'
);
cy.findByText('Automate deployment scripts').should('not.exist');

cy.get('tr').then((value) => {
const rowCount = Cypress.$(value).length - 1; // Remove the table title row

cy.get('[id="search-by-label,-username,-or-description"]')
cy.findByPlaceholderText('Search by Label, Username, or Description')
.click()
.type(`${stackScript.label}{enter}`);
cy.get(`[data-qa-table-row="${stackScript.label}"]`).should('be.visible');
Expand Down Expand Up @@ -311,7 +283,7 @@ describe('Community Stackscripts integration tests', () => {
cy.visitWithLogin('/stackscripts/community');
cy.wait(['@getStackScripts', '@getPreferences']);

cy.get('[id="search-by-label,-username,-or-description"]')
cy.findByPlaceholderText('Search by Label, Username, or Description')
.click()
.type(`${stackScriptName}{enter}`);
cy.get(`[data-qa-table-row="${stackScriptName}"]`)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ describe('Display stackscripts', () => {
cy.wait('@getStackScripts');

cy.findByText('Automate deployment scripts').should('be.visible');
cy.get('[data-qa-stackscript-empty-msg="true"]')

cy.get('[data-qa-placeholder-container="resources-section"]')
.should('be.visible')
.within(() => {
ui.button
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ describe('Update stackscripts', () => {
.should('be.visible')
.click();
ui.dialog
.findByTitle('Woah, just a word of caution...')
.findByTitle(`Make StackScript ${stackScripts[0].label} Public?`)
.should('be.visible')
.within(() => {
ui.button.findByTitle('Cancel').should('be.visible').click();
Expand Down Expand Up @@ -262,11 +262,11 @@ describe('Update stackscripts', () => {
'mockGetStackScripts'
);
ui.dialog
.findByTitle('Woah, just a word of caution...')
.findByTitle(`Make StackScript ${stackScripts[0].label} Public?`)
.should('be.visible')
.within(() => {
ui.button
.findByTitle('Yes, make me a star!')
.findByTitle('Confirm')
.should('be.visible')
.click();
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { getAPIFilterFromQuery } from '@linode/search';
import { Typography } from '@linode/ui';
import {
Box,
Button,
Expand All @@ -17,7 +16,6 @@ import { useController, useFormContext } from 'react-hook-form';
import { Waypoint } from 'react-waypoint';
import { debounce } from 'throttle-debounce';

import { Code } from 'src/components/Code/Code';
import { Table } from 'src/components/Table';
import { TableBody } from 'src/components/TableBody';
import { TableCell } from 'src/components/TableCell/TableCell';
Expand All @@ -27,6 +25,7 @@ import { TableRowEmpty } from 'src/components/TableRowEmpty/TableRowEmpty';
import { TableRowError } from 'src/components/TableRowError/TableRowError';
import { TableRowLoading } from 'src/components/TableRowLoading/TableRowLoading';
import { TableSortCell } from 'src/components/TableSortCell';
import { StackScriptSearchHelperText } from 'src/features/StackScripts/Partials/StackScriptSearchHelperText';
import { useOrder } from 'src/hooks/useOrder';
import {
useStackScriptQuery,
Expand Down Expand Up @@ -176,36 +175,12 @@ export const StackScriptSelectionList = ({ type }: Props) => {
</InputAdornment>
),
}}
tooltipText={
<Stack spacing={1}>
<Typography>
You can search for a specific item by prepending your search term
with "username:", "label:", or "description:".
</Typography>
<Box>
<Typography fontFamily={(theme) => theme.font.bold}>
Examples
</Typography>
<Typography fontSize="0.8rem">
<Code>username: linode</Code>
</Typography>
<Typography fontSize="0.8rem">
<Code>label: sql</Code>
</Typography>
<Typography fontSize="0.8rem">
<Code>description: "ubuntu server"</Code>
</Typography>
<Typography fontSize="0.8rem">
<Code>label: sql or label: php</Code>
</Typography>
</Box>
</Stack>
}
hideLabel
label="Search"
onChange={debounce(400, (e) => setQuery(e.target.value))}
placeholder="Search StackScripts"
spellCheck={false}
tooltipText={<StackScriptSearchHelperText />}
tooltipWidth={300}
value={query}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import React from 'react';
import { InlineMenuAction } from 'src/components/InlineMenuAction/InlineMenuAction';
import { TableCell } from 'src/components/TableCell';
import { TableRow } from 'src/components/TableRow';
import { isLKEStackScript } from 'src/features/StackScripts/stackScriptUtils';
import { truncate } from 'src/utilities/truncate';

import type { StackScript } from '@linode/api-v4';
Expand All @@ -20,9 +21,9 @@ interface Props {
export const StackScriptSelectionRow = (props: Props) => {
const { disabled, isSelected, onOpenDetails, onSelect, stackscript } = props;

// Never show LKE StackScripts. We try to hide these from the user, even though they
// Never show LKE StackScripts. We try to hide these from the user even though they
// are returned by the API.
if (stackscript.username.startsWith('lke-service-account-')) {
if (isLKEStackScript(stackscript)) {
return null;
}

Expand Down
Loading

0 comments on commit c19c8ce

Please sign in to comment.