Merge remote-tracking branch 'origin/main' into query-history-app

This commit is contained in:
Ryan McKinley
2025-10-29 13:25:47 +03:00
16 changed files with 379 additions and 97 deletions
+6 -1
View File
@@ -146,7 +146,12 @@ type ScopeNodeSpec struct {
NodeType NodeType `json:"nodeType"` // container | leaf
Title string `json:"title"`
Title string `json:"title"`
//+optional
// Displays next to the title to provide more context.
SubTitle string `json:"subTitle,omitempty"`
//+optional
Description string `json:"description,omitempty"`
DisableMultiSelect bool `json:"disableMultiSelect"`
@@ -838,6 +838,13 @@ func schema_pkg_apis_scope_v0alpha1_ScopeNodeSpec(ref common.ReferenceCallback)
Format: "",
},
},
"subTitle": {
SchemaProps: spec.SchemaProps{
Description: "Displays next to the title to provide more context.",
Type: []string{"string"},
Format: "",
},
},
"description": {
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
@@ -9,7 +9,6 @@ import {
openScopesSelector,
searchScopes,
selectScope,
TestScope,
} from '../utils/scope-helpers';
import { testScopes } from '../utils/scopes';
+6 -5
View File
@@ -128,7 +128,7 @@ export async function scopeSelectRequest(page: Page, selectedScope: TestScope):
export async function selectScope(page: Page, scopeName: string, selectedScope?: TestScope) {
const click = async () => {
const element = page.locator(
`[data-testid="scopes-tree-${scopeName}-checkbox"], [data-testid="scopes-tree-${scopeName}-radio"]`
`[data-testid="scopes-tree-${scopeName}-checkbox"], [data-testid="scopes-tree-${scopeName}-radio"], [data-testid="scopes-tree-${scopeName}-link"]`
);
await element.scrollIntoViewIfNeeded();
await element.click({ force: true });
@@ -254,9 +254,9 @@ export async function getScopeTreeName(page: Page, nth: number): Promise<string>
}
export async function getScopeLeafName(page: Page, nth: number): Promise<string> {
const locator = page.getByTestId(/^scopes-tree-.*-(checkbox|radio)/).nth(nth);
const locator = page.getByTestId(/^scopes-tree-.*-(checkbox|radio|link)/).nth(nth);
const fullTestId = await locator.getAttribute('data-testid');
const scopeName = fullTestId?.replace(/^scopes-tree-/, '').replace(/-(checkbox|radio)/, '');
const scopeName = fullTestId?.replace(/^scopes-tree-/, '').replace(/-(checkbox|radio|link)/, '');
if (!scopeName) {
throw new Error('There are no scopes in the selector');
@@ -266,10 +266,11 @@ export async function getScopeLeafName(page: Page, nth: number): Promise<string>
}
export async function getScopeLeafTitle(page: Page, nth: number): Promise<string> {
const leafLocator = page.getByTestId(/^scopes-tree-.*-(checkbox|radio)/).nth(nth);
// Get the nth selectable tree item (checkbox, radio, or link)
const leafLocator = page.getByTestId(/^scopes-tree-.*-(checkbox|radio|link)/).nth(nth);
// Find the closest ancestor element that has the main tree item test id
const titleLocator = leafLocator.locator(
'xpath=ancestor::*[@data-testid][starts-with(@data-testid, "scopes-tree-") and not(contains(@data-testid, "-checkbox")) and not(contains(@data-testid, "-radio")) and not(contains(@data-testid, "-expand"))]'
'xpath=ancestor::*[@data-testid][starts-with(@data-testid, "scopes-tree-") and not(contains(@data-testid, "-checkbox")) and not(contains(@data-testid, "-radio")) and not(contains(@data-testid, "-link")) and not(contains(@data-testid, "-expand"))]'
);
const scopeTitle = await titleLocator.textContent();
if (!scopeTitle) {
+1 -1
View File
@@ -70,7 +70,7 @@ export type ScopeNodeLinkType = 'scope';
export interface ScopeNodeSpec {
nodeType: ScopeNodeNodeType;
title: string;
subTitle?: string;
description?: string;
// If true for a scope category/type, it means only single child can be selected at a time.
+1
View File
@@ -153,6 +153,7 @@ func (p *IndexProvider) HandleRequest(writer http.ResponseWriter, request *http.
p.runIndexDataHooks(reqCtx, &data)
writer.Header().Set("Content-Type", "text/html; charset=UTF-8")
writer.Header().Set("Cache-Control", "no-store")
writer.WriteHeader(200)
if err := p.index.Execute(writer, &data); err != nil {
if errors.Is(err, syscall.EPIPE) { // Client has stopped listening.
+1
View File
@@ -92,6 +92,7 @@ func TestFrontendService_WebAssets(t *testing.T) {
assert.Equal(t, 200, recorder.Code)
assert.Contains(t, recorder.Header().Get("Content-Type"), "text/html")
assert.Contains(t, recorder.Header().Get("Cache-Control"), "no-store")
// The response should contain references to the assets
body := recorder.Body.String()
@@ -2,13 +2,177 @@ import { css, cx } from '@emotion/css';
import Highlighter from 'react-highlight-words';
import { GrafanaTheme2 } from '@grafana/data';
import { t } from '@grafana/i18n';
import { Checkbox, Icon, RadioButtonDot, useStyles2 } from '@grafana/ui';
import { Checkbox, Icon, RadioButtonDot, useStyles2, Text } from '@grafana/ui';
import { useScopesServices } from '../ScopesContextProvider';
import { ScopesTree } from './ScopesTree';
import { isNodeExpandable, isNodeSelectable } from './scopesTreeUtils';
import { NodesMap, SelectedScope, TreeNode } from './types';
// Helper components for rendering different selectable content types
interface RadioButtonDotProps {
scopeNodeId: string;
selected: boolean;
onChange: () => void;
children?: React.ReactNode;
'aria-labelledby'?: string;
}
function ScopeRadioButtonDot({
scopeNodeId,
selected,
onChange,
children,
'aria-labelledby': ariaLabelledby,
}: RadioButtonDotProps) {
return (
<RadioButtonDot
id={scopeNodeId}
name={scopeNodeId}
checked={selected}
label={children ?? undefined}
data-testid={`scopes-tree-${scopeNodeId}-radio`}
onChange={onChange}
aria-labelledby={ariaLabelledby ?? undefined}
/>
);
}
interface LinkLikeButtonProps {
scopeNodeId: string;
onClick: () => void;
children: React.ReactNode;
}
function ScopeLinkLikeButton({ scopeNodeId, onClick, children }: LinkLikeButtonProps) {
const styles = useStyles2(getStyles);
return (
<button className={styles.linkLikeItem} data-testid={`scopes-tree-${scopeNodeId}-link`} onClick={onClick}>
{children}
</button>
);
}
interface CheckboxWithLabelProps {
scopeNodeId: string;
selected: boolean;
showLabel: boolean;
onChange: () => void;
children?: React.ReactNode;
'aria-labelledby'?: string;
}
function ScopeCheckboxWithLabel({
scopeNodeId,
selected,
showLabel,
onChange,
children,
'aria-labelledby': ariaLabelledby,
}: CheckboxWithLabelProps) {
const styles = useStyles2(getStyles);
return (
<div className={styles.checkboxWithLabel}>
<Checkbox
id={scopeNodeId}
checked={selected}
data-testid={`scopes-tree-${scopeNodeId}-checkbox`}
label=""
onChange={onChange}
aria-labelledby={ariaLabelledby ?? undefined}
/>
{showLabel && (
<label htmlFor={scopeNodeId} className={styles.checkboxLabel}>
{children}
</label>
)}
</div>
);
}
interface TitleContentProps {
shouldHighlight: boolean;
titleText: string;
searchWords: string[];
}
function TitleContent({ shouldHighlight, titleText, searchWords }: TitleContentProps) {
if (shouldHighlight) {
return <Highlighter textToHighlight={titleText} searchWords={searchWords} autoEscape />;
}
return <>{titleText}</>;
}
interface ExpandButtonProps {
scopeNodeId: string;
expanded: boolean;
onClick: () => void;
children: React.ReactNode;
controlsId: string;
isSelectable: boolean;
disableMultiSelect: boolean;
selected: boolean;
onSelect: () => void;
}
function ScopeExpandButton({
scopeNodeId,
expanded,
onClick,
children,
controlsId,
isSelectable,
disableMultiSelect,
selected,
onSelect,
}: ExpandButtonProps) {
const styles = useStyles2(getStyles);
const buttonId = getTreeItemElementId(scopeNodeId) + '-button';
const SelectComponent = () => {
if (!isSelectable || expanded) {
return null;
}
if (disableMultiSelect) {
return (
<ScopeRadioButtonDot
scopeNodeId={scopeNodeId}
selected={selected}
onChange={onSelect}
aria-labelledby={buttonId}
/>
);
}
return (
<ScopeCheckboxWithLabel
scopeNodeId={scopeNodeId}
selected={selected}
showLabel={false}
onChange={onSelect}
aria-labelledby={buttonId}
/>
);
};
return (
<>
<SelectComponent />
<button
id={buttonId}
className={styles.expand}
data-testid={`scopes-tree-${scopeNodeId}-expand`}
onClick={onClick}
aria-expanded={expanded}
aria-controls={controlsId}
>
<Icon name={!expanded ? 'angle-right' : 'angle-down'} />
{children}
</button>
</>
);
}
export interface ScopesTreeItemProps {
anyChildExpanded: boolean;
loadingNodeName: string | undefined;
@@ -38,7 +202,8 @@ export function ScopesTreeItem({
toggleExpandedNode,
}: ScopesTreeItemProps) {
const styles = useStyles2(getStyles);
const services = useScopesServices();
const { closeAndApply } = services?.scopesSelectorService || {};
if (anyChildExpanded && !treeNode.expanded) {
return null;
}
@@ -48,6 +213,7 @@ export function ScopesTreeItem({
// Should not happen as only way we show a tree is if we also load the nodes.
return null;
}
const parentNode = scopeNode.spec.parentName ? scopeNodes[scopeNode.spec.parentName] : undefined;
const disableMultiSelect = parentNode?.spec.disableMultiSelect ?? false;
@@ -57,9 +223,11 @@ export function ScopesTreeItem({
// Create search words for highlighting if there's a query
// Only highlight if we have a query AND this node is not expanded (not a parent showing children)
const titleText = scopeNode.spec.title;
const shouldHighlight = treeNode.query && !treeNode.expanded;
const shouldHighlight = Boolean(treeNode.query && !treeNode.expanded);
const searchWords = shouldHighlight ? getSearchWordsFromQuery(treeNode.query) : [];
const childrenId = getTreeItemElementId(treeNode.scopeNodeId) + '-children';
return (
<div
key={treeNode.scopeNodeId}
@@ -78,75 +246,57 @@ export function ScopesTreeItem({
)}
data-testid={`scopes-tree-${treeNode.scopeNodeId}`}
>
{isSelectable && !treeNode.expanded ? (
disableMultiSelect ? (
<RadioButtonDot
id={treeNode.scopeNodeId}
name={treeNode.scopeNodeId}
checked={selected}
label={
isExpandable ? (
''
) : shouldHighlight ? (
<Highlighter textToHighlight={titleText} searchWords={searchWords} autoEscape />
) : (
titleText
)
}
data-testid={`scopes-tree-${treeNode.scopeNodeId}-radio`}
onClick={() => {
selected ? deselectScope(treeNode.scopeNodeId) : selectScope(treeNode.scopeNodeId);
}}
/>
) : (
<div className={styles.checkboxWithLabel}>
<Checkbox
id={treeNode.scopeNodeId}
checked={selected}
data-testid={`scopes-tree-${treeNode.scopeNodeId}-checkbox`}
label=""
{isSelectable && !isExpandable && !treeNode.expanded && (
<>
{disableMultiSelect && (
<ScopeLinkLikeButton
scopeNodeId={treeNode.scopeNodeId}
onClick={() => {
selectScope(treeNode.scopeNodeId);
closeAndApply?.();
}}
>
<TitleContent shouldHighlight={shouldHighlight} titleText={titleText} searchWords={searchWords} />
</ScopeLinkLikeButton>
)}
{!disableMultiSelect && (
<ScopeCheckboxWithLabel
scopeNodeId={treeNode.scopeNodeId}
selected={selected}
showLabel={!isExpandable}
onChange={() => {
selected ? deselectScope(treeNode.scopeNodeId) : selectScope(treeNode.scopeNodeId);
}}
/>
{!isExpandable && (
<label htmlFor={treeNode.scopeNodeId} className={styles.checkboxLabel}>
{shouldHighlight ? (
<Highlighter textToHighlight={titleText} searchWords={searchWords} autoEscape />
) : (
titleText
)}
</label>
)}
</div>
)
) : null}
>
<TitleContent shouldHighlight={shouldHighlight} titleText={titleText} searchWords={searchWords} />
</ScopeCheckboxWithLabel>
)}
</>
)}
{isExpandable && (
<button
className={styles.expand}
data-testid={`scopes-tree-${treeNode.scopeNodeId}-expand`}
aria-label={
treeNode.expanded
? t('scopes.tree.collapse', 'Collapse {{title}}', { title: titleText })
: t('scopes.tree.expand', 'Expand {{title}}', { title: titleText })
}
onClick={() => {
toggleExpandedNode(treeNode.scopeNodeId);
}}
<ScopeExpandButton
scopeNodeId={treeNode.scopeNodeId}
expanded={treeNode.expanded}
controlsId={childrenId}
onClick={() => toggleExpandedNode(treeNode.scopeNodeId)}
onSelect={() => (selected ? deselectScope(treeNode.scopeNodeId) : selectScope(treeNode.scopeNodeId))}
isSelectable={isSelectable}
disableMultiSelect={disableMultiSelect}
selected={selected}
>
<Icon name={!treeNode.expanded ? 'angle-right' : 'angle-down'} />
<TitleContent shouldHighlight={shouldHighlight} titleText={titleText} searchWords={searchWords} />
</ScopeExpandButton>
)}
{shouldHighlight ? (
<Highlighter textToHighlight={titleText} searchWords={searchWords} autoEscape />
) : (
titleText
)}
</button>
{scopeNode.spec.subTitle && (
<Text truncate variant="body" color="secondary">
{scopeNode.spec.subTitle}
</Text>
)}
</div>
<div className={styles.children}>
<div id={childrenId} className={styles.children}>
{treeNode.expanded && (
<ScopesTree
tree={treeNode}
@@ -227,6 +377,20 @@ const getStyles = (theme: GrafanaTheme2) => {
margin: 0,
padding: 0,
}),
linkLikeItem: css({
alignItems: 'center',
background: 'none',
border: 0,
display: 'flex',
gap: theme.spacing(1),
margin: 0,
padding: 0,
textDecoration: 'none',
'&:hover': {
textDecoration: 'underline',
},
}),
children: css({
display: 'flex',
flexDirection: 'column',
@@ -79,7 +79,12 @@ export function ScopesTreeSearch({
setInputState({ value, dirty: true });
}}
onFocus={onFocus}
onBlur={onBlur}
onBlur={() => {
// TODO:Handle weird race condition where the blur event interupts selection of a radio button. This is because disableHighlighting is called, which forces a re-render of the tree. This re-render causes the radio button to lose focus, and the selection to be interrupted.
setTimeout(() => {
onBlur();
}, 0);
}}
/>
);
}
@@ -1,5 +1,7 @@
import { useState } from 'react';
import { useScopesServices } from '../ScopesContextProvider';
import { getTreeItemElementId } from './ScopesTreeItem';
import { isNodeExpandable, isNodeSelectable } from './scopesTreeUtils';
import { NodesMap, SelectedScope, TreeNode } from './types';
@@ -28,6 +30,8 @@ export function useScopesHighlighting({
}: UseScopesHighlightingParams) {
// Enable keyboard highlighting when the search field is focused
const [highlightEnabled, setHighlightEnabled] = useState(false);
const services = useScopesServices();
const { changeScopes } = services?.scopesSelectorService || {};
const items = [...selectedNodes, ...resultNodes];
@@ -52,6 +56,16 @@ export function useScopesHighlighting({
return;
}
// If parent has disableMultiSelect, apply scope directly
const parentNode = scopeNodes[nodeId]?.spec.parentName
? scopeNodes[scopeNodes[nodeId]?.spec.parentName]
: undefined;
if (parentNode?.spec.disableMultiSelect && changeScopes && scopeNodes[nodeId]?.spec.linkId) {
changeScopes([scopeNodes[nodeId].spec.linkId], parentNode.metadata.name);
return;
}
// Toggle selection
if (selectedScopes.some((s) => s.scopeNodeId === nodeId)) {
deselectScope(nodeId);
+33 -9
View File
@@ -21,6 +21,9 @@ import {
selectResultCloud,
selectResultCloudDev,
selectResultCloudOps,
expandResultEnvironments,
selectResultEnvironmentsDev,
selectResultEnvironmentsProd,
updateScopes,
} from './utils/actions';
import {
@@ -33,10 +36,10 @@ import {
expectResultApplicationsMimirNotPresent,
expectResultApplicationsMimirPresent,
expectResultApplicationsMimirSelected,
expectResultCloudDevNotSelected,
expectResultCloudDevSelected,
expectResultCloudOpsNotSelected,
expectResultCloudOpsSelected,
expectResultEnvironmentsDevNotSelected,
expectResultEnvironmentsDevSelected,
expectResultEnvironmentsProdNotSelected,
expectResultEnvironmentsProdSelected,
expectScopesHeadline,
expectScopesSelectorValue,
} from './utils/assertions';
@@ -133,12 +136,33 @@ describe('Tree', () => {
await openSelector();
await expandResultCloud();
await selectResultCloudDev();
expectResultCloudDevSelected();
expectResultCloudOpsNotSelected();
// Verify the content of the scopes selector input
expectScopesSelectorValue('Dev');
// Single leaf node links always apply the scope, hence we need to open the selector again
await openSelector();
await selectResultCloudOps();
expectResultCloudDevNotSelected();
expectResultCloudOpsSelected();
expectScopesSelectorValue('Ops');
});
it('Can only select one selectable container at a time', async () => {
await openSelector();
await expandResultEnvironments();
// Select the Development environment container
await selectResultEnvironmentsDev();
expectResultEnvironmentsDevSelected(); // Check selection state before applying
expectResultEnvironmentsProdNotSelected(); // Production should not be selected
// Select the Production environment container - should replace Development
await selectResultEnvironmentsProd();
expectResultEnvironmentsProdSelected(); // Check selection state before applying
expectResultEnvironmentsDevNotSelected(); // Development should no longer be selected
// Apply scopes and verify final state
await applyScopes();
expectScopesSelectorValue('Production');
});
it('Search works', async () => {
@@ -276,7 +300,7 @@ describe('Tree', () => {
await openSelector();
// Verify that Cloud is expanded
expect(screen.getByRole('button', { name: 'Collapse Cloud' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Cloud' })).toBeInTheDocument();
});
describe('Keyboard Navigation', () => {
@@ -22,16 +22,19 @@ import {
getResultApplicationsCloudSelect,
getResultApplicationsGrafanaSelect,
getResultApplicationsMimirSelect,
getResultCloudDevRadio,
getResultCloudExpand,
getResultCloudOpsRadio,
getResultCloudSelect,
getResultEnvironmentsExpand,
getResultEnvironmentsDevSelect,
getResultEnvironmentsProdSelect,
getSelectorApply,
getSelectorCancel,
getSelectorClear,
getSelectorInput,
getTreeSearch,
findResultApplicationsExpand,
getResultCloudDevLink,
getResultCloudOpsLink,
} from './selectors';
const click = async (selector: () => HTMLElement) => act(() => userEvent.click(selector()));
@@ -68,8 +71,12 @@ export const selectResultApplicationsMimir = async () => click(getResultApplicat
export const selectResultApplicationsCloud = async () => click(getResultApplicationsCloudSelect);
export const selectResultApplicationsCloudDev = async () => click(getResultApplicationsCloudDevSelect);
export const selectResultCloud = async () => click(getResultCloudSelect);
export const selectResultCloudDev = async () => click(getResultCloudDevRadio);
export const selectResultCloudOps = async () => click(getResultCloudOpsRadio);
export const selectResultCloudDev = async () => click(getResultCloudDevLink);
export const selectResultCloudOps = async () => click(getResultCloudOpsLink);
export const expandResultEnvironments = async () => click(getResultEnvironmentsExpand);
export const selectResultEnvironmentsDev = async () => click(getResultEnvironmentsDevSelect);
export const selectResultEnvironmentsProd = async () => click(getResultEnvironmentsProdSelect);
export const toggleDashboards = async () => click(getDashboardsExpand);
export const searchDashboards = async (value: string) => type(getDashboardsSearch, value);
@@ -12,8 +12,6 @@ import {
getResultApplicationsCloudSelect,
getResultApplicationsGrafanaSelect,
getResultApplicationsMimirSelect,
getResultCloudDevRadio,
getResultCloudOpsRadio,
getSelectorInput,
getTreeHeadline,
queryAllDashboard,
@@ -28,14 +26,16 @@ import {
queryResultApplicationsCloudSelect,
queryResultApplicationsGrafanaSelect,
queryResultApplicationsMimirSelect,
getResultEnvironmentsDevSelect,
getResultEnvironmentsProdSelect,
querySelectorApply,
} from './selectors';
const expectInDocument = (selector: () => HTMLElement) => expect(selector()).toBeInTheDocument();
const expectNotInDocument = (selector: () => HTMLElement | null) => expect(selector()).not.toBeInTheDocument();
const expectChecked = (selector: () => HTMLInputElement) => expect(selector()).toBeChecked();
const expectRadioChecked = (selector: () => HTMLInputElement) => expect(selector().checked).toBe(true);
const expectRadioNotChecked = (selector: () => HTMLInputElement) => expect(selector().checked).toBe(false);
const expectNotChecked = (selector: () => HTMLInputElement) => expect(selector()).not.toBeChecked();
const expectValue = (selector: () => HTMLInputElement, value: string) => expect(selector().value).toBe(value);
const expectTextContent = (selector: () => HTMLElement, text: string) => expect(selector()).toHaveTextContent(text);
const expectDisabled = (selector: () => HTMLElement) => expect(selector()).toBeDisabled();
@@ -62,10 +62,11 @@ export const expectResultApplicationsMimirPresent = () => expectInDocument(getRe
export const expectResultApplicationsMimirNotPresent = () => expectNotInDocument(queryResultApplicationsMimirSelect);
export const expectResultApplicationsCloudPresent = () => expectInDocument(getResultApplicationsCloudSelect);
export const expectResultApplicationsCloudNotPresent = () => expectNotInDocument(queryResultApplicationsCloudSelect);
export const expectResultCloudDevSelected = () => expectRadioChecked(getResultCloudDevRadio);
export const expectResultCloudDevNotSelected = () => expectRadioNotChecked(getResultCloudDevRadio);
export const expectResultCloudOpsSelected = () => expectRadioChecked(getResultCloudOpsRadio);
export const expectResultCloudOpsNotSelected = () => expectRadioNotChecked(getResultCloudOpsRadio);
export const expectResultEnvironmentsDevSelected = () => expectChecked(getResultEnvironmentsDevSelect);
export const expectResultEnvironmentsDevNotSelected = () => expectNotChecked(getResultEnvironmentsDevSelect);
export const expectResultEnvironmentsProdSelected = () => expectChecked(getResultEnvironmentsProdSelect);
export const expectResultEnvironmentsProdNotSelected = () => expectNotChecked(getResultEnvironmentsProdSelect);
export const expectDashboardsDisabled = () => expectDisabled(getDashboardsExpand);
export const expectDashboardsClosed = () => expectNotInDocument(queryDashboardsContainer);
@@ -59,6 +59,20 @@ export const mocksScopes: Scope[] = [
filters: [{ key: 'app', value: 'tempo', operator: 'equals' }],
},
},
{
metadata: { name: 'dev-env' },
spec: {
title: 'Development',
filters: [{ key: 'environment', value: 'dev', operator: 'equals' }],
},
},
{
metadata: { name: 'prod-env' },
spec: {
title: 'Production',
filters: [{ key: 'environment', value: 'prod', operator: 'equals' }],
},
},
] as const;
const dashboardBindingsGenerator = (
@@ -341,6 +355,38 @@ export const mocksNodes: ScopeNode[] = [
parentName: 'cloud-applications',
},
},
{
metadata: { name: 'environments' },
spec: {
nodeType: 'container',
title: 'Environments',
description: 'Environment Scopes',
disableMultiSelect: true,
parentName: '',
},
},
{
metadata: { name: 'environments-dev' },
spec: {
nodeType: 'container',
title: 'Development',
description: 'Development Environment',
linkType: 'scope',
linkId: 'dev-env',
parentName: 'environments',
},
},
{
metadata: { name: 'environments-prod' },
spec: {
nodeType: 'container',
title: 'Production',
description: 'Production Environment',
linkType: 'scope',
linkId: 'prod-env',
parentName: 'environments',
},
},
] as const;
export const dashboardReloadSpy = jest.spyOn(getDashboardScenePageStateManager(), 'reloadDashboard');
@@ -9,6 +9,7 @@ const selectors = {
headline: 'scopes-tree-headline',
select: (nodeId: string) => `scopes-tree-${nodeId}-checkbox`,
radio: (nodeId: string) => `scopes-tree-${nodeId}-radio`,
link: (nodeId: string) => `scopes-tree-${nodeId}-link`,
expand: (nodeId: string) => `scopes-tree-${nodeId}-expand`,
title: (nodeId: string) => `scopes-tree-${nodeId}-title`,
},
@@ -93,7 +94,15 @@ export const getResultApplicationsCloudDevSelect = () =>
export const getResultCloudSelect = () => screen.getByTestId(selectors.tree.select('cloud'));
export const getResultCloudExpand = () => screen.getByTestId(selectors.tree.expand('cloud'));
export const getResultCloudDevRadio = () => screen.getByTestId<HTMLInputElement>(selectors.tree.radio('cloud-dev'));
export const getResultCloudOpsRadio = () => screen.getByTestId<HTMLInputElement>(selectors.tree.radio('cloud-ops'));
export const getResultCloudDevLink = () => screen.getByTestId<HTMLButtonElement>(selectors.tree.link('cloud-dev'));
export const getResultCloudOpsLink = () => screen.getByTestId<HTMLButtonElement>(selectors.tree.link('cloud-ops'));
export const getResultEnvironmentsExpand = () => screen.getByTestId(selectors.tree.expand('environments'));
export const getResultEnvironmentsDevSelect = () =>
screen.getByTestId<HTMLInputElement>(selectors.tree.radio('environments-dev'));
export const getResultEnvironmentsProdSelect = () =>
screen.getByTestId<HTMLInputElement>(selectors.tree.radio('environments-prod'));
export const queryResultEnvironmentsDevSelect = () => screen.queryByTestId(selectors.tree.radio('environments-dev'));
export const queryResultEnvironmentsProdSelect = () => screen.queryByTestId(selectors.tree.radio('environments-prod'));
export const getListOfScopes = (service: ScopesService) => service.state.value;
-2
View File
@@ -12305,8 +12305,6 @@
"title": "Select scopes"
},
"tree": {
"collapse": "Collapse {{title}}",
"expand": "Expand {{title}}",
"headline": {
"noResults": "No results found for your query",
"recommended": "Recommended",