8000 feat(ui): add full-row expandable jobs toggle with cookie persistence by VLukiantsev · Pull Request #1567 · google/xmc · GitHub
[go: up one dir, main page]

Skip to content

feat(ui): add full-row expandable jobs toggle with cookie persistence - #1567

Draft
VLukiantsev wants to merge 1 commit into
mainfrom
feat/expandable-jobs-cookie-setting
Draft

VLukiantsev wants to merge 1 commit into
mainfrom
feat/expandable-jobs-cookie-setting

Conversation

@VLukiantsev
@VLukiantsev VLukiantsev commented Nov 3, 2026 •
8000
Copy link
Copy Markdown
Collaborator
cast.3.webm

Summary

Adds a "View in full width" / "Exit full width" toggle button to the queue jobs card on the /resources (Resource Observability Dashboard) page that expands the jobs table to the top full-width row with the cluster queues list stacked below (flex-direction: column-reverse), and persists the user's layout preference in the xmc_jobs_full_width browser cookie.

Motivation & Context

Wide multi-column job rows in the side-by-side split view can feel cramped, while hiding the cluster queues list altogether prevents inspecting queues alongside jobs. Expanding the jobs card to a full-width top row with the cluster queues list below keeps all dashboard widgets visible and allows persisting the preferred layout in a browser cookie so the view is restored on future visits.

Key Changes

  • Queue Jobs Table (cmd/ui/app/side_panel/contents/queue_jobs_table/):
    • Added showFullWidthToggle signal input (default false) and two-way isFullWidth signal model (model<boolean>(false)) on QueueJobsTable so existing side-panel consumers remain unaffected.
    • Added a pill-shaped toggle button in .title-row switching between outlined "View in full width" (fullscreen icon) and filled primary "Exit full width" (fullscreen_exit icon).
    • Extended QueueJobsTableHarness and queue_jobs_table_test.ts to cover default visibility, icon/label state transitions, active styling, and two-way isFullWidth model updates.
  • Cookie Layout Preference (cmd/ui/app/resource_dashboard/jobs_layout_preference.ts):
    • Added readJobsFullWidthCookie and writeJobsFullWidthCookie for reading and persisting xmc_jobs_full_width (Path=/; Max-Age=31536000; SameSite=Lax) with 1-to-1 unit tests in jobs_layout_preference_test.ts.
  • Resource Dashboard (cmd/ui/app/resource_dashboard/):
    • Initialized isJobsFullWidth from readJobsFullWidthCookie and synchronized updates via effect().
    • Bound [class.full-width]="isJobsFullWidth()" on .rod-split-container to stack .jobs-side-pane (top, 100% width, position: static) and .rod-main-content (bottom, 100% width) via flex-direction: column-reverse without hiding any widgets.
    • Extended ResourceDashboardHarness and resource_dashboard_test.ts to verify full-row vertical stacking (jobs pane above queue list), cookie persistence, initial hydration from cookie, and state preservation across Smart Resource Finder queries and tab switches.
    • Regenerated Playwright visual baseline snapshots (cmd/ui/screenshot_tests/resource_dashboard/resource_dashboard.screenshot.spec.ts-snapshots/).

Assumptions

None — requirements were fully specified.

Verification & Testing

Automated Tests

  • Commands Executed: ./.githooks/pre-commit && cd cmd/ui && npm run lint && npm run format:check && npm test -- --watch=false --browsers=ChromeHeadlessNoSandbox && npm run test:screenshot
  • Coverage & Test Status: All 713 Angular UI unit tests pass (including 1-to-1 unit tests for cookie persistence, toggle button interactions, and full-row dashboard stacking), ESLint/Prettier checks pass, and all 10 Playwright visual screenshot tests pass.

Live Deployment & Manual Testing

  • Deployment Target: Live GKE Development Environment via tools/dev_env/dev_env.py gke-upgrade
  • Cluster Health: Verified via deploy/ctk_v2/verify_xmc.py — all 5 sections ✅ PASS (14/14 Kubernetes pods Running and Ready, services, Gateway & HTTPRoutes, TLS certificates, and GKE Log Monitor)
  • Manual Test Scenarios Executed:
    1. UI Container Rollout & Bundle Verification:
      • Action: Rebuilt and pushed UI container image (//cmd/ui:ui_image_push), rolled out deployment/ui-deployment, and verified /resources route serving.
      • Result: ui-deployment rolled out cleanly (1/1 Running); /resources serves the updated bundle with the "View in full width" toggle button, full-row vertical stacking with the jobs card at the top (flex-direction: column-reverse), and xmc_jobs_full_width cookie persistence.
Live Execution Output / Logs
=== Structured Verification Summary ===
✅ PASS | 1. Kubernetes Pods         | 14/14 Ready
✅ PASS | 2. Kubernetes Services     | Core ClusterIP services present
✅ PASS | 3. Gateway & HTTPRoutes    | api-route, grpc-route, ui-route bound
✅ PASS | 4. TLS Certificates        | validator-ca Ready=True
✅ PASS | 5. Log Monitor (GKE)       | Runs as GKE Deployment; Cloud Run check skipped
=======================================

Related Issues / Bugs

N/A

TAG=agy
CONV=c0655880-9a67-4bdd-aaa7-bc8e8631798f

Add a "View in full width" / "Exit full width" toggle button to the
queue jobs table on the Resource Observability Dashboard (/resources)
that stacks the cluster queues list and jobs table into full-width rows
instead of hiding the queue list, and persists the layout preference in
the xmc_jobs_full_width browser cookie.

TAG=agy
CONV=c0655880-9a67-4bdd-aaa7-bc8e8631798f
@VLukiantsev
Copy link
Copy Markdown
Collaborator Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a full-width layout preference for the jobs table in the Resource Dashboard, persisting the user's choice via a cookie. It adds a toggle button to the QueueJobsTable component, updates the dashboard's layout styles to support a vertically stacked full-width mode, and includes comprehensive tests. The feedback recommends addressing a flexbox layout issue in resource_dashboard.scss where flex: 1 1 100% on a vertical flex container incorrectly targets height instead of width. Additionally, it is suggested to simplify the toggle button's styling by conditionally rendering mat-flat-button and mat-stroked-button using Angular's control flow rather than manually overriding Material's internal CSS classes.

Comment on lines +63 to +67
.rod-main-content,
.jobs-side-pane {
flex: 1 1 100%;
width: 100%;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

In a flex container with flex-direction: column-reverse, the main axis is vertical. Therefore, setting flex: 1 1 100% sets the flex-basis (initial height) of .rod-main-content and .jobs-side-pane to 100% of the container's height, rather than their width.

To make both elements stack vertically and span the full width of the container while maintaining their natural height based on content, you should set flex: 1 1 auto (or flex: none) and keep width: 100%.

Suggested change
.rod-main-content,
.jobs-side-pane {
flex: 1 1 100%;
width: 100%;
}
.rod-main-content,
.jobs-side-pane {
flex: 1 1 auto;
width: 100%;
}

Comment on lines +22 to +33
@if (showFullWidthToggle()) {
<button
type="button"
mat-stroked-button
class="full-width-toggle-btn"
[class.active]="isFullWidth()"
(click)="toggleFullWidth()"
>
<mat-icon aria-hidden="true">{{ fullWidthToggleIcon() }}</mat-icon>
{{ fullWidthToggleLabel() }}
</button>
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Instead of manually overriding Angular Material's stroked button styles (like background color, border color, text color, hover, focus, and ripple states) via custom CSS classes when the button is active, it is much cleaner and more idiomatic to conditionally render either a mat-flat-button (with color="primary") or a mat-stroked-button using Angular's @if / @else control flow.

This ensures that the button natively inherits the correct Material Design styles, hover overlays, focus rings, and ripple colors for both states without requiring complex and fragile CSS overrides.

      @if (showFullWidthToggle()) {
        @if (isFullWidth()) {
          <button
            type="button"
            mat-flat-button
            color="primary"
            class="full-width-toggle-btn"
            (click)="toggleFullWidth()"
          >
            <mat-icon aria-hidden="true">{{ fullWidthToggleIcon() }}</mat-icon>
            {{ fullWidthToggleLabel() }}
          </button>
        } @else {
          <button
            type="button"
            mat-stroked-button
            class="full-width-toggle-btn"
            (click)="toggleFullWidth()"
          >
            <mat-icon aria-hidden="true">{{ fullWidthToggleIcon() }}</mat-icon>
            {{ fullWidthToggleLabel() }}
          </button>
        }
      }

Comment on lines +48 to 66
button.full-width-toggle-btn.mat-mdc-outlined-button {
border-radius: 9999px;
border-color: #dadce0;
background-color: #ffffff;
color: #1a73e8;
font-size: 14px;
font-weight: 500;
white-space: nowrap;

.mat-icon {
color: inherit;
}

&.active {
background-color: #1a73e8;
border-color: #1a73e8;
color: #ffffff;
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

By using the native mat-flat-button and mat-stroked-button directives, we can eliminate almost all of the custom CSS overrides. We only need to style the button's border-radius to make it pill-shaped. This also avoids coupling our styles to internal Angular Material classes like .mat-mdc-outlined-button, which are implementation details and subject to change in future library updates.

.full-width-toggle-btn {
  border-radius: 9999px;
}

@gemini-code-assist gemini-code-assist Bot left a comment
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a full-width layout preference for the jobs table in the Resource Dashboard, persisting the user's choice via cookies. The feedback suggests adding defensive null/undefined checks to the document cookie utility functions for non-browser environments, adjusting the flex layout properties to prevent vertical stretching, and avoiding coupling custom styles to internal Angular Material MDC class names.

Comment on lines +22 to +39
export function readJobsFullWidthCookie(doc: Document): boolean {
const cookieHeader = doc.cookie || '';
if (!cookieHeader) {
return false;
}
for (const rawEntry of cookieHeader.split(';')) {
const [rawKey, ...rawValueParts] = rawEntry.trim().split('=');
if (rawKey === JOBS_FULL_WIDTH_COOKIE_NAME) {
return rawValueParts.join('=') === 'true';
}
}
return false;
}

/** Persists the jobs table full-width layout preference to document cookies. */
export function writeJobsFullWidthCookie(doc: Document, isFullWidth: boolean): void {
doc.cookie = `${JOBS_FULL_WIDTH_COOKIE_NAME}=${isFullWidth}; Path=/; Max-Age=${JOBS_FULL_WIDTH_COOKIE_MAX_AGE_SECONDS}; SameSite=Lax`;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

To ensure robustness, especially in non-browser environments (such as Server-Side Rendering or unit testing where the DOCUMENT token might be null, undefined, or partially mocked), we should add defensive null/undefined checks to the doc parameter before accessing doc.cookie.

export function readJobsFullWidthCookie(doc: Document | null | undefined): boolean {
  const cookieHeader = doc?.cookie || '';
  if (!cookieHeader) {
    return false;
  }
  for (const rawEntry of cookieHeader.split(';')) {
    const [rawKey, ...rawValueParts] = rawEntry.trim().split('=');
    if (rawKey === JOBS_FULL_WIDTH_COOKIE_NAME) {
      return rawValueParts.join('=') === 'true';
    }
  }
  return false;
}

/** Persists the jobs table full-width layout preference to document cookies. */
export function writeJobsFullWidthCookie(doc: Document | null | undefined, isFullWidth: boolean): void {
  if (!doc) {
    return;
  }
  doc.cookie = `${JOBS_FULL_WIDTH_COOKIE_NAME}=${isFullWidth}; Path=/; Max-Age=${JOBS_FULL_WIDTH_COOKIE_MAX_AGE_SECONDS}; SameSite=Lax`;
}

Comment on lines +63 to +67
.rod-main-content,
.jobs-side-pane {
flex: 1 1 100%;
width: 100%;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

In a column-reverse flex layout, setting flex: 1 1 100% on the child elements forces their main size (height) to start at 100% of the parent container. This can cause unintended vertical stretching, squishing, or overflow issues. Since we want the panes to stack vertically and take their natural content height, we should use flex: 0 0 auto (or flex: none) instead.

Suggested change
.rod-main-content,
.jobs-side-pane {
flex: 1 1 100%;
width: 100%;
}
.rod-main-content,
.jobs-side-pane {
flex: 0 0 auto;
width: 100%;
}

Comment on lines +48 to 66
button.full-width-toggle-btn.mat-mdc-outlined-button {
border-radius: 9999px;
border-color: #dadce0;
background-color: #ffffff;
color: #1a73e8;
font-size: 14px;
font-weight: 500;
white-space: nowrap;

.mat-icon {
color: inherit;
}

&.active {
background-color: #1a73e8;
border-color: #1a73e8;
color: #ffffff;
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Avoid coupling the custom styles directly to Angular Material's internal MDC class names like .mat-mdc-outlined-button. These classes are implementation details of the library and are subject to change in future versions of Angular Material. Styling .full-width-toggle-btn directly is cleaner and more maintainable.

Suggested change
button.full-width-toggle-btn.mat-mdc-outlined-button {
border-radius: 9999px;
border-color: #dadce0;
background-color: #ffffff;
color: #1a73e8;
font-size: 14px;
font-weight: 500;
white-space: nowrap;
.mat-icon {
color: inherit;
}
&.active {
background-color: #1a73e8;
border-color: #1a73e8;
color: #ffffff;
}
}
button.full-width-toggle-btn {
border-radius: 9999px;
border-color: #dadce0;
background-color: #ffffff;
color: #1a73e8;
font-size: 14px;
font-weight: 500;
white-space: nowrap;
.mat-icon {
color: inherit;
}
&.active {
background-color: #1a73e8;
border-color: #1a73e8;
color: #ffffff;
}
}

janzak-dev added a commit that referenced this pull request Nov 4, 2026
…ect header actions

Intentionally refines two internal design choices from #1567 while preserving
identical visual layout and user behavior:
- Replace `document.cookie` (`xmc_jobs_full_width`) with `window.localStorage`
  (`xmc-jobs-full-width`) to align with existing XMC frontend preference
  conventions (`StatusMessageFilterService`) and avoid sending a client-only
  layout flag in HTTP `Cookie` headers on API requests.
- Replace `showFullWidthToggle` / `isFullWidth` inputs on `QueueJobsTable` with
  a generic `<ng-content select="[headerActions]" />` slot in `.title-row` so
  `QueueJobsTable` stays layout-agnostic and `ResourceDashboard` owns the
  full-width toggle button directly.

TAG=agy
CONV=23cac48a-f5e0-4dc9-b0fa-c46ba04c7f87

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

0