지난 시간에 이어 48번 workflows-toolbar를 함수형 컴포넌트로 수정해보겠다.
workflows-toolbar.tsx
는 다음과 같다.
Workflow list page에 들어갔을 때
각 workflow의 seleted box를 클릭하면 나오는 toolbar를 의미한다.
cluster, cron, template에 있는 workflows를 모두 실행시키고 전체 체크박스를 눌러서 삭제를 하면
아래 안내판이 표시된다.
interface WorkflowsToolbarProps {
selectedWorkflows: Map<string, Workflow>; // 선택된 workflows를 전달받음
loadWorkflows: () => void; // action을 반영한 workflow List를 load함
isDisabled: Actions.OperationDisabled; // 사용할 수 있는 버튼인지
clearSelection: () => void; // 선택된 workflows를 초기화
}
interface WorkflowGroupAction extends WorkflowOperation { // 해당 Operation에 추가 속성 추가
groupIsDisabled: boolean; // 사용할 수 있는 그룹인지
className: string; // 이름
groupAction: () => Promise<any>;
}
export class WorkflowsToolbar extends React.Component<WorkflowsToolbarProps, {}> {
constructor(props: WorkflowsToolbarProps) {
super(props);
}
public render() {
return (
<Consumer>
{ctx => (
<div className={`workflows-toolbar ${this.getNumberSelected() === 0 ? 'hidden' : ''}`}>
<div className='workflows-toolbar__count'>
{this.getNumberSelected() === 0 ? 'No' : this.getNumberSelected()}
workflow{this.getNumberSelected() === 1 ? '' : 's'} selected
</div>
<div className='workflows-toolbar__actions'>{this.renderActions(ctx)}</div>
</div>
)}
</Consumer>
);
}
private getNumberSelected(): number { /* 선택된 workflows 개수를 출력 */
return this.props.selectedWorkflows.size;
}
private async performActionOnSelectedWorkflows(ctx: ContextApis, title: string, action: WorkflowOperationAction): Promise<any> {
/* 선택된 workflow에 대해서 Action을 수행 */
// ctx: ContextApis는 React에서 정의한 Context
/* 버튼 선택 후 확인 여부 안내 문구 */
const confirmed = await ctx.popup.confirm('Confirm', `Are you sure you want to ${title.toLowerCase()} all selected workflows?`);
if (!confirmed) { // 안내 문구 취소 선택 시
return Promise.resolve(false); // Promise는 성공, 이때 값은 false로 리턴
}
/* 삭제 버튼 시 archived workflows에 대해서 삭제 여부 안내 문구 */
let deleteArchived = false;
if (title === 'DELETE') {
for (const entry of this.props.selectedWorkflows) {
if (isArchivedWorkflow(entry[1])) { // 저장된 workflow인 경우
deleteArchived = await ctx.popup.confirm('Confirm', 'Do you also want to delete them from the Archived Workflows database?');
break;
}
}
}
/* 선택한 workflows에 대해서 Action API를 Promise에저장 */
const promises: Promise<any>[] = [];
this.props.selectedWorkflows.forEach((wf: Workflow) => { // 선택한 workflows에 대해서
if (title === 'DELETE') {
// The ones without archivalStatus label or with 'Archived' labels are the live workflows.
if (isWorkflowInCluster(wf)) { // 클러스터에 존재하는 workflows면
promises.push(
services.workflows.delete(wf.metadata.name, wf.metadata.namespace).catch(reason =>
ctx.notifications.show({ // 에러 catch
content: `Unable to delete workflow ${wf.metadata.name} in the cluster: ${reason.toString()}`,
type: NotificationType.Error
})
)
);
}
if (deleteArchived && isArchivedWorkflow(wf)) { // deleteArchive에 존재하고 archive된 workflow라면?
promises.push(
services.workflows.deleteArchived(wf.metadata.uid, wf.metadata.namespace).catch(reason =>
ctx.notifications.show({
content: `Unable to delete workflow ${wf.metadata.name} in database: ${reason.toString()}`,
type: NotificationType.Error
})
)
);
}
} else {
promises.push( // 나머지 경우에 대해서
action(wf).catch(reason => {
this.props.loadWorkflows();
ctx.notifications.show({
content: `Unable to ${title} workflow: ${reason.content.toString()}`,
type: NotificationType.Error
});
})
);
}
});
return Promise.all(promises); // promise 병렬 수행, 각 action에 대한 각 workflow의 action들이 담기고 이를 비동기로 처리하여 수행한 결과를 리턴하게 된다.
}
private renderActions(ctx: ContextApis): JSX.Element[] { // 수행할 수 있는 버튼에 대해 조사 후 render
/* --- renderAction 처리를 위한 로컬 변수 선언 --- */
const actionButtons = []; // 빈 배열 선언
const actions: any = Actions.WorkflowOperationsMap; // 여러 연산들(RETRY, RESUBMIT, SUBMIT 등)의 Map
const disabled = this.props.isDisabled; // 사용여부 변수 배열, 이름마다 담겨있음
const groupActions: WorkflowGroupAction[] = Object.keys(actions).map((actionName: WorkflowOperationName) => { // actions의 keys만 가져온 후 map으로 묶음
const action = actions[actionName]; // action 하나에 대해서
return {
title: action.title, // WorkflowOperation에 정의된 것
iconClassName: action.iconClassName, // WorkflowOperation에 정의된것
groupIsDisabled: disabled[actionName], // 선택된 workflow(=group)에 대한 사용여부, 우리가 굳이 사용 여부 판단할 필요는 없음(workflows-list에서 처리)
action, // WorkflowOperaion에 정의된 것, 딱히 필요없어보임
groupAction: async () => { // 선택된 workflow(=group)에 대해 처리할 액션 담기
const confirmed = await this.performActionOnSelectedWorkflows(ctx, action.title, action.action); // performActionOnSelectedWorkflows 수행 후 값을 리턴 받음
if (!confirmed) { // false를 받았을 때 아무처리 안 함
return;
}
this.props.clearSelection(); // 선택된 workflow 해제, workflow-list에서 selectedWorkflows를 비우는 함수를 진행
ctx.notifications.show({
content: `Performed '${action.title}' on selected workflows.`,
type: NotificationType.Success
});
this.props.loadWorkflows(); // 다시 workflows를 load함
},
className: action.title, // 위와 동일
disabled: () => false
} as WorkflowGroupAction;
});
/*--- 각 action에 대해서 각종 설정(사용가능여부, 클릭이벤트 처리) 해놓고 추가 ---*/
for (const groupAction of groupActions) { // 즉 처리될 버튼에 따라 action이 다르므로
actionButtons.push(
<button
key={groupAction.title}
onClick={() => {
groupAction.groupAction().catch(); // 성공시 groupAction()만 실패시 catch()까지
}}
className={`workflows-toolbar__actions--${groupAction.className} workflows-toolbar__actions--action`}
disabled={this.getNumberSelected() === 0 || groupAction.groupIsDisabled}>
<i className={groupAction.iconClassName} />
{groupAction.title}
</button>
);
}
return actionButtons;
}
}
export interface WorkflowOperation {
title: WorkflowOperationName; // type으로 정의 'RETRY','RESUBMIT'.'SUSPEND'.'RESUME','STOP','TERMINATE','DELETE' 가 있음
action: WorkflowOperationAction; // 버튼 클릭 시 발생하는 action
iconClassName: string; // 버튼안에 같이 쓰일 icon 이름
disabled: (wf: Workflow) => boolean; // 사용가능 여부
}
interface WorkflowGroupAction extends WorkflowOperation {
groupIsDisabled: boolean; // group(선택된 workflow)의 사용 가능 여부
className: string;
groupAction: () => Promise<any>; // 모든 버튼의 Promise 객체
}
export type WorkflowOperationAction = (wf: Workflow) => Promise<Workflow | WorkflowDeleteResponse>;
// eg. (wf: Workflow) => services.workflows.retry(wf.metadata.name, wf.metadata.namespace, null)
<button>
객체를 모으는 배열 export const WorkflowOperationsMap: WorkflowOperations = {
RETRY: {
title: 'RETRY',
iconClassName: 'fa fa-undo',
disabled: (wf: Workflow) => {
const workflowPhase: NodePhase = wf && wf.status ? wf.status.phase : undefined;
return workflowPhase === undefined || !(workflowPhase === 'Failed' || workflowPhase === 'Error');
},
action: (wf: Workflow) => services.workflows.retry(wf.metadata.name, wf.metadata.namespace, null)
},
RESUBMIT: {
title: 'RESUBMIT',
iconClassName: 'fa fa-plus-circle',
disabled: () => false,
action: (wf: Workflow) => services.workflows.resubmit(wf.metadata.name, wf.metadata.namespace, null)
},
export type OperationDisabled = {
[action in WorkflowOperationName]: boolean;
};
중간에 Promise 객체는 무엇이며 어떻게 처리할까?
자바스크립트에서 비동기처리가 필요한 이유
화면에서 서버로 데이터를 요청했을 때 서버가 언제 그 요청에 대한 응답을 할지도 모르는 상태에서 다른 코드를 실행 안하고 기다릴 수는 없기 때문이다.
(내가 생각한 비동기 그냥 동시에 실행 가능 한 것? 동기는 순차적으로 실행 가는한 것 -> 간단하게 함수 실행 시 응답(ex.값)이 보장되면 동기 보장이 안 되면 비동기, 즉 비동기 함수 코드가 종료되지 않았어도 대기하지 않고 다음 코드를 실행하는게 비동기)
: 비동기 처리를 위한 javascript 객체로 비동기로 실행된 작업의 결과를 나타내는 객체의 이름
: Promise가 생성된 시점에는 알려지지 않았을 수도 있는 값을 위한 대리자, 비동기 연산이 종료된 이후에 결과값과 실패 사유를 처리하기 위한 처리기를 연결할 수 있음
: executor라는 함수를 만들어서 비동기(시작 시간이 상관없음)로 실행, 성공이든 실패든 값을 반환 / 이때, 두 개의 callback 파라미터를 갖고 있는데 resolve(value)는 함수 안의 처리가 끝났을 때 성공시 value를 반환하는 함수를 호출, reject(error)는 실패시 error를 반환하는 함수
: then(메서드) - callback 함수를 인자로 받는 애 (성공과 실패에 대한 처리 모두 가능) 그러나 보통 resolve가 호출되면 실행
: catch - error 처리 부분 reject가 호출되면 실행
Promise도 결국 then을 이용하는데 좀 더 쉽게 처리하기 위해서는 async와 await을 사용
async : 암시적으로 Promise 객체를 반환
awiat : 기다려주는 함수
try / catch : reject 잡는 부분
Promise 객체가 사용되는 부분은 perfomAction 부분 박에 없는데.. 즉 Promise.all 메소드를 통해 해당 action에 대한 각 workflows 처리를 동시해 실행시킨다.
이 부분을 어떻게 처리하느냐가 관건인듯
윤우님께 여쭤보다
즉 Prmoise.all은 그대로 두되 다른 애들을 처리한다.
loadWorkflows()
는 변경된 workflow list를 불러오는 함수를 전달받는 것인데 결국 ListWatch 객체에서 업데이트하기 때문에 삭제한다. loadWorkflows()의 역할은 select를 다 해제하고 workflows를 load하는 역할임 그리고 props.loadWorkflows()를 호출하는 부분도 다 삭제! 따라서 최종적으로 다음과 같은 props가 존재한다.
interface WorkflowsToolbarProps {
selectedWorkflows: Map<string, Workflow>;
isDisabled: Actions.OperationDisabled;
clearSelection: () => void;
}
WorkflowsToolBar
가 계속 rendering 되면 이 getNumberSelected도 계속 호출하게 된다... function getNumberSelected(): number {
console.log('나 호출됨');
return props.selectedWorkflows.size;
}
콘솔창에 로그가 찍히도록 두니까 역시나 체크박스를 다르게 클릭할 때마다 계속 로그가 찍힌다. 즉 코드에 보면 rendering할 때 계속 이 getNuberSelected()함수가 계속 호출된다..
근데.. 내가 얻고 싶은 값은 부모 컴포넌트에서 변화가 일어나고 그 값을 받는다.
즉 부모에서 seletedWorkflows에 변화가 일어나면 이를 ToolBar 컴포넌트에 props를 전달하기 때문에 렌더링이 되어야 한다.
React는 정확히 언제 렌더링? 글을 참고!
따라서 함수호출을 할 필요 없이 그냥 Toolbar 컴포넌트에 변수를 할당한 후 해당 변수를 전달 받는 것이 효율적이다. (어차피 리렌더링 되면 props를 다시 받아와야 하니까!
const numberSelected: number = props.selectedWorkflows.size;
요렇게 정의했다.
Consumer
라는 객체로 감싸져있는 것을 확인할 수 있는데return (
<Consumer>
{ctx => (
<div className={`workflows-toolbar ${numberSelected === 0 ? 'hidden' : ''}`}>
<div className='workflows-toolbar__count'>
{numberSelected === 0 ? 'No' : numberSelected}
workflow{numberSelected === 1 ? '' : 's'} selected
</div>
<div className='workflows-toolbar__actions'>{renderActions(ctx)}</div>
</div>
)}
</Consumer>
);
지금 렌더링 파트를 보면 < Consumer >
객체를 확인할 수 있는데 Consumer 객체는 ctx라는 일종의 props 값을 전달하고 이 ctx에서 popup과 notification을 사용한다.
// 이것이 context.tx
import {AppContext as ArgoAppContext, NavigationApi, NotificationsApi, PopupApi} from 'argo-ui';
import {History} from 'history';
import * as React from 'react';
export type AppContext = ArgoAppContext & {apis: {popup: PopupApi; notifications: NotificationsApi; navigation: NavigationApi; baseHref: string}};
export interface ContextApis {
popup: PopupApi;
notifications: NotificationsApi;
navigation: NavigationApi;
history: History;
}
export const Context = React.createContext<ContextApis>(null);
export const {Provider, Consumer} = Context;
우리에겐.. useContext가 있지요 후후
먼저 여기서 사용하려는 context가 다 ContextApi이고 이는 context.ts에서 React.createContext<ContextApis>(null)
로 컨텍스트가 생성되었기 때문에 생성된 Context를 import를 통해 부르고 useContext를 통해서 popup과 notification 값을 가져온다.
import {Context} from '../../../shared/context';
export function WorkflowsToolbar(props: WorkflowsToolbarProps) {
const {popup, notifications} = useContext(Context);
이렇게 사용해서 파라미터에 ctx가 있는 부분을 다 제거해주고 popup과 notification만 사용해도 가능가능
그리고
return (
<div className={`workflows-toolbar ${numberSelected === 0 ? 'hidden' : ''}`}>
<div className='workflows-toolbar__count'>
{numberSelected === 0 ? 'No' : numberSelected}
workflow{numberSelected === 1 ? '' : 's'} selected
</div>
<div className='workflows-toolbar__actions'>{renderActions()}</div>
</div>
);
위와 같이 return을 수정해줘도 된다.
function renderActions(): JSX.Element[] {
const actionButtons = [];
const actions: any = Actions.WorkflowOperationsMap;
const disabled = props.isDisabled;
const groupActions: WorkflowGroupAction[] = Object.keys(actions).map((actionName: WorkflowOperationName) => {
const action = actions[actionName];
return {
title: action.title,
iconClassName: action.iconClassName,
groupIsDisabled: disabled[actionName],
action,
groupAction: async () => {
const confirmed = await performActionOnSelectedWorkflows(action.title, action.action);
if (!confirmed) {
return;
}
props.clearSelection();
notifications.show({
content: `Performed '${action.title}' on selected workflows.`,
type: NotificationType.Success
});
},
className: action.title,
disabled: () => false
} as WorkflowGroupAction;
});
for (const groupAction of groupActions) {
actionButtons.push(
<button
key={groupAction.title}
onClick={() => {
groupAction.groupAction().catch();
}}
className={`workflows-toolbar__actions--${groupAction.className} workflows-toolbar__actions--action`}
disabled={numberSelected === 0 || groupAction.groupIsDisabled}>
<i className={groupAction.iconClassName} />
{groupAction.title}
</button>
);
}
return actionButtons;
}
지금 renderActions 함수는 groupAction 각각에 대해서 정의한 변수들을 담은 groupActions 변수를 이용해서 각 action에 대한 버튼을 부여한 button 태그를 반환하고 있다. render하는 부분과 변수들을 정의하는 부분을 분리하는게 필요하다!!
우선 button 객체 생성하는 부분은 renderActions() 대신 다음이 들어간다.
{groupActions.map(action => {
return (
<button
key={action.title}
onClick={() => {
action.groupAction().catch();
}}
className={`workflows-toolbar__actions--${action.className} workflows-toolbar__actions--action`}
disabled={numberSelected === 0 || action.groupIsDisabled}>
<i className={action.iconClassName} />
{action.title}
</button>
);
})}
그리고 renderAcitons()에 들어가는 지역변수들은 WorkflowsToolbar 컴포넌트 변수로 넘겼다.
const groupActions = useMemo<WorkflowGroupAction[]>(() => {
const disabled = props.isDisabled;
return Object.keys(actions).map((actionName: WorkflowOperationName) => {
const action = actions[actionName];
return {
title: action.title,
iconClassName: action.iconClassName,
groupIsDisabled: disabled[actionName],
action,
groupAction: async () => {
const confirmed = await performActionOnSelectedWorkflows(action.title, action.action);
if (!confirmed) {
return;
}
props.clearSelection();
notifications.show({
content: `Performed '${action.title}' on selected workflows.`,
type: NotificationType.Success
});
},
className: action.title,
disabled: () => false
} as WorkflowGroupAction;
});
}, [props.selectedWorkflows]);
이때 renderAcitons 자체가 이 ToolBar가 렌더링되면 호출되는데, seletedWorkflows이 바뀜에 따라 렌더링되면 호출된다..
useEffect 함수 안에 groupAction을 지정하도록 할까 고민이었다.
그러나 useEffect의 경우 dependency 뿐만 아니라 생성될 때 언마운트 될 때 모두 호출하기 때문에
useMemo를 설정해두었다. 그런데 useMemo도 과연 활용하는게 맞나 싶은게 renderAction 자체가 selectedWorkflows가 바뀜에 따라 호출되는 아이인디 그냥 원래 바뀌잖슴??? useEffect, useMemo
mount되었을 때 7호출됨이 8개가 호출되었고 그 이후 selectedWorkflows에 변화가 있을 때 7호출이 쌓였다.
참고로 delete 이후 UI 업데이트가 안 되는 이슈에서 select 버튼을 누르면 workflow가 사라지는데 이 떄 저 toolbar는 select를 누른 상태를 유지하고 있어서 계속 저런 화면이 뜬다.( 정확히 말하면 selected Workflows에서 변화가 없기 때문에 )
또한 참고로 호출됨이 왜 하필 7번일까를 생각해보면 지금 toolbar 버튼이 7개이기 때문에 7개에 대해서 selected를 확인해야 하기 때문이다!!
const groupAction = useMemo<WorkflowsOperation[]>(() => {
console.log('호출됨');
const disabled = props.actionsIsDisabled;
return Object.keys(actions).map((actionName: WorkflowOperationName) => {
const action = actions[actionName];
console.log('호출됨22');
return {
title: action.title,
iconClassName: action.iconClassName,
isDisabled: disabled[actionName],
action,
workflowsAction: async () => {
const confirmed = await performActionOnSelectedWorkflows(action.title, action.action);
if (!confirmed) {
return;
}
props.clearSelection();
notifications.show({
content: `Performed '${action.title}' on selected workflows.`,
type: NotificationType.Success
});
},
disabled: () => false
} as WorkflowsOperation;
});
}, [props.selectedWorkflows]);
한 번 Mount 될 때 selectWorkflows에 변화가 있을 때 호출됨과 호출됨22가 각각 1번, 7번씩 호출된다.
위와 다른 점은 useMemo 때는 Mount 시 7호출됨이 한 번만 발생한다는 점이다.
const disabled = props.actionsIsDisabled;
const groupAction = Object.keys(actions).map((actionName: WorkflowOperationName) => {
const action = actions[actionName];
console.log('뭥미');
return {
title: action.title,
iconClassName: action.iconClassName,
isDisabled: disabled[actionName],
action,
workflowsAction: async () => {
const confirmed = await performActionOnSelectedWorkflows(action.title, action.action);
if (!confirmed) {
return;
}
props.clearSelection();
notifications.show({
content: `Performed '${action.title}' on selected workflows.`,
type: NotificationType.Success
});
},
disabled: () => false
} as WorkflowsOperation;
});
useEffect(() => {
console.log('여기서도 호출되구');
groupAction;
}, []);
뭐야 useMemo랑 아예 없는거랑 합친거나 다름 없...
useMemo의 경우 selectedWorkflows가 자주 바뀌면 의미가 없다고 한다. 그래서 이걸 사용하는게 의미가 있는지는 모르겠지만 우선 useMemo react Hook을 사용한 후 사용할 필요가 있을지 물어보도록 한다.
interface workflowGroupAction
은 음.. 여기서 group이 workflow를 의미하는지, Action을 의미하는지 모호하다...interface WorkflowsAction extends WorkflowOperation {
actionIsDisabled: boolean;
workflowsAction: () => Promise<any>;
}
선택된 workflows를 action처리할거니? -> 취소를 누르면 Promise.resolve(false)가 나오는데 false라는 값을 가진 Promise 객체가 만들어진다.
workflow 중에 아카이브에 저장된 workflow가 하나라도 있다면 -> 삭제 할거니 물어보고 -> 데이터 받아온다.
Promise 객체에 담고 각각 선택된 workflows에 대해서 action을 수행하면 된다.
async function asyncCall() {
return "Hi";
}
function asyncCallTwo() {
return Promise.resolve("Hi");
}
function main(){
const t1 = asyncCall();
const t2 = asyncCallTwo();
if(t1){
console.log("t1 호출");
console.log(t1);
}
if(t2){
console.log("t2 호출");
console.log(t2);
}
}
main();
> "t1 호출"
> [object Promise]
> "t2 호출"
> [object Promise]
위의 결과가 한 번에 출력된다.
우리가 원하는 리턴값을 얻고 싶으면 어떻게 해야할까?
async function asyncCall() {
return await "Hi";
}
function asyncCallTwo() {
return Promise.resolve("Hello");
}
async function main(){ // async 키워드 추가
const t1 = await asyncCall(); // await 추가
const t2 = await asyncCallTwo(); // await 추가
if(t1){
console.log("t1 호출");
console.log(t1);
}
if(t2){
console.log("t2 호출");
console.log(t2);
}
}
main();
> "t1 호출"
> "Hi"
> "t2 호출"
> "Hello"
즉 promise 객체를 반환하는 함수(async 함수) 앞에 await 키워드가 있다면 resolve안의 값을 리턴한다.
잠깐!! async가 붙었다고 다 비동기 함수는 아니라고?!
공식문서를 보면 다음과 같이 적혀있다.
async 함수의 본문은 0개 이상의 await 문으로 분할된 것으로 생각할 수 있습니다. 첫번째 await 문을 포함하는 최상위 코드는 동기적으로 실행됩니다. 따라서 await 문이 없는 async 함수는 동기적으로 실행됩니다. 하지만 await 문이 있다면 async 함수는 항상 비동기적으로 완료됩니다.
즉 async 함수라도 동기적으로 실행되었다가 await 키워드가 존재하는 순간 부터 비동기적으로(순서없이 실행!!)된다는 의미이다.
그냥 performed acction에는 삭제할 때 필요한 명령어들만 두고 알림판은 위로 따로 구분지었다.
workflowsAction: async () => {
//check for action
const confirmed = await popup.confirm('Confirm', `Are you sure you want to ${action.title.toLowerCase()} all selected workflows?`);
if (!confirmed) {
return;
}
// check for delete from archived workflows
let deleteArchived = false;
if (action.title === 'DELETE') {
// check for delete workflows from archived workflows
for (const entry of props.selectedWorkflows) {
if (isArchivedWorkflow(entry[1])) {
deleteArchived = await popup.confirm('Confirm', 'Do you also want to delete them from the Archived Workflows database?');
break;
}
}
}
performActionOnSelectedWorkflows(action.title, action.action, deleteArchived);
props.clearSelection();
notifications.show({
content: `Performed '${action.title}' on selected workflows.`,
type: NotificationType.Success
});
},
disabled: () => false
} as WorkflowsOperation;
});
}, [props.selectedWorkflows]);
function performActionOnSelectedWorkflows(title: string, action: WorkflowOperationAction, deleteArchived: boolean): Promise<any> {
const promises: Promise<any>[] = [];
props.selectedWorkflows.forEach((wf: Workflow) => {
if (title === 'DELETE') {
// The ones without archivalStatus label or with 'Archived' labels are the live workflows.
if (isWorkflowInCluster(wf)) {
promises.push(
services.workflows.delete(wf.metadata.name, wf.metadata.namespace).catch(reason =>
notifications.show({
content: `Unable to delete workflow ${wf.metadata.name} in the cluster: ${reason.toString()}`,
type: NotificationType.Error
})
)
);
}
if (deleteArchived && isArchivedWorkflow(wf)) {
promises.push(
services.workflows.deleteArchived(wf.metadata.uid, wf.metadata.namespace).catch(reason =>
notifications.show({
content: `Unable to delete workflow ${wf.metadata.name} in database: ${reason.toString()}`,
type: NotificationType.Error
})
)
);
}
} else {
promises.push(
action(wf).catch(reason => {
notifications.show({
content: `Unable to ${title} workflow: ${reason.content.toString()}`,
type: NotificationType.Error
});
})
);
}
});
return Promise.all(promises);
}