Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
c5cb980
Added workload-dashboard
marcelofukumoto May 18, 2026
7c1a8a4
Removed the TO added on STATUS CARD
marcelofukumoto May 19, 2026
4091873
Remove headless option and make the title only
marcelofukumoto May 19, 2026
17992fe
Fix lint
marcelofukumoto May 19, 2026
ecdfeef
Added the workload-dashboard type.
marcelofukumoto May 19, 2026
27fff42
Upgrades to the code and translation file
marcelofukumoto May 19, 2026
37bab37
Added fetch error and empty state
marcelofukumoto May 19, 2026
42bbb23
Added some errors and changed to ts and composition
marcelofukumoto May 20, 2026
2f82e5f
Added STORE cache for the state colors.
marcelofukumoto May 20, 2026
37c8f3d
Fixed the filter, used the same logic as the other pages.
marcelofukumoto May 20, 2026
100cf2d
Update the subtitle, fixed api request,
marcelofukumoto May 20, 2026
35451b7
Fixed some UIs Added the filter
marcelofukumoto May 21, 2026
15e0278
Remove the route, not useful
marcelofukumoto May 21, 2026
9d93cec
Broke into composables and 2 components. Separate type.ts as well.
marcelofukumoto May 21, 2026
f1f339c
Added unit tests
marcelofukumoto May 21, 2026
1d5116e
Added e2e test
marcelofukumoto May 22, 2026
c9b94cc
Fixed e2e test
marcelofukumoto May 22, 2026
16dd1c5
Changed to reduce specificity
marcelofukumoto May 26, 2026
0d9ee46
Applied code review improvements
marcelofukumoto May 26, 2026
36431d6
Removed prefs STATE_COLOR and fixed bug on state color request by clu…
marcelofukumoto May 26, 2026
201d7bd
Move the stateColor to a composable
marcelofukumoto May 26, 2026
e618784
e2e test fixes
marcelofukumoto May 26, 2026
4ba249b
Fix e2e test
marcelofukumoto May 26, 2026
e10e265
Stopped reusing the components
marcelofukumoto May 26, 2026
5d89ac0
Added some aria labels and focus
marcelofukumoto May 26, 2026
c518a45
Added missing testid
marcelofukumoto May 26, 2026
2f193b6
Removed unnecesary changes on SubtleLink Fixed any cases
marcelofukumoto May 27, 2026
6a9058b
Reposition files
marcelofukumoto May 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions cypress/e2e/po/pages/explorer/workloads/workload-dashboard.po.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import PagePo from '@/cypress/e2e/po/pages/page.po';
import BurgerMenuPo from '@/cypress/e2e/po/side-bars/burger-side-menu.po';
import ProductNavPo from '@/cypress/e2e/po/side-bars/product-side-nav.po';
import CardPo from '@/cypress/e2e/po/components/Resource/Detail/Card/statusCard.po';
import BannersPo from '@/cypress/e2e/po/components/banners.po';

export default class WorkloadDashboardPagePo extends PagePo {
private static createPath(clusterId: string) {
return `/c/${ clusterId }/explorer/workload-dashboard`;
}

static goTo(clusterId: string): Cypress.Chainable<Cypress.AUTWindow> {
return super.goTo(WorkloadDashboardPagePo.createPath(clusterId));
}

constructor(clusterId = 'local') {
super(WorkloadDashboardPagePo.createPath(clusterId));
}

static navTo(clusterId = 'local') {
const burgerMenu = new BurgerMenuPo();
const sideNav = new ProductNavPo();

burgerMenu.goToCluster(clusterId);
sideNav.navToSideMenuGroupByLabel('Workloads');
}

title() {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there'll be a Po for this, think it's something around the masthead. should also have something for subtitle

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not using the MastHead, the header has different structure.
I added some testid to make it better on this PO.

return cy.get('[data-testid="workload-dashboard-title"]');
}

subtitle() {
return cy.get('[data-testid="workload-dashboard-subtitle"]');
}

byStateSection() {
return cy.get('[data-testid="workload-dashboard-by-state"]');
}

stateCards() {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same for cards, if we're using generic components there'll be a PO

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated!

return cy.get('[data-testid="workload-dashboard-state-card"]');
}

byTypeSection() {
return cy.get('[data-testid="workload-dashboard-by-type"]');
}

byTypeCard(index = 0) {
return new CardPo(`[data-testid="resource-detail-status-card"]:eq(${ index })`);
}

byTypeCards() {
return cy.get('[data-testid="resource-detail-status-card"]');
}

interceptSummariesAsEmpty() {
return cy.intercept('GET', '/v1/*?summary=*', {
summary: [], count: 0, data: []
}).as('emptySummary');
}

waitForEmptySummaries() {
return cy.wait('@emptySummary');
}

emptyState() {
return cy.get('[data-testid="workload-dashboard-empty"]');
}

errorBanner() {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

banner po

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok! Done!

return new BannersPo('.banner.error');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import WorkloadDashboardPagePo from '@/cypress/e2e/po/pages/explorer/workloads/workload-dashboard.po';

const workloadDashboard = new WorkloadDashboardPagePo('local');

describe('Workload Dashboard', { testIsolation: 'off', tags: ['@explorer2', '@adminUser'] }, () => {
before(() => {
cy.login();
cy.updateNamespaceFilter('local', 'none', '{"local":[]}');
WorkloadDashboardPagePo.navTo();
workloadDashboard.waitForPage();
});

it('should display the title', () => {
workloadDashboard.title().should('contain.text', 'Workloads Overview');
});

it('should display a namespace subtitle with workload count', () => {
workloadDashboard.subtitle().should('be.visible');
workloadDashboard.subtitle().invoke('text').should('match', /\(\d+\)/);
});

it('should display the By State section with state cards', () => {
workloadDashboard.byStateSection().should('be.visible');
workloadDashboard.stateCards().should('have.length.gte', 1);
});

it('should display the By Type section with type cards', () => {
workloadDashboard.byTypeSection().should('be.visible');
workloadDashboard.byTypeCards().should('have.length.gte', 1);
});

it('should navigate to the resource list when clicking a By Type card', () => {
WorkloadDashboardPagePo.navTo();
workloadDashboard.waitForPage();

workloadDashboard.byTypeCards().first().click();

cy.url().should('match', /\/c\/local\/explorer\/(apps\.|batch\.)?[a-z]+/);
});

it('should show empty state when namespace filter matches no workloads', () => {
workloadDashboard.interceptSummariesAsEmpty();

WorkloadDashboardPagePo.navTo();
workloadDashboard.waitForPage();
workloadDashboard.waitForEmptySummaries();

workloadDashboard.emptyState().should('be.visible');
});

after(() => {
cy.login();
cy.updateNamespaceFilter('local', 'none', '{"local":["all://user"]}');
});
});
23 changes: 23 additions & 0 deletions shell/assets/translations/en-us.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7425,6 +7425,29 @@ wm:
kubectlShell:
title: "Kubectl: {name}"

workloadDashboard:
title: Workloads Overview
subtitle:
allNamespaces: "For all namespaces ({count})"
userNamespaces: "Only for user namespaces ({count})"
systemNamespaces: "Only for system namespaces ({count})"
namespacedOnly: "Only for namespaced resources ({count})"
clusterOnly: "Only for cluster resources ({count})"
project: "For the namespaces of Project {name} ({count})"
namespace: "For the namespace {name} ({count})"
multipleSelected: "For {selected} items selected ({count})"
sections:
byState: By State
byType: By Type
errors:
noAccess: "No access to {type}"
fetchType: "Failed to fetch {type}"
fetchAll: Failed to fetch workload summaries
empty:
title: No workloads to show
message: "Tips: undo the last namespace filter you applied or <resetLink>reset the namespaces filter</resetLink>."
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this use case is different, the user may have not set any and the default only user namespaces is used

Suggested change
message: "Tips: undo the last namespace filter you applied or <resetLink>reset the namespaces filter</resetLink>."
message: "Tips: Update the namespace filter above or <resetLink>reset the namespaces filter</resetLink>."

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one is provided by @oboc-sts from the Figma.
Let me ask him to check here directly.

Copy link
Copy Markdown

@oboc-sts oboc-sts May 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I totally missed the "suggested change"...

Yeah, we can go with the suggestion @richard-cox made above, it makes sense, I like it ;)

docsMessage: "Want to learn more about Workloads? Read our <docsLink>documentation</docsLink>."

workload:
scaleWorkloads: Scale workloads
healthWorkloads: Jobs/Pods health status
Expand Down
37 changes: 31 additions & 6 deletions shell/components/SubtleLink.vue
Comment thread
marcelofukumoto marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -1,25 +1,50 @@
<script setup lang="ts">
import { computed } from 'vue';
import { RouterLink, RouteLocationRaw } from 'vue-router';

export interface Props {
to: RouteLocationRaw;
to?: RouteLocationRaw;
href?: string;
target?: string;
openInNewTabLabel?: string;
}

const { to } = defineProps<Props>();
const props = defineProps<Props>();

const isExternal = computed(() => !!props.href);
</script>

<template>
<RouterLink
<component
:is="isExternal ? 'a' : RouterLink"
class="subtle-link"
:to="to"
v-bind="isExternal
? { href, target, rel: target === '_blank' ? 'noopener noreferrer nofollow' : undefined }
: { to }"
>
<slot name="default" />
</RouterLink>
<span
v-if="openInNewTabLabel"
class="sr-only"
>{{ openInNewTabLabel }}</span>
<slot name="default" /><span v-if="openInNewTabLabel">&nbsp;</span><i
v-if="openInNewTabLabel"
class="link-icon icon icon-external-link"
/>
</component>
</template>

<style lang="scss" scoped>
.subtle-link {
text-decoration: underline;
color: var(--body-text);

&:hover,
&:active {
text-decoration: none;
}
}

.link-icon {
display: inline;
}
</style>
Loading
Loading