Skip to content

Commit

Permalink
feat(declarative-instigator-status): add button to reset instigator s…
Browse files Browse the repository at this point in the history
…tatus (#19258)

## Summary & Motivation

Building off of #19234 and
#19242, add a button to reset
the instigator status to track what's defined in code.

To do this, we've taken the instigator switch and put it in a row in the
instigator details table. We render a button in this row if the user can
reset the instigator. This button should only show up if you've taken
manual intervention to change the instigator status.

In a follow-up PR, we can this reset behavior to the bulk actions menu.

## How I Tested These Changes


https://github.com/dagster-io/dagster/assets/16431325/f0f880d7-a8ba-4733-84f2-cb26d617e92e
  • Loading branch information
rexledesma authored Jan 19, 2024
1 parent 42f17af commit 2e20220
Show file tree
Hide file tree
Showing 14 changed files with 270 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
import {useState} from 'react';

import {SchedulePartitionStatus} from './SchedulePartitionStatus';
import {ScheduleResetButton} from './ScheduleResetButton';
import {ScheduleSwitch} from './ScheduleSwitch';
import {TimestampDisplay} from './TimestampDisplay';
import {humanCronString} from './humanCronString';
Expand Down Expand Up @@ -46,12 +47,7 @@ export const ScheduleDetails = (props: {
return (
<>
<PageHeader
title={
<Box flex={{direction: 'row', alignItems: 'center', gap: 12}}>
<Heading>{name}</Heading>
<ScheduleSwitch repoAddress={repoAddress} schedule={schedule} />
</Box>
}
title={<Heading>{name}</Heading>}
tags={
<Tag icon="schedule">
Schedule in <RepositoryLink repoAddress={repoAddress} />
Expand Down Expand Up @@ -127,6 +123,21 @@ export const ScheduleDetails = (props: {
/>
</td>
</tr>
<tr>
<td>
<Box flex={{alignItems: 'center'}} style={{height: '32px'}}>
Running
</Box>
</td>
<td>
<Box flex={{direction: 'row', alignItems: 'center'}}>
<ScheduleSwitch repoAddress={repoAddress} schedule={schedule} />
{schedule.canReset && (
<ScheduleResetButton repoAddress={repoAddress} schedule={schedule} />
)}
</Box>
</td>
</tr>
<tr>
<td>Partition set</td>
<td>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import {gql} from '@apollo/client';

import {StartThisScheduleMutation, StopScheduleMutation} from './types/ScheduleMutations.types';
import {
ResetScheduleMutation,
StartThisScheduleMutation,
StopScheduleMutation,
} from './types/ScheduleMutations.types';
import {showCustomAlert} from '../app/CustomAlertProvider';
import {PYTHON_ERROR_FRAGMENT} from '../app/PythonErrorFragment';
import {PythonErrorInfo} from '../app/PythonErrorInfo';
Expand Down Expand Up @@ -48,8 +52,28 @@ export const STOP_SCHEDULE_MUTATION = gql`
${PYTHON_ERROR_FRAGMENT}
`;

export const RESET_SCHEDULE_MUTATION = gql`
mutation ResetSchedule($scheduleSelector: ScheduleSelector!) {
resetSchedule(scheduleSelector: $scheduleSelector) {
... on ScheduleStateResult {
scheduleState {
id
status
runningCount
}
}
... on UnauthorizedError {
message
}
...PythonErrorFragment
}
}
${PYTHON_ERROR_FRAGMENT}
`;

export const displayScheduleMutationErrors = (
data: StartThisScheduleMutation | StopScheduleMutation,
data: StartThisScheduleMutation | StopScheduleMutation | ResetScheduleMutation,
) => {
let error;
if ('startSchedule' in data && data.startSchedule.__typename === 'PythonError') {
Expand All @@ -59,6 +83,8 @@ export const displayScheduleMutationErrors = (
data.stopRunningSchedule.__typename === 'PythonError'
) {
error = data.stopRunningSchedule;
} else if ('resetSchedule' in data && data.resetSchedule.__typename === 'PythonError') {
error = data.resetSchedule;
}

if (error) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import {useMutation} from '@apollo/client';
import {Button, Tooltip} from '@dagster-io/ui-components';
import * as React from 'react';

import {RESET_SCHEDULE_MUTATION, displayScheduleMutationErrors} from './ScheduleMutations';
import {
ResetScheduleMutation,
ResetScheduleMutationVariables,
} from './types/ScheduleMutations.types';
import {ScheduleFragment} from './types/ScheduleUtils.types';
import {DEFAULT_DISABLED_REASON, usePermissionsForLocation} from '../app/Permissions';
import {repoAddressToSelector} from '../workspace/repoAddressToSelector';
import {RepoAddress} from '../workspace/types';

interface Props {
repoAddress: RepoAddress;
schedule: ScheduleFragment;
}

export const ScheduleResetButton = ({repoAddress, schedule}: Props) => {
const {
permissions: {canStartSchedule, canStopRunningSchedule},
} = usePermissionsForLocation(repoAddress.location);

const {name} = schedule;
const scheduleSelector = {
...repoAddressToSelector(repoAddress),
scheduleName: name,
};

const [resetSchedule, {loading: toggleOnInFlight}] = useMutation<
ResetScheduleMutation,
ResetScheduleMutationVariables
>(RESET_SCHEDULE_MUTATION, {
onCompleted: displayScheduleMutationErrors,
});
const onClick = () => {
resetSchedule({variables: {scheduleSelector}});
};

const hasPermission = canStartSchedule && canStopRunningSchedule;
const disabled = toggleOnInFlight || !hasPermission;
const tooltipContent = hasPermission
? `In code, a default status for "${name}" has been set to "${schedule.defaultStatus}". Click here to reset the schedule status to track the status set in code.`
: DEFAULT_DISABLED_REASON;

return (
<Tooltip content={tooltipContent} display="flex">
<Button disabled={disabled} onClick={onClick}>
Reset schedule status
</Button>
</Tooltip>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ export const SCHEDULE_FRAGMENT = gql`
id
name
}
defaultStatus
canReset
scheduleState {
id
...InstigationStateFragment
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {useState} from 'react';

import {EditCursorDialog} from './EditCursorDialog';
import {SensorMonitoredAssets} from './SensorMonitoredAssets';
import {SensorResetButton} from './SensorResetButton';
import {SensorSwitch} from './SensorSwitch';
import {SensorTargetList} from './SensorTargetList';
import {SensorFragment} from './types/SensorFragment.types';
Expand Down Expand Up @@ -83,19 +84,12 @@ export const SensorDetails = ({
return (
<>
<PageHeader
title={
<Box flex={{direction: 'row', alignItems: 'center', gap: 12}}>
<Heading>{name}</Heading>
<SensorSwitch repoAddress={repoAddress} sensor={sensor} />
</Box>
}
title={<Heading>{name}</Heading>}
icon="sensors"
tags={
<>
<Tag icon="sensors">
Sensor in <RepositoryLink repoAddress={repoAddress} />
</Tag>
</>
<Tag icon="sensors">
Sensor in <RepositoryLink repoAddress={repoAddress} />
</Tag>
}
right={
<Box margin={{top: 4}} flex={{direction: 'row', alignItems: 'center', gap: 8}}>
Expand Down Expand Up @@ -164,6 +158,22 @@ export const SensorDetails = ({
</td>
</tr>
) : null}
<tr>
<td>
<Box flex={{alignItems: 'center'}} style={{height: '32px'}}>
Running
</Box>
</td>
<td>
<Box
flex={{direction: 'row', gap: 12, alignItems: 'center'}}
style={{height: '32px'}}
>
<SensorSwitch repoAddress={repoAddress} sensor={sensor} />
{sensor.canReset && <SensorResetButton repoAddress={repoAddress} sensor={sensor} />}
</Box>
</td>
</tr>
<tr>
<td>Frequency</td>
<td>{humanizeSensorInterval(sensor.minIntervalSeconds)}</td>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ export const SENSOR_FRAGMENT = gql`
nextTick {
timestamp
}
defaultStatus
canReset
sensorState {
id
...InstigationStateFragment
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import {gql} from '@apollo/client';

import {StartSensorMutation, StopRunningSensorMutation} from './types/SensorMutations.types';
import {
ResetSensorMutation,
StartSensorMutation,
StopRunningSensorMutation,
} from './types/SensorMutations.types';
import {showCustomAlert} from '../app/CustomAlertProvider';
import {PYTHON_ERROR_FRAGMENT} from '../app/PythonErrorFragment';
import {PythonErrorInfo} from '../app/PythonErrorInfo';
Expand Down Expand Up @@ -47,19 +51,44 @@ export const STOP_SENSOR_MUTATION = gql`
${PYTHON_ERROR_FRAGMENT}
`;

export const RESET_SENSOR_MUTATION = gql`
mutation ResetSensor($sensorSelector: SensorSelector!) {
resetSensor(sensorSelector: $sensorSelector) {
... on Sensor {
id
sensorState {
id
status
}
}
... on SensorNotFoundError {
message
}
... on UnauthorizedError {
message
}
...PythonErrorFragment
}
}
${PYTHON_ERROR_FRAGMENT}
`;

export const displaySensorMutationErrors = (
data: StartSensorMutation | StopRunningSensorMutation,
data: StartSensorMutation | StopRunningSensorMutation | ResetSensorMutation,
) => {
let error;
if ('startSensor' in data && data.startSensor.__typename === 'PythonError') {
error = data.startSensor;
} else if ('stopSensor' in data && data.stopSensor.__typename === 'PythonError') {
error = data.stopSensor;
} else if ('resetSensor' in data && data.resetSensor.__typename === 'PythonError') {
error = data.resetSensor;
}

if (error) {
showCustomAlert({
title: 'Schedule Response',
title: 'Sensor Response',
body: <PythonErrorInfo error={error} />,
});
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import {useMutation} from '@apollo/client';
import {Button, Tooltip} from '@dagster-io/ui-components';
import * as React from 'react';

import {RESET_SENSOR_MUTATION, displaySensorMutationErrors} from './SensorMutations';
import {SensorFragment} from './types/SensorFragment.types';
import {ResetSensorMutation, ResetSensorMutationVariables} from './types/SensorMutations.types';
import {DEFAULT_DISABLED_REASON, usePermissionsForLocation} from '../app/Permissions';
import {repoAddressToSelector} from '../workspace/repoAddressToSelector';
import {RepoAddress} from '../workspace/types';

interface Props {
repoAddress: RepoAddress;
sensor: SensorFragment;
}

export const SensorResetButton = ({repoAddress, sensor}: Props) => {
const {
permissions: {canStartSensor, canStopSensor},
} = usePermissionsForLocation(repoAddress.location);

const {name} = sensor;
const sensorSelector = {
...repoAddressToSelector(repoAddress),
sensorName: name,
};

const [resetSensor, {loading: toggleOnInFlight}] = useMutation<
ResetSensorMutation,
ResetSensorMutationVariables
>(RESET_SENSOR_MUTATION, {
onCompleted: displaySensorMutationErrors,
});
const onClick = () => {
resetSensor({variables: {sensorSelector}});
};

const hasPermission = canStartSensor && canStopSensor;
const disabled = toggleOnInFlight || !hasPermission;
const tooltipContent = hasPermission
? `In code, a default status for "${name}" has been set to "${sensor.defaultStatus}". Click here to reset the sensor status to track the status set in code.`
: DEFAULT_DISABLED_REASON;

return (
<Tooltip content={tooltipContent} display="flex">
<Button disabled={disabled} onClick={onClick}>
Reset sensor status
</Button>
</Tooltip>
);
};

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

1 comment on commit 2e20220

@github-actions
Copy link

Choose a reason for hiding this comment

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

Deploy preview for dagit-core-storybook ready!

✅ Preview
https://dagit-core-storybook-8wl6ywysa-elementl.vercel.app

Built with commit 2e20220.
This pull request is being automatically deployed with vercel-action

Please sign in to comment.