mirror of
				https://github.com/lingble/chatwoot.git
				synced 2025-11-03 20:48:07 +00:00 
			
		
		
		
	feat: Ability to remove inbox avatar (#2885)
* Delete inbox avatar 1) New API endpoint added for deleting inbox avatar. 2) Delete avatar button in the inbox settings page. Co-authored-by: Sojan Jose <sojan@pepalo.com> Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
This commit is contained in:
		@@ -15,6 +15,11 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
 | 
				
			|||||||
    @campaigns = @inbox.campaigns
 | 
					    @campaigns = @inbox.campaigns
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def avatar
 | 
				
			||||||
 | 
					    @inbox.avatar.attachment.destroy! if @inbox.avatar.attached?
 | 
				
			||||||
 | 
					    head :ok
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def create
 | 
					  def create
 | 
				
			||||||
    ActiveRecord::Base.transaction do
 | 
					    ActiveRecord::Base.transaction do
 | 
				
			||||||
      channel = create_channel
 | 
					      channel = create_channel
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,6 +13,10 @@ class Inboxes extends ApiClient {
 | 
				
			|||||||
  getCampaigns(inboxId) {
 | 
					  getCampaigns(inboxId) {
 | 
				
			||||||
    return axios.get(`${this.url}/${inboxId}/campaigns`);
 | 
					    return axios.get(`${this.url}/${inboxId}/campaigns`);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  deleteInboxAvatar(inboxId) {
 | 
				
			||||||
 | 
					    return axios.delete(`${this.url}/${inboxId}/avatar`);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default new Inboxes();
 | 
					export default new Inboxes();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -27,5 +27,12 @@ describe('#InboxesAPI', () => {
 | 
				
			|||||||
        '/api/v1/inboxes/2/campaigns'
 | 
					        '/api/v1/inboxes/2/campaigns'
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it('#deleteInboxAvatar', () => {
 | 
				
			||||||
 | 
					      inboxesAPI.deleteInboxAvatar(2);
 | 
				
			||||||
 | 
					      expect(context.axiosMock.delete).toHaveBeenCalledWith(
 | 
				
			||||||
 | 
					        '/api/v1/inboxes/2/avatar'
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,21 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
 | 
					  <div>
 | 
				
			||||||
    <label>
 | 
					    <label>
 | 
				
			||||||
      <span v-if="label">{{ label }}</span>
 | 
					      <span v-if="label">{{ label }}</span>
 | 
				
			||||||
 | 
					    </label>
 | 
				
			||||||
    <woot-thumbnail v-if="src" size="80px" :src="src" />
 | 
					    <woot-thumbnail v-if="src" size="80px" :src="src" />
 | 
				
			||||||
 | 
					    <div v-if="src && deleteAvatar" class="avatar-delete-btn">
 | 
				
			||||||
 | 
					      <woot-button
 | 
				
			||||||
 | 
					        color-scheme="alert"
 | 
				
			||||||
 | 
					        variant="hollow"
 | 
				
			||||||
 | 
					        size="tiny"
 | 
				
			||||||
 | 
					        @click="onAvatarDelete"
 | 
				
			||||||
 | 
					        >{{
 | 
				
			||||||
 | 
					          this.$t('INBOX_MGMT.DELETE.AVATAR_DELETE_BUTTON_TEXT')
 | 
				
			||||||
 | 
					        }}</woot-button
 | 
				
			||||||
 | 
					      >
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					    <label>
 | 
				
			||||||
      <input
 | 
					      <input
 | 
				
			||||||
        id="file"
 | 
					        id="file"
 | 
				
			||||||
        ref="file"
 | 
					        ref="file"
 | 
				
			||||||
@@ -11,6 +25,7 @@
 | 
				
			|||||||
      />
 | 
					      />
 | 
				
			||||||
      <slot></slot>
 | 
					      <slot></slot>
 | 
				
			||||||
    </label>
 | 
					    </label>
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script>
 | 
					<script>
 | 
				
			||||||
@@ -24,6 +39,10 @@ export default {
 | 
				
			|||||||
      type: String,
 | 
					      type: String,
 | 
				
			||||||
      default: '',
 | 
					      default: '',
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    deleteAvatar: {
 | 
				
			||||||
 | 
					      type: Boolean,
 | 
				
			||||||
 | 
					      default: false,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  watch: {},
 | 
					  watch: {},
 | 
				
			||||||
  methods: {
 | 
					  methods: {
 | 
				
			||||||
@@ -35,6 +54,17 @@ export default {
 | 
				
			|||||||
        url: URL.createObjectURL(file),
 | 
					        url: URL.createObjectURL(file),
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    onAvatarDelete() {
 | 
				
			||||||
 | 
					      this.$refs.file.value = null;
 | 
				
			||||||
 | 
					      this.$emit('onAvatarDelete');
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style lang="scss" scoped>
 | 
				
			||||||
 | 
					.avatar-delete-btn {
 | 
				
			||||||
 | 
					  margin-top: var(--space-smaller);
 | 
				
			||||||
 | 
					  margin-bottom: var(--space-smaller);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -232,6 +232,7 @@
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
    "DELETE": {
 | 
					    "DELETE": {
 | 
				
			||||||
      "BUTTON_TEXT": "Delete",
 | 
					      "BUTTON_TEXT": "Delete",
 | 
				
			||||||
 | 
					      "AVATAR_DELETE_BUTTON_TEXT": "Delete Avatar",
 | 
				
			||||||
      "CONFIRM": {
 | 
					      "CONFIRM": {
 | 
				
			||||||
        "TITLE": "Confirm Deletion",
 | 
					        "TITLE": "Confirm Deletion",
 | 
				
			||||||
        "MESSAGE": "Are you sure to delete ",
 | 
					        "MESSAGE": "Are you sure to delete ",
 | 
				
			||||||
@@ -241,7 +242,9 @@
 | 
				
			|||||||
      },
 | 
					      },
 | 
				
			||||||
      "API": {
 | 
					      "API": {
 | 
				
			||||||
        "SUCCESS_MESSAGE": "Inbox deleted successfully",
 | 
					        "SUCCESS_MESSAGE": "Inbox deleted successfully",
 | 
				
			||||||
        "ERROR_MESSAGE": "Could not delete inbox. Please try again later."
 | 
					        "ERROR_MESSAGE": "Could not delete inbox. Please try again later.",
 | 
				
			||||||
 | 
					        "AVATAR_SUCCESS_MESSAGE": "Inbox avatar deleted successfully",
 | 
				
			||||||
 | 
					        "AVATAR_ERROR_MESSAGE": "Could not delete the inbox avatar. Please try again later."
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "TABS": {
 | 
					    "TABS": {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -22,7 +22,9 @@
 | 
				
			|||||||
        <woot-avatar-uploader
 | 
					        <woot-avatar-uploader
 | 
				
			||||||
          :label="$t('INBOX_MGMT.ADD.WEBSITE_CHANNEL.CHANNEL_AVATAR.LABEL')"
 | 
					          :label="$t('INBOX_MGMT.ADD.WEBSITE_CHANNEL.CHANNEL_AVATAR.LABEL')"
 | 
				
			||||||
          :src="avatarUrl"
 | 
					          :src="avatarUrl"
 | 
				
			||||||
 | 
					          deleteAvatar
 | 
				
			||||||
          @change="handleImageUpload"
 | 
					          @change="handleImageUpload"
 | 
				
			||||||
 | 
					          @onAvatarDelete="handleAvatarDelete"
 | 
				
			||||||
        />
 | 
					        />
 | 
				
			||||||
        <woot-input
 | 
					        <woot-input
 | 
				
			||||||
          v-model.trim="selectedInboxName"
 | 
					          v-model.trim="selectedInboxName"
 | 
				
			||||||
@@ -544,6 +546,23 @@ export default {
 | 
				
			|||||||
      this.avatarFile = file;
 | 
					      this.avatarFile = file;
 | 
				
			||||||
      this.avatarUrl = url;
 | 
					      this.avatarUrl = url;
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    async handleAvatarDelete() {
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        await this.$store.dispatch(
 | 
				
			||||||
 | 
					          'inboxes/deleteInboxAvatar',
 | 
				
			||||||
 | 
					          this.currentInboxId
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        this.avatarFile = null;
 | 
				
			||||||
 | 
					        this.avatarUrl = '';
 | 
				
			||||||
 | 
					        this.showAlert(this.$t('INBOX_MGMT.DELETE.API.AVATAR_SUCCESS_MESSAGE'));
 | 
				
			||||||
 | 
					      } catch (error) {
 | 
				
			||||||
 | 
					        this.showAlert(
 | 
				
			||||||
 | 
					          error.message
 | 
				
			||||||
 | 
					            ? error.message
 | 
				
			||||||
 | 
					            : this.$t('INBOX_MGMT.DELETE.API.AVATAR_ERROR_MESSAGE')
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  validations: {
 | 
					  validations: {
 | 
				
			||||||
    selectedAgents: {
 | 
					    selectedAgents: {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -173,6 +173,13 @@ export const actions = {
 | 
				
			|||||||
      throw new Error(error.message);
 | 
					      throw new Error(error.message);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					  deleteInboxAvatar: async (_, inboxId) => {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      await InboxesAPI.deleteInboxAvatar(inboxId);
 | 
				
			||||||
 | 
					    } catch (error) {
 | 
				
			||||||
 | 
					      throw new Error(error);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const mutations = {
 | 
					export const mutations = {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -128,4 +128,19 @@ describe('#actions', () => {
 | 
				
			|||||||
      ]);
 | 
					      ]);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe('#deleteInboxAvatar', () => {
 | 
				
			||||||
 | 
					    it('sends correct actions if API is success', async () => {
 | 
				
			||||||
 | 
					      axios.delete.mockResolvedValue();
 | 
				
			||||||
 | 
					      await expect(
 | 
				
			||||||
 | 
					        actions.deleteInboxAvatar({}, inboxList[0].id)
 | 
				
			||||||
 | 
					      ).resolves.toBe();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    it('sends correct actions if API is error', async () => {
 | 
				
			||||||
 | 
					      axios.delete.mockRejectedValue({ message: 'Incorrect header' });
 | 
				
			||||||
 | 
					      await expect(
 | 
				
			||||||
 | 
					        actions.deleteInboxAvatar({}, inboxList[0].id)
 | 
				
			||||||
 | 
					      ).rejects.toThrow(Error);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -53,4 +53,8 @@ class InboxPolicy < ApplicationPolicy
 | 
				
			|||||||
  def set_agent_bot?
 | 
					  def set_agent_bot?
 | 
				
			||||||
    @account_user.administrator?
 | 
					    @account_user.administrator?
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def avatar?
 | 
				
			||||||
 | 
					    @account_user.administrator?
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -102,6 +102,7 @@ Rails.application.routes.draw do
 | 
				
			|||||||
            get :campaigns, on: :member
 | 
					            get :campaigns, on: :member
 | 
				
			||||||
            get :agent_bot, on: :member
 | 
					            get :agent_bot, on: :member
 | 
				
			||||||
            post :set_agent_bot, on: :member
 | 
					            post :set_agent_bot, on: :member
 | 
				
			||||||
 | 
					            delete :avatar, on: :member
 | 
				
			||||||
          end
 | 
					          end
 | 
				
			||||||
          resources :inbox_members, only: [:create, :show], param: :inbox_id
 | 
					          resources :inbox_members, only: [:create, :show], param: :inbox_id
 | 
				
			||||||
          resources :labels, only: [:index, :show, :create, :update, :destroy]
 | 
					          resources :labels, only: [:index, :show, :create, :update, :destroy]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -113,6 +113,42 @@ RSpec.describe 'Inboxes API', type: :request do
 | 
				
			|||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe 'DELETE /api/v1/accounts/{account.id}/inboxes/{inbox.id}/avatar' do
 | 
				
			||||||
 | 
					    let(:inbox) { create(:inbox, account: account) }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    context 'when it is an unauthenticated user' do
 | 
				
			||||||
 | 
					      it 'returns unauthorized' do
 | 
				
			||||||
 | 
					        delete "/api/v1/accounts/#{account.id}/inboxes/#{inbox.id}/avatar"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        expect(response).to have_http_status(:unauthorized)
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    context 'when it is an authenticated user' do
 | 
				
			||||||
 | 
					      before do
 | 
				
			||||||
 | 
					        create(:inbox_member, user: agent, inbox: inbox)
 | 
				
			||||||
 | 
					        inbox.avatar.attach(io: File.open(Rails.root.join('spec/assets/avatar.png')), filename: 'avatar.png', content_type: 'image/png')
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it 'delete inbox avatar for administrator user' do
 | 
				
			||||||
 | 
					        delete "/api/v1/accounts/#{account.id}/inboxes/#{inbox.id}/avatar",
 | 
				
			||||||
 | 
					               headers: admin.create_new_auth_token,
 | 
				
			||||||
 | 
					               as: :json
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        expect { inbox.avatar.attachment.reload }.to raise_error(ActiveRecord::RecordNotFound)
 | 
				
			||||||
 | 
					        expect(response).to have_http_status(:success)
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it 'returns unauthorized for agent user' do
 | 
				
			||||||
 | 
					        delete "/api/v1/accounts/#{account.id}/inboxes/#{inbox.id}/avatar",
 | 
				
			||||||
 | 
					               headers: agent.create_new_auth_token,
 | 
				
			||||||
 | 
					               as: :json
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        expect(response).to have_http_status(:unauthorized)
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  describe 'DELETE /api/v1/accounts/{account.id}/inboxes/:id' do
 | 
					  describe 'DELETE /api/v1/accounts/{account.id}/inboxes/:id' do
 | 
				
			||||||
    let(:inbox) { create(:inbox, account: account) }
 | 
					    let(:inbox) { create(:inbox, account: account) }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user