Skip to content

Commit

Permalink
278-action-buttons: Add tooltip with the reason an action is disabled
Browse files Browse the repository at this point in the history
  • Loading branch information
Ruben van Leeuwen committed Oct 9, 2023
1 parent a125670 commit 83bf318
Show file tree
Hide file tree
Showing 6 changed files with 192 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,20 @@ import {
EuiAvatar,
EuiTitle,
EuiPopover,
EuiToolTip,
} from '@elastic/eui';
import { useTranslations } from 'next-intl';

import { useOrchestratorTheme } from '../../hooks';

import { TranslationValues } from 'next-intl';

import {
SubscriptionAction,
useSubscriptionActions,
} from '../../hooks/useSubscriptionActions';
import { WFOXCircleFill } from '../../icons';
import { ReactJSXElement } from '@emotion/react/types/jsx-namespace';

type MenuItemProps = {
key: string;
Expand All @@ -39,25 +47,9 @@ export type WFOSubscriptionActionsProps = {
export const WFOSubscriptionActions: FC<WFOSubscriptionActionsProps> = ({
subscriptionId,
}) => {
const MenuItem: FC<MenuItemProps> = ({ icon, action, key }) => {
return (
<Link
href={{
pathname: `/start-workflow/${action.name}`,
query: { subscriptionId: subscriptionId },
}}
>
<EuiContextMenuItem
key={key}
icon={<EuiAvatar name={icon} size="s" />}
>
{action.description}
</EuiContextMenuItem>
</Link>
);
};
const { theme } = useOrchestratorTheme();

const t = useTranslations('subscriptions.detail.workflow');
const t = useTranslations('subscriptions.detail.actions');
const [isPopoverOpen, setPopover] = useState(false);
const { data: subscriptionActions } =
useSubscriptionActions(subscriptionId);
Expand All @@ -70,6 +62,113 @@ export const WFOSubscriptionActions: FC<WFOSubscriptionActionsProps> = ({
setPopover(false);
};

const MenuItem: FC<MenuItemProps> = ({ icon, action }) => {
// Change icon to include x if there's a reason
// Add tooltip with reason
const linkIt = (actionItem: ReactJSXElement) => {
return (
<Link
href={{
pathname: `/start-workflow/${action.name}`,
query: { subscriptionId: subscriptionId },
}}
>
{actionItem}
</Link>
);
};

const flattenArrayProps = (): TranslationValues => {
const flatObject: TranslationValues = {};
for (const [key, value] of Object.entries(action)) {
if (Array.isArray(value)) {
flatObject[key] = value.join(', ');
} else {
flatObject[key] = value;
}
}
return action ? flatObject : {};
};

const tooltipIt = (actionItem: ReactJSXElement) => {
/**
Whether an action is disabled is indicated by it having a reason property.
The value of the reason property is as a translation key that should
be part of the local translations under subscription.details.workflow.disableReasons
Some of these reasons may contain dynamic values. The values are passed as extra keys next to
the reason key. The complete reason object is passed to the translate function to make this work.
An extra variable passed in might be of type array, before passing it in arrays are flattened to ,
concatenated strings.
Example action item response for an action that is disabled
const reason = {
name: "...",
description: "...",
reason: "random_reason_translation_key" =>
this maps to a key in subscription.details.workflow.disableReasons containing
".... {randomVar1} .... {randomVar2} "
randomVar: [
"array value 1",
"array value 2"
],
randomVar2: "flat string"
}
// Translation function invocation
t('randonReason', reason)
*/
if (!action.reason) return actionItem;

const tooltipContent = t(action.reason, flattenArrayProps());

return (
<div>
<EuiToolTip position="top" content={tooltipContent}>
{actionItem}
</EuiToolTip>
</div>
);
};

const getIcon = () => {
return action.reason ? (
<div css={{ display: 'flex', width: '32px' }}>
<EuiAvatar
name={icon}
size="s"
color={theme.colors.lightShade}
/>
<div
css={{
transform: 'translate(-11px, -8px);',
}}
>
<WFOXCircleFill
width={20}
height={20}
color={theme.colors.danger}
/>
</div>
</div>
) : (
<div css={{ width: '32px' }}>
<EuiAvatar name={icon} size="s" />
</div>
);
};

const ActionItem = () => (
<EuiContextMenuItem icon={getIcon()} disabled={!!action.reason}>
{action.description}
</EuiContextMenuItem>
);

return action?.reason
? tooltipIt(<ActionItem />)
: linkIt(<ActionItem />);
};

const button = (
<EuiButton
iconType="arrowDown"
Expand All @@ -91,20 +190,6 @@ export const WFOSubscriptionActions: FC<WFOSubscriptionActionsProps> = ({
>
<EuiContextMenuPanel>
<EuiPanel color="transparent" paddingSize="s">
{subscriptionActions && subscriptionActions.create && (
<>
<MenuBlock title={t('create')}></MenuBlock>
{subscriptionActions.create.map((action, index) => (
<MenuItem
key={`c_${index}`}
icon={'CreateLong'}
action={action}
index={index}
/>
))}
</>
)}

{subscriptionActions && subscriptionActions.modify && (
<>
<MenuBlock title={t('modify')}></MenuBlock>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { SubscriptionAction } from '../../../hooks';
import { FieldValue } from '../../../types';
import {
getFieldFromProductBlockInstanceValues,
getProductBlockTitle,
flattenArrayProps,
} from './utils';

describe('getFieldFromProductBlockInstanceValues()', () => {
Expand Down Expand Up @@ -68,3 +70,33 @@ describe('getProductBlockTitle()', () => {
expect(getProductBlockTitle(instanceValues)).toBe('');
});
});

describe('flattenArrayProps', () => {
it('should flatten an object with array values into a comma-separated string', () => {
const action: SubscriptionAction = {
name: 'action name',
description: 'action description',
usable_when: ['Status1', 'Status2', 'Status3'],
};

const result = flattenArrayProps(action);

expect(result).toEqual({
name: 'action name',
description: 'action description',
usable_when: 'Status1, Status2, Status3',
});
});

it('should handle an object with non-array values', () => {
const action: SubscriptionAction = {
name: 'action name',
description: 'action description',
};
const result = flattenArrayProps(action);
expect(result).toEqual({
name: 'action name',
description: 'action description',
});
});
});
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import React from 'react';
import { EuiIcon } from '@elastic/eui';
import { TranslationValues } from 'next-intl';

import { SubscriptionAction } from '../../../hooks';
import { FieldValue } from '../../../types';

const MAX_LABEL_LENGTH = 45;
Expand Down Expand Up @@ -63,3 +65,17 @@ export const getProductBlockTitle = (
? `${title.substring(0, MAX_LABEL_LENGTH)}...`
: title;
};

export const flattenArrayProps = (
action: SubscriptionAction,
): TranslationValues => {
const flatObject: TranslationValues = {};
for (const [key, value] of Object.entries(action)) {
if (Array.isArray(value)) {
flatObject[key] = value.join(', ');
} else {
flatObject[key] = value;
}
}
return action ? flatObject : {};
};
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ export interface SubscriptionAction {
interface SubscriptionActions {
reason?: string;
locked_relations?: string[];
create: SubscriptionAction[];
modify: SubscriptionAction[];
terminate: SubscriptionAction[];
system: SubscriptionAction[];
Expand All @@ -27,15 +26,16 @@ export const useSubscriptionActions = (subscriptionId: string) => {
const { subscriptionActionsEndpoint } = useContext(
OrchestratorConfigContext,
);
//https://orchestrator.dev.automation.surf.net/api/subscriptions/workflows/77466b50-951f-4362-a817-96ee66e63574

const fetchSubscriptionActions = async () => {
const response = await fetch(
`${subscriptionActionsEndpoint}/${subscriptionId}`,
{
method: 'GET',
},
);
return (await response.json()) as SubscriptionActions;
const actions = (await response.json()) as SubscriptionActions;
return actions;
};

return useQuery('subscriptionActions', fetchSubscriptionActions);
Expand Down
14 changes: 12 additions & 2 deletions packages/orchestrator-ui-components/src/messages/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -138,12 +138,22 @@
"relatedSubscriptions": "Related subscriptions"
},
"loadingStatus": "Loading status",
"workflow": {
"actions": {
"create": "Create workflow",
"modify": "Modify workflow",
"system": "System workflow",
"terminate": "Terminate workflow",
"actions": "Actions"
"actions": "Actions",
"subscription": {
"no_modify_deleted_related_objects": "This subscription can not be modified because it contains references to other systems that are deleted.",
"no_modify_in_use_by_subscription": "This subscription can not be {action} as it is used in other subscriptions: {unterminated_in_use_by_subscriptions}",
"no_modify_invalid_status": "This subscription can not be modified because of the status: {status}. Only subscriptions with status {usable_when} can be {action}.",
"no_modify_workflow": "This subscription can not be modified as the product has no modify workflows.",
"no_termination_workflow": "This subscription can not be terminated as the product has no termination workflows.",
"no_validate_workflow": "This subscription can not be validated as the product has no validate workflows.",
"not_in_sync": "This subscription can not be modified because it is not in sync. This means there is some error in the registration of the subscription or that it is being modified by another workflow.",
"relations_not_in_sync": "This subscription can not be modified because some related subscriptions are not insync. Locked subscriptions: {locked_relations}"
}
},
"subscriptionDetails": "Subscription details",
"fixedInputs": "Fixed inputs",
Expand Down
14 changes: 12 additions & 2 deletions packages/orchestrator-ui-components/src/messages/nl-NL.json
Original file line number Diff line number Diff line change
Expand Up @@ -138,12 +138,22 @@
"relatedSubscriptions": "Geralateerde subscripties"
},
"loadingStatus": "Laden",
"workflow": {
"actions": {
"create": "Create workflow",
"modify": "Modify workflow",
"system": "System workflow",
"terminate": "Terminate workflow",
"actions": "Acties"
"actions": "Acties",
"subscription": {
"no_modify_deleted_related_objects": "This subscription can not be modified because it contains references to other systems that are deleted.",
"no_modify_in_use_by_subscription": "This subscription can not be {action} as it is used in other subscriptions: {unterminated_in_use_by_subscriptions}",
"no_modify_invalid_status": "This subscription can not be modified because of the status: {status}. Only subscriptions with status {usable_when} can be {action}.",
"no_modify_workflow": "This subscription can not be modified as the product has no modify workflows.",
"no_termination_workflow": "This subscription can not be terminated as the product has no termination workflows.",
"no_validate_workflow": "This subscription can not be validated as the product has no validate workflows.",
"not_in_sync": "This subscription can not be modified because it is not in sync. This means there is some error in the registration of the subscription or that it is being modified by another workflow.",
"relations_not_in_sync": "This subscription can not be modified because some related subscriptions are not insync. Locked subscriptions: {locked_relations}"
}
},
"subscriptionDetails": "Subscription detail",
"fixedInputs": "Fixed inputs",
Expand Down

0 comments on commit 83bf318

Please sign in to comment.