diff --git a/ee/app/assets/javascripts/api.js b/ee/app/assets/javascripts/api.js
index 44b4a88a98c39ddff4542794548c7226dd07b2f8..9d015c86990eaf0c70f743a2bde7ba3137622c3e 100644
--- a/ee/app/assets/javascripts/api.js
+++ b/ee/app/assets/javascripts/api.js
@@ -328,7 +328,7 @@ export default {
     return axios.get(metricImagesUrl);
   },
 
-  uploadIssueMetricImage({ issueIid, id, file, url = null }) {
+  uploadIssueMetricImage({ issueIid, id, file, url = null, urlText = null }) {
     const options = { headers: { ...ContentTypeMultipartFormData } };
     const metricImagesUrl = Api.buildUrl(this.issueMetricImagesPath)
       .replace(':id', encodeURIComponent(id))
@@ -340,10 +340,31 @@ export default {
     if (url) {
       formData.append('url', url);
     }
+    if (urlText) {
+      formData.append('url_text', urlText);
+    }
 
     return axios.post(metricImagesUrl, formData, options);
   },
 
+  updateIssueMetricImage({ issueIid, id, imageId, url = null, urlText = null }) {
+    const metricImagesUrl = Api.buildUrl(this.issueMetricSingleImagePath)
+      .replace(':id', encodeURIComponent(id))
+      .replace(':issue_iid', encodeURIComponent(issueIid))
+      .replace(':image_id', encodeURIComponent(imageId));
+
+    // Construct multipart form data
+    const formData = new FormData();
+    if (url != null) {
+      formData.append('url', url);
+    }
+    if (urlText != null) {
+      formData.append('url_text', urlText);
+    }
+
+    return axios.put(metricImagesUrl, formData);
+  },
+
   deleteMetricImage({ issueIid, id, imageId }) {
     const individualMetricImageUrl = Api.buildUrl(this.issueMetricSingleImagePath)
       .replace(':id', encodeURIComponent(id))
diff --git a/ee/app/assets/javascripts/issues/show/components/incidents/metrics_image.vue b/ee/app/assets/javascripts/issues/show/components/incidents/metrics_image.vue
index 8e9f0ee2063365535ee959f3e58f11d49fcc845a..8eb8e52728dc624e6c3de28258679470ce9c685f 100644
--- a/ee/app/assets/javascripts/issues/show/components/incidents/metrics_image.vue
+++ b/ee/app/assets/javascripts/issues/show/components/incidents/metrics_image.vue
@@ -1,5 +1,15 @@
 <script>
-import { GlButton, GlCard, GlIcon, GlLink, GlModal, GlSprintf } from '@gitlab/ui';
+import {
+  GlButton,
+  GlFormGroup,
+  GlFormInput,
+  GlCard,
+  GlIcon,
+  GlLink,
+  GlModal,
+  GlSprintf,
+  GlTooltipDirective,
+} from '@gitlab/ui';
 import { mapActions } from 'vuex';
 import { __, s__ } from '~/locale';
 
@@ -9,15 +19,24 @@ export default {
     modalDescription: s__('Incident|Are you sure you wish to delete this image?'),
     modalCancel: __('Cancel'),
     modalTitle: s__('Incident|Deleting %{filename}'),
+    editModalUpdate: __('Update'),
+    editModalTitle: s__('Incident|Editing %{filename}'),
+    editIconTitle: s__('Incident|Edit image text or link'),
+    deleteIconTitle: s__('Incident|Delete image'),
   },
   components: {
     GlButton,
+    GlFormGroup,
+    GlFormInput,
     GlCard,
     GlIcon,
     GlLink,
     GlModal,
     GlSprintf,
   },
+  directives: {
+    GlTooltip: GlTooltipDirective,
+  },
   inject: ['canUpdate'],
   props: {
     id: {
@@ -37,16 +56,25 @@ export default {
       required: false,
       default: null,
     },
+    urlText: {
+      type: String,
+      required: false,
+      default: null,
+    },
   },
   data() {
     return {
       isCollapsed: false,
       isDeleting: false,
+      isUpdating: false,
       modalVisible: false,
+      editModalVisible: false,
+      modalUrl: this.url,
+      modalUrlText: this.urlText,
     };
   },
   computed: {
-    actionPrimaryProps() {
+    deleteActionPrimaryProps() {
       return {
         text: this.$options.i18n.modalDelete,
         attributes: {
@@ -57,6 +85,17 @@ export default {
         },
       };
     },
+    updateActionPrimaryProps() {
+      return {
+        text: this.$options.i18n.editModalUpdate,
+        attributes: {
+          loading: this.isUpdating,
+          disabled: this.isUpdating,
+          category: 'primary',
+          variant: 'confirm',
+        },
+      };
+    },
     arrowIconName() {
       return this.isCollapsed ? 'chevron-right' : 'chevron-down';
     },
@@ -70,10 +109,16 @@ export default {
     },
   },
   methods: {
-    ...mapActions(['deleteImage']),
+    ...mapActions(['deleteImage', 'updateImage']),
     toggleCollapsed() {
       this.isCollapsed = !this.isCollapsed;
     },
+    resetEditFields() {
+      this.modalUrl = this.url;
+      this.modalUrlText = this.urlText;
+      this.editModalVisible = false;
+      this.modalVisible = false;
+    },
     async onDelete() {
       try {
         this.isDeleting = true;
@@ -83,6 +128,21 @@ export default {
         this.modalVisible = false;
       }
     },
+    async onUpdate() {
+      try {
+        this.isUpdating = true;
+        await this.updateImage({
+          imageId: this.id,
+          url: this.modalUrl,
+          urlText: this.modalUrlText,
+        });
+      } finally {
+        this.isUpdating = false;
+        this.modalUrl = '';
+        this.modalUrlText = '';
+        this.editModalVisible = false;
+      }
+    },
   },
 };
 </script>
@@ -98,10 +158,10 @@ export default {
       modal-id="delete-metric-modal"
       size="sm"
       :visible="modalVisible"
-      :action-primary="actionPrimaryProps"
+      :action-primary="deleteActionPrimaryProps"
       :action-cancel="{ text: $options.i18n.modalCancel }"
       @primary.prevent="onDelete"
-      @hidden="modalVisible = false"
+      @hidden="resetEditFields"
     >
       <template #modal-title>
         <gl-sprintf :message="$options.i18n.modalTitle">
@@ -112,6 +172,46 @@ export default {
       </template>
       <p>{{ $options.i18n.modalDescription }}</p>
     </gl-modal>
+
+    <gl-modal
+      modal-id="edit-metric-modal"
+      size="sm"
+      :action-primary="updateActionPrimaryProps"
+      :action-cancel="{ text: $options.i18n.modalCancel }"
+      :visible="editModalVisible"
+      data-testid="metric-image-edit-modal"
+      @hidden="resetEditFields"
+      @primary.prevent="onUpdate"
+    >
+      <template #modal-title>
+        <gl-sprintf :message="$options.i18n.editModalTitle">
+          <template #filename>
+            {{ filename }}
+          </template>
+        </gl-sprintf>
+      </template>
+
+      <gl-form-group :label="__('Text (optional)')" label-for="upload-text-input">
+        <gl-form-input
+          id="upload-text-input"
+          v-model="modalUrlText"
+          data-testid="metric-image-text-field"
+        />
+      </gl-form-group>
+
+      <gl-form-group
+        :label="__('Link (optional)')"
+        label-for="upload-url-input"
+        :description="s__('Incidents|Must start with http or https')"
+      >
+        <gl-form-input
+          id="upload-url-input"
+          v-model="modalUrl"
+          data-testid="metric-image-url-field"
+        />
+      </gl-form-group>
+    </gl-modal>
+
     <template #header>
       <div class="gl-w-full gl-display-flex gl-flex-direction-row gl-justify-content-space-between">
         <div class="gl-display-flex gl-flex-direction-row gl-align-items-center gl-w-full">
@@ -125,18 +225,33 @@ export default {
           >
             <gl-icon class="gl-mr-2" :name="arrowIconName" />
           </gl-button>
-          <gl-link v-if="url" :href="url">
-            {{ filename }}
+          <gl-link v-if="url" :href="url" target="_blank" data-testid="metric-image-label-span">
+            {{ urlText == null || urlText == '' ? filename : urlText }}
+            <gl-icon name="external-link" class="gl-vertical-align-middle" />
           </gl-link>
-          <span v-else>{{ filename }}</span>
-          <gl-button
-            v-if="canUpdate"
-            class="gl-ml-auto"
-            icon="remove"
-            :aria-label="__('Delete')"
-            data-testid="delete-button"
-            @click="modalVisible = true"
-          />
+          <span v-else data-testid="metric-image-label-span">{{
+            urlText == null || urlText == '' ? filename : urlText
+          }}</span>
+          <div class="gl-ml-auto btn-group">
+            <gl-button
+              v-if="canUpdate"
+              v-gl-tooltip.bottom
+              icon="pencil"
+              :aria-label="__('Edit')"
+              :title="$options.i18n.editIconTitle"
+              data-testid="edit-button"
+              @click="editModalVisible = true"
+            />
+            <gl-button
+              v-if="canUpdate"
+              v-gl-tooltip.bottom
+              icon="remove"
+              :aria-label="__('Delete')"
+              :title="$options.i18n.deleteIconTitle"
+              data-testid="delete-button"
+              @click="modalVisible = true"
+            />
+          </div>
         </div>
       </div>
     </template>
diff --git a/ee/app/assets/javascripts/issues/show/components/incidents/metrics_tab.vue b/ee/app/assets/javascripts/issues/show/components/incidents/metrics_tab.vue
index b419cb4009066102f6d8e24f9f60351c1b8e365a..a2d72534ded0f70d74e51f612d4f42512611664a 100644
--- a/ee/app/assets/javascripts/issues/show/components/incidents/metrics_tab.vue
+++ b/ee/app/assets/javascripts/issues/show/components/incidents/metrics_tab.vue
@@ -22,6 +22,7 @@ export default {
       currentFiles: [],
       modalVisible: false,
       modalUrl: '',
+      modalUrlText: '',
     };
   },
   store: createStore(),
@@ -34,7 +35,7 @@ export default {
           loading: this.isUploadingImage,
           disabled: this.isUploadingImage,
           category: 'primary',
-          variant: 'success',
+          variant: 'confirm',
         },
       };
     },
@@ -48,6 +49,7 @@ export default {
     clearInputs() {
       this.modalVisible = false;
       this.modalUrl = '';
+      this.modalUrlText = '';
       this.currentFile = false;
     },
     openMetricDialog(files) {
@@ -56,7 +58,11 @@ export default {
     },
     async onUpload() {
       try {
-        await this.uploadImage({ files: this.currentFiles, url: this.modalUrl });
+        await this.uploadImage({
+          files: this.currentFiles,
+          url: this.modalUrl,
+          urlText: this.modalUrlText,
+        });
         // Error case handled within action
       } finally {
         this.clearInputs();
@@ -66,9 +72,9 @@ export default {
   i18n: {
     modalUpload: __('Upload'),
     modalCancel: __('Cancel'),
-    modalTitle: s__('Incidents|Add a URL'),
+    modalTitle: s__('Incidents|Add image details'),
     modalDescription: s__(
-      'Incidents|You can optionally add a URL to link users to the original graph.',
+      "Incidents|Add text or a link to display with your image. If you don't add either, the file name displays instead.",
     ),
     dropDescription: s__(
       'Incidents|Drop or %{linkStart}upload%{linkEnd} a metric screenshot to attach it to the incident',
@@ -93,8 +99,12 @@ export default {
       @primary.prevent="onUpload"
     >
       <p>{{ $options.i18n.modalDescription }}</p>
+      <gl-form-group :label="__('Text (optional)')" label-for="upload-text-input">
+        <gl-form-input id="upload-text-input" v-model="modalUrlText" />
+      </gl-form-group>
+
       <gl-form-group
-        :label="__('URL')"
+        :label="__('Link (optional)')"
         label-for="upload-url-input"
         :description="s__('Incidents|Must start with http or https')"
       >
diff --git a/ee/app/assets/javascripts/issues/show/components/incidents/service.js b/ee/app/assets/javascripts/issues/show/components/incidents/service.js
index 138f304fb256bb7fc8b800f2a56cff3db605b234..c80049c58bdbbb34d34ebc7f6885febcab2079d5 100644
--- a/ee/app/assets/javascripts/issues/show/components/incidents/service.js
+++ b/ee/app/assets/javascripts/issues/show/components/incidents/service.js
@@ -11,6 +11,11 @@ export const uploadMetricImage = async (payload) => {
   return convertObjectPropsToCamelCase(response.data);
 };
 
+export const updateMetricImage = async (payload) => {
+  const response = await Api.updateIssueMetricImage(payload);
+  return convertObjectPropsToCamelCase(response.data);
+};
+
 export const deleteMetricImage = async (payload) => {
   const response = await Api.deleteMetricImage(payload);
   return convertObjectPropsToCamelCase(response.data);
diff --git a/ee/app/assets/javascripts/issues/show/components/incidents/store/actions.js b/ee/app/assets/javascripts/issues/show/components/incidents/store/actions.js
index c8677e713a08312db2ea094dce1c476a7be97308..33a9beed1715d0f244ffff1c67cebd423bbfe55e 100644
--- a/ee/app/assets/javascripts/issues/show/components/incidents/store/actions.js
+++ b/ee/app/assets/javascripts/issues/show/components/incidents/store/actions.js
@@ -1,6 +1,11 @@
 import createFlash from '~/flash';
 import { s__ } from '~/locale';
-import { deleteMetricImage, getMetricImages, uploadMetricImage } from '../service';
+import {
+  deleteMetricImage,
+  getMetricImages,
+  uploadMetricImage,
+  updateMetricImage,
+} from '../service';
 import * as types from './mutation_types';
 
 export const fetchMetricImages = async ({ state, commit }) => {
@@ -17,13 +22,19 @@ export const fetchMetricImages = async ({ state, commit }) => {
   }
 };
 
-export const uploadImage = async ({ state, commit }, { files, url }) => {
+export const uploadImage = async ({ state, commit }, { files, url, urlText }) => {
   commit(types.REQUEST_METRIC_UPLOAD);
 
   const { issueIid, projectId } = state;
 
   try {
-    const response = await uploadMetricImage({ file: files.item(0), id: projectId, issueIid, url });
+    const response = await uploadMetricImage({
+      file: files.item(0),
+      id: projectId,
+      issueIid,
+      url,
+      urlText,
+    });
     commit(types.RECEIVE_METRIC_UPLOAD_SUCCESS, response);
   } catch (error) {
     commit(types.RECEIVE_METRIC_UPLOAD_ERROR);
@@ -31,6 +42,26 @@ export const uploadImage = async ({ state, commit }, { files, url }) => {
   }
 };
 
+export const updateImage = async ({ state, commit }, { imageId, url, urlText }) => {
+  commit(types.REQUEST_METRIC_UPLOAD);
+
+  const { issueIid, projectId } = state;
+
+  try {
+    const response = await updateMetricImage({
+      issueIid,
+      id: projectId,
+      imageId,
+      url,
+      urlText,
+    });
+    commit(types.RECEIVE_METRIC_UPDATE_SUCCESS, response);
+  } catch (error) {
+    commit(types.RECEIVE_METRIC_UPLOAD_ERROR);
+    createFlash({ message: s__('Incidents|There was an issue updating your image.') });
+  }
+};
+
 export const deleteImage = async ({ state, commit }, imageId) => {
   const { issueIid, projectId } = state;
 
diff --git a/ee/app/assets/javascripts/issues/show/components/incidents/store/mutation_types.js b/ee/app/assets/javascripts/issues/show/components/incidents/store/mutation_types.js
index 43cdbdbdf9bc93a82cae76c5fa36f938131b846f..8f1b31217a23d9c8997c5c4cd4224597e15668bc 100644
--- a/ee/app/assets/javascripts/issues/show/components/incidents/store/mutation_types.js
+++ b/ee/app/assets/javascripts/issues/show/components/incidents/store/mutation_types.js
@@ -6,6 +6,8 @@ export const REQUEST_METRIC_UPLOAD = 'REQUEST_METRIC_UPLOAD';
 export const RECEIVE_METRIC_UPLOAD_SUCCESS = 'RECEIVE_METRIC_UPLOAD_SUCCESS';
 export const RECEIVE_METRIC_UPLOAD_ERROR = 'RECEIVE_METRIC_UPLOAD_ERROR';
 
+export const RECEIVE_METRIC_UPDATE_SUCCESS = 'RECEIVE_METRIC_UPDATE_SUCCESS';
+
 export const RECEIVE_METRIC_DELETE_SUCCESS = 'RECEIVE_METRIC_DELETE_SUCCESS';
 
 export const SET_INITIAL_DATA = 'SET_INITIAL_DATA';
diff --git a/ee/app/assets/javascripts/issues/show/components/incidents/store/mutations.js b/ee/app/assets/javascripts/issues/show/components/incidents/store/mutations.js
index 60845ffff646f44d8eddc757943ad6a2798b4ced..56ff1b6d95d035145a6cfbea70d62d0f3c9be1dd 100644
--- a/ee/app/assets/javascripts/issues/show/components/incidents/store/mutations.js
+++ b/ee/app/assets/javascripts/issues/show/components/incidents/store/mutations.js
@@ -21,6 +21,13 @@ export default {
   [types.RECEIVE_METRIC_UPLOAD_ERROR](state) {
     state.isUploadingImage = false;
   },
+  [types.RECEIVE_METRIC_UPDATE_SUCCESS](state, image) {
+    state.isUploadingImage = false;
+    const metricIndex = state.metricImages.findIndex((img) => img.id === image.id);
+    if (metricIndex >= 0) {
+      state.metricImages.splice(metricIndex, 1, image);
+    }
+  },
   [types.RECEIVE_METRIC_DELETE_SUCCESS](state, imageId) {
     const metricIndex = state.metricImages.findIndex((image) => image.id === imageId);
     state.metricImages.splice(metricIndex, 1);
diff --git a/ee/spec/frontend/api_spec.js b/ee/spec/frontend/api_spec.js
index a94844b361089a6d213d008216389886856bd29d..979b0dceaf95e6686dbc345ae780784b39f293d9 100644
--- a/ee/spec/frontend/api_spec.js
+++ b/ee/spec/frontend/api_spec.js
@@ -732,12 +732,13 @@ describe('Api', () => {
     describe('uploadIssueMetricImage', () => {
       const file = 'mock file';
       const url = 'mock url';
+      const urlText = 'mock urlText';
 
       it('uploads an image', async () => {
         jest.spyOn(axios, 'post');
         mock.onPost(expectedUrl).replyOnce(httpStatus.OK, {});
 
-        await Api.uploadIssueMetricImage({ issueIid, id: projectId, file, url }).then(
+        await Api.uploadIssueMetricImage({ issueIid, id: projectId, file, url, urlText }).then(
           ({ data }) => {
             expect(data).toEqual({});
             expect(axios.post.mock.calls[0][2]).toEqual({
diff --git a/ee/spec/frontend/issues/show/components/incidents/__snapshots__/metrics_image_spec.js.snap b/ee/spec/frontend/issues/show/components/incidents/__snapshots__/metrics_image_spec.js.snap
index 43d3e7509d55984264f00ba6d9c36eb73ba31ad8..5dd12d9edf5eee7cb850b897a83a2c1f59fdcb5c 100644
--- a/ee/spec/frontend/issues/show/components/incidents/__snapshots__/metrics_image_spec.js.snap
+++ b/ee/spec/frontend/issues/show/components/incidents/__snapshots__/metrics_image_spec.js.snap
@@ -22,6 +22,43 @@ exports[`Metrics upload item render the metrics image component 1`] = `
       Are you sure you wish to delete this image?
     </p>
   </gl-modal-stub>
+   
+  <gl-modal-stub
+    actioncancel="[object Object]"
+    actionprimary="[object Object]"
+    data-testid="metric-image-edit-modal"
+    dismisslabel="Close"
+    modalclass=""
+    modalid="edit-metric-modal"
+    size="sm"
+    titletag="h4"
+  >
+     
+    <gl-form-group-stub
+      label="Text (optional)"
+      label-for="upload-text-input"
+      labeldescription=""
+      optionaltext="(optional)"
+    >
+      <gl-form-input-stub
+        data-testid="metric-image-text-field"
+        id="upload-text-input"
+      />
+    </gl-form-group-stub>
+     
+    <gl-form-group-stub
+      description="Must start with http or https"
+      label="Link (optional)"
+      label-for="upload-url-input"
+      labeldescription=""
+      optionaltext="(optional)"
+    >
+      <gl-form-input-stub
+        data-testid="metric-image-url-field"
+        id="upload-url-input"
+      />
+    </gl-form-group-stub>
+  </gl-modal-stub>
     
   <div
     class="gl-display-flex gl-flex-direction-column"
diff --git a/ee/spec/frontend/issues/show/components/incidents/metrics_image_spec.js b/ee/spec/frontend/issues/show/components/incidents/metrics_image_spec.js
index a91918755ba3789491c481b35193da712dc18ef2..c9997cfef46001f55fe97bb76e2c6f51b4b54dad 100644
--- a/ee/spec/frontend/issues/show/components/incidents/metrics_image_spec.js
+++ b/ee/spec/frontend/issues/show/components/incidents/metrics_image_spec.js
@@ -47,14 +47,22 @@ describe('Metrics upload item', () => {
   });
 
   const findImageLink = () => wrapper.findComponent(GlLink);
+  const findLabelTextSpan = () => wrapper.find('[data-testid="metric-image-label-span"]');
   const findCollapseButton = () => wrapper.find('[data-testid="collapse-button"]');
   const findMetricImageBody = () => wrapper.find('[data-testid="metric-image-body"]');
   const findModal = () => wrapper.findComponent(GlModal);
+  const findEditModal = () => wrapper.find('[data-testid="metric-image-edit-modal"]');
   const findDeleteButton = () => wrapper.find('[data-testid="delete-button"]');
+  const findEditButton = () => wrapper.find('[data-testid="edit-button"]');
+  const findImageTextInput = () => wrapper.find('[data-testid="metric-image-text-field"]');
+  const findImageUrlInput = () => wrapper.find('[data-testid="metric-image-url-field"]');
 
   const closeModal = () => findModal().vm.$emit('hidden');
   const submitModal = () => findModal().vm.$emit('primary', mockEvent);
   const deleteImage = () => findDeleteButton().vm.$emit('click');
+  const closeEditModal = () => findEditModal().vm.$emit('hidden');
+  const submitEditModal = () => findEditModal().vm.$emit('primary', mockEvent);
+  const editImage = () => findEditButton().vm.$emit('click');
 
   it('render the metrics image component', () => {
     mountComponent({}, shallowMount);
@@ -70,6 +78,22 @@ describe('Metrics upload item', () => {
     expect(findImageLink().text()).toBe(defaultProps.filename);
   });
 
+  it('shows a link with the url text, if url text is present', () => {
+    const testUrl = 'test_url';
+    const testUrlText = 'test_url_text';
+    mountComponent({ propsData: { url: testUrl, urlText: testUrlText } });
+
+    expect(findImageLink().attributes('href')).toBe(testUrl);
+    expect(findImageLink().text()).toBe(testUrlText);
+  });
+
+  it('shows the url text with no url, if no url is present', () => {
+    const testUrlText = 'test_url_text';
+    mountComponent({ propsData: { urlText: testUrlText } });
+
+    expect(findLabelTextSpan().text()).toBe(testUrlText);
+  });
+
   describe('expand and collapse', () => {
     beforeEach(() => {
       mountComponent();
@@ -89,7 +113,7 @@ describe('Metrics upload item', () => {
   });
 
   describe('delete functionality', () => {
-    it('should open the modal when clicked', async () => {
+    it('should open the delete modal when clicked', async () => {
       mountComponent({ stubs: { GlModal: true } });
 
       deleteImage();
@@ -138,4 +162,69 @@ describe('Metrics upload item', () => {
       });
     });
   });
+
+  describe('edit functionality', () => {
+    it('should open the delete modal when clicked', async () => {
+      mountComponent({ stubs: { GlModal: true } });
+
+      editImage();
+
+      await waitForPromises();
+
+      expect(findEditModal().attributes('visible')).toBe('true');
+    });
+
+    describe('when the modal is open', () => {
+      beforeEach(() => {
+        mountComponent({
+          data() {
+            return { editModalVisible: true };
+          },
+          propsData: { urlText: 'test' },
+          stubs: { GlModal: true },
+        });
+      });
+
+      it('should close the modal when cancelled', async () => {
+        closeEditModal();
+
+        await waitForPromises();
+
+        expect(findEditModal().attributes('visible')).toBeFalsy();
+      });
+
+      it('should delete the image when selected', async () => {
+        const dispatchSpy = jest.spyOn(store, 'dispatch').mockImplementation(jest.fn());
+
+        submitEditModal();
+
+        await waitForPromises();
+
+        expect(dispatchSpy).toHaveBeenCalledWith('updateImage', {
+          imageId: defaultProps.id,
+          url: null,
+          urlText: 'test',
+        });
+      });
+
+      it('should clear edits when the modal is closed', async () => {
+        await findImageTextInput().setValue('test value');
+        await findImageUrlInput().setValue('http://www.gitlab.com');
+
+        expect(findImageTextInput().element.value).toBe('test value');
+        expect(findImageUrlInput().element.value).toBe('http://www.gitlab.com');
+
+        closeEditModal();
+
+        await waitForPromises();
+
+        editImage();
+
+        await waitForPromises();
+
+        expect(findImageTextInput().element.value).toBe('test');
+        expect(findImageUrlInput().element.value).toBe('');
+      });
+    });
+  });
 });
diff --git a/ee/spec/frontend/issues/show/components/incidents/metrics_tab_spec.js b/ee/spec/frontend/issues/show/components/incidents/metrics_tab_spec.js
index b52a051fe2ba72bf5c5959be4282706d19536d97..51969ce54f0af6123e7136a1566451d6384a1b6b 100644
--- a/ee/spec/frontend/issues/show/components/incidents/metrics_tab_spec.js
+++ b/ee/spec/frontend/issues/show/components/incidents/metrics_tab_spec.js
@@ -131,7 +131,11 @@ describe('Metrics tab', () => {
 
       await waitForPromises();
 
-      expect(dispatchSpy).toHaveBeenCalledWith('uploadImage', { files: fileList, url: testUrl });
+      expect(dispatchSpy).toHaveBeenCalledWith('uploadImage', {
+        files: fileList,
+        url: testUrl,
+        urlText: '',
+      });
     });
 
     describe('url field', () => {
@@ -144,7 +148,11 @@ describe('Metrics tab', () => {
       });
 
       it('should display the url field', () => {
-        expect(wrapper.findComponent(GlFormInput).attributes('value')).toBe(testUrl);
+        expect(wrapper.find('#upload-url-input').attributes('value')).toBe(testUrl);
+      });
+
+      it('should display the url text field', () => {
+        expect(wrapper.find('#upload-text-input').attributes('value')).toBe('');
       });
 
       it('should clear url when cancelled', async () => {
diff --git a/ee/spec/frontend/issues/show/components/incidents/service_spec.js b/ee/spec/frontend/issues/show/components/incidents/service_spec.js
index 3b1bff10c4e9995eb9c2755ad115d604b091e63c..508c8ef8eb267ac2f0d21edf86bff97aa9de80d8 100644
--- a/ee/spec/frontend/issues/show/components/incidents/service_spec.js
+++ b/ee/spec/frontend/issues/show/components/incidents/service_spec.js
@@ -1,10 +1,15 @@
 import Api from 'ee/api';
-import { getMetricImages, uploadMetricImage } from 'ee/issues/show/components/incidents/service';
+import {
+  getMetricImages,
+  uploadMetricImage,
+  updateMetricImage,
+} from 'ee/issues/show/components/incidents/service';
 import { fileList, fileListRaw } from './mock_data';
 
 jest.mock('ee/api', () => ({
   fetchIssueMetricImages: jest.fn(),
   uploadIssueMetricImage: jest.fn(),
+  updateIssueMetricImage: jest.fn(),
 }));
 
 describe('Incidents service', () => {
@@ -23,4 +28,12 @@ describe('Incidents service', () => {
     expect(Api.uploadIssueMetricImage).toHaveBeenCalled();
     expect(result).toEqual(fileList[0]);
   });
+
+  it('updates a metric image', async () => {
+    Api.updateIssueMetricImage.mockResolvedValue({ data: fileListRaw[0] });
+    const result = await updateMetricImage();
+
+    expect(Api.updateIssueMetricImage).toHaveBeenCalled();
+    expect(result).toEqual(fileList[0]);
+  });
 });
diff --git a/ee/spec/frontend/issues/show/components/incidents/store/actions_spec.js b/ee/spec/frontend/issues/show/components/incidents/store/actions_spec.js
index 8d28d13a2015adf0d61a91581281921cee131d18..0a7931e70144987e935c7ebd5654792441deaac3 100644
--- a/ee/spec/frontend/issues/show/components/incidents/store/actions_spec.js
+++ b/ee/spec/frontend/issues/show/components/incidents/store/actions_spec.js
@@ -3,6 +3,7 @@ import Vuex from 'vuex';
 import {
   getMetricImages,
   uploadMetricImage,
+  updateMetricImage,
   deleteMetricImage,
 } from 'ee/issues/show/components/incidents/service';
 import createStore from 'ee/issues/show/components/incidents/store';
@@ -17,6 +18,7 @@ jest.mock('~/flash');
 jest.mock('ee/issues/show/components/incidents/service', () => ({
   getMetricImages: jest.fn(),
   uploadMetricImage: jest.fn(),
+  updateMetricImage: jest.fn(),
   deleteMetricImage: jest.fn(),
 }));
 
@@ -104,6 +106,37 @@ describe('Metrics tab store actions', () => {
     });
   });
 
+  describe('updating metric images', () => {
+    const payload = {
+      url: 'test_url',
+      urlText: 'url text',
+    };
+
+    it('should call success action when updating an image', () => {
+      updateMetricImage.mockImplementation(() => Promise.resolve());
+
+      testAction(actions.updateImage, payload, state, [
+        { type: types.REQUEST_METRIC_UPLOAD },
+        {
+          type: types.RECEIVE_METRIC_UPDATE_SUCCESS,
+        },
+      ]);
+    });
+
+    it('should call error action when failing to update an image', async () => {
+      updateMetricImage.mockImplementation(() => Promise.reject());
+
+      await testAction(
+        actions.updateImage,
+        payload,
+        state,
+        [{ type: types.REQUEST_METRIC_UPLOAD }, { type: types.RECEIVE_METRIC_UPLOAD_ERROR }],
+        [],
+      );
+      expect(createFlash).toHaveBeenCalled();
+    });
+  });
+
   describe('deleting a metric image', () => {
     const payload = fileList[0].id;
 
diff --git a/ee/spec/frontend/issues/show/components/incidents/store/mutation_spec.js b/ee/spec/frontend/issues/show/components/incidents/store/mutation_spec.js
index 5ffc8e464a3ea3228fc74670cb6f5cce87e3dfb8..02203f2477246d0bf45e52165a0a539c2138459a 100644
--- a/ee/spec/frontend/issues/show/components/incidents/store/mutation_spec.js
+++ b/ee/spec/frontend/issues/show/components/incidents/store/mutation_spec.js
@@ -101,6 +101,25 @@ describe('Metric images mutations', () => {
     });
   });
 
+  describe('RECEIVE_METRIC_UPDATE_SUCCESS', () => {
+    const initialImage = testImages[0];
+    const newImage = testImages[0];
+    newImage.url = 'https://www.gitlab.com';
+
+    beforeEach(() => {
+      createState({ metricImages: [initialImage] });
+      mutations[types.RECEIVE_METRIC_UPDATE_SUCCESS](state, newImage);
+    });
+
+    it('should unset the loading state', () => {
+      expect(state.isUploadingImage).toBe(false);
+    });
+
+    it('should replace the existing image with the new one', () => {
+      expect(state.metricImages).toMatchObject([newImage]);
+    });
+  });
+
   describe('RECEIVE_METRIC_DELETE_SUCCESS', () => {
     const deletedImageId = testImages[1].id;
     const expectedResult = [testImages[0], testImages[2]];
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 5bff1e0406422d1c5bcea288cec1757dc6165202..91845fc31c70f88411b03315d78bf685f3e237ad 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -18954,7 +18954,10 @@ msgstr ""
 msgid "Incidents"
 msgstr ""
 
-msgid "Incidents|Add a URL"
+msgid "Incidents|Add image details"
+msgstr ""
+
+msgid "Incidents|Add text or a link to display with your image. If you don't add either, the file name displays instead."
 msgstr ""
 
 msgid "Incidents|Drop or %{linkStart}upload%{linkEnd} a metric screenshot to attach it to the incident"
@@ -18969,10 +18972,10 @@ msgstr ""
 msgid "Incidents|There was an issue loading metric images."
 msgstr ""
 
-msgid "Incidents|There was an issue uploading your image."
+msgid "Incidents|There was an issue updating your image."
 msgstr ""
 
-msgid "Incidents|You can optionally add a URL to link users to the original graph."
+msgid "Incidents|There was an issue uploading your image."
 msgstr ""
 
 msgid "Incident|Alert details"
@@ -18981,9 +18984,18 @@ msgstr ""
 msgid "Incident|Are you sure you wish to delete this image?"
 msgstr ""
 
+msgid "Incident|Delete image"
+msgstr ""
+
 msgid "Incident|Deleting %{filename}"
 msgstr ""
 
+msgid "Incident|Edit image text or link"
+msgstr ""
+
+msgid "Incident|Editing %{filename}"
+msgstr ""
+
 msgid "Incident|Metrics"
 msgstr ""
 
@@ -21483,6 +21495,9 @@ msgstr ""
 msgid "Link"
 msgstr ""
 
+msgid "Link (optional)"
+msgstr ""
+
 msgid "Link Prometheus monitoring to GitLab."
 msgstr ""
 
@@ -35619,6 +35634,9 @@ msgstr ""
 msgid "Tests"
 msgstr ""
 
+msgid "Text (optional)"
+msgstr ""
+
 msgid "Text added to the body of all email messages. %{character_limit} character limit"
 msgstr ""