diff --git a/api/app/assets/javascripts/mno_enterprise/config.js.coffee.erb b/api/app/assets/javascripts/mno_enterprise/config.js.coffee.erb index 33f2e123f..f1870a495 100644 --- a/api/app/assets/javascripts/mno_enterprise/config.js.coffee.erb +++ b/api/app/assets/javascripts/mno_enterprise/config.js.coffee.erb @@ -11,6 +11,7 @@ angular.module('mnoEnterprise.configuration', []) .constant('PRICING_CONFIG', <%= Hash(Settings.pricing).to_json %>) .constant('DOCK_CONFIG', <%= Hash(Settings.dock).to_json %>) .constant('DEVELOPER_SECTION_CONFIG', <%= Hash(Settings.developer).to_json %>) + .constant('REVIEWS_CONFIG', <%= Hash(Settings.reviews).to_json %>) .constant('GOOGLE_TAG_CONTAINER_ID', <%= MnoEnterprise.google_tag_container.to_json %>) .constant('INTERCOM_ID', <%= MnoEnterprise.intercom_app_id.to_json %>) .constant('APP_NAME', <%= MnoEnterprise.app_name.to_json %>) diff --git a/api/app/controllers/mno_enterprise/jpi/v1/admin/app_reviews_controller.rb b/api/app/controllers/mno_enterprise/jpi/v1/admin/app_reviews_controller.rb new file mode 100644 index 000000000..16b0c25b8 --- /dev/null +++ b/api/app/controllers/mno_enterprise/jpi/v1/admin/app_reviews_controller.rb @@ -0,0 +1,30 @@ +module MnoEnterprise + class Jpi::V1::Admin::AppReviewsController < Jpi::V1::Admin::BaseResourceController + # GET /mnoe/jpi/v1/admin/app_reviews + def index + @app_reviews = MnoEnterprise::AppReview + @app_reviews = @app_reviews.limit(params[:limit]) if params[:limit] + @app_reviews = @app_reviews.skip(params[:offset]) if params[:offset] + @app_reviews = @app_reviews.order_by(params[:order_by]) if params[:order_by] + @app_reviews = @app_reviews.where(params[:where]) if params[:where] + @app_reviews = @app_reviews.all.fetch + response.headers['X-Total-Count'] = @app_reviews.metadata[:pagination][:count] + end + + # GET /mnoe/jpi/v1/admin/app_reviews/1 + def show + @app_review = MnoEnterprise::AppReview.find(params[:id]) + end + + # PATCH /mnoe/jpi/v1/admin/app_reviews/1 + def update + @app_review = MnoEnterprise::AppReview.find(params[:id]) + @app_review.update(app_review_params) + render :show + end + + def app_review_params + params.require(:app_review).permit(:status) + end + end +end diff --git a/api/app/controllers/mno_enterprise/jpi/v1/app_reviews_controller.rb b/api/app/controllers/mno_enterprise/jpi/v1/app_reviews_controller.rb new file mode 100644 index 000000000..d4bcb6a4c --- /dev/null +++ b/api/app/controllers/mno_enterprise/jpi/v1/app_reviews_controller.rb @@ -0,0 +1,34 @@ +module MnoEnterprise + class Jpi::V1::AppReviewsController < Jpi::V1::BaseResourceController + # GET /mnoe/jpi/v1/marketplace/:id/app_reviews + def index + @app_reviews = MnoEnterprise::AppReview.approved.where(reviewable_id: params[:id]) + @app_reviews = @app_reviews.limit(params[:limit]) if params[:limit] + @app_reviews = @app_reviews.skip(params[:offset]) if params[:offset] + @app_reviews = @app_reviews.order_by(params[:order_by]) if params[:order_by] + @app_reviews = @app_reviews.where(params[:where]) if params[:where] + @app_reviews = @app_reviews.all.fetch + response.headers['X-Total-Count'] = @app_reviews.metadata[:pagination][:count] + end + + # POST /mnoe/jpi/v1/marketplace/:id/app_reviews + def create + @app = MnoEnterprise::App.find(params[:id]) + return render json: "could not find App #{params[:id]}", status: :not_found unless @app + + # TODO: use the has_many associations -> @app.reviews.build + @app_review = MnoEnterprise::AppReview.new(review_params(@app.id)) + if @app_review.save + @average_rating = @app.reload.average_rating + render :show + else + render json: @app_review.errors, status: :bad_request + end + end + + def review_params(app_id) + params.require(:app_review).permit(:rating, :description, :organization_id) + .merge(app_id: app_id, user_id: current_user.id) + end + end +end diff --git a/api/app/views/mno_enterprise/jpi/v1/admin/app_reviews/_app_review.json.jbuilder b/api/app/views/mno_enterprise/jpi/v1/admin/app_reviews/_app_review.json.jbuilder new file mode 100644 index 000000000..938895814 --- /dev/null +++ b/api/app/views/mno_enterprise/jpi/v1/admin/app_reviews/_app_review.json.jbuilder @@ -0,0 +1,12 @@ +json.id app_review.id +json.rating app_review.rating +json.description app_review.description +json.status app_review.status +json.app_id app_review.app_id +json.app_name app_review.app_name +json.user_id app_review.user_id +json.user_name app_review.user_name +json.organization_id app_review.organization_id +json.organization_name app_review.organization_name +json.created_at app_review.created_at +json.updated_at app_review.updated_at diff --git a/api/app/views/mno_enterprise/jpi/v1/admin/app_reviews/index.json.jbuilder b/api/app/views/mno_enterprise/jpi/v1/admin/app_reviews/index.json.jbuilder new file mode 100644 index 000000000..29128d36f --- /dev/null +++ b/api/app/views/mno_enterprise/jpi/v1/admin/app_reviews/index.json.jbuilder @@ -0,0 +1 @@ +json.app_reviews @app_reviews, partial: 'app_review', as: :app_review diff --git a/api/app/views/mno_enterprise/jpi/v1/admin/app_reviews/show.json.jbuilder b/api/app/views/mno_enterprise/jpi/v1/admin/app_reviews/show.json.jbuilder new file mode 100644 index 000000000..992a4dd74 --- /dev/null +++ b/api/app/views/mno_enterprise/jpi/v1/admin/app_reviews/show.json.jbuilder @@ -0,0 +1,3 @@ +json.app_review do + json.partial! 'app_review', app_review: @app_review +end diff --git a/api/app/views/mno_enterprise/jpi/v1/app_reviews/_resource.json.jbuilder b/api/app/views/mno_enterprise/jpi/v1/app_reviews/_resource.json.jbuilder new file mode 100644 index 000000000..e2fbd8743 --- /dev/null +++ b/api/app/views/mno_enterprise/jpi/v1/app_reviews/_resource.json.jbuilder @@ -0,0 +1,12 @@ +json.id app_review[:id] +json.rating app_review[:rating] +json.description app_review[:description] +json.status app_review[:status] +json.user_id app_review[:user_id] +json.user_name app_review[:user_name] +json.organization_id app_review[:organization_id] +json.organization_name app_review[:organization_name] +json.app_id app_review[:app_id] +json.app_name app_review[:app_name] +json.created_at app_review[:created_at] +json.updated_at app_review[:updated_at] diff --git a/api/app/views/mno_enterprise/jpi/v1/app_reviews/index.json.jbuilder b/api/app/views/mno_enterprise/jpi/v1/app_reviews/index.json.jbuilder new file mode 100644 index 000000000..7d5ec6304 --- /dev/null +++ b/api/app/views/mno_enterprise/jpi/v1/app_reviews/index.json.jbuilder @@ -0,0 +1,6 @@ +json.app_reviews do + json.array! @app_reviews do |app_review| + json.partial! 'resource', app_review: app_review + end +end + diff --git a/api/app/views/mno_enterprise/jpi/v1/app_reviews/show.json.jbuilder b/api/app/views/mno_enterprise/jpi/v1/app_reviews/show.json.jbuilder new file mode 100644 index 000000000..99a484132 --- /dev/null +++ b/api/app/views/mno_enterprise/jpi/v1/app_reviews/show.json.jbuilder @@ -0,0 +1,4 @@ +json.app_review do + json.partial! 'resource', app_review: @app_review +end +json.average_rating @average_rating diff --git a/api/app/views/mno_enterprise/jpi/v1/marketplace/_app.json.jbuilder b/api/app/views/mno_enterprise/jpi/v1/marketplace/_app.json.jbuilder index df9088a49..ebd158932 100644 --- a/api/app/views/mno_enterprise/jpi/v1/marketplace/_app.json.jbuilder +++ b/api/app/views/mno_enterprise/jpi/v1/marketplace/_app.json.jbuilder @@ -10,6 +10,7 @@ json.is_coming_soon app.coming_soon? json.single_billing app.single_billing? json.multi_instantiable app.multi_instantiable json.subcategories app.subcategories +json.average_rating app.average_rating if app.logo json.logo app.logo.to_s diff --git a/api/app/views/mno_enterprise/jpi/v1/marketplace/index.json.jbuilder b/api/app/views/mno_enterprise/jpi/v1/marketplace/index.json.jbuilder index 96885f97f..0d716c13e 100644 --- a/api/app/views/mno_enterprise/jpi/v1/marketplace/index.json.jbuilder +++ b/api/app/views/mno_enterprise/jpi/v1/marketplace/index.json.jbuilder @@ -1,4 +1,2 @@ -json.cache! ['v1', 'marketplace'], expires_in: 20.minutes do - json.categories @categories - json.apps @apps, partial: 'app', as: :app -end +json.categories @categories +json.apps @apps, partial: 'app', as: :app diff --git a/api/app/views/mno_enterprise/jpi/v1/marketplace/show.json.jbuilder b/api/app/views/mno_enterprise/jpi/v1/marketplace/show.json.jbuilder index f00a81ec2..89d0bf899 100644 --- a/api/app/views/mno_enterprise/jpi/v1/marketplace/show.json.jbuilder +++ b/api/app/views/mno_enterprise/jpi/v1/marketplace/show.json.jbuilder @@ -1,3 +1,3 @@ json.app do json.partial! 'app', app: @app -end \ No newline at end of file +end diff --git a/api/config/routes.rb b/api/config/routes.rb index 02ea31a24..24f299a92 100644 --- a/api/config/routes.rb +++ b/api/config/routes.rb @@ -91,7 +91,11 @@ #============================================================ namespace :jpi do namespace :v1 do - resources :marketplace, only: [:index, :show] + resources :marketplace, only: [:index, :show] do + member do + resources :app_reviews, only: [:index, :create] + end + end resource :current_user, only: [:show, :update] do put :update_password put :register_developer @@ -150,6 +154,7 @@ namespace :admin, defaults: {format: 'json'} do resources :audit_events, only: [:index] resources :app_instances, only: [:destroy], shallow: true + resources :app_reviews, only: [:index, :show, :update] resources :users, only: [:index, :show, :destroy, :update, :create] do collection do get :count diff --git a/api/spec/controllers/mno_enterprise/jpi/v1/admin/app_reviews_controller_spec.rb b/api/spec/controllers/mno_enterprise/jpi/v1/admin/app_reviews_controller_spec.rb new file mode 100644 index 000000000..c26cef774 --- /dev/null +++ b/api/spec/controllers/mno_enterprise/jpi/v1/admin/app_reviews_controller_spec.rb @@ -0,0 +1,102 @@ +require 'rails_helper' + +module MnoEnterprise + include MnoEnterprise::TestingSupport::SharedExamples::JpiV1Admin + + describe Jpi::V1::Admin::AppReviewsController, type: :controller do + render_views + routes { MnoEnterprise::Engine.routes } + before { request.env['HTTP_ACCEPT'] = 'application/json' } + let(:user) { build(:user, :admin, :with_organizations) } + let(:app_review) { build(:app_review, user: user) } + before do + api_stub_for(get: '/app_reviews', response: from_api([app_review])) + api_stub_for(get: "/app_reviews/#{app_review.id}", response: from_api(app_review)) + api_stub_for(get: "/users/#{user.id}", response: from_api(user)) + sign_in user + end + + def partial_hash_for_app_review(app_review) + { + 'id' => app_review.id, + 'rating' => app_review.rating, + 'description' => app_review.description, + 'status' => app_review.status, + 'app_id' => app_review.app_id, + 'app_name' => app_review.app_name, + 'user_id' => app_review.user_id, + 'user_name' => app_review.user_name, + 'organization_id' => app_review.organization_id, + 'organization_name' => app_review.organization_name, + 'created_at' => app_review.created_at, + 'updated_at' => app_review.updated_at, + } + end + + def hash_for_app_review(app_review) + { + 'app_review' => partial_hash_for_app_review(app_review) + } + end + + def hash_for_app_reviews(app_reviews) + { + 'app_reviews' => app_reviews.map { |o| partial_hash_for_app_review(o) } + } + end + + + describe '#index' do + subject { get :index } + + it_behaves_like "a jpi v1 admin action" + + context 'success' do + before { subject } + + it 'returns a list of app_review' do + expect(response).to be_success + expect(JSON.parse(response.body)).to eq(JSON.parse(hash_for_app_reviews([app_review]).to_json)) + end + end + end + + describe 'GET #show' do + subject { get :show, id: app_review.id } + + it_behaves_like "a jpi v1 admin action" + + context 'success' do + before { subject } + + it 'returns a complete description of the app_review' do + expect(response).to be_success + expect(JSON.parse(response.body)).to eq(JSON.parse(hash_for_app_review(app_review).to_json)) + end + end + end + + + describe 'PUT #update' do + subject { put :update, id: app_review.id, app_review: {status: 'rejected'} } + + before do + sign_in user + api_stub_for(put: "/app_reviews/#{app_review.id}", response: -> { app_review.status = 'rejected'; from_api(app_review) }) + end + + it_behaves_like "a jpi v1 admin action" + + context 'success' do + before { subject } + + it { expect(response).to be_success } + + # Test that the app_review is updated by testing the api endpoint was called + it { expect(app_review.status).to eq('rejected') } + end + end + + + end +end diff --git a/api/spec/controllers/mno_enterprise/jpi/v1/app_reviews_controller_spec.rb b/api/spec/controllers/mno_enterprise/jpi/v1/app_reviews_controller_spec.rb new file mode 100644 index 000000000..2db7fbea6 --- /dev/null +++ b/api/spec/controllers/mno_enterprise/jpi/v1/app_reviews_controller_spec.rb @@ -0,0 +1,75 @@ +require 'rails_helper' + +module MnoEnterprise + describe Jpi::V1::AppReviewsController, type: :controller do + include MnoEnterprise::TestingSupport::JpiV1TestHelper + render_views + routes { MnoEnterprise::Engine.routes } + before { request.env["HTTP_ACCEPT"] = 'application/json' } + + + #=============================================== + # Assignments + #=============================================== + let(:user) { build(:user) } + before { api_stub_for(get: "/users/#{user.id}", response: from_api(user)) } + before { sign_in user } + + let(:app) { build(:app) } + let(:app_review) { build(:app_review) } + let(:expected_hash_for_review) do + attrs = %w(id rating description status user_id user_name organization_id organization_name app_id app_name) + app_review.attributes.slice(*attrs).merge({'created_at' => app_review.created_at.as_json, 'updated_at' => app_review.updated_at.as_json}) + end + let(:expected_hash_for_reviews) do + { + 'app_reviews' => [expected_hash_for_review], + # 'metadata' => {'pagination' => {'count' => 1}} + } + end + + before do + api_stub_for(get: "/apps/#{app.id}", response: from_api(app)) + end + + describe 'GET #index' do + + before do + api_stub_for(get: "/app_reviews?filter[reviewable_id]=#{app.id}", response: from_api([app_review])) + end + + subject { get :index, id: app.id } + + it_behaves_like "jpi v1 protected action" + + it_behaves_like "a paginated action" + + it 'renders the list of reviews' do + subject + expect(JSON.parse(response.body)).to eq(expected_hash_for_reviews) + end + end + + describe 'POST #create', focus: true do + let(:params) { {organization_id: 1, description: 'A Review', rating: 5, foo: 'bar'} } + let(:app_review) { build(:app_review) } + + before do + api_stub_for(post: "/app_reviews", response: from_api(app_review)) + api_stub_for(get: "/app_reviews/#{app_review.id}", response: from_api(app_review)) + end + + subject { post :create, id: app.id, app_review: params } + + it_behaves_like "jpi v1 protected action" + + it 'renders the new review' do + expect(JSON.parse(subject.body)).to include('app_review' => expected_hash_for_review) + end + + it 'renders the new average rating' do + expect(JSON.parse(subject.body)).to include('average_rating' => app.average_rating) + end + end + end +end diff --git a/api/spec/controllers/mno_enterprise/jpi/v1/marketplace_controller_spec.rb b/api/spec/controllers/mno_enterprise/jpi/v1/marketplace_controller_spec.rb index f0e91efb4..5e2a58990 100644 --- a/api/spec/controllers/mno_enterprise/jpi/v1/marketplace_controller_spec.rb +++ b/api/spec/controllers/mno_enterprise/jpi/v1/marketplace_controller_spec.rb @@ -37,7 +37,7 @@ def partial_hash_for_app(app) 'rank' => app.rank, 'multi_instantiable' => app.multi_instantiable, 'subcategories' => app.subcategories, - + 'average_rating' => app.average_rating, } end @@ -73,10 +73,7 @@ def hash_for_apps(apps) ) end - it 'is successful' do - subject - expect(response).to be_success - end + it { is_expected.to be_success } it 'returns the right response' do subject @@ -90,10 +87,7 @@ def hash_for_apps(apps) api_stub_for(get: '/apps', response: from_api([app])) end - it 'is successful' do - subject - expect(response).to be_success - end + it { is_expected.to be_success } it 'returns the right response' do subject @@ -106,10 +100,7 @@ def hash_for_apps(apps) before { api_stub_for(get: "/apps/#{app.id}", response: from_api(app)) } subject { get :show, id: app.id } - it 'is successful' do - subject - expect(response).to be_success - end + it { is_expected.to be_success } it 'returns the right response' do subject @@ -129,10 +120,7 @@ def hash_for_apps(apps) api_stub_for(get: '/apps', response: from_api([app1,app2])) end - it 'is successful' do - subject - expect(response).to be_success - end + it { is_expected.to be_success } it 'returns the right response' do subject diff --git a/api/spec/routing/mno_enterprise/jpi/v1/admin/app_reviews_controller_routing_spec.rb b/api/spec/routing/mno_enterprise/jpi/v1/admin/app_reviews_controller_routing_spec.rb new file mode 100644 index 000000000..afe060233 --- /dev/null +++ b/api/spec/routing/mno_enterprise/jpi/v1/admin/app_reviews_controller_routing_spec.rb @@ -0,0 +1,19 @@ +require 'rails_helper' + +module MnoEnterprise + RSpec.describe Jpi::V1::Admin::UsersController, type: :routing do + routes { MnoEnterprise::Engine.routes } + + it 'routes to #index' do + expect(get('/jpi/v1/admin/app_reviews')).to route_to('mno_enterprise/jpi/v1/admin/app_reviews#index', format: 'json') + end + + it 'routes to #show' do + expect(get('/jpi/v1/admin/app_reviews/1')).to route_to('mno_enterprise/jpi/v1/admin/app_reviews#show', format: 'json', id: '1') + end + + it 'routes to #update' do + expect(put('/jpi/v1/admin/app_reviews/1')).to route_to('mno_enterprise/jpi/v1/admin/app_reviews#update', id: '1', format: 'json') + end + end +end diff --git a/api/spec/routing/mno_enterprise/jpi/v1/app_reviews_controller_routing_spec.rb b/api/spec/routing/mno_enterprise/jpi/v1/app_reviews_controller_routing_spec.rb new file mode 100644 index 000000000..e8b132939 --- /dev/null +++ b/api/spec/routing/mno_enterprise/jpi/v1/app_reviews_controller_routing_spec.rb @@ -0,0 +1,15 @@ +require 'rails_helper' + +module MnoEnterprise + RSpec.describe Jpi::V1::AppReviewsController, type: :routing do + routes { MnoEnterprise::Engine.routes } + + it 'routes to #index' do + expect(get('/jpi/v1/marketplace/1/app_reviews')).to route_to("mno_enterprise/jpi/v1/app_reviews#index", id: '1') + end + it 'routes to #create' do + expect(post('/jpi/v1/marketplace/1/app_reviews')).to route_to("mno_enterprise/jpi/v1/app_reviews#create", id: '1') + end + end +end + diff --git a/api/spec/routing/mno_enterprise/jpi/v1/marketplace_controller_routing_spec.rb b/api/spec/routing/mno_enterprise/jpi/v1/marketplace_controller_routing_spec.rb index 5f264a305..ae3c4e8f0 100644 --- a/api/spec/routing/mno_enterprise/jpi/v1/marketplace_controller_routing_spec.rb +++ b/api/spec/routing/mno_enterprise/jpi/v1/marketplace_controller_routing_spec.rb @@ -3,11 +3,11 @@ module MnoEnterprise RSpec.describe Jpi::V1::MarketplaceController, type: :routing do routes { MnoEnterprise::Engine.routes } - + it 'routes to #index' do expect(get('/jpi/v1/marketplace')).to route_to("mno_enterprise/jpi/v1/marketplace#index") end - + it 'routes to #show' do expect(get('/jpi/v1/marketplace/1')).to route_to("mno_enterprise/jpi/v1/marketplace#show", id: '1') end diff --git a/core/app/models/mno_enterprise/app.rb b/core/app/models/mno_enterprise/app.rb index e8e73fb12..163683903 100644 --- a/core/app/models/mno_enterprise/app.rb +++ b/core/app/models/mno_enterprise/app.rb @@ -31,8 +31,14 @@ class App < BaseResource scope :cloud, -> { where(stack: 'cloud') } attributes :id, :uid, :nid, :name, :description, :tiny_description, :created_at, :updated_at, :logo, :website, :slug, - :categories, :key_benefits, :key_features, :testimonials, :worldwide_usage, :tiny_description, - :popup_description, :stack, :terms_url, :pictures, :tags, :api_key, :metadata_url, :metadata, :details, :rank, :multi_instantiable, :subcategories + :categories, :key_benefits, :key_features, :testimonials, :worldwide_usage, :tiny_description, + :popup_description, :stack, :terms_url, :pictures, :tags, :api_key, :metadata_url, :metadata, :details, :rank, :multi_instantiable, :subcategories, :reviews, :average_rating + + + #================================ + # Associations + #================================ + has_many :reviews, class_name: 'AppReview' # Return the list of available categories def self.categories(list = nil) diff --git a/core/app/models/mno_enterprise/app_review.rb b/core/app/models/mno_enterprise/app_review.rb new file mode 100644 index 000000000..6a76a4d81 --- /dev/null +++ b/core/app/models/mno_enterprise/app_review.rb @@ -0,0 +1,7 @@ +module MnoEnterprise + class AppReview < BaseResource + attributes :id, :rating, :description, :created_at, :updated_at, :app_id, :user_id, :organization_id, :status + + scope :approved, -> { where(status: 'approved') } + end +end diff --git a/core/config/locales/templates/dashboard/marketplace/en.yml b/core/config/locales/templates/dashboard/marketplace/en.yml index d8a1733f6..14996a7e2 100644 --- a/core/config/locales/templates/dashboard/marketplace/en.yml +++ b/core/config/locales/templates/dashboard/marketplace/en.yml @@ -20,6 +20,19 @@ en: success_notification_title: "{{name}} has been added to your account" success_launch_notification_body: "To start using {{name}}, click on the {{name}} icon then click on \"Launch\"." success_connect_notification_body: "To start using {{name}}, click on the {{name}} icon then click on \"Connect\"." + no_reviews: "No reviews on" + write_review: "Write a review" + number_of_reviews: "Reviews per page:" + success_toastr: "Your review has been taken into account!" + error_toastr: "An error occurred while saving your review." + from: "from" + "on": "on" + just_now: "Just Now" + company_feedback: "Company feedback" + review: "Review" + tell_others: "Tell others what you think about this application (optional)" + cancel: "Cancel" + submit: "Submit" app_selection: next: "Next" diff --git a/core/lib/generators/mno_enterprise/install/templates/config/settings.yml b/core/lib/generators/mno_enterprise/install/templates/config/settings.yml index e53eb7aec..a472cb13c 100644 --- a/core/lib/generators/mno_enterprise/install/templates/config/settings.yml +++ b/core/lib/generators/mno_enterprise/install/templates/config/settings.yml @@ -7,9 +7,20 @@ mno: paths: root: /api/mnoe/v1 frontend_host: + +#=============================================== +# Feature Flags +#=============================================== + +# Display App Pricing on Marketplace pricing: enabled: false +# Enable the App Dock dock: enabled: true +# Display the Developer section on "My Account" developer: enabled: false +# Enable Reviews in the marketplace +reviews: + enabled: false diff --git a/core/lib/mno_enterprise/testing_support/factories/app_review.rb b/core/lib/mno_enterprise/testing_support/factories/app_review.rb new file mode 100644 index 000000000..b30b23756 --- /dev/null +++ b/core/lib/mno_enterprise/testing_support/factories/app_review.rb @@ -0,0 +1,23 @@ +# Read about factories at https://github.com/thoughtbot/factory_girl + +FactoryGirl.define do + factory :mno_enterprise_app_review, :class => 'AppReview' do + + factory :app_review, class: MnoEnterprise::AppReview do + sequence(:id) + description 'Some Description' + status 'approved' + rating 3 + app_id 'app-id' + app_name 'the app' + user_id 'usr-11' + user_name 'Jean Bon' + organization_id 'org-11' + organization_name 'Organization 11' + created_at 3.days.ago + updated_at 1.hour.ago + # Properly build the resource with Her + initialize_with { new(attributes).tap { |e| e.clear_attribute_changes! } } + end + end +end diff --git a/core/lib/mno_enterprise/testing_support/factories/apps.rb b/core/lib/mno_enterprise/testing_support/factories/apps.rb index f1baaa28a..36f7e7e10 100644 --- a/core/lib/mno_enterprise/testing_support/factories/apps.rb +++ b/core/lib/mno_enterprise/testing_support/factories/apps.rb @@ -5,7 +5,7 @@ sequence(:id) { |n| n } sequence(:name) { |n| "TestApp#{n}" } nid { name.parameterize } - + description "This is a description" created_at 1.day.ago updated_at 2.hours.ago @@ -14,30 +14,31 @@ slug { "#{id}-myapp" } categories ["CRM"] tags ['Foo', 'Bar'] - key_benefits ['Super','Hyper','Good'] - key_features ['Super','Hyper','Good'] - testimonials [{text:'Bla', company:'Doe Pty Ltd', author: 'John'}] + key_benefits ['Super', 'Hyper', 'Good'] + key_features ['Super', 'Hyper', 'Good'] + testimonials [{text: 'Bla', company: 'Doe Pty Ltd', author: 'John'}] worldwide_usage 120000 tiny_description "A great app" stack 'cube' terms_url "http://opensource.org/licenses/MIT" appinfo { {} } + average_rating 1 sequence(:rank) { |n| n } - pricing_plans {{ - 'default' =>[{name: 'Monthly Plan', price: '20.0', currency: 'AUD', factor: '/month'}] - }} + pricing_plans { { + 'default' => [{name: 'Monthly Plan', price: '20.0', currency: 'AUD', factor: '/month'}] + } } trait :cloud do stack 'cloud' end - + trait :connector do stack 'connector' end - + factory :cloud_app, traits: [:cloud] factory :connector_app, traits: [:connector] - + # Properly build the resource with Her initialize_with { new(attributes).tap { |e| e.clear_attribute_changes! } } end diff --git a/core/lib/mno_enterprise/testing_support/jpi_v1_test_helper.rb b/core/lib/mno_enterprise/testing_support/jpi_v1_test_helper.rb index 74418a096..3bbcfe2b8 100644 --- a/core/lib/mno_enterprise/testing_support/jpi_v1_test_helper.rb +++ b/core/lib/mno_enterprise/testing_support/jpi_v1_test_helper.rb @@ -79,4 +79,10 @@ module MnoEnterprise::TestingSupport::JpiV1TestHelper end end + shared_examples_for "a paginated action" do + it 'adds the pagination metadata' do + subject + expect(response.headers['X-Total-Count']).to be_a(Fixnum) + end + end end diff --git a/frontend-admin/src/app/components/mnoe-api/admin/reviews.svc.coffee b/frontend-admin/src/app/components/mnoe-api/admin/reviews.svc.coffee new file mode 100644 index 000000000..e0158f9b6 --- /dev/null +++ b/frontend-admin/src/app/components/mnoe-api/admin/reviews.svc.coffee @@ -0,0 +1,27 @@ +# Service for managing the comments and reviews. +@App.service 'MnoeReviews', (MnoeAdminApiSvc) -> + _self = @ + + # GET List /mnoe/jpi/v1/admin/app_reviews + @list = () -> + MnoeAdminApiSvc.all('app_reviews').getList().then( + (response) -> + response + (error) -> + # Display an error + $log.error('Error while fetching reviews', error) + toastr.error('An error occured while fetching the reviews.') + ) + + # UPDATE /mnoe/jpi/v1/admin/app_reviews/1 + @updateRating = (review) -> + promise = MnoeAdminApiSvc.one('app_reviews', review.id).patch({status: review.status}).then( + (response) -> + response + (error) -> + # Display an error + $log.error('Error while updating review', error) + toastr.error('An error occured while updating the review.') + ) + + return @ diff --git a/frontend-admin/src/app/components/mnoe-reviews-list/mnoe-reviews-list.coffee b/frontend-admin/src/app/components/mnoe-reviews-list/mnoe-reviews-list.coffee new file mode 100644 index 000000000..f8448cb11 --- /dev/null +++ b/frontend-admin/src/app/components/mnoe-reviews-list/mnoe-reviews-list.coffee @@ -0,0 +1,27 @@ +@App.directive('mnoeReviewsList', ($filter, $log, MnoeReviews) -> + restric:'E' + scope: { + } + templateUrl:'app/components/mnoe-reviews-list/mnoe-reviews-list.html' + link: (scope) -> + + scope.editmode = [] + scope.listOfReviews = [] + scope.statuses = ['approved', 'rejected'] + + fetchReviews = () -> + return MnoeReviews.list().then( + (response) -> + scope.listOfReviews = response.data + ) + + scope.update = (review) -> + MnoeReviews.updateRating(review).then( + (response) -> + # Remove the edit mode for this review + delete scope.editmode[review.id] + ) + + fetchReviews() + return +) diff --git a/frontend-admin/src/app/components/mnoe-reviews-list/mnoe-reviews-list.html b/frontend-admin/src/app/components/mnoe-reviews-list/mnoe-reviews-list.html new file mode 100644 index 000000000..ed27f3fcb --- /dev/null +++ b/frontend-admin/src/app/components/mnoe-reviews-list/mnoe-reviews-list.html @@ -0,0 +1,60 @@ + + +  All comments + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + +
AuthorCompanyAppCommentRatingDateStatus
+ {{review.user_name}} + + {{review.organization_name}} + + {{review.app_name}} + + {{review.description}} + + {{review.rating}} + + {{review.created_at | date: 'dd/MM/yyyy' }} + {{review.status}} + + + + + + + +
+ +
+
+
+ diff --git a/frontend-admin/src/app/index.default-config.coffee b/frontend-admin/src/app/index.default-config.coffee new file mode 100644 index 000000000..d4e527bbc --- /dev/null +++ b/frontend-admin/src/app/index.default-config.coffee @@ -0,0 +1,6 @@ +# For backward compatibility with the mnoe backend +# Define null/default values for all the constants so that if the backend hasn't been upgraded +# to define this constants you don't get errors like: +# Uncaught Error: [$injector:unpr] Unknown provider: INTERCOM_IDProvider <- INTERCOM_ID <- AnalyticsSvc +angular.module('mnoEnterprise.defaultConfiguration', []) + .constant('REVIEWS_CONFIG', {enabled: false}) diff --git a/frontend-admin/src/app/index.module.coffee b/frontend-admin/src/app/index.module.coffee index 87898abb2..dcff8e89f 100644 --- a/frontend-admin/src/app/index.module.coffee +++ b/frontend-admin/src/app/index.module.coffee @@ -1,4 +1,7 @@ @App = angular.module 'frontendAdmin', [ + # Default configuration + 'mnoEnterprise.defaultConfiguration', + # Runtime configuration 'mnoEnterprise.configuration', 'ngAnimate', diff --git a/frontend-admin/src/app/index.route.coffee b/frontend-admin/src/app/index.route.coffee index da2496a92..8a0e83c6d 100644 --- a/frontend-admin/src/app/index.route.coffee +++ b/frontend-admin/src/app/index.route.coffee @@ -55,6 +55,17 @@ label: 'Staff' resolve: skip: (MnoeCurrentUser) -> MnoeCurrentUser.skipIfNotAdmin() + .state 'dashboard.reviews', + data: + pageTitle:'Reviews' + url: '/reviews' + templateUrl: 'app/views/reviews/reviews.html' + controllerAs: 'vm' + ncyBreadcrumb: + label: 'Reviews' + resolve: + skip: (MnoeCurrentUser) -> MnoeCurrentUser.skipIfNotAdmin() + skipCondition: (RoutingHelper, REVIEWS_CONFIG) -> RoutingHelper.skipUnlessCondition(REVIEWS_CONFIG && REVIEWS_CONFIG.enabled) .state 'dashboard.customers', data: pageTitle:'Customers' diff --git a/frontend-admin/src/app/services/routing-helper/routing-helper.coffee b/frontend-admin/src/app/services/routing-helper/routing-helper.coffee new file mode 100644 index 000000000..716466401 --- /dev/null +++ b/frontend-admin/src/app/services/routing-helper/routing-helper.coffee @@ -0,0 +1,15 @@ +@App.service 'RoutingHelper', ($state, $q) -> + _self = @ + + # Used to redirect to home if condition is not met + @skipUnlessCondition = (condition) -> + if condition + return $q.resolve() + else + $timeout(-> + # Runs after the main promise has been rejected. + $state.go('dashboard.home') + ) + $q.reject() + + return @ diff --git a/frontend-admin/src/app/views/dashboard.controller.coffee b/frontend-admin/src/app/views/dashboard.controller.coffee index 97b854ed0..439d9f9f7 100644 --- a/frontend-admin/src/app/views/dashboard.controller.coffee +++ b/frontend-admin/src/app/views/dashboard.controller.coffee @@ -1,10 +1,12 @@ -@App.controller 'DashboardController', ($scope, $cookies, $sce, MnoeMarketplace, MnoErrorsHandler, MnoeCurrentUser, STAFF_PAGE_AUTH) -> +@App.controller 'DashboardController', ($scope, $cookies, $sce, MnoeMarketplace, MnoErrorsHandler, MnoeCurrentUser, STAFF_PAGE_AUTH, REVIEWS_CONFIG) -> 'ngInject' main = this main.errorHandler = MnoErrorsHandler main.staffPageAuthorized = STAFF_PAGE_AUTH + main.isReviewingEnabled = REVIEWS_CONFIG && REVIEWS_CONFIG.enabled + main.trustSrc = (src) -> $sce.trustAsResourceUrl(src) diff --git a/frontend-admin/src/app/views/dashboard.layout.html b/frontend-admin/src/app/views/dashboard.layout.html index ee2fa3d2d..86d2fe018 100644 --- a/frontend-admin/src/app/views/dashboard.layout.html +++ b/frontend-admin/src/app/views/dashboard.layout.html @@ -22,6 +22,9 @@ +