From 76c6c7538c0860ba0f34d2d1cd15e3daf458dac7 Mon Sep 17 00:00:00 2001
From: Rajat Jain <rjain@gitlab.com>
Date: Mon, 24 May 2021 09:34:49 +0530
Subject: [PATCH] Replace Flash with GlAlert in Boards

Deprecating Flash, and replacing all instances of usage within
boards with GlAlert

Changelog: changed
---
 .../boards/components/board_form.vue          | 21 ++++++++++++++-----
 .../sidebar/board_sidebar_due_date.vue        |  5 ++---
 .../sidebar/board_sidebar_labels_select.vue   | 11 +++++-----
 .../board_sidebar_milestone_select.vue        |  9 ++++----
 .../sidebar/board_sidebar_subscription.vue    |  6 ++----
 .../sidebar/board_sidebar_title.vue           |  5 ++---
 app/assets/javascripts/boards/index.js        | 16 ++++++++------
 .../javascripts/boards/stores/actions.js      |  2 +-
 .../components/board_settings_wip_limit.vue   |  7 +++----
 .../boards/components/epic_lane.vue           |  5 ++---
 .../sidebar/board_sidebar_weight_input.vue    |  5 ++---
 .../boards/components/board_form_spec.js      | 17 ++++++++-------
 .../board_settings_wip_limit_spec.js          |  7 ++-----
 .../board_sidebar_weight_input_spec.js        |  6 ++----
 .../boards/components/board_form_spec.js      | 20 +++++++++++-------
 .../sidebar/board_sidebar_due_date_spec.js    |  6 ++----
 .../board_sidebar_labels_select_spec.js       |  6 ++----
 .../board_sidebar_milestone_select_spec.js    |  6 ++----
 .../board_sidebar_subscription_spec.js        | 11 +++++-----
 .../sidebar/board_sidebar_title_spec.js       |  6 ++----
 20 files changed, 87 insertions(+), 90 deletions(-)

diff --git a/app/assets/javascripts/boards/components/board_form.vue b/app/assets/javascripts/boards/components/board_form.vue
index 78da4137d6959..ea92d3125d69f 100644
--- a/app/assets/javascripts/boards/components/board_form.vue
+++ b/app/assets/javascripts/boards/components/board_form.vue
@@ -1,7 +1,6 @@
 <script>
-import { GlModal } from '@gitlab/ui';
-import { mapGetters } from 'vuex';
-import { deprecatedCreateFlash as Flash } from '~/flash';
+import { GlModal, GlAlert } from '@gitlab/ui';
+import { mapGetters, mapActions, mapState } from 'vuex';
 import { convertToGraphQLId } from '~/graphql_shared/utils';
 import { getParameterByName } from '~/lib/utils/common_utils';
 import { visitUrl } from '~/lib/utils/url_utility';
@@ -44,6 +43,7 @@ export default {
     BoardScope: () => import('ee_component/boards/components/board_scope.vue'),
     GlModal,
     BoardConfigurationOptions,
+    GlAlert,
   },
   inject: {
     fullPath: {
@@ -107,6 +107,7 @@ export default {
     };
   },
   computed: {
+    ...mapState(['error']),
     ...mapGetters(['isIssueBoard', 'isGroupBoard', 'isProjectBoard']),
     isNewForm() {
       return this.currentPage === formType.new;
@@ -222,6 +223,7 @@ export default {
     }
   },
   methods: {
+    ...mapActions(['setError', 'unsetError']),
     setIteration(iterationId) {
       this.board.iteration_id = iterationId;
     },
@@ -263,7 +265,7 @@ export default {
           await this.deleteBoard();
           visitUrl(this.rootPath);
         } catch {
-          Flash(this.$options.i18n.deleteErrorMessage);
+          this.setError({ message: this.$options.i18n.deleteErrorMessage });
         } finally {
           this.isLoading = false;
         }
@@ -272,7 +274,7 @@ export default {
           const url = await this.createOrUpdateBoard();
           visitUrl(url);
         } catch {
-          Flash(this.$options.i18n.saveErrorMessage);
+          this.setError({ message: this.$options.i18n.saveErrorMessage });
         } finally {
           this.isLoading = false;
         }
@@ -308,6 +310,15 @@ export default {
     @close="cancel"
     @hide.prevent
   >
+    <gl-alert
+      v-if="error"
+      class="gl-mb-3"
+      variant="danger"
+      :dismissible="true"
+      @dismiss="unsetError"
+    >
+      {{ error }}
+    </gl-alert>
     <p v-if="isDeleteForm" data-testid="delete-confirmation-message">
       {{ $options.i18n.deleteConfirmationMessage }}
     </p>
diff --git a/app/assets/javascripts/boards/components/sidebar/board_sidebar_due_date.vue b/app/assets/javascripts/boards/components/sidebar/board_sidebar_due_date.vue
index 13e1e2326762d..87ae17025ea0b 100644
--- a/app/assets/javascripts/boards/components/sidebar/board_sidebar_due_date.vue
+++ b/app/assets/javascripts/boards/components/sidebar/board_sidebar_due_date.vue
@@ -2,7 +2,6 @@
 import { GlButton, GlDatepicker } from '@gitlab/ui';
 import { mapGetters, mapActions } from 'vuex';
 import BoardEditableItem from '~/boards/components/sidebar/board_editable_item.vue';
-import createFlash from '~/flash';
 import { dateInWords, formatDate, parsePikadayDate } from '~/lib/utils/datetime_utility';
 import { __ } from '~/locale';
 
@@ -38,7 +37,7 @@ export default {
     },
   },
   methods: {
-    ...mapActions(['setActiveIssueDueDate']),
+    ...mapActions(['setActiveIssueDueDate', 'setError']),
     async openDatePicker() {
       await this.$nextTick();
       this.$refs.datePicker.calendar.show();
@@ -51,7 +50,7 @@ export default {
         const dueDate = date ? formatDate(date, 'yyyy-mm-dd') : null;
         await this.setActiveIssueDueDate({ dueDate, projectPath: this.projectPathForActiveIssue });
       } catch (e) {
-        createFlash({ message: this.$options.i18n.updateDueDateError });
+        this.setError({ message: this.$options.i18n.updateDueDateError });
       } finally {
         this.loading = false;
       }
diff --git a/app/assets/javascripts/boards/components/sidebar/board_sidebar_labels_select.vue b/app/assets/javascripts/boards/components/sidebar/board_sidebar_labels_select.vue
index 919ef0d3783ba..29febd0fa51e7 100644
--- a/app/assets/javascripts/boards/components/sidebar/board_sidebar_labels_select.vue
+++ b/app/assets/javascripts/boards/components/sidebar/board_sidebar_labels_select.vue
@@ -3,7 +3,6 @@ import { GlLabel } from '@gitlab/ui';
 import { mapGetters, mapActions } from 'vuex';
 import Api from '~/api';
 import BoardEditableItem from '~/boards/components/sidebar/board_editable_item.vue';
-import createFlash from '~/flash';
 import { getIdFromGraphQLId } from '~/graphql_shared/utils';
 import { isScopedLabel } from '~/lib/utils/common_utils';
 import { mergeUrlParams } from '~/lib/utils/url_utility';
@@ -50,10 +49,10 @@ export default {
       /*
        Labels fetched in epic boards are always group-level labels
        and the correct path are passed from the backend (injected through labelsFetchPath)
-    
+
        For issue boards, we should always include project-level labels and use a different endpoint.
        (it requires knowing the project path of a selected issue.)
-    
+
        Note 1. that we will be using GraphQL to fetch labels when we create a labels select widget.
        And this component will be removed _wholesale_ https://gitlab.com/gitlab-org/gitlab/-/issues/300653.
 
@@ -74,7 +73,7 @@ export default {
     },
   },
   methods: {
-    ...mapActions(['setActiveBoardItemLabels']),
+    ...mapActions(['setActiveBoardItemLabels', 'setError']),
     async setLabels(payload) {
       this.loading = true;
       this.$refs.sidebarItem.collapse();
@@ -88,7 +87,7 @@ export default {
         const input = { addLabelIds, removeLabelIds, projectPath: this.projectPathForActiveIssue };
         await this.setActiveBoardItemLabels(input);
       } catch (e) {
-        createFlash({ message: __('An error occurred while updating labels.') });
+        this.setError({ error: e, message: __('An error occurred while updating labels.') });
       } finally {
         this.loading = false;
       }
@@ -101,7 +100,7 @@ export default {
         const input = { removeLabelIds, projectPath: this.projectPathForActiveIssue };
         await this.setActiveBoardItemLabels(input);
       } catch (e) {
-        createFlash({ message: __('An error occurred when removing the label.') });
+        this.setError({ error: e, message: __('An error occurred when removing the label.') });
       } finally {
         this.loading = false;
       }
diff --git a/app/assets/javascripts/boards/components/sidebar/board_sidebar_milestone_select.vue b/app/assets/javascripts/boards/components/sidebar/board_sidebar_milestone_select.vue
index ad225c7bf5cd6..2a1bda9278c30 100644
--- a/app/assets/javascripts/boards/components/sidebar/board_sidebar_milestone_select.vue
+++ b/app/assets/javascripts/boards/components/sidebar/board_sidebar_milestone_select.vue
@@ -9,7 +9,6 @@ import {
 } from '@gitlab/ui';
 import { mapGetters, mapActions } from 'vuex';
 import BoardEditableItem from '~/boards/components/sidebar/board_editable_item.vue';
-import createFlash from '~/flash';
 import { __, s__ } from '~/locale';
 import projectMilestones from '../../graphql/project_milestones.query.graphql';
 
@@ -50,8 +49,8 @@ export default {
         const edges = data?.project?.milestones?.edges ?? [];
         return edges.map((item) => item.node);
       },
-      error() {
-        createFlash({ message: this.$options.i18n.fetchMilestonesError });
+      error(error) {
+        this.setError({ error, message: this.$options.i18n.fetchMilestonesError });
       },
     },
   },
@@ -73,7 +72,7 @@ export default {
     },
   },
   methods: {
-    ...mapActions(['setActiveIssueMilestone']),
+    ...mapActions(['setActiveIssueMilestone', 'setError']),
     handleOpen() {
       this.edit = true;
       this.$refs.dropdown.show();
@@ -91,7 +90,7 @@ export default {
         const input = { milestoneId, projectPath: this.projectPath };
         await this.setActiveIssueMilestone(input);
       } catch (e) {
-        createFlash({ message: this.$options.i18n.updateMilestoneError });
+        this.setError({ error: e, message: this.$options.i18n.updateMilestoneError });
       } finally {
         this.loading = false;
       }
diff --git a/app/assets/javascripts/boards/components/sidebar/board_sidebar_subscription.vue b/app/assets/javascripts/boards/components/sidebar/board_sidebar_subscription.vue
index 376985f7cb6c9..4f5c55d0c5d71 100644
--- a/app/assets/javascripts/boards/components/sidebar/board_sidebar_subscription.vue
+++ b/app/assets/javascripts/boards/components/sidebar/board_sidebar_subscription.vue
@@ -1,7 +1,6 @@
 <script>
 import { GlToggle } from '@gitlab/ui';
 import { mapGetters, mapActions } from 'vuex';
-import createFlash from '~/flash';
 import { __, s__ } from '~/locale';
 
 export default {
@@ -39,17 +38,16 @@ export default {
     },
   },
   methods: {
-    ...mapActions(['setActiveItemSubscribed']),
+    ...mapActions(['setActiveItemSubscribed', 'setError']),
     async handleToggleSubscription() {
       this.loading = true;
-
       try {
         await this.setActiveItemSubscribed({
           subscribed: !this.activeBoardItem.subscribed,
           projectPath: this.projectPathForActiveIssue,
         });
       } catch (error) {
-        createFlash({ message: this.$options.i18n.updateSubscribedErrorMessage });
+        this.setError({ error, message: this.$options.i18n.updateSubscribedErrorMessage });
       } finally {
         this.loading = false;
       }
diff --git a/app/assets/javascripts/boards/components/sidebar/board_sidebar_title.vue b/app/assets/javascripts/boards/components/sidebar/board_sidebar_title.vue
index b8d3107c37789..e77aadfa50ee6 100644
--- a/app/assets/javascripts/boards/components/sidebar/board_sidebar_title.vue
+++ b/app/assets/javascripts/boards/components/sidebar/board_sidebar_title.vue
@@ -2,7 +2,6 @@
 import { GlAlert, GlButton, GlForm, GlFormGroup, GlFormInput } from '@gitlab/ui';
 import { mapGetters, mapActions } from 'vuex';
 import BoardEditableItem from '~/boards/components/sidebar/board_editable_item.vue';
-import createFlash from '~/flash';
 import { joinPaths } from '~/lib/utils/url_utility';
 import { __ } from '~/locale';
 import autofocusonshow from '~/vue_shared/directives/autofocusonshow';
@@ -53,7 +52,7 @@ export default {
     },
   },
   methods: {
-    ...mapActions(['setActiveItemTitle']),
+    ...mapActions(['setActiveItemTitle', 'setError']),
     getPendingChangesKey(item) {
       if (!item) {
         return '';
@@ -97,7 +96,7 @@ export default {
         this.showChangesAlert = false;
       } catch (e) {
         this.title = this.item.title;
-        createFlash({ message: this.$options.i18n.updateTitleError });
+        this.setError({ error: e, message: this.$options.i18n.updateTitleError });
       } finally {
         this.loading = false;
       }
diff --git a/app/assets/javascripts/boards/index.js b/app/assets/javascripts/boards/index.js
index 1888645ef781e..fb347ce852d7b 100644
--- a/app/assets/javascripts/boards/index.js
+++ b/app/assets/javascripts/boards/index.js
@@ -27,7 +27,6 @@ import FilteredSearchBoards from '~/boards/filtered_search_boards';
 import store from '~/boards/stores';
 import boardsStore from '~/boards/stores/boards_store';
 import toggleFocusMode from '~/boards/toggle_focus';
-import { deprecatedCreateFlash as Flash } from '~/flash';
 import createDefaultClient from '~/lib/graphql';
 import {
   NavigationType,
@@ -196,7 +195,7 @@ export default () => {
       }
     },
     methods: {
-      ...mapActions(['setInitialBoardData', 'performSearch']),
+      ...mapActions(['setInitialBoardData', 'performSearch', 'setError']),
       initialBoardLoad() {
         boardsStore
           .all()
@@ -205,8 +204,11 @@ export default () => {
             lists.forEach((list) => boardsStore.addList(list));
             this.loading = false;
           })
-          .catch(() => {
-            Flash(__('An error occurred while fetching the board lists. Please try again.'));
+          .catch((error) => {
+            this.setError({
+              error,
+              message: __('An error occurred while fetching the board lists. Please try again.'),
+            });
           });
       },
       updateTokens() {
@@ -250,7 +252,7 @@ export default () => {
             .catch(() => {
               newIssue.setFetchingState('subscriptions', false);
               setWeightFetchingState(newIssue, false);
-              Flash(__('An error occurred while fetching sidebar data'));
+              this.setError({ message: __('An error occurred while fetching sidebar data') });
             });
         }
 
@@ -287,7 +289,9 @@ export default () => {
             })
             .catch(() => {
               issue.setFetchingState('subscriptions', false);
-              Flash(__('An error occurred when toggling the notification subscription'));
+              this.setError({
+                message: __('An error occurred when toggling the notification subscription'),
+              });
             });
         }
       },
diff --git a/app/assets/javascripts/boards/stores/actions.js b/app/assets/javascripts/boards/stores/actions.js
index eda0fae117cc5..bc44b84da5191 100644
--- a/app/assets/javascripts/boards/stores/actions.js
+++ b/app/assets/javascripts/boards/stores/actions.js
@@ -726,7 +726,7 @@ export default {
     }
   },
 
-  setError: ({ commit }, { message, error, captureError = false }) => {
+  setError: ({ commit }, { message, error, captureError = true }) => {
     commit(types.SET_ERROR, message);
 
     if (captureError) {
diff --git a/ee/app/assets/javascripts/boards/components/board_settings_wip_limit.vue b/ee/app/assets/javascripts/boards/components/board_settings_wip_limit.vue
index 9b26fa1991e30..5578109112e1f 100644
--- a/ee/app/assets/javascripts/boards/components/board_settings_wip_limit.vue
+++ b/ee/app/assets/javascripts/boards/components/board_settings_wip_limit.vue
@@ -3,7 +3,6 @@ import { GlButton, GlFormInput } from '@gitlab/ui';
 import { mapActions, mapGetters, mapState } from 'vuex';
 import boardsStoreEE from 'ee/boards/stores/boards_store_ee';
 import { inactiveId } from '~/boards/constants';
-import createFlash from '~/flash';
 import { __, n__ } from '~/locale';
 import autofocusonshow from '~/vue_shared/directives/autofocusonshow';
 
@@ -49,7 +48,7 @@ export default {
     },
   },
   methods: {
-    ...mapActions(['unsetActiveId', 'updateListWipLimit']),
+    ...mapActions(['unsetActiveId', 'updateListWipLimit', 'setError']),
     showInput() {
       this.edit = true;
       this.currentWipLimit = this.maxIssueCount > 0 ? this.maxIssueCount : null;
@@ -84,7 +83,7 @@ export default {
           })
           .catch(() => {
             this.unsetActiveId();
-            createFlash({
+            this.setError({
               message: __('Something went wrong while updating your list settings'),
             });
           })
@@ -104,7 +103,7 @@ export default {
         })
         .catch(() => {
           this.unsetActiveId();
-          createFlash({
+          this.setError({
             message: __('Something went wrong while updating your list settings'),
           });
         })
diff --git a/ee/app/assets/javascripts/boards/components/epic_lane.vue b/ee/app/assets/javascripts/boards/components/epic_lane.vue
index 721cef64b3377..6f3ab6a49d005 100644
--- a/ee/app/assets/javascripts/boards/components/epic_lane.vue
+++ b/ee/app/assets/javascripts/boards/components/epic_lane.vue
@@ -1,7 +1,6 @@
 <script>
 import { GlButton, GlIcon, GlLink, GlPopover, GlTooltipDirective } from '@gitlab/ui';
 import { mapActions, mapGetters, mapState } from 'vuex';
-import createFlash from '~/flash';
 import { formatDate } from '~/lib/utils/datetime_utility';
 import { __, n__, sprintf } from '~/locale';
 import timeagoMixin from '~/vue_shared/mixins/timeago';
@@ -86,7 +85,7 @@ export default {
     },
   },
   methods: {
-    ...mapActions(['updateBoardEpicUserPreferences']),
+    ...mapActions(['updateBoardEpicUserPreferences', 'setError']),
     toggleCollapsed() {
       this.isCollapsed = !this.isCollapsed;
 
@@ -94,7 +93,7 @@ export default {
         collapsed: this.isCollapsed,
         epicId: this.epic.id,
       }).catch(() => {
-        createFlash({ message: __('Unable to save your preference'), captureError: true });
+        this.setError({ message: __('Unable to save your preference'), captureError: true });
       });
     },
   },
diff --git a/ee/app/assets/javascripts/boards/components/sidebar/board_sidebar_weight_input.vue b/ee/app/assets/javascripts/boards/components/sidebar/board_sidebar_weight_input.vue
index 3f7414efbab3a..d1830a3bb40db 100644
--- a/ee/app/assets/javascripts/boards/components/sidebar/board_sidebar_weight_input.vue
+++ b/ee/app/assets/javascripts/boards/components/sidebar/board_sidebar_weight_input.vue
@@ -2,7 +2,6 @@
 import { GlButton, GlForm, GlFormInput } from '@gitlab/ui';
 import { mapGetters, mapActions } from 'vuex';
 import BoardEditableItem from '~/boards/components/sidebar/board_editable_item.vue';
-import createFlash from '~/flash';
 import { __ } from '~/locale';
 import autofocusonshow from '~/vue_shared/directives/autofocusonshow';
 
@@ -37,7 +36,7 @@ export default {
     },
   },
   methods: {
-    ...mapActions(['setActiveIssueWeight']),
+    ...mapActions(['setActiveIssueWeight', 'setError']),
     handleFormSubmit() {
       this.$refs.sidebarItem.collapse({ emitEvent: false });
       this.setWeight();
@@ -56,7 +55,7 @@ export default {
         this.weight = weight;
       } catch (e) {
         this.weight = this.activeBoardItem.weight;
-        createFlash({ message: __('An error occurred when updating the issue weight') });
+        this.setError({ message: __('An error occurred when updating the issue weight') });
       } finally {
         this.loading = false;
       }
diff --git a/ee/spec/frontend/boards/components/board_form_spec.js b/ee/spec/frontend/boards/components/board_form_spec.js
index fc5b447f0ebc5..181aff1197e13 100644
--- a/ee/spec/frontend/boards/components/board_form_spec.js
+++ b/ee/spec/frontend/boards/components/board_form_spec.js
@@ -12,14 +12,12 @@ import { TEST_HOST } from 'helpers/test_constants';
 import waitForPromises from 'helpers/wait_for_promises';
 
 import { formType } from '~/boards/constants';
-import { deprecatedCreateFlash as createFlash } from '~/flash';
 import { visitUrl } from '~/lib/utils/url_utility';
 
 jest.mock('~/lib/utils/url_utility', () => ({
   visitUrl: jest.fn().mockName('visitUrlMock'),
   stripFinalUrlSegment: jest.requireActual('~/lib/utils/url_utility').stripFinalUrlSegment,
 }));
-jest.mock('~/flash');
 
 Vue.use(Vuex);
 
@@ -168,9 +166,10 @@ describe('BoardForm', () => {
         expect(visitUrl).toHaveBeenCalledWith('test-path');
       });
 
-      it('shows an error flash if GraphQL mutation fails', async () => {
+      it('shows a GlAlert if GraphQL mutation fails', async () => {
         mutate = jest.fn().mockRejectedValue('Houston, we have a problem');
         createComponent({ canAdminBoard: true, currentPage: formType.new });
+        jest.spyOn(wrapper.vm, 'setError').mockImplementation(() => {});
         fillForm();
 
         await waitForPromises();
@@ -179,7 +178,7 @@ describe('BoardForm', () => {
 
         await waitForPromises();
         expect(visitUrl).not.toHaveBeenCalled();
-        expect(createFlash).toHaveBeenCalled();
+        expect(wrapper.vm.setError).toHaveBeenCalled();
       });
     });
   });
@@ -213,9 +212,10 @@ describe('BoardForm', () => {
       expect(visitUrl).toHaveBeenCalledWith('test-path');
     });
 
-    it('shows an error flash if GraphQL mutation fails', async () => {
+    it('shows a GlAlert if GraphQL mutation fails', async () => {
       mutate = jest.fn().mockRejectedValue('Houston, we have a problem');
       createComponent({ canAdminBoard: true, currentPage: formType.edit });
+      jest.spyOn(wrapper.vm, 'setError').mockImplementation(() => {});
       findInput().trigger('keyup.enter', { metaKey: true });
 
       await waitForPromises();
@@ -224,7 +224,7 @@ describe('BoardForm', () => {
 
       await waitForPromises();
       expect(visitUrl).not.toHaveBeenCalled();
-      expect(createFlash).toHaveBeenCalled();
+      expect(wrapper.vm.setError).toHaveBeenCalled();
     });
   });
 
@@ -258,9 +258,10 @@ describe('BoardForm', () => {
       expect(visitUrl).toHaveBeenCalledWith('root');
     });
 
-    it('shows an error flash if GraphQL mutation fails', async () => {
+    it('shows a GlAlert if GraphQL mutation fails', async () => {
       mutate = jest.fn().mockRejectedValue('Houston, we have a problem');
       createComponent({ canAdminBoard: true, currentPage: formType.delete });
+      jest.spyOn(wrapper.vm, 'setError').mockImplementation(() => {});
       findModal().vm.$emit('primary');
 
       await waitForPromises();
@@ -269,7 +270,7 @@ describe('BoardForm', () => {
 
       await waitForPromises();
       expect(visitUrl).not.toHaveBeenCalled();
-      expect(createFlash).toHaveBeenCalled();
+      expect(wrapper.vm.setError).toHaveBeenCalled();
     });
   });
 });
diff --git a/ee/spec/frontend/boards/components/board_settings_wip_limit_spec.js b/ee/spec/frontend/boards/components/board_settings_wip_limit_spec.js
index 34e272ba80574..4012eec54fbe4 100644
--- a/ee/spec/frontend/boards/components/board_settings_wip_limit_spec.js
+++ b/ee/spec/frontend/boards/components/board_settings_wip_limit_spec.js
@@ -8,9 +8,6 @@ import Vuex from 'vuex';
 import BoardSettingsWipLimit from 'ee_component/boards/components/board_settings_wip_limit.vue';
 import waitForPromises from 'helpers/wait_for_promises';
 import boardsStore from '~/boards/stores/boards_store';
-import createFlash from '~/flash';
-
-jest.mock('~/flash');
 
 const localVue = createLocalVue();
 
@@ -185,7 +182,6 @@ describe('BoardSettingsWipLimit', () => {
     });
 
     afterEach(() => {
-      createFlash.mockReset();
       boardsStore.removeList(listId);
     });
 
@@ -289,6 +285,7 @@ describe('BoardSettingsWipLimit', () => {
               actions: { updateListWipLimit: spy, unsetActiveId: noop },
               localState: { edit: true, currentWipLimit },
             });
+            jest.spyOn(wrapper.vm, 'setError').mockImplementation(() => {});
 
             triggerBlur(blurMethod);
 
@@ -296,7 +293,7 @@ describe('BoardSettingsWipLimit', () => {
           });
 
           it('calls flash with expected error', () => {
-            expect(createFlash).toHaveBeenCalledTimes(1);
+            expect(wrapper.vm.setError).toHaveBeenCalledTimes(1);
           });
         });
       });
diff --git a/ee/spec/frontend/boards/components/sidebar/board_sidebar_weight_input_spec.js b/ee/spec/frontend/boards/components/sidebar/board_sidebar_weight_input_spec.js
index 233e5365df3d2..734d81f842c0a 100644
--- a/ee/spec/frontend/boards/components/sidebar/board_sidebar_weight_input_spec.js
+++ b/ee/spec/frontend/boards/components/sidebar/board_sidebar_weight_input_spec.js
@@ -3,13 +3,10 @@ import { shallowMount } from '@vue/test-utils';
 import BoardSidebarWeightInput from 'ee/boards/components/sidebar/board_sidebar_weight_input.vue';
 import BoardEditableItem from '~/boards/components/sidebar/board_editable_item.vue';
 import { createStore } from '~/boards/stores';
-import createFlash from '~/flash';
 
 const TEST_WEIGHT = 1;
 const TEST_ISSUE = { id: 'gid://gitlab/Issue/1', iid: 9, weight: 0, referencePath: 'h/b#2' };
 
-jest.mock('~/flash');
-
 describe('ee/boards/components/sidebar/board_sidebar_weight_input.vue', () => {
   let wrapper;
   let store;
@@ -115,6 +112,7 @@ describe('ee/boards/components/sidebar/board_sidebar_weight_input.vue', () => {
       jest.spyOn(wrapper.vm, 'setActiveIssueWeight').mockImplementation(() => {
         throw new Error(['failed mutation']);
       });
+      jest.spyOn(wrapper.vm, 'setError').mockImplementation(() => {});
       findWeightInput().vm.$emit('input', -1);
       findWeightForm().vm.$emit('submit', { preventDefault: () => {} });
       await wrapper.vm.$nextTick();
@@ -123,7 +121,7 @@ describe('ee/boards/components/sidebar/board_sidebar_weight_input.vue', () => {
     it('collapses sidebar and renders former issue weight', () => {
       expect(findCollapsed().isVisible()).toBe(true);
       expect(findCollapsed().text()).toContain(TEST_WEIGHT);
-      expect(createFlash).toHaveBeenCalled();
+      expect(wrapper.vm.setError).toHaveBeenCalled();
     });
   });
 });
diff --git a/spec/frontend/boards/components/board_form_spec.js b/spec/frontend/boards/components/board_form_spec.js
index 24fcdd528d54f..80d740458dc19 100644
--- a/spec/frontend/boards/components/board_form_spec.js
+++ b/spec/frontend/boards/components/board_form_spec.js
@@ -9,14 +9,12 @@ import createBoardMutation from '~/boards/graphql/board_create.mutation.graphql'
 import destroyBoardMutation from '~/boards/graphql/board_destroy.mutation.graphql';
 import updateBoardMutation from '~/boards/graphql/board_update.mutation.graphql';
 import { createStore } from '~/boards/stores';
-import { deprecatedCreateFlash as createFlash } from '~/flash';
 import { visitUrl } from '~/lib/utils/url_utility';
 
 jest.mock('~/lib/utils/url_utility', () => ({
   visitUrl: jest.fn().mockName('visitUrlMock'),
   stripFinalUrlSegment: jest.requireActual('~/lib/utils/url_utility').stripFinalUrlSegment,
 }));
-jest.mock('~/flash');
 
 const currentBoard = {
   id: 1,
@@ -194,9 +192,11 @@ describe('BoardForm', () => {
         expect(visitUrl).toHaveBeenCalledWith('test-path');
       });
 
-      it('shows an error flash if GraphQL mutation fails', async () => {
+      it('shows a GlAlert if GraphQL mutation fails', async () => {
         mutate = jest.fn().mockRejectedValue('Houston, we have a problem');
         createComponent({ canAdminBoard: true, currentPage: formType.new });
+        jest.spyOn(wrapper.vm, 'setError').mockImplementation(() => {});
+
         fillForm();
 
         await waitForPromises();
@@ -205,7 +205,7 @@ describe('BoardForm', () => {
 
         await waitForPromises();
         expect(visitUrl).not.toHaveBeenCalled();
-        expect(createFlash).toHaveBeenCalled();
+        expect(wrapper.vm.setError).toHaveBeenCalled();
       });
     });
   });
@@ -290,9 +290,11 @@ describe('BoardForm', () => {
       expect(visitUrl).toHaveBeenCalledWith('test-path?group_by=epic');
     });
 
-    it('shows an error flash if GraphQL mutation fails', async () => {
+    it('shows a GlAlert if GraphQL mutation fails', async () => {
       mutate = jest.fn().mockRejectedValue('Houston, we have a problem');
       createComponent({ canAdminBoard: true, currentPage: formType.edit });
+      jest.spyOn(wrapper.vm, 'setError').mockImplementation(() => {});
+
       findInput().trigger('keyup.enter', { metaKey: true });
 
       await waitForPromises();
@@ -301,7 +303,7 @@ describe('BoardForm', () => {
 
       await waitForPromises();
       expect(visitUrl).not.toHaveBeenCalled();
-      expect(createFlash).toHaveBeenCalled();
+      expect(wrapper.vm.setError).toHaveBeenCalled();
     });
   });
 
@@ -335,9 +337,11 @@ describe('BoardForm', () => {
       expect(visitUrl).toHaveBeenCalledWith('root');
     });
 
-    it('shows an error flash if GraphQL mutation fails', async () => {
+    it('dispatches `setError` action when GraphQL mutation fails', async () => {
       mutate = jest.fn().mockRejectedValue('Houston, we have a problem');
       createComponent({ canAdminBoard: true, currentPage: formType.delete });
+      jest.spyOn(wrapper.vm, 'setError').mockImplementation(() => {});
+
       findModal().vm.$emit('primary');
 
       await waitForPromises();
@@ -346,7 +350,7 @@ describe('BoardForm', () => {
 
       await waitForPromises();
       expect(visitUrl).not.toHaveBeenCalled();
-      expect(createFlash).toHaveBeenCalled();
+      expect(wrapper.vm.setError).toHaveBeenCalled();
     });
   });
 });
diff --git a/spec/frontend/boards/components/sidebar/board_sidebar_due_date_spec.js b/spec/frontend/boards/components/sidebar/board_sidebar_due_date_spec.js
index 8fd178a085641..2f91beda275ba 100644
--- a/spec/frontend/boards/components/sidebar/board_sidebar_due_date_spec.js
+++ b/spec/frontend/boards/components/sidebar/board_sidebar_due_date_spec.js
@@ -3,15 +3,12 @@ import { shallowMount } from '@vue/test-utils';
 import BoardEditableItem from '~/boards/components/sidebar/board_editable_item.vue';
 import BoardSidebarDueDate from '~/boards/components/sidebar/board_sidebar_due_date.vue';
 import { createStore } from '~/boards/stores';
-import createFlash from '~/flash';
 
 const TEST_DUE_DATE = '2020-02-20';
 const TEST_FORMATTED_DUE_DATE = 'Feb 20, 2020';
 const TEST_PARSED_DATE = new Date(2020, 1, 20);
 const TEST_ISSUE = { id: 'gid://gitlab/Issue/1', iid: 9, dueDate: null, referencePath: 'h/b#2' };
 
-jest.mock('~/flash');
-
 describe('~/boards/components/sidebar/board_sidebar_due_date.vue', () => {
   let wrapper;
   let store;
@@ -124,6 +121,7 @@ describe('~/boards/components/sidebar/board_sidebar_due_date.vue', () => {
       jest.spyOn(wrapper.vm, 'setActiveIssueDueDate').mockImplementation(() => {
         throw new Error(['failed mutation']);
       });
+      jest.spyOn(wrapper.vm, 'setError').mockImplementation(() => {});
       findDatePicker().vm.$emit('input', 'Invalid date');
       await wrapper.vm.$nextTick();
     });
@@ -131,7 +129,7 @@ describe('~/boards/components/sidebar/board_sidebar_due_date.vue', () => {
     it('collapses sidebar and renders former issue due date', () => {
       expect(findCollapsed().isVisible()).toBe(true);
       expect(findCollapsed().text()).toContain(TEST_FORMATTED_DUE_DATE);
-      expect(createFlash).toHaveBeenCalled();
+      expect(wrapper.vm.setError).toHaveBeenCalled();
     });
   });
 });
diff --git a/spec/frontend/boards/components/sidebar/board_sidebar_labels_select_spec.js b/spec/frontend/boards/components/sidebar/board_sidebar_labels_select_spec.js
index ad682774ee624..8992a5780f3b5 100644
--- a/spec/frontend/boards/components/sidebar/board_sidebar_labels_select_spec.js
+++ b/spec/frontend/boards/components/sidebar/board_sidebar_labels_select_spec.js
@@ -9,11 +9,8 @@ import {
 import BoardEditableItem from '~/boards/components/sidebar/board_editable_item.vue';
 import BoardSidebarLabelsSelect from '~/boards/components/sidebar/board_sidebar_labels_select.vue';
 import { createStore } from '~/boards/stores';
-import createFlash from '~/flash';
 import { getIdFromGraphQLId } from '~/graphql_shared/utils';
 
-jest.mock('~/flash');
-
 const TEST_LABELS_PAYLOAD = TEST_LABELS.map((label) => ({ ...label, set: true }));
 const TEST_LABELS_TITLES = TEST_LABELS.map((label) => label.title);
 
@@ -154,6 +151,7 @@ describe('~/boards/components/sidebar/board_sidebar_labels_select.vue', () => {
       jest.spyOn(wrapper.vm, 'setActiveBoardItemLabels').mockImplementation(() => {
         throw new Error(['failed mutation']);
       });
+      jest.spyOn(wrapper.vm, 'setError').mockImplementation(() => {});
       findLabelsSelect().vm.$emit('updateSelectedLabels', [{ id: '?' }]);
       await wrapper.vm.$nextTick();
     });
@@ -161,7 +159,7 @@ describe('~/boards/components/sidebar/board_sidebar_labels_select.vue', () => {
     it('collapses sidebar and renders former issue weight', () => {
       expect(findCollapsed().isVisible()).toBe(true);
       expect(findLabelsTitles()).toEqual(TEST_LABELS_TITLES);
-      expect(createFlash).toHaveBeenCalled();
+      expect(wrapper.vm.setError).toHaveBeenCalled();
     });
   });
 });
diff --git a/spec/frontend/boards/components/sidebar/board_sidebar_milestone_select_spec.js b/spec/frontend/boards/components/sidebar/board_sidebar_milestone_select_spec.js
index 8706424a2969f..6cf750be972e0 100644
--- a/spec/frontend/boards/components/sidebar/board_sidebar_milestone_select_spec.js
+++ b/spec/frontend/boards/components/sidebar/board_sidebar_milestone_select_spec.js
@@ -4,12 +4,9 @@ import { mockMilestone as TEST_MILESTONE } from 'jest/boards/mock_data';
 import BoardEditableItem from '~/boards/components/sidebar/board_editable_item.vue';
 import BoardSidebarMilestoneSelect from '~/boards/components/sidebar/board_sidebar_milestone_select.vue';
 import { createStore } from '~/boards/stores';
-import createFlash from '~/flash';
 
 const TEST_ISSUE = { id: 'gid://gitlab/Issue/1', iid: 9, referencePath: 'h/b#2' };
 
-jest.mock('~/flash');
-
 describe('~/boards/components/sidebar/board_sidebar_milestone_select.vue', () => {
   let wrapper;
   let store;
@@ -165,6 +162,7 @@ describe('~/boards/components/sidebar/board_sidebar_milestone_select.vue', () =>
       jest.spyOn(wrapper.vm, 'setActiveIssueMilestone').mockImplementation(() => {
         throw new Error(['failed mutation']);
       });
+      jest.spyOn(wrapper.vm, 'setError').mockImplementation(() => {});
       findDropdownItem().vm.$emit('click');
       await wrapper.vm.$nextTick();
     });
@@ -172,7 +170,7 @@ describe('~/boards/components/sidebar/board_sidebar_milestone_select.vue', () =>
     it('collapses sidebar and renders former milestone', () => {
       expect(findCollapsed().isVisible()).toBe(true);
       expect(findCollapsed().text()).toContain(testMilestone.title);
-      expect(createFlash).toHaveBeenCalled();
+      expect(wrapper.vm.setError).toHaveBeenCalled();
     });
   });
 });
diff --git a/spec/frontend/boards/components/sidebar/board_sidebar_subscription_spec.js b/spec/frontend/boards/components/sidebar/board_sidebar_subscription_spec.js
index 7976e73ff2f91..8847f626c1f7e 100644
--- a/spec/frontend/boards/components/sidebar/board_sidebar_subscription_spec.js
+++ b/spec/frontend/boards/components/sidebar/board_sidebar_subscription_spec.js
@@ -5,11 +5,8 @@ import Vuex from 'vuex';
 import BoardSidebarSubscription from '~/boards/components/sidebar/board_sidebar_subscription.vue';
 import { createStore } from '~/boards/stores';
 import * as types from '~/boards/stores/mutation_types';
-import createFlash from '~/flash';
 import { mockActiveIssue } from '../../mock_data';
 
-jest.mock('~/flash.js');
-
 Vue.use(Vuex);
 
 describe('~/boards/components/sidebar/board_sidebar_subscription_spec.vue', () => {
@@ -153,13 +150,15 @@ describe('~/boards/components/sidebar/board_sidebar_subscription_spec.vue', () =
       jest.spyOn(wrapper.vm, 'setActiveItemSubscribed').mockImplementation(async () => {
         throw new Error();
       });
+      jest.spyOn(wrapper.vm, 'setError').mockImplementation(() => {});
 
       findToggle().trigger('click');
 
       await wrapper.vm.$nextTick();
-      expect(createFlash).toHaveBeenNthCalledWith(1, {
-        message: wrapper.vm.$options.i18n.updateSubscribedErrorMessage,
-      });
+      expect(wrapper.vm.setError).toHaveBeenCalled();
+      expect(wrapper.vm.setError.mock.calls[0][0].message).toBe(
+        wrapper.vm.$options.i18n.updateSubscribedErrorMessage,
+      );
     });
   });
 });
diff --git a/spec/frontend/boards/components/sidebar/board_sidebar_title_spec.js b/spec/frontend/boards/components/sidebar/board_sidebar_title_spec.js
index c8ccd4c88a5c7..4a8eda298f282 100644
--- a/spec/frontend/boards/components/sidebar/board_sidebar_title_spec.js
+++ b/spec/frontend/boards/components/sidebar/board_sidebar_title_spec.js
@@ -3,7 +3,6 @@ import { shallowMount } from '@vue/test-utils';
 import BoardEditableItem from '~/boards/components/sidebar/board_editable_item.vue';
 import BoardSidebarTitle from '~/boards/components/sidebar/board_sidebar_title.vue';
 import { createStore } from '~/boards/stores';
-import createFlash from '~/flash';
 
 const TEST_TITLE = 'New item title';
 const TEST_ISSUE_A = {
@@ -19,8 +18,6 @@ const TEST_ISSUE_B = {
   referencePath: 'h/b#2',
 };
 
-jest.mock('~/flash');
-
 describe('~/boards/components/sidebar/board_sidebar_title.vue', () => {
   let wrapper;
   let store;
@@ -168,6 +165,7 @@ describe('~/boards/components/sidebar/board_sidebar_title.vue', () => {
       jest.spyOn(wrapper.vm, 'setActiveItemTitle').mockImplementation(() => {
         throw new Error(['failed mutation']);
       });
+      jest.spyOn(wrapper.vm, 'setError').mockImplementation(() => {});
       findFormInput().vm.$emit('input', 'Invalid title');
       findForm().vm.$emit('submit', { preventDefault: () => {} });
       await wrapper.vm.$nextTick();
@@ -176,7 +174,7 @@ describe('~/boards/components/sidebar/board_sidebar_title.vue', () => {
     it('collapses sidebar and renders former item title', () => {
       expect(findCollapsed().isVisible()).toBe(true);
       expect(findTitle().text()).toContain(TEST_ISSUE_B.title);
-      expect(createFlash).toHaveBeenCalled();
+      expect(wrapper.vm.setError).toHaveBeenCalled();
     });
   });
 });
-- 
GitLab