Skip to content

Commit

Permalink
Added Notifications for Approvals
Browse files Browse the repository at this point in the history
  • Loading branch information
Lucifergene committed Apr 18, 2024
1 parent 0b33678 commit 87e401b
Show file tree
Hide file tree
Showing 6 changed files with 214 additions and 1 deletion.
11 changes: 11 additions & 0 deletions frontend/packages/pipelines-plugin/console-extensions.json
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,17 @@
"column": "right"
}
},
{
"type": "console.context-provider",
"properties": {
"provider": { "$codeRef": "pipelineApprovalContext.PipelineApprovalContextProvider" },
"useValueHook": { "$codeRef": "pipelineApprovalContext.usePipelineApprovalToast" }
},
"flags": {
"required": ["OPENSHIFT_PIPELINE_APPROVAL_TASK"],
"disallowed": ["CONSOLE_PIPELINE_PLUGIN"]
}
},
{
"type": "console.page/resource/list",
"properties": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -447,12 +447,20 @@
"Reject": "Reject",
"Approval status": "Approval status",
"Approval tasks": "Approval tasks",
"Your approval has been requested on {{plrs}} pipeline": "Your approval has been requested on {{plrs}} pipeline",
"runs.": "runs.",
"run.": "run.",
"Go to Approvals tab": "Go to Approvals tab",
"runs in other namespaces.": "runs in other namespaces.",
"run in other namespaces.": "run in other namespaces.",
"Go to Admin Approvals tab": "Go to Admin Approvals tab",
"An error occurred. Please try again": "An error occurred. Please try again",
"Are you sure you want to approve": "Are you sure you want to approve",
"Please provide a reason for not approving": "Please provide a reason for not approving",
"in": "in",
"Reason": "Reason",
"Submit": "Submit",
"Task approval required": "Task approval required",
"{{taskLabel}} details": "{{taskLabel}} details",
"CustomRun": "CustomRun",
"CustomRuns": "CustomRuns",
Expand Down
3 changes: 2 additions & 1 deletion frontend/packages/pipelines-plugin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
"tasksComponent": "src/components/tasks-index.ts",
"triggersComponent": "src/components/triggers-index.ts",
"pacComponent": "src/components/pac/index.ts",
"resultsComponent": "src/components/shared/results"
"resultsComponent": "src/components/shared/results",
"pipelineApprovalContext": "src/components/tasks/approval-tasks/pipeline-approval-context.tsx"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.odc-pl-approval-toast__link {
padding-top: 5px;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import * as React from 'react';
import { useTranslation } from 'react-i18next';
import { ExternalLink } from '@console/internal/components/utils';

import './ApprovalToastContent.scss';

interface ApprovalToastContentProps {
type: string;
uniquePipelineRuns: number;
devconsolePath?: string;
adminconsolePath?: string;
}

const ApprovalToastContent: React.FC<ApprovalToastContentProps> = ({
type,
uniquePipelineRuns,
devconsolePath,
adminconsolePath,
}) => {
const { t } = useTranslation();
if (type === 'current') {
return (
<>
{t('pipelines-plugin~Your approval has been requested on {{plrs}} pipeline', {
plrs: uniquePipelineRuns,
})}
{uniquePipelineRuns > 1 ? t('pipelines-plugin~runs.') : t('pipelines-plugin~run.')}
<p className="odc-pl-approval-toast__link">
<ExternalLink href={devconsolePath} text={t('pipelines-plugin~Go to Approvals tab')} />
</p>
</>
);
}
if (type === 'other') {
return (
<>
{t('pipelines-plugin~Your approval has been requested on {{plrs}} pipeline', {
plrs: uniquePipelineRuns,
})}
{uniquePipelineRuns > 1
? t('pipelines-plugin~runs in other namespaces.')
: t('pipelines-plugin~run in other namespaces.')}
<p className="odc-pl-approval-toast__link">
<ExternalLink
href={adminconsolePath}
text={t('pipelines-plugin~Go to Admin Approvals tab')}
/>
</p>
</>
);
}
return null;
};

export default ApprovalToastContent;
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import * as React from 'react';
import { AlertVariant } from '@patternfly/react-core';
import { useTranslation } from 'react-i18next';
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: FIXME missing exports due to out-of-sync @types/react-redux version
import { useSelector } from 'react-redux';
import { UserInfo, WatchK8sResource, getUser } from '@console/dynamic-plugin-sdk';
import { getGroupVersionKindForModel } from '@console/dynamic-plugin-sdk/src/utils/k8s/k8s-ref';
import { useK8sWatchResource } from '@console/internal/components/utils/k8s-watch-hook';
import { RootState } from '@console/internal/redux';
import { ApprovalTaskModel } from '@console/pipelines-plugin/src/models';
import { ApprovalStatus, ApprovalTaskKind } from '@console/pipelines-plugin/src/types';
import { useActiveNamespace } from '@console/shared/src';
import { useToast } from '@console/shared/src/components/toast';
import ApprovalToastContent from './ApprovalToastContent';

const getPipelineRunNameofEachApprover = (approvalTask: ApprovalTaskKind): string => {
/* TODO: Use the label tekton.dev/pipelineRunName */
return approvalTask?.metadata?.name?.split('-').slice(0, -1).join('-');
};

const getPipelineRunsofApprovals = (approvalTasks: ApprovalTaskKind[]): string[] => {
const pipelineRuns = [];
approvalTasks.forEach((approvalTask) => {
pipelineRuns.push(getPipelineRunNameofEachApprover(approvalTask));
});
return pipelineRuns;
};

const checkUserIsApprover = (approvalTask: ApprovalTaskKind, user: string): boolean => {
const approverList = approvalTask?.status?.approvals ?? [];
if (user === 'kube:admin' && approverList?.includes('kubernetes-admin')) {
return true;
}
return approverList?.includes(user);
};

export const PipelineApprovalContext = React.createContext({});

export const PipelineApprovalContextProvider = PipelineApprovalContext.Provider;

export const usePipelineApprovalToast = () => {
const { t } = useTranslation();
const toastContext = useToast();
const [namespace] = useActiveNamespace();
const user: UserInfo = useSelector<RootState, object>(getUser);
const [currentToasts, setCurrentToasts] = React.useState<{ [key: string]: { toastId: string } }>(
{},
);
const devconsolePath = `/dev-pipelines/ns/${namespace}/approvals?rowFilter-status=wait`;
const adminconsolePath = `pipelines/all-namespaces/approvals?rowFilter-status=wait`;

const approvalsResource: WatchK8sResource = {
groupVersionKind: getGroupVersionKindForModel(ApprovalTaskModel),
isList: true,
};
const [approvalTasks] = useK8sWatchResource<ApprovalTaskKind[]>(approvalsResource);

React.useEffect(() => {
if (currentToasts?.current?.toastId) {
toastContext.removeToast(currentToasts.current.toastId);
setCurrentToasts((toasts) => ({ ...toasts, current: { toastId: '' } }));
}
if (currentToasts?.other?.toastId) {
toastContext.removeToast(currentToasts.other.toastId);
setCurrentToasts((toasts) => ({ ...toasts, other: { toastId: '' } }));
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [approvalTasks, toastContext]);

React.useEffect(() => {
let toastID = '';
const userApprovalTasksInWait = approvalTasks.filter(
(approvalTask) =>
checkUserIsApprover(approvalTask, user.username) &&
approvalTask?.status?.approvalState === ApprovalStatus.RequestSent,
);

const [currentNsApprovalTasks, otherNsApprovalTasks]: [
ApprovalTaskKind[],
ApprovalTaskKind[],
] = userApprovalTasksInWait.reduce(
(acc, approvalTask) => {
approvalTask?.metadata?.namespace === namespace
? acc[0].push(approvalTask)
: acc[1].push(approvalTask);
return acc;
},
[[], []],
);

if (currentNsApprovalTasks.length > 0) {
const uniquePipelineRuns = new Set(getPipelineRunsofApprovals(currentNsApprovalTasks)).size;

if (uniquePipelineRuns > 0) {
toastID = toastContext.addToast({
variant: AlertVariant.custom,
title: t('pipelines-plugin~Task approval required'),
content: (
<ApprovalToastContent
type="current"
uniquePipelineRuns={uniquePipelineRuns}
devconsolePath={devconsolePath}
/>
),
timeout: 25000,
dismissible: true,
});
}
setCurrentToasts((toasts) => ({ ...toasts, current: { toastId: toastID } }));
}

if (otherNsApprovalTasks.length > 0) {
const uniquePipelineRuns = new Set(getPipelineRunsofApprovals(otherNsApprovalTasks)).size;

if (uniquePipelineRuns > 0) {
toastID = toastContext.addToast({
variant: AlertVariant.custom,
title: t('pipelines-plugin~Task approval required'),
content: (
<ApprovalToastContent
type="other"
uniquePipelineRuns={uniquePipelineRuns}
adminconsolePath={adminconsolePath}
/>
),
timeout: 25000,
dismissible: true,
});
}
setCurrentToasts((toasts) => ({ ...toasts, other: { toastId: toastID } }));
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [approvalTasks, user, t, toastContext]);
};

0 comments on commit 87e401b

Please sign in to comment.