Skip to content

Commit

Permalink
feat: add status dropdown with watcher support
Browse files Browse the repository at this point in the history
  • Loading branch information
Pablo Carmona authored and starpit committed Sep 18, 2022
1 parent ee16c63 commit 534c94f
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 11 deletions.
78 changes: 70 additions & 8 deletions plugins/plugin-codeflare/src/components/ProfileExplorer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,16 @@ import {
DescriptionListTerm,
DescriptionListDescription,
Divider,
Select,
SelectVariant,
SelectOption,
} from "@patternfly/react-core"

import ProfileSelect from "./ProfileSelect"
import DashboardSelect from "./DashboardSelect"
import ProfileWatcher from "../tray/watchers/profile/list"
import ProfileStatusWatcher from "../tray/watchers/profile/status"
import UpdateFunction from "../tray/update"
import { handleBoot, handleShutdown } from "../controller/profile/actions"

import "../../web/scss/components/Dashboard/Description.scss"
Expand All @@ -49,9 +54,12 @@ type Props = {

type State = {
watcher: ProfileWatcher
statusWatcher: ProfileStatusWatcher
selectedProfile?: string
profiles?: Profiles.Profile[]
catastrophicError?: unknown

updateCount: number
}

export default class ProfileExplorer extends React.PureComponent<Props, State> {
Expand All @@ -60,6 +68,12 @@ export default class ProfileExplorer extends React.PureComponent<Props, State> {
this.init()
}

private readonly statusWatcherUpdateFn: UpdateFunction = () => {
this.setState((curState) => ({
updateCount: (curState?.updateCount || 0) + 1,
}))
}

private readonly _handleProfileSelection = (selectedProfile: string) => {
this.setState({ selectedProfile })

Expand All @@ -70,7 +84,7 @@ export default class ProfileExplorer extends React.PureComponent<Props, State> {

private updateDebouncer: null | ReturnType<typeof setTimeout> = null

private readonly updateFn = () => {
private readonly profileWatcherUpdateFn = () => {
if (this.updateDebouncer) {
clearTimeout(this.updateDebouncer)
}
Expand Down Expand Up @@ -117,7 +131,10 @@ export default class ProfileExplorer extends React.PureComponent<Props, State> {

private async init() {
try {
const watcher = await new ProfileWatcher(this.updateFn, await Profiles.profilesPath({}, true)).init()
const watcher = await new ProfileWatcher(
this.profileWatcherUpdateFn,
await Profiles.profilesPath({}, true)
).init()
this.setState({
watcher,
profiles: [],
Expand All @@ -129,13 +146,19 @@ export default class ProfileExplorer extends React.PureComponent<Props, State> {
}

public componentWillUnmount() {
if (this.state && this.state.watcher) {
this.state.watcher.close()
this.state?.watcher?.close()
}

public componentDidUpdate(prevProps: Props, prevState: State) {
if (prevState?.selectedProfile !== this.state?.selectedProfile) {
if (!this.state?.selectedProfile) return
const statusWatcher = new ProfileStatusWatcher(this.state.selectedProfile, this.statusWatcherUpdateFn)
this.setState({ statusWatcher })
}
}

public render() {
if (this.state && this.state.catastrophicError) {
if (this.state?.catastrophicError) {
return "Internal Error"
} else if (!this.state || !this.state.profiles || !this.state.selectedProfile) {
return <Loading />
Expand All @@ -146,20 +169,38 @@ export default class ProfileExplorer extends React.PureComponent<Props, State> {
profile={this.state.selectedProfile}
profiles={this.state.profiles}
onSelectProfile={this._handleProfileSelection}
profileReadiness={this.state.statusWatcher?.readiness}
profileStatus={this.state.statusWatcher}
/>
</div>
)
}
}
}

class ProfileCard extends React.PureComponent<{
type ProfileCardProps = {
profile: string
profiles: Profiles.Profile[]
onSelectProfile: (profile: string) => void
}> {

profileReadiness: string
profileStatus: ProfileStatusWatcher
}

type ProfileCardState = {
isOpen: boolean
}

class ProfileCard extends React.PureComponent<ProfileCardProps, ProfileCardState> {
public constructor(props: ProfileCardProps) {
super(props)
this.state = {
isOpen: false,
}
}
private readonly _handleBoot = () => handleBoot(this.props.profile)
private readonly _handleShutdown = () => handleShutdown(this.props.profile)
private readonly _onToggle = () => this.setState({ isOpen: !this.state.isOpen })

private title() {
return (
Expand All @@ -174,7 +215,28 @@ class ProfileCard extends React.PureComponent<{
}

private actions() {
return "Status: pending"
const StatusTitle = ({ readiness }: { readiness: string | undefined }) => (
<React.Fragment>
<span>Status</span>
<div
className={`codeflare--profile-explorer--status-light codeflare--profile-explorer--status-light--${readiness}`}
></div>
</React.Fragment>
)
return (
<Select
className="codeflare--profile-explorer--select-status"
variant={SelectVariant.single}
placeholderText={<StatusTitle readiness={this.props.profileStatus?.readiness} />}
label="Status select"
onToggle={this._onToggle}
isOpen={this.state.isOpen}
aria-labelledby="select-status-label"
>
<SelectOption isPlaceholder>{this.props.profileStatus?.head.label}</SelectOption>
<SelectOption isPlaceholder>{this.props.profileStatus?.workers.label}</SelectOption>
</Select>
)
}

private body() {
Expand Down
25 changes: 23 additions & 2 deletions plugins/plugin-codeflare/src/tray/watchers/profile/status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,21 @@ export default class ProfileStatusWatcher {
/* this._job = */ this.initJob(profile)
}

public get readiness() {
return this.headReadiness === "pending" || this.workerReadiness === "pending"
? "pending"
: this.headReadiness === "error" || this.workerReadiness === "error"
? "error"
: !this.isReady(this.headReadiness) && !this.isReady(this.workerReadiness)
? "pending"
: "success"
}

private isReady(readiness: string) {
const match = readiness.match(/^(\d)+\/(\d)+$/)
return match && match[1] === match[2]
}

public get head() {
return { label: `Head nodes: ${this.headReadiness}` }
}
Expand Down Expand Up @@ -102,12 +117,15 @@ export default class ProfileStatusWatcher {
})

job.stdout.on("data", (data) => {
const headBefore = this.headReadiness
const workersBefore = this.workerReadiness

data
.toString()
.split(/\n/)
.forEach((line: string) => {
Debug("codeflare")("profile status watcher line", line)
const match = line.match(/^(head|workers)\s+(\S+)$/)
Debug("codeflare")("profile status watcher line", this.profile, line, match)
if (!match) {
// console.error('Bogus line emitted by ray cluster readiness probe', line)
} else {
Expand All @@ -121,7 +139,10 @@ export default class ProfileStatusWatcher {
}
})

this.updateFunction()
if (this.headReadiness !== headBefore || this.workerReadiness !== workersBefore) {
Debug("codeflare")("profile status watcher change", this.profile)
this.updateFunction()
}
})

return job
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,34 @@
* limitations under the License.
*/

.codeflare--profile-explorer {
@mixin ProfileExplorer {
.codeflare--profile-explorer {
@content;
}
}

@mixin ProfileStatus {
.codeflare--profile-explorer--select-status {
@content;
}
}

@include ProfileExplorer {
font-family: var(--font-sans-serif);

.pf-c-card {
--pf-c-card--BackgroundColor: var(--color-base00);
}

.pf-c-select {
--pf-c-select__toggle--BackgroundColor: var(--color-base00);

button,
.pf-c-select__toggle-text {
color: var(--color-text-01) !important;
}
}

hr.pf-c-divider {
margin: 0;
border: none;
Expand All @@ -25,4 +50,25 @@
overflow: hidden;
text-overflow: ellipsis;
}

&--status-light {
width: 10px;
height: 10px;
border-radius: 50%;
display: inline-block;
margin-left: 10px;
background-color: var(--color-gray);

&--error {
background-color: var(--color-error);
}

&--pending {
background-color: var(--color-warning);
}

&--success {
background-color: var(--color-ok);
}
}
}

0 comments on commit 534c94f

Please sign in to comment.