Skip to content

Commit

Permalink
Convert more components to functional components
Browse files Browse the repository at this point in the history
Convert some of the remaining class-based React components to
functional components, ensuring no change in props or functionality.

This also allows us to reduce the number of instances of the `injectIntl`
HOC in favour of the preferred `useIntl` hook. A side effect of this
is that some of the stories on the Storybook welcome page now render
their code correctly without the `injectIntl` wrapper.
  • Loading branch information
AlanGreene committed Aug 23, 2024
1 parent 918764a commit e360b5f
Show file tree
Hide file tree
Showing 8 changed files with 618 additions and 686 deletions.
124 changes: 66 additions & 58 deletions packages/components/src/components/Actions/Actions.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

import { Component } from 'react';
import { injectIntl } from 'react-intl';
import { useState } from 'react';
import { useIntl } from 'react-intl';
import {
Button,
MenuButton,
Expand All @@ -24,38 +24,53 @@ import {

import Modal from '../Modal';

class Actions extends Component {
state = { showDialog: false };
export default function Actions({ items, kind, resource }) {
const intl = useIntl();

handleModalAction = () => {
const { action } = this.state;
action();
this.handleClose();
};
const [state, setState] = useState({
action: undefined,
modal: {},
showDialog: false
});

handleClose = () => {
this.setState({ showDialog: false });
};
function handleClose() {
setState(prevState => ({ ...prevState, showDialog: false }));
}

function handleModalAction() {
state.action();
handleClose();
}

handleClick = (action, modalProperties) => {
function handleClick(itemAction, modalProperties) {
if (modalProperties) {
this.setState({ action, modal: modalProperties, showDialog: true });
setState(prevState => ({
...prevState,
action: itemAction,
modal: modalProperties,
showDialog: true
}));
} else {
action();
itemAction();
}
};
}

getButton() {
const { intl, items, kind, resource } = this.props;
function getButton() {
const isButton = kind === 'button';

if (isButton && items.length === 1) {
const { action, actionText, icon, modalProperties } = items[0];
const {
action: itemAction,
actionText,
danger,
icon,
modalProperties
} = items[0];
return (
<Button
kind="tertiary"
kind={danger ? 'danger' : 'tertiary'}
onClick={() =>
this.handleClick(() => action(resource), modalProperties)
handleClick(() => itemAction(resource), modalProperties)
}
renderIcon={icon}
size="md"
Expand All @@ -82,7 +97,7 @@ class Actions extends Component {
{items.map(item => {
const {
actionText,
action,
action: itemAction,
danger,
disable,
hasDivider,
Expand All @@ -98,7 +113,7 @@ class Actions extends Component {
kind={danger ? 'danger' : 'default'}
label={actionText}
onClick={() =>
this.handleClick(() => action(resource), modalProperties)
handleClick(() => itemAction(resource), modalProperties)
}
/>
</>
Expand All @@ -120,7 +135,7 @@ class Actions extends Component {
{items.map(item => {
const {
actionText,
action,
action: itemAction,
danger,
disable,
hasDivider,
Expand All @@ -135,7 +150,7 @@ class Actions extends Component {
itemText={actionText}
key={actionText}
onClick={() =>
this.handleClick(() => action(resource), modalProperties)
handleClick(() => itemAction(resource), modalProperties)
}
requireTitle
/>
Expand All @@ -145,38 +160,31 @@ class Actions extends Component {
);
}

render() {
const { modal = {}, showDialog } = this.state;
const { intl, resource } = this.props;
const dialog = state.showDialog ? (
<Modal
open={state.showDialog}
modalHeading={state.modal.heading}
primaryButtonText={state.modal.primaryButtonText}
secondaryButtonText={
state.modal.secondaryButtonText ||
intl.formatMessage({
id: 'dashboard.modal.cancelButton',
defaultMessage: 'Cancel'
})
}
onRequestClose={handleClose}
onRequestSubmit={handleModalAction}
onSecondarySubmit={handleClose}
danger={state.modal.danger}
>
{state.modal.body && state.modal.body(resource)}
</Modal>
) : null;

const dialog = showDialog ? (
<Modal
open={showDialog}
modalHeading={modal.heading}
primaryButtonText={modal.primaryButtonText}
secondaryButtonText={
modal.secondaryButtonText ||
intl.formatMessage({
id: 'dashboard.modal.cancelButton',
defaultMessage: 'Cancel'
})
}
onRequestClose={this.handleClose}
onRequestSubmit={this.handleModalAction}
onSecondarySubmit={this.handleClose}
danger={modal.danger}
>
{modal.body && modal.body(resource)}
</Modal>
) : null;

return (
<>
{this.getButton()}
{dialog}
</>
);
}
return (
<>
{getButton()}
{dialog}
</>
);
}

export default injectIntl(Actions);
36 changes: 16 additions & 20 deletions packages/components/src/components/Actions/Actions.stories.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,33 +44,29 @@ const deleteAction = {
}
};

const props = {
items: [
{ action: () => {}, actionText: 'Rerun' },
{
action: () => {},
actionText: 'disabled option',
disable: () => true,
modalProperties: {
body: () => 'modal body',
heading: 'Modal Heading',
primaryButtonText: 'primary text',
secondaryButtonText: 'secondary text'
}
},
deleteAction
]
};

export const Default = {
args: {
...props
items: [
{ action: () => {}, actionText: 'Rerun' },
{
action: () => {},
actionText: 'disabled option',
disable: () => true,
modalProperties: {
body: () => 'modal body',
heading: 'Modal Heading',
primaryButtonText: 'primary text',
secondaryButtonText: 'secondary text'
}
},
deleteAction
]
}
};

export const Button = {
args: {
...props,
...Default.args,
kind: 'button'
}
};
Expand Down
109 changes: 52 additions & 57 deletions packages/components/src/components/DetailsHeader/DetailsHeader.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,26 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

import { Component } from 'react';
import { Pending as DefaultIcon } from '@carbon/react/icons';
import { injectIntl } from 'react-intl';
import { useIntl } from 'react-intl';
import { getStatus } from '@tektoncd/dashboard-utils';

import FormattedDuration from '../FormattedDuration';
import StatusIcon from '../StatusIcon';

class DetailsHeader extends Component {
getDuration() {
const { intl, stepStatus, taskRun } = this.props;
export default function DetailsHeader({
displayName,
exitCode,
hasWarning,
reason,
stepStatus,
status,
taskRun = {},
type = 'step'
}) {
const intl = useIntl();

function getDuration() {
let { completionTime: endTime, startTime } = taskRun.status || {};
if (stepStatus) {
({ finishedAt: endTime, startedAt: startTime } =
Expand Down Expand Up @@ -53,8 +62,7 @@ class DetailsHeader extends Component {
);
}

statusLabel() {
const { exitCode, hasWarning, intl, reason, status, taskRun } = this.props;
function getStatusLabel() {
const { reason: taskReason, status: taskStatus } = getStatus(taskRun);

if (
Expand Down Expand Up @@ -108,58 +116,45 @@ class DetailsHeader extends Component {
});
}

render() {
const {
displayName,
hasWarning,
intl,
taskRun,
type = 'step'
} = this.props;
let { reason, status } = this.props;
let statusLabel;
let statusLabel;

const duration = this.getDuration();
const duration = getDuration();

if (type === 'taskRun') {
({ reason, status } = getStatus(taskRun));
statusLabel =
reason ||
intl.formatMessage({
id: 'dashboard.taskRun.status.pending',
defaultMessage: 'Pending'
});
} else {
statusLabel = this.statusLabel();
}
let reasonToUse = reason;
let statusToUse = status;

return (
<header
className="tkn--step-details-header"
data-status={status}
data-reason={reason}
>
<h2 className="tkn--details-header--heading">
<StatusIcon
DefaultIcon={props => <DefaultIcon size={24} {...props} />}
hasWarning={hasWarning}
reason={reason}
status={status}
{...(type === 'step' ? { type: 'inverse' } : null)}
/>
<span className="tkn--run-details-name" title={displayName}>
{displayName}
</span>
<span className="tkn--status-label">{statusLabel}</span>
</h2>
{duration}
</header>
);
if (type === 'taskRun') {
({ reason: reasonToUse, status: statusToUse } = getStatus(taskRun));
statusLabel =
reasonToUse ||
intl.formatMessage({
id: 'dashboard.taskRun.status.pending',
defaultMessage: 'Pending'
});
} else {
statusLabel = getStatusLabel();
}
}

DetailsHeader.defaultProps = {
taskRun: {}
};

export default injectIntl(DetailsHeader);
return (
<header
className="tkn--step-details-header"
data-status={statusToUse}
data-reason={reasonToUse}
>
<h2 className="tkn--details-header--heading">
<StatusIcon
DefaultIcon={props => <DefaultIcon size={24} {...props} />}
hasWarning={hasWarning}
reason={reasonToUse}
status={statusToUse}
{...(type === 'step' ? { type: 'inverse' } : null)}
/>
<span className="tkn--run-details-name" title={displayName}>
{displayName}
</span>
<span className="tkn--status-label">{statusLabel}</span>
</h2>
{duration}
</header>
);
}
Loading

0 comments on commit e360b5f

Please sign in to comment.