Skip to content

Commit

Permalink
web: clean up action bar in detail view for disabled resources (#5215)
Browse files Browse the repository at this point in the history
* (feat) Hide endpoint and pod info, plus log menu for disabled resources in Detail View
* (chore) Add storybook resource buttons for linting and compile checks
  • Loading branch information
lizzthabet committed Nov 24, 2021
1 parent 646aa11 commit 7b4286c
Show file tree
Hide file tree
Showing 17 changed files with 615 additions and 84 deletions.
82 changes: 78 additions & 4 deletions web/src/OverviewActionBar.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,24 @@ import {
expectIncrs,
mockAnalyticsCalls,
} from "./analytics_test_helpers"
import { ApiButton } from "./ApiButton"
import { ApiButton, ButtonSet } from "./ApiButton"
import { InstrumentedButton } from "./instrumentedComponents"
import LogActions from "./LogActions"
import { EMPTY_FILTER_TERM, FilterLevel, FilterSource } from "./logfilters"
import OverviewActionBar, {
ActionBarBottomRow,
ActionBarTopRow,
ButtonLeftPill,
CopyButton,
createLogSearch,
Endpoint,
FilterRadioButton,
FilterTermField,
FILTER_FIELD_ID,
FILTER_INPUT_DEBOUNCE,
} from "./OverviewActionBar"
import { EmptyBar, FullBar } from "./OverviewActionBar.stories"
import { oneButton } from "./testdata"
import { disableButton, oneButton, oneResourceNoAlerts } from "./testdata"

let history: MemoryHistory
beforeEach(() => {
Expand Down Expand Up @@ -55,6 +58,14 @@ it("shows endpoints", () => {
expect(endpoints).toHaveLength(2)
})

it("shows pod ID", () => {
const root = mountBar(<FullBar />)
const podId = root.find(ActionBarTopRow).find(CopyButton)

expect(podId).toHaveLength(1)
expect(podId.text()).toContain("my-pod-deadbeef Pod ID") // Hardcoded from test data
})

it("skips the top bar when empty", () => {
let root = mountBar(<EmptyBar />)
let topBar = root.find(ActionBarTopRow)
Expand Down Expand Up @@ -92,6 +103,69 @@ it("navigates to build warning filter", () => {
expect(history.location.search).toEqual("?level=warn&source=build")
})

describe("disabled resource view", () => {
let root: ReactWrapper<any, any>

beforeEach(() => {
const resource = oneResourceNoAlerts({
name: "i-am-not-enabled",
disabled: true,
})
const filterSet = {
level: FilterLevel.all,
source: FilterSource.all,
term: EMPTY_FILTER_TERM,
}
const buttonSet: ButtonSet = {
default: [oneButton(0, "i-am-not-enabled")],
toggleDisable: disableButton("i-am-not-enabled", false),
}

root = mountBar(
<OverviewActionBar
resource={resource}
filterSet={filterSet}
buttons={buttonSet}
/>
)
})

it("should display the disable toggle button", () => {
const bottomRowButtons = root.find(ActionBarBottomRow).find(ApiButton)
expect(bottomRowButtons.length).toBeGreaterThanOrEqual(1)
expect(
bottomRowButtons.at(bottomRowButtons.length - 1).prop("uiButton").metadata
?.name
).toEqual("toggle-i-am-not-enabled-disable")
})

it("should NOT display any `default` custom buttons", () => {
const topRowButtons = root.find(ActionBarTopRow).find(ApiButton)
expect(topRowButtons.length).toBe(0)
})

it("should NOT display the filter menu", () => {
const bottomRow = root.find(ActionBarBottomRow)
const filterButtons = bottomRow.find(FilterRadioButton)
const filterTermField = bottomRow.find(FilterTermField)
const logActionsMenu = bottomRow.find(LogActions)

expect(filterButtons).toHaveLength(0)
expect(filterTermField).toHaveLength(0)
expect(logActionsMenu).toHaveLength(0)
})

it("should NOT display endpoint information", () => {
const endpoints = root.find(ActionBarTopRow).find(Endpoint)
expect(endpoints).toHaveLength(0)
})

it("should NOT display podId information", () => {
const podInfo = root.find(ActionBarTopRow).find(CopyButton)
expect(podInfo).toHaveLength(0)
})
})

describe("buttons", () => {
it("shows endpoint buttons", () => {
let root = mountBar(<FullBar />)
Expand All @@ -102,7 +176,7 @@ describe("buttons", () => {
expect(endpoints).toHaveLength(2)
})

it("disables disabled buttons", () => {
it("disables a button that should be disabled", () => {
let uiButtons = [oneButton(1, "vigoda")]
uiButtons[0].spec!.disabled = true
let filterSet = {
Expand Down Expand Up @@ -140,7 +214,7 @@ describe("buttons", () => {
})
})

describe("Term filter input", () => {
describe("term filter input", () => {
const FILTER_INPUT = `input#${FILTER_FIELD_ID}`
let root: ReactWrapper<any, Readonly<{}>, React.Component<{}, {}, any>>

Expand Down
147 changes: 97 additions & 50 deletions web/src/OverviewActionBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { PopoverOrigin } from "@material-ui/core/Popover"
import { makeStyles } from "@material-ui/core/styles"
import ExpandMoreIcon from "@material-ui/icons/ExpandMore"
import { History } from "history"
import React, { ChangeEvent, useEffect, useRef, useState } from "react"
import React, { ChangeEvent, useEffect, useState } from "react"
import { useHistory, useLocation } from "react-router"
import styled from "styled-components"
import { Alert } from "./alerts"
Expand Down Expand Up @@ -36,6 +36,7 @@ import { useLogStore } from "./LogStore"
import OverviewActionBarKeyboardShortcuts from "./OverviewActionBarKeyboardShortcuts"
import { OverviewButtonMixin } from "./OverviewButton"
import { usePathBuilder } from "./PathBuilder"
import { resourceIsDisabled } from "./ResourceStatus"
import SrOnly from "./SrOnly"
import {
AnimDuration,
Expand Down Expand Up @@ -91,7 +92,7 @@ let useMenuStyles = makeStyles((theme) => ({

// Menu to filter logs by source (e.g., build-only, runtime-only).
function FilterSourceMenu(props: FilterSourceMenuProps) {
let { id, anchorEl, level, open, filterSet, onClose } = props
let { id, anchorEl, level, open, onClose } = props
let alerts = props.alerts || []

let classes = useMenuStyles()
Expand Down Expand Up @@ -187,17 +188,22 @@ const CustomActionButton = styled(ApiButton)`
`

const DisableButton = styled(ApiButton)`
margin-right: ${SizeUnit(0.5)};
button {
${OverviewButtonMixin};
background-color: ${Color.grayDark};
&:hover {
background-color: ${Color.grayDark};
}
}
.MuiButton-label {
// hardcode a width to workaround this bug:
// https://app.shortcut.com/windmill/story/12912/uibuttons-created-by-togglebuttons-have-different-sizes-when-toggled
width: ${SizeUnit(3.6)};
}
margin-left: ${SizeUnit(0.5)};
`

const ButtonRoot = styled(InstrumentedButton)`
Expand All @@ -213,6 +219,11 @@ const WidgetRoot = styled.div`

let ButtonPill = styled.div`
display: flex;
margin-right: ${SizeUnit(0.5)};
&.isCentered {
margin-left: auto;
}
`

export let ButtonLeftPill = styled(ButtonRoot)`
Expand Down Expand Up @@ -308,6 +319,8 @@ type FilterRadioButtonProps = {

// All the alerts for the current resource.
alerts?: Alert[]

className?: string
}

export function createLogSearch(
Expand Down Expand Up @@ -390,15 +403,14 @@ export function FilterRadioButton(props: FilterRadioButtonProps) {
})
}

let rightPillRef = useRef(null as any)
let [sourceMenuAnchor, setSourceMenuAnchor] = useState(null)
let onMenuOpen = (e: any) => {
setSourceMenuAnchor(e.currentTarget)
}
let sourceMenuOpen = !!sourceMenuAnchor

return (
<ButtonPill style={{ marginRight: SizeUnit(0.5) }}>
<ButtonPill className={props.className}>
<ButtonLeftPill
className={leftClassName}
onClick={onClick}
Expand Down Expand Up @@ -573,7 +585,7 @@ let TruncateText = styled.div`
max-width: 250px;
`

function CopyButton(props: CopyButtonProps) {
export function CopyButton(props: CopyButtonProps) {
let [showCopySuccess, setShowCopySuccess] = useState(false)

let copyClick = () => {
Expand Down Expand Up @@ -619,6 +631,7 @@ export let ActionBarBottomRow = styled.div`
flex-wrap: wrap;
align-items: center;
border-bottom: 1px solid ${Color.grayLighter};
min-height: ${SizeUnit(1)};
padding-left: ${SizeUnit(0.5)};
padding-right: ${SizeUnit(0.5)};
padding-top: ${SizeUnit(0.35)};
Expand Down Expand Up @@ -684,33 +697,37 @@ function DisableButtonSection(props: { button?: UIButton }) {

export default function OverviewActionBar(props: OverviewActionBarProps) {
let { resource, filterSet, alerts, buttons } = props
const logStore = useLogStore()
const isSnapshot = usePathBuilder().isSnapshot()
const isDisabled = resourceIsDisabled(resource)

let endpoints = resource?.status?.endpointLinks || []
let podId = resource?.status?.k8sResourceInfo?.podName || ""
const resourceName = resource
? resource.metadata?.name || ""
: ResourceName.all
const isSnapshot = usePathBuilder().isSnapshot()
const logStore = useLogStore()

let endpointEls: any = []
endpoints.forEach((ep, i) => {
if (i !== 0) {
endpointEls.push(<span key={`spacer-${i}`}>,&nbsp;</span>)
}
endpointEls.push(
<Endpoint
onClick={() =>
void incr("ui.web.endpoint", { action: AnalyticsAction.Click })
}
href={ep.url}
// We use ep.url as the target, so that clicking the link re-uses the tab.
target={ep.url}
key={ep.url}
>
<TruncateText>{ep.name || displayURL(ep)}</TruncateText>
</Endpoint>
)
})
let endpointEls: JSX.Element[] = []
if (endpoints.length && !isDisabled) {
endpoints.forEach((ep, i) => {
if (i !== 0) {
endpointEls.push(<span key={`spacer-${i}`}>,&nbsp;</span>)
}
endpointEls.push(
<Endpoint
onClick={() =>
void incr("ui.web.endpoint", { action: AnalyticsAction.Click })
}
href={ep.url}
// We use ep.url as the target, so that clicking the link re-uses the tab.
target={ep.url}
key={ep.url}
>
<TruncateText>{ep.name || displayURL(ep)}</TruncateText>
</Endpoint>
)
})
}

let topRowEls = new Array<JSX.Element>()
if (endpointEls.length) {
Expand All @@ -721,19 +738,68 @@ export default function OverviewActionBar(props: OverviewActionBarProps) {
</EndpointSet>
)
}
if (podId) {
if (podId && !isDisabled) {
topRowEls.push(<CopyButton podId={podId} key="copyPodId" />)
}

const widgets = OverviewWidgets({ buttons: buttons?.default })
if (widgets) {
if (widgets && !isDisabled) {
topRowEls.push(widgets)
}

const topRow = topRowEls.length ? (
<ActionBarTopRow key="top">{topRowEls}</ActionBarTopRow>
) : null

// By default, add the disable toggle button regardless of a resource's disabled status
const bottomRow: JSX.Element[] = [
<DisableButtonSection
key="toggleDisable"
button={buttons?.toggleDisable}
/>,
]
const disableButtonVisible = !!buttons?.toggleDisable
const firstFilterButtonClass = disableButtonVisible ? "isCentered" : ""

// Only display log filter controls if a resource is enabled
if (!isDisabled) {
bottomRow.push(
<FilterRadioButton
key="filterLevelAll"
className={firstFilterButtonClass}
level={FilterLevel.all}
filterSet={filterSet}
alerts={alerts}
/>
)
bottomRow.push(
<FilterRadioButton
key="filterLevelError"
level={FilterLevel.error}
filterSet={filterSet}
alerts={alerts}
/>
)
bottomRow.push(
<FilterRadioButton
key="filterLevelWarn"
level={FilterLevel.warn}
filterSet={filterSet}
alerts={alerts}
/>
)
bottomRow.push(
<FilterTermField key="filterTermField" termFromUrl={filterSet.term} />
)
bottomRow.push(
<LogActions
key="logActions"
resourceName={resourceName}
isSnapshot={isSnapshot}
/>
)
}

return (
<ActionBarRoot>
<OverviewActionBarKeyboardShortcuts
Expand All @@ -743,26 +809,7 @@ export default function OverviewActionBar(props: OverviewActionBarProps) {
openEndpointUrl={openEndpointUrl}
/>
{topRow}
<ActionBarBottomRow>
<FilterRadioButton
level={FilterLevel.all}
filterSet={filterSet}
alerts={alerts}
/>
<FilterRadioButton
level={FilterLevel.error}
filterSet={filterSet}
alerts={alerts}
/>
<FilterRadioButton
level={FilterLevel.warn}
filterSet={filterSet}
alerts={alerts}
/>
<FilterTermField termFromUrl={filterSet.term} />
<LogActions resourceName={resourceName} isSnapshot={isSnapshot} />
<DisableButtonSection button={buttons?.toggleDisable} />
</ActionBarBottomRow>
<ActionBarBottomRow>{bottomRow}</ActionBarBottomRow>
</ActionBarRoot>
)
}

0 comments on commit 7b4286c

Please sign in to comment.