From 6e1493501a7f3c2131ddbfd65c84718e44beab8d Mon Sep 17 00:00:00 2001 From: Pranav Raj S Date: Fri, 23 Jul 2021 17:04:33 +0530 Subject: [PATCH] feat: Add APIs for custom attribute definitions (#2689) --- ...custom_attribute_definitions_controller.rb | 50 ++++++ app/models/account.rb | 2 + app/models/custom_attribute_definition.rb | 34 +++++ .../create.json.jbuilder | 1 + .../index.json.jbuilder | 3 + .../show.json.jbuilder | 1 + .../update.json.jbuilder | 1 + ..._custom_attribute_definition.json.jbuilder | 7 + config/routes.rb | 1 + ...2095814_add_custom_attribute_definition.rb | 16 ++ db/schema.rb | 15 +- ...m_attribute_definitions_controller_spec.rb | 142 ++++++++++++++++++ .../factories/custom_attribute_definitions.rb | 12 ++ 13 files changed, 284 insertions(+), 1 deletion(-) create mode 100644 app/controllers/api/v1/accounts/custom_attribute_definitions_controller.rb create mode 100644 app/models/custom_attribute_definition.rb create mode 100644 app/views/api/v1/accounts/custom_attribute_definitions/create.json.jbuilder create mode 100644 app/views/api/v1/accounts/custom_attribute_definitions/index.json.jbuilder create mode 100644 app/views/api/v1/accounts/custom_attribute_definitions/show.json.jbuilder create mode 100644 app/views/api/v1/accounts/custom_attribute_definitions/update.json.jbuilder create mode 100644 app/views/api/v1/models/_custom_attribute_definition.json.jbuilder create mode 100644 db/migrate/20210722095814_add_custom_attribute_definition.rb create mode 100644 spec/controllers/api/v1/accounts/custom_attribute_definitions_controller_spec.rb create mode 100644 spec/factories/custom_attribute_definitions.rb diff --git a/app/controllers/api/v1/accounts/custom_attribute_definitions_controller.rb b/app/controllers/api/v1/accounts/custom_attribute_definitions_controller.rb new file mode 100644 index 000000000..687da55bd --- /dev/null +++ b/app/controllers/api/v1/accounts/custom_attribute_definitions_controller.rb @@ -0,0 +1,50 @@ +class Api::V1::Accounts::CustomAttributeDefinitionsController < Api::V1::Accounts::BaseController + before_action :fetch_custom_attributes_definitions, except: [:create] + before_action :fetch_custom_attribute_definition, only: [:show, :update, :destroy] + DEFAULT_ATTRIBUTE_MODEL = 'conversation_attribute'.freeze + + def index; end + + def show; end + + def create + @custom_attribute_definition = Current.account.custom_attribute_definitions.create!( + permitted_payload + ) + end + + def update + @custom_attribute_definition.update!(permitted_payload) + end + + def destroy + @custom_attribute_definition.destroy + head :no_content + end + + private + + def fetch_custom_attributes_definitions + @custom_attribute_definitions = Current.account.custom_attribute_definitions.where( + attribute_model: permitted_params[:attribute_model] || DEFAULT_ATTRIBUTE_MODEL + ) + end + + def fetch_custom_attribute_definition + @custom_attribute_definition = @custom_attribute_definitions.find(permitted_params[:id]) + end + + def permitted_payload + params.require(:custom_attribute_definition).permit( + :attribute_display_name, + :attribute_display_type, + :attribute_key, + :attribute_model, + :default_value + ) + end + + def permitted_params + params.permit(:id, :filter_type) + end +end diff --git a/app/models/account.rb b/app/models/account.rb index ba73d1eaa..62bb0d450 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -61,6 +61,8 @@ class Account < ApplicationRecord has_many :kbase_articles, dependent: :destroy, class_name: '::Kbase::Article' has_many :teams, dependent: :destroy has_many :custom_filters, dependent: :destroy + has_many :custom_attribute_definitions, dependent: :destroy + has_flags ACCOUNT_SETTINGS_FLAGS.merge(column: 'settings_flags').merge(DEFAULT_QUERY_SETTING) enum locale: LANGUAGES_CONFIG.map { |key, val| [val[:iso_639_1_code], key] }.to_h diff --git a/app/models/custom_attribute_definition.rb b/app/models/custom_attribute_definition.rb new file mode 100644 index 000000000..41fd40a2c --- /dev/null +++ b/app/models/custom_attribute_definition.rb @@ -0,0 +1,34 @@ +# == Schema Information +# +# Table name: custom_attribute_definitions +# +# id :bigint not null, primary key +# attribute_display_name :string +# attribute_display_type :integer default("text") +# attribute_key :string +# attribute_model :integer default("conversation_attribute") +# default_value :integer +# created_at :datetime not null +# updated_at :datetime not null +# account_id :bigint +# +# Indexes +# +# attribute_key_model_index (attribute_key,attribute_model) UNIQUE +# index_custom_attribute_definitions_on_account_id (account_id) +# +class CustomAttributeDefinition < ApplicationRecord + validates :attribute_display_name, presence: true + + validates :attribute_key, + presence: true, + uniqueness: { scope: :attribute_model } + + validates :attribute_display_type, presence: true + validates :attribute_model, presence: true + + enum attribute_model: { conversation_attribute: 0, contact_attribute: 1 } + enum attribute_display_type: { text: 0, number: 1, currency: 2, percent: 3, link: 4 } + + belongs_to :account +end diff --git a/app/views/api/v1/accounts/custom_attribute_definitions/create.json.jbuilder b/app/views/api/v1/accounts/custom_attribute_definitions/create.json.jbuilder new file mode 100644 index 000000000..d5be04284 --- /dev/null +++ b/app/views/api/v1/accounts/custom_attribute_definitions/create.json.jbuilder @@ -0,0 +1 @@ +json.partial! 'api/v1/models/custom_attribute_definition.json.jbuilder', resource: @custom_attribute_definition diff --git a/app/views/api/v1/accounts/custom_attribute_definitions/index.json.jbuilder b/app/views/api/v1/accounts/custom_attribute_definitions/index.json.jbuilder new file mode 100644 index 000000000..7bf1dd8aa --- /dev/null +++ b/app/views/api/v1/accounts/custom_attribute_definitions/index.json.jbuilder @@ -0,0 +1,3 @@ +json.array! @custom_attribute_definitions do |custom_attribute_definition| + json.partial! 'api/v1/models/custom_attribute_definition.json.jbuilder', resource: custom_attribute_definition +end diff --git a/app/views/api/v1/accounts/custom_attribute_definitions/show.json.jbuilder b/app/views/api/v1/accounts/custom_attribute_definitions/show.json.jbuilder new file mode 100644 index 000000000..d5be04284 --- /dev/null +++ b/app/views/api/v1/accounts/custom_attribute_definitions/show.json.jbuilder @@ -0,0 +1 @@ +json.partial! 'api/v1/models/custom_attribute_definition.json.jbuilder', resource: @custom_attribute_definition diff --git a/app/views/api/v1/accounts/custom_attribute_definitions/update.json.jbuilder b/app/views/api/v1/accounts/custom_attribute_definitions/update.json.jbuilder new file mode 100644 index 000000000..d5be04284 --- /dev/null +++ b/app/views/api/v1/accounts/custom_attribute_definitions/update.json.jbuilder @@ -0,0 +1 @@ +json.partial! 'api/v1/models/custom_attribute_definition.json.jbuilder', resource: @custom_attribute_definition diff --git a/app/views/api/v1/models/_custom_attribute_definition.json.jbuilder b/app/views/api/v1/models/_custom_attribute_definition.json.jbuilder new file mode 100644 index 000000000..e26c65bf2 --- /dev/null +++ b/app/views/api/v1/models/_custom_attribute_definition.json.jbuilder @@ -0,0 +1,7 @@ +json.attribute_display_name resource.attribute_display_name +json.attribute_display_type resource.attribute_display_type +json.attribute_key resource.attribute_key +json.attribute_model resource.attribute_model +json.default_value resource.default_value +json.created_at resource.created_at +json.updated_at resource.updated_at diff --git a/config/routes.rb b/config/routes.rb index 6d2ed531b..103063b13 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -92,6 +92,7 @@ Rails.application.routes.draw do get :metrics end end + resources :custom_attribute_definitions, only: [:index, :show, :create, :update, :destroy] resources :custom_filters, only: [:index, :show, :create, :update, :destroy] resources :inboxes, only: [:index, :create, :update, :destroy] do get :assignable_agents, on: :member diff --git a/db/migrate/20210722095814_add_custom_attribute_definition.rb b/db/migrate/20210722095814_add_custom_attribute_definition.rb new file mode 100644 index 000000000..3aae0b2f7 --- /dev/null +++ b/db/migrate/20210722095814_add_custom_attribute_definition.rb @@ -0,0 +1,16 @@ +class AddCustomAttributeDefinition < ActiveRecord::Migration[6.0] + def change + create_table :custom_attribute_definitions do |t| + t.string :attribute_display_name + t.string :attribute_key + t.integer :attribute_display_type, default: 0 + t.integer :default_value + t.integer :attribute_model, default: 0 + t.references :account, index: true + + t.timestamps + end + + add_index :custom_attribute_definitions, [:attribute_key, :attribute_model], unique: true, name: 'attribute_key_model_index' + end +end diff --git a/db/schema.rb b/db/schema.rb index b8e10c455..fed4bb135 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2021_07_21_182458) do +ActiveRecord::Schema.define(version: 2021_07_22_095814) do # These are extensions that must be enabled in order to support this database enable_extension "pg_stat_statements" @@ -292,6 +292,19 @@ ActiveRecord::Schema.define(version: 2021_07_21_182458) do t.index ["message_id"], name: "index_csat_survey_responses_on_message_id", unique: true end + create_table "custom_attribute_definitions", force: :cascade do |t| + t.string "attribute_display_name" + t.string "attribute_key" + t.integer "attribute_display_type", default: 0 + t.integer "default_value" + t.integer "attribute_model", default: 0 + t.bigint "account_id" + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.index ["account_id"], name: "index_custom_attribute_definitions_on_account_id" + t.index ["attribute_key", "attribute_model"], name: "attribute_key_model_index", unique: true + end + create_table "custom_filters", force: :cascade do |t| t.string "name", null: false t.integer "filter_type", default: 0, null: false diff --git a/spec/controllers/api/v1/accounts/custom_attribute_definitions_controller_spec.rb b/spec/controllers/api/v1/accounts/custom_attribute_definitions_controller_spec.rb new file mode 100644 index 000000000..5b0430e47 --- /dev/null +++ b/spec/controllers/api/v1/accounts/custom_attribute_definitions_controller_spec.rb @@ -0,0 +1,142 @@ +require 'rails_helper' + +RSpec.describe 'Custom Attribute Definitions API', type: :request do + let(:account) { create(:account) } + let(:user) { create(:user, account: account) } + + describe 'GET /api/v1/accounts/{account.id}/custom_attribute_definitions' do + context 'when it is an unauthenticated user' do + it 'returns unauthorized' do + get "/api/v1/accounts/#{account.id}/custom_attribute_definitions" + expect(response).to have_http_status(:unauthorized) + end + end + + context 'when it is an authenticated user' do + let!(:custom_attribute_definition) { create(:custom_attribute_definition, account: account) } + + it 'returns all customer attribute definitions related to the account' do + create(:custom_attribute_definition, attribute_model: 'contact_attribute', account: account) + + get "/api/v1/accounts/#{account.id}/custom_attribute_definitions", + headers: user.create_new_auth_token, + as: :json + + expect(response).to have_http_status(:success) + response_body = JSON.parse(response.body) + + expect(response_body.count).to eq(1) + expect(response_body.first['attribute_key']).to eq(custom_attribute_definition.attribute_key) + end + end + end + + describe 'GET /api/v1/accounts/{account.id}/custom_attribute_definitions/:id' do + let!(:custom_attribute_definition) { create(:custom_attribute_definition, account: account) } + + context 'when it is an unauthenticated user' do + it 'returns unauthorized' do + get "/api/v1/accounts/#{account.id}/custom_attribute_definitions/#{custom_attribute_definition.id}" + + expect(response).to have_http_status(:unauthorized) + end + end + + context 'when it is an authenticated user' do + it 'shows the custom attribute definition' do + get "/api/v1/accounts/#{account.id}/custom_attribute_definitions/#{custom_attribute_definition.id}", + headers: user.create_new_auth_token, + as: :json + + expect(response).to have_http_status(:success) + expect(response.body).to include(custom_attribute_definition.attribute_key) + end + end + end + + describe 'POST /api/v1/accounts/{account.id}/custom_attribute_definitions' do + let(:payload) do + { + custom_attribute_definition: { + attribute_display_name: 'Developer ID', + attribute_key: 'developer_id', + attribute_model: 'contact_attribute', + attribute_display_type: 'text', + default_value: '' + } + } + end + + context 'when it is an unauthenticated user' do + it 'returns unauthorized' do + expect do + post "/api/v1/accounts/#{account.id}/custom_attribute_definitions", + params: payload + end.to change(CustomAttributeDefinition, :count).by(0) + + expect(response).to have_http_status(:unauthorized) + end + end + + context 'when it is an authenticated user' do + it 'creates the filter' do + expect do + post "/api/v1/accounts/#{account.id}/custom_attribute_definitions", headers: user.create_new_auth_token, + params: payload + end.to change(CustomAttributeDefinition, :count).by(1) + + expect(response).to have_http_status(:success) + json_response = JSON.parse(response.body) + expect(json_response['attribute_key']).to eq 'developer_id' + end + end + end + + describe 'PATCH /api/v1/accounts/{account.id}/custom_attribute_definitions/:id' do + let(:payload) { { custom_attribute_definition: { attribute_display_name: 'Developer ID', attribute_key: 'developer_id' } } } + let!(:custom_attribute_definition) { create(:custom_attribute_definition, account: account) } + + context 'when it is an unauthenticated user' do + it 'returns unauthorized' do + put "/api/v1/accounts/#{account.id}/custom_attribute_definitions/#{custom_attribute_definition.id}", + params: payload + + expect(response).to have_http_status(:unauthorized) + end + end + + context 'when it is an authenticated user' do + it 'updates the custom attribute definition' do + patch "/api/v1/accounts/#{account.id}/custom_attribute_definitions/#{custom_attribute_definition.id}", + headers: user.create_new_auth_token, + params: payload, + as: :json + expect(response).to have_http_status(:success) + expect(custom_attribute_definition.reload.attribute_display_name).to eq('Developer ID') + expect(custom_attribute_definition.reload.attribute_key).to eq('developer_id') + expect(custom_attribute_definition.reload.attribute_model).to eq('conversation_attribute') + end + end + end + + describe 'DELETE /api/v1/accounts/{account.id}/custom_attribute_definitions/:id' do + let!(:custom_attribute_definition) { create(:custom_attribute_definition, account: account) } + + context 'when it is an unauthenticated user' do + it 'returns unauthorized' do + delete "/api/v1/accounts/#{account.id}/custom_attribute_definitions/#{custom_attribute_definition.id}" + expect(response).to have_http_status(:unauthorized) + end + end + + context 'when it is an authenticated admin user' do + it 'deletes custom attribute' do + delete "/api/v1/accounts/#{account.id}/custom_attribute_definitions/#{custom_attribute_definition.id}", + headers: user.create_new_auth_token, + as: :json + expect(response).to have_http_status(:no_content) + expect(account.custom_attribute_definitions.count).to be 0 + end + end + end +end diff --git a/spec/factories/custom_attribute_definitions.rb b/spec/factories/custom_attribute_definitions.rb new file mode 100644 index 000000000..9ac6df4ca --- /dev/null +++ b/spec/factories/custom_attribute_definitions.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :custom_attribute_definition do + sequence(:attribute_display_name) { |n| "Custom Attribute Definition #{n}" } + sequence(:attribute_key) { |n| "custom_attribute_#{n}" } + attribute_display_type { 1 } + attribute_model { 0 } + default_value { nil } + account + end +end