Application page authenticated and authorized with pagination can be navigated via next and previous controls has changed.
spec/system/application_spec.rb
diff --git a/app/controllers/applications_controller.rb b/app/controllers/applications_controller.rb
index 62d85043..e476ed52 100644
--- a/app/controllers/applications_controller.rb
+++ b/app/controllers/applications_controller.rb
@@ -13,37 +13,37 @@ class ApplicationsController < ApplicationController
SCANS_PER_PAGE = 25
private_constant :SCANS_PER_PAGE
- TEST_APP_NAME = 'Featured App'
+ TEST_APP_NAME = "Featured App"
public_constant :TEST_APP_NAME
FEATURED_APPS = {
# NOTE: This won't result in AppLand being displayed on the Explore page unless its public flag is set to true.
# It's useful to have this for development, because appmaps of the other featured apps are not as easily available.
- 'appland/AppLand' => {
- description: 'AppLand records your running code, and automatically creates visualizations and data tables that show you exactly how your code works'
+ "appland/AppLand" => {
+ description: "AppLand records your running code, and automatically creates visualizations and data tables that show you exactly how your code works",
},
# For testing
- 'Featured App' => {
- description: 'This app is particularly interesting'
+ "Featured App" => {
+ description: "This app is particularly interesting",
},
- 'land-of-apps/rails_sample_app_6th_ed' => {
- description: 'Reference implementation of the sample application from Ruby on Rails Tutorial: Learn Web Development with Rails (6th Edition) by Michael Hartl'
+ "land-of-apps/rails_sample_app_6th_ed" => {
+ description: "Reference implementation of the sample application from Ruby on Rails Tutorial: Learn Web Development with Rails (6th Edition) by Michael Hartl",
},
- 'land-of-apps/Conjur' => {
- description: 'Conjur provides secrets management and application identity for modern infrastructure'
+ "land-of-apps/Conjur" => {
+ description: "Conjur provides secrets management and application identity for modern infrastructure",
},
- 'land-of-apps/discourse' => {
- description: 'Discourse is the 100% open source discussion platform built for the next decade of the Internet'
+ "land-of-apps/discourse" => {
+ description: "Discourse is the 100% open source discussion platform built for the next decade of the Internet",
},
- 'land-of-apps/ifme' => {
- description: 'Free, open source mental health communication web app to share experiences with loved ones'
+ "land-of-apps/ifme" => {
+ description: "Free, open source mental health communication web app to share experiences with loved ones",
},
- 'land-of-apps/refinerycms' => {
- description: 'An open source content management system for Rails'
+ "land-of-apps/refinerycms" => {
+ description: "An open source content management system for Rails",
+ },
+ "land-of-apps/spring-petclinic" => {
+ description: "The canonical sample application for Spring Boot",
},
- 'land-of-apps/spring-petclinic' => {
- description: 'The canonical sample application for Spring Boot'
- }
}.freeze
private_constant :FEATURED_APPS
@@ -52,6 +52,7 @@ class ApplicationsController < ApplicationController
before_action :find_app, only: %i[show component_diagram related_scenarios scenarios_list update mapset delete_form destroy settings update_repository]
before_action :check_to_redirect, only: %i[show]
before_action :find_recent_scans, only: %i[show]
+ before_action :find_preferred_mapsets, only: %i[show]
before_action :find_summary_stats, only: %i[show]
before_action :find_mapset, only: %i[component_diagram mapset related_scenarios scenarios_list]
@@ -64,9 +65,9 @@ class ApplicationsController < ApplicationController
end
def process_scenarios
- render 'processing', locals: {
- paths: @mapset.pending_scenarios.select(:uuid).map { |s| scenario_path s, process: true }
- }
+ render "processing", locals: {
+ paths: @mapset.pending_scenarios.select(:uuid).map { |s| scenario_path s, process: true },
+ }
end
def update
@@ -79,19 +80,19 @@ class ApplicationsController < ApplicationController
end
def destroy
- if(@app.name == params[:application_name])
+ if (@app.name == params[:application_name])
@app.destroy!(current_user)
- render partial: 'deleted'
+ render partial: "deleted"
else
- @app.errors.add :name, 'Application name does not match'
+ @app.errors.add :name, "Application name does not match"
@errors = @app.errors
- render 'delete_form', layout: !request.xhr?
+ render "delete_form", layout: !request.xhr?
end
end
def examples
- render layout: 'grid_full_public'
+ render layout: "grid_full_public"
end
def settings
@@ -103,21 +104,21 @@ class ApplicationsController < ApplicationController
def select_layout
if configuration.show_legacy_teardown?
- 'three_column'
+ "three_column"
else
- 'grid_filters'
+ "grid_filters"
end
end
def key_stats_owner_type
- 'mapset'
+ "mapset"
end
def public_apps
@public_apps ||= search_scope(App, scope: Search::SCOPE_PUBLIC).search(
offset: params[:offset],
optional_columns: %i[scenario_count],
- order: Sequel[:apps][:name]
+ order: Sequel[:apps][:name],
)
end
@@ -148,13 +149,18 @@ class ApplicationsController < ApplicationController
def find_recent_scans
@current_page = begin
- Integer(params[:p], 10)
- rescue ArgumentError
- 1
- end
+ Integer(params[:p], 10)
+ rescue ArgumentError
+ 1
+ end
num_scans = ScannerJob.count_jobs_for_app(@app)
@total_pages = (num_scans / Float(SCANS_PER_PAGE)).ceil
@scans = ScannerJob.most_recent_for_app(@app, limit: SCANS_PER_PAGE, offset: (@current_page - 1) * SCANS_PER_PAGE)
end
+
+ def find_preferred_mapsets
+ @mapsets = @app.mapsets
+ # byebug
+ end
end
diff --git a/app/models/app/show.rb b/app/models/app/show.rb
index ff290f91..4aef6e71 100644
--- a/app/models/app/show.rb
+++ b/app/models/app/show.rb
@@ -17,7 +17,7 @@ class App
end
def mapsets
- @mapsets ||= Mapset.ordered_list(@app)
+ @mapsets ||= Mapset.ordered_list(@app).map { |mapset| mapset.app = self; mapset }
end
def org
diff --git a/app/models/scanner_job.rb b/app/models/scanner_job.rb
index ecd8e23a..3913a58a 100644
--- a/app/models/scanner_job.rb
+++ b/app/models/scanner_job.rb
@@ -15,7 +15,11 @@ class ScannerJob
]
private_constant :LIST_ITEM_COLUMNS
- ListItem = Struct.new(*LIST_ITEM_COLUMNS)
+ ListItem = Struct.new(*LIST_ITEM_COLUMNS) do
+ extend Forwardable
+ include Accessor::ScannerJob
+ end
+
private_constant :ListItem
SummaryItem = Struct.new(
@@ -41,17 +45,17 @@ class ScannerJob
def self.subquery_findings_count
Sequel::Model.db[:check_findings_v]
.select {
- [
- Sequel[:check_findings_v][:scanner_job_id],
- sum(Sequel[:check_findings_v][:findings_count]).cast(:integer).as(:findings_count)
- ]
- }
+ [
+ Sequel[:check_findings_v][:scanner_job_id],
+ sum(Sequel[:check_findings_v][:findings_count]).cast(:integer).as(:findings_count),
+ ]
+ }
.group_by(:scanner_job_id)
.as(:findings)
end
# Retrieve the most recent scanner results for the given user
- def self.most_recent_for_user(user, limit=10, offset=0)
+ def self.most_recent_for_user(user, limit = 10, offset = 0)
most_recent_jobs = DAO::Mapset
.select(Sequel[:scanner_jobs].*, Sequel[:mapsets][:app_id])
.inner_join(:scanner_jobs, mapset_id: :id)
@@ -90,7 +94,7 @@ class ScannerJob
Sequel[:scanner_jobs][:mapset_id],
Sequel[:scanner_jobs][:created_at],
Sequel[:mapsets][:branch],
- Sequel.lit('SUBSTRING(mapsets.commit, 1, 7)').as(:commit),
+ Sequel.lit("SUBSTRING(mapsets.commit, 1, 7)").as(:commit),
Sequel.lit("COALESCE((scanner_jobs.summary->>'numChecks')::integer, 0) as checks_count"),
Sequel.lit("COALESCE((SUM(check_findings_v.findings_count) FILTER (WHERE check_findings_v.status = 'new'))::integer, 0) as new_count"),
Sequel.lit("COALESCE((SUM(check_findings_v.findings_count) FILTER (WHERE check_findings_v.status = 'deferred'))::integer, 0) as deferred_count"),
@@ -108,12 +112,12 @@ class ScannerJob
end
def self.summary_by_category(job_id)
- appmaps_count = DAO::Scanner::Job[job_id].summary['numAppMaps']
+ appmaps_count = DAO::Scanner::Job[job_id].summary["numAppMaps"]
Sequel::Model(:scanner_checks)
.select(
:impact_category,
- Sequel.lit('COUNT(DISTINCT scanner_checks.check_id) as rules_count'),
- Sequel.lit('COUNT(finding_occurrences.*)::integer as findings_count'),
+ Sequel.lit("COUNT(DISTINCT scanner_checks.check_id) as rules_count"),
+ Sequel.lit("COUNT(finding_occurrences.*)::integer as findings_count"),
Sequel.lit('COUNT(finding_occurrences.*) FILTER (WHERE status = \'new\') as new_count'),
Sequel.lit('COUNT(finding_occurrences.*) FILTER (WHERE status = \'deferred\') as deferred_count')
)
@@ -124,15 +128,15 @@ class ScannerJob
.group_by(:impact_category)
.order_by(:impact_category)
.map do |row|
- SummaryItem.new(
- row[:impact_category],
- appmaps_count,
- row[:rules_count],
- row[:rules_count] * appmaps_count - row[:findings_count],
- row[:findings_count],
- row[:new_count],
- row[:deferred_count],
- )
+ SummaryItem.new(
+ row[:impact_category],
+ appmaps_count,
+ row[:rules_count],
+ row[:rules_count] * appmaps_count - row[:findings_count],
+ row[:findings_count],
+ row[:new_count],
+ row[:deferred_count],
+ )
end
end
end
diff --git a/app/views/applications/show.html.haml b/app/views/applications/show.html.haml
index cdd497f5..65cb7f19 100644
--- a/app/views/applications/show.html.haml
+++ b/app/views/applications/show.html.haml
@@ -11,22 +11,22 @@
%img.icon{ src: '/img/gear.svg' }
%section.scan-history
- - if @scans.empty?
+ - if @scans.empty? && @mapsets.empty?
.card{ data: { spec: 'no-data' } }
- %h3.mb-3 Waiting on your first scan...
+ %h3.mb-3 Waiting on your first upload...
%p Looking for a way to get started? Visit our documentation:
%ul
%li
- %a{ href: 'https://appland.com/docs/analysis/getting-started.html' } Getting started
+ %a{ href: 'https://appmap.io/docs/recording-methods.html', target: '_blank' } Recording AppMaps
%li
- %a{ href: 'https://appland.com/docs/analysis/uploading.html' } Uploading findings
+ %a{ href: 'https://appmap.io/docs/openapi.html', target: '_blank' } Generating OpenAPI
%li
- %a{ href: 'https://appland.com/docs/analysis/integrating-with-ci.html' } Integrating with CI
- - else
+ %a{ href: 'https://appmap.io/docs/analysis', target: '_blank' } Runtime Analysis
+ - unless @scans.empty?
= render partial: 'partials/findings_overview'
%h3.subhead Scan history
- @scans.each do |scan|
- %a.card.app{href: scanner_job_path(scan.id), data: { scan_status: scan.new_count == 0 ? 'pass' : 'fail', spec: 'scan' }}
+ %a.card.app{href: scanner_job_path(scan), data: { scan_status: scan.new_count == 0 ? 'pass' : 'fail', spec: 'scan' }}
.stacked-title
.label Branch
.title{data: { spec: 'branch' }}= scan.branch.present? ? scan.branch : 'unknown'
@@ -45,7 +45,7 @@
%li.info.column
.info--key.label Commit
.info--value{data: { spec: 'commit' }}
- = scan.commit.present? ? scan.commit[0..7] : 'unknown'
+ = scan.commit.present? ? scan.commit[0..7] : ' '.html_safe
%li.info.column
.info--key.label Created
.info--value{data: { spec: 'time-created' }}
@@ -61,3 +61,28 @@
%a.page-link{ href: application_path(@app, p: page_num), data: { spec: "page", page: page_num } }= page_num
%li.page-item{ class: ('disabled' if @current_page == @total_pages) }
%a.page-link{ href: application_path(@app, p: @current_page + 1), data: { spec: 'next' } } Next
+
+ - unless @mapsets.empty?
+ %h3.subhead Mapset history
+ - @mapsets.each do |mapset|
+ %a.card.app{href: mapset_path(mapset)}
+ .stacked-title
+ .label Branch
+ .title{data: { spec: 'branch' }}= mapset.branch.present? ? mapset.branch : 'unknown'
+ %ul.metrics
+ %li.info.column
+ .info--key.label Commit
+ .info--value{data: { spec: 'commit' }}
+ = mapset.commit.present? ? mapset.commit[0..7] : ' '.html_safe
+ %li.info.column
+ .info--key.label Environment
+ .info--value{data: { spec: 'environment' }}
+ = mapset.environment || ' '.html_safe
+ %li.info.column
+ .info--key.label Version
+ .info--value{data: { spec: 'version' }}
+ = mapset.version || ' '.html_safe
+ %li.info.column
+ .info--key.label Created
+ .info--value{data: { spec: 'time-created' }}
+ = "#{time_ago_in_words mapset.created_at} ago"
diff --git a/app/views/partials/_findings_overview.html.haml b/app/views/partials/_findings_overview.html.haml
index f0219a56..1676f341 100644
--- a/app/views/partials/_findings_overview.html.haml
+++ b/app/views/partials/_findings_overview.html.haml
@@ -1,6 +1,6 @@
%section.findings-overview
.findings-overview__head
- Trends
+ Analysis Trends
%span.findings-overview__periods
- @time_ranges.each do |days|
%a.findings-overview__period{ class: ('findings-overview__period--active' if @current_time_range == days), href: "?time_range=#{days}", data: { selected: true, spec: 'time-range-select', days: days} }
4 times: added function call `controllers#find_preferred_mapsets`
4 times: added function call `models#mapsets`
4 times: added function call `models.ordered_list`
4 times: added function call `models#ordered_mapset_dataset`
4 times: added SQL `SELECT *, "mapsets"."name", "mapsets"."branch", "mapsets"."commit", "mapsets"."environment", "mapsets"."version", (SELECT "login" FROM "users" WHERE ("id" = "user_id")), (SELECT count(*) AS "scenario_count" FROM "scenarios" WHERE ("mapset_id" = "mapsets"."id")) FROM "mapsets_preference_ordered" AS "mapsets" WHERE ("mapsets"."app_id" = 1)`
4 times: added function call `helpers#base_slug`
7 times: added function call `models#org`
20 times: added function call `helpers#mapset_path`
16 times: added function call `helpers#base_slug`
13 times: added function call `models#org`
Application page authenticated and authorized with pagination renders the expected scans on each page has changed.
spec/system/application_spec.rb
diff --git a/app/controllers/applications_controller.rb b/app/controllers/applications_controller.rb
index 62d85043..e476ed52 100644
--- a/app/controllers/applications_controller.rb
+++ b/app/controllers/applications_controller.rb
@@ -13,37 +13,37 @@ class ApplicationsController < ApplicationController
SCANS_PER_PAGE = 25
private_constant :SCANS_PER_PAGE
- TEST_APP_NAME = 'Featured App'
+ TEST_APP_NAME = "Featured App"
public_constant :TEST_APP_NAME
FEATURED_APPS = {
# NOTE: This won't result in AppLand being displayed on the Explore page unless its public flag is set to true.
# It's useful to have this for development, because appmaps of the other featured apps are not as easily available.
- 'appland/AppLand' => {
- description: 'AppLand records your running code, and automatically creates visualizations and data tables that show you exactly how your code works'
+ "appland/AppLand" => {
+ description: "AppLand records your running code, and automatically creates visualizations and data tables that show you exactly how your code works",
},
# For testing
- 'Featured App' => {
- description: 'This app is particularly interesting'
+ "Featured App" => {
+ description: "This app is particularly interesting",
},
- 'land-of-apps/rails_sample_app_6th_ed' => {
- description: 'Reference implementation of the sample application from Ruby on Rails Tutorial: Learn Web Development with Rails (6th Edition) by Michael Hartl'
+ "land-of-apps/rails_sample_app_6th_ed" => {
+ description: "Reference implementation of the sample application from Ruby on Rails Tutorial: Learn Web Development with Rails (6th Edition) by Michael Hartl",
},
- 'land-of-apps/Conjur' => {
- description: 'Conjur provides secrets management and application identity for modern infrastructure'
+ "land-of-apps/Conjur" => {
+ description: "Conjur provides secrets management and application identity for modern infrastructure",
},
- 'land-of-apps/discourse' => {
- description: 'Discourse is the 100% open source discussion platform built for the next decade of the Internet'
+ "land-of-apps/discourse" => {
+ description: "Discourse is the 100% open source discussion platform built for the next decade of the Internet",
},
- 'land-of-apps/ifme' => {
- description: 'Free, open source mental health communication web app to share experiences with loved ones'
+ "land-of-apps/ifme" => {
+ description: "Free, open source mental health communication web app to share experiences with loved ones",
},
- 'land-of-apps/refinerycms' => {
- description: 'An open source content management system for Rails'
+ "land-of-apps/refinerycms" => {
+ description: "An open source content management system for Rails",
+ },
+ "land-of-apps/spring-petclinic" => {
+ description: "The canonical sample application for Spring Boot",
},
- 'land-of-apps/spring-petclinic' => {
- description: 'The canonical sample application for Spring Boot'
- }
}.freeze
private_constant :FEATURED_APPS
@@ -52,6 +52,7 @@ class ApplicationsController < ApplicationController
before_action :find_app, only: %i[show component_diagram related_scenarios scenarios_list update mapset delete_form destroy settings update_repository]
before_action :check_to_redirect, only: %i[show]
before_action :find_recent_scans, only: %i[show]
+ before_action :find_preferred_mapsets, only: %i[show]
before_action :find_summary_stats, only: %i[show]
before_action :find_mapset, only: %i[component_diagram mapset related_scenarios scenarios_list]
@@ -64,9 +65,9 @@ class ApplicationsController < ApplicationController
end
def process_scenarios
- render 'processing', locals: {
- paths: @mapset.pending_scenarios.select(:uuid).map { |s| scenario_path s, process: true }
- }
+ render "processing", locals: {
+ paths: @mapset.pending_scenarios.select(:uuid).map { |s| scenario_path s, process: true },
+ }
end
def update
@@ -79,19 +80,19 @@ class ApplicationsController < ApplicationController
end
def destroy
- if(@app.name == params[:application_name])
+ if (@app.name == params[:application_name])
@app.destroy!(current_user)
- render partial: 'deleted'
+ render partial: "deleted"
else
- @app.errors.add :name, 'Application name does not match'
+ @app.errors.add :name, "Application name does not match"
@errors = @app.errors
- render 'delete_form', layout: !request.xhr?
+ render "delete_form", layout: !request.xhr?
end
end
def examples
- render layout: 'grid_full_public'
+ render layout: "grid_full_public"
end
def settings
@@ -103,21 +104,21 @@ class ApplicationsController < ApplicationController
def select_layout
if configuration.show_legacy_teardown?
- 'three_column'
+ "three_column"
else
- 'grid_filters'
+ "grid_filters"
end
end
def key_stats_owner_type
- 'mapset'
+ "mapset"
end
def public_apps
@public_apps ||= search_scope(App, scope: Search::SCOPE_PUBLIC).search(
offset: params[:offset],
optional_columns: %i[scenario_count],
- order: Sequel[:apps][:name]
+ order: Sequel[:apps][:name],
)
end
@@ -148,13 +149,18 @@ class ApplicationsController < ApplicationController
def find_recent_scans
@current_page = begin
- Integer(params[:p], 10)
- rescue ArgumentError
- 1
- end
+ Integer(params[:p], 10)
+ rescue ArgumentError
+ 1
+ end
num_scans = ScannerJob.count_jobs_for_app(@app)
@total_pages = (num_scans / Float(SCANS_PER_PAGE)).ceil
@scans = ScannerJob.most_recent_for_app(@app, limit: SCANS_PER_PAGE, offset: (@current_page - 1) * SCANS_PER_PAGE)
end
+
+ def find_preferred_mapsets
+ @mapsets = @app.mapsets
+ # byebug
+ end
end
diff --git a/app/models/app/show.rb b/app/models/app/show.rb
index ff290f91..4aef6e71 100644
--- a/app/models/app/show.rb
+++ b/app/models/app/show.rb
@@ -17,7 +17,7 @@ class App
end
def mapsets
- @mapsets ||= Mapset.ordered_list(@app)
+ @mapsets ||= Mapset.ordered_list(@app).map { |mapset| mapset.app = self; mapset }
end
def org
diff --git a/app/models/scanner_job.rb b/app/models/scanner_job.rb
index ecd8e23a..3913a58a 100644
--- a/app/models/scanner_job.rb
+++ b/app/models/scanner_job.rb
@@ -15,7 +15,11 @@ class ScannerJob
]
private_constant :LIST_ITEM_COLUMNS
- ListItem = Struct.new(*LIST_ITEM_COLUMNS)
+ ListItem = Struct.new(*LIST_ITEM_COLUMNS) do
+ extend Forwardable
+ include Accessor::ScannerJob
+ end
+
private_constant :ListItem
SummaryItem = Struct.new(
@@ -41,17 +45,17 @@ class ScannerJob
def self.subquery_findings_count
Sequel::Model.db[:check_findings_v]
.select {
- [
- Sequel[:check_findings_v][:scanner_job_id],
- sum(Sequel[:check_findings_v][:findings_count]).cast(:integer).as(:findings_count)
- ]
- }
+ [
+ Sequel[:check_findings_v][:scanner_job_id],
+ sum(Sequel[:check_findings_v][:findings_count]).cast(:integer).as(:findings_count),
+ ]
+ }
.group_by(:scanner_job_id)
.as(:findings)
end
# Retrieve the most recent scanner results for the given user
- def self.most_recent_for_user(user, limit=10, offset=0)
+ def self.most_recent_for_user(user, limit = 10, offset = 0)
most_recent_jobs = DAO::Mapset
.select(Sequel[:scanner_jobs].*, Sequel[:mapsets][:app_id])
.inner_join(:scanner_jobs, mapset_id: :id)
@@ -90,7 +94,7 @@ class ScannerJob
Sequel[:scanner_jobs][:mapset_id],
Sequel[:scanner_jobs][:created_at],
Sequel[:mapsets][:branch],
- Sequel.lit('SUBSTRING(mapsets.commit, 1, 7)').as(:commit),
+ Sequel.lit("SUBSTRING(mapsets.commit, 1, 7)").as(:commit),
Sequel.lit("COALESCE((scanner_jobs.summary->>'numChecks')::integer, 0) as checks_count"),
Sequel.lit("COALESCE((SUM(check_findings_v.findings_count) FILTER (WHERE check_findings_v.status = 'new'))::integer, 0) as new_count"),
Sequel.lit("COALESCE((SUM(check_findings_v.findings_count) FILTER (WHERE check_findings_v.status = 'deferred'))::integer, 0) as deferred_count"),
@@ -108,12 +112,12 @@ class ScannerJob
end
def self.summary_by_category(job_id)
- appmaps_count = DAO::Scanner::Job[job_id].summary['numAppMaps']
+ appmaps_count = DAO::Scanner::Job[job_id].summary["numAppMaps"]
Sequel::Model(:scanner_checks)
.select(
:impact_category,
- Sequel.lit('COUNT(DISTINCT scanner_checks.check_id) as rules_count'),
- Sequel.lit('COUNT(finding_occurrences.*)::integer as findings_count'),
+ Sequel.lit("COUNT(DISTINCT scanner_checks.check_id) as rules_count"),
+ Sequel.lit("COUNT(finding_occurrences.*)::integer as findings_count"),
Sequel.lit('COUNT(finding_occurrences.*) FILTER (WHERE status = \'new\') as new_count'),
Sequel.lit('COUNT(finding_occurrences.*) FILTER (WHERE status = \'deferred\') as deferred_count')
)
@@ -124,15 +128,15 @@ class ScannerJob
.group_by(:impact_category)
.order_by(:impact_category)
.map do |row|
- SummaryItem.new(
- row[:impact_category],
- appmaps_count,
- row[:rules_count],
- row[:rules_count] * appmaps_count - row[:findings_count],
- row[:findings_count],
- row[:new_count],
- row[:deferred_count],
- )
+ SummaryItem.new(
+ row[:impact_category],
+ appmaps_count,
+ row[:rules_count],
+ row[:rules_count] * appmaps_count - row[:findings_count],
+ row[:findings_count],
+ row[:new_count],
+ row[:deferred_count],
+ )
end
end
end
diff --git a/app/views/applications/show.html.haml b/app/views/applications/show.html.haml
index cdd497f5..65cb7f19 100644
--- a/app/views/applications/show.html.haml
+++ b/app/views/applications/show.html.haml
@@ -11,22 +11,22 @@
%img.icon{ src: '/img/gear.svg' }
%section.scan-history
- - if @scans.empty?
+ - if @scans.empty? && @mapsets.empty?
.card{ data: { spec: 'no-data' } }
- %h3.mb-3 Waiting on your first scan...
+ %h3.mb-3 Waiting on your first upload...
%p Looking for a way to get started? Visit our documentation:
%ul
%li
- %a{ href: 'https://appland.com/docs/analysis/getting-started.html' } Getting started
+ %a{ href: 'https://appmap.io/docs/recording-methods.html', target: '_blank' } Recording AppMaps
%li
- %a{ href: 'https://appland.com/docs/analysis/uploading.html' } Uploading findings
+ %a{ href: 'https://appmap.io/docs/openapi.html', target: '_blank' } Generating OpenAPI
%li
- %a{ href: 'https://appland.com/docs/analysis/integrating-with-ci.html' } Integrating with CI
- - else
+ %a{ href: 'https://appmap.io/docs/analysis', target: '_blank' } Runtime Analysis
+ - unless @scans.empty?
= render partial: 'partials/findings_overview'
%h3.subhead Scan history
- @scans.each do |scan|
- %a.card.app{href: scanner_job_path(scan.id), data: { scan_status: scan.new_count == 0 ? 'pass' : 'fail', spec: 'scan' }}
+ %a.card.app{href: scanner_job_path(scan), data: { scan_status: scan.new_count == 0 ? 'pass' : 'fail', spec: 'scan' }}
.stacked-title
.label Branch
.title{data: { spec: 'branch' }}= scan.branch.present? ? scan.branch : 'unknown'
@@ -45,7 +45,7 @@
%li.info.column
.info--key.label Commit
.info--value{data: { spec: 'commit' }}
- = scan.commit.present? ? scan.commit[0..7] : 'unknown'
+ = scan.commit.present? ? scan.commit[0..7] : ' '.html_safe
%li.info.column
.info--key.label Created
.info--value{data: { spec: 'time-created' }}
@@ -61,3 +61,28 @@
%a.page-link{ href: application_path(@app, p: page_num), data: { spec: "page", page: page_num } }= page_num
%li.page-item{ class: ('disabled' if @current_page == @total_pages) }
%a.page-link{ href: application_path(@app, p: @current_page + 1), data: { spec: 'next' } } Next
+
+ - unless @mapsets.empty?
+ %h3.subhead Mapset history
+ - @mapsets.each do |mapset|
+ %a.card.app{href: mapset_path(mapset)}
+ .stacked-title
+ .label Branch
+ .title{data: { spec: 'branch' }}= mapset.branch.present? ? mapset.branch : 'unknown'
+ %ul.metrics
+ %li.info.column
+ .info--key.label Commit
+ .info--value{data: { spec: 'commit' }}
+ = mapset.commit.present? ? mapset.commit[0..7] : ' '.html_safe
+ %li.info.column
+ .info--key.label Environment
+ .info--value{data: { spec: 'environment' }}
+ = mapset.environment || ' '.html_safe
+ %li.info.column
+ .info--key.label Version
+ .info--value{data: { spec: 'version' }}
+ = mapset.version || ' '.html_safe
+ %li.info.column
+ .info--key.label Created
+ .info--value{data: { spec: 'time-created' }}
+ = "#{time_ago_in_words mapset.created_at} ago"
diff --git a/app/views/partials/_findings_overview.html.haml b/app/views/partials/_findings_overview.html.haml
index f0219a56..1676f341 100644
--- a/app/views/partials/_findings_overview.html.haml
+++ b/app/views/partials/_findings_overview.html.haml
@@ -1,6 +1,6 @@
%section.findings-overview
.findings-overview__head
- Trends
+ Analysis Trends
%span.findings-overview__periods
- @time_ranges.each do |days|
%a.findings-overview__period{ class: ('findings-overview__period--active' if @current_time_range == days), href: "?time_range=#{days}", data: { selected: true, spec: 'time-range-select', days: days} }
2 times: added function call `controllers#find_preferred_mapsets`
2 times: added function call `models#mapsets`
2 times: added function call `models.ordered_list`
2 times: added function call `models#ordered_mapset_dataset`
2 times: added SQL `SELECT *, "mapsets"."name", "mapsets"."branch", "mapsets"."commit", "mapsets"."environment", "mapsets"."version", (SELECT "login" FROM "users" WHERE ("id" = "user_id")), (SELECT count(*) AS "scenario_count" FROM "scenarios" WHERE ("mapset_id" = "mapsets"."id")) FROM "mapsets_preference_ordered" AS "mapsets" WHERE ("mapsets"."app_id" = 1)`
2 times: added function call `helpers#base_slug`
4 times: added function call `models#org`
10 times: added function call `helpers#mapset_path`
8 times: added function call `helpers#base_slug`
6 times: added function call `models#org`
ApplicationsController .component_diagram logged in for a single feature gets a component diagram of matching scenarios has changed.
spec/requests/application_component_diagram_spec.rb
diff --git a/app/models/app/show.rb b/app/models/app/show.rb
index ff290f91..4aef6e71 100644
--- a/app/models/app/show.rb
+++ b/app/models/app/show.rb
@@ -17,7 +17,7 @@ class App
end
def mapsets
- @mapsets ||= Mapset.ordered_list(@app)
+ @mapsets ||= Mapset.ordered_list(@app).map { |mapset| mapset.app = self; mapset }
end
def org
diff --git a/app/models/mapset.rb b/app/models/mapset.rb
index 68c6d9b6..8d8782a2 100644
--- a/app/models/mapset.rb
+++ b/app/models/mapset.rb
@@ -13,12 +13,13 @@ class Mapset
def find_in_app!(app, id)
mapset = app.mapsets.find { |mapset| mapset.id == id }
- raise Exceptions::RecordNotFound.new('Mapset', id) unless mapset
+ raise Exceptions::RecordNotFound.new("Mapset", id) unless mapset
Mapset::Show.new(DAO::Mapset[mapset.id])
end
# ordered_list provides a list of Mapsets in app-preferred order.
+ # To access this data, use Mapset::Show#mapsets, rather than calling this method directly.
def ordered_list(app)
sum_scenario_count = 0
app.ordered_mapset_dataset
@@ -34,13 +35,13 @@ class Mapset
.select(Sequel.as(Sequel.function(:count).*, :scenario_count))
)
.map do |row|
- Mapset::ListItem.new(*row.values.values_at(:id, :created_at, :name, :branch,
- :commit, :environment, :version, :login)).tap do |mapset|
- sum_scenario_count += row[:scenario_count]
- mapset.scenario_count = row[:scenario_count]
+ Mapset::ListItem.new(*row.values.values_at(:id, :created_at, :name, :branch,
+ :commit, :environment, :version, :login)).tap do |mapset|
+ sum_scenario_count += row[:scenario_count]
+ mapset.scenario_count = row[:scenario_count]
end
end
- .filter do |mapset|
+ .filter do |mapset|
mapset.scenario_count > 0 || sum_scenario_count == 0
end
end
spec/system/dashboard_summary_spec.rb
diff --git a/app/controllers/applications_controller.rb b/app/controllers/applications_controller.rb
index 62d85043..e476ed52 100644
--- a/app/controllers/applications_controller.rb
+++ b/app/controllers/applications_controller.rb
@@ -13,37 +13,37 @@ class ApplicationsController < ApplicationController
SCANS_PER_PAGE = 25
private_constant :SCANS_PER_PAGE
- TEST_APP_NAME = 'Featured App'
+ TEST_APP_NAME = "Featured App"
public_constant :TEST_APP_NAME
FEATURED_APPS = {
# NOTE: This won't result in AppLand being displayed on the Explore page unless its public flag is set to true.
# It's useful to have this for development, because appmaps of the other featured apps are not as easily available.
- 'appland/AppLand' => {
- description: 'AppLand records your running code, and automatically creates visualizations and data tables that show you exactly how your code works'
+ "appland/AppLand" => {
+ description: "AppLand records your running code, and automatically creates visualizations and data tables that show you exactly how your code works",
},
# For testing
- 'Featured App' => {
- description: 'This app is particularly interesting'
+ "Featured App" => {
+ description: "This app is particularly interesting",
},
- 'land-of-apps/rails_sample_app_6th_ed' => {
- description: 'Reference implementation of the sample application from Ruby on Rails Tutorial: Learn Web Development with Rails (6th Edition) by Michael Hartl'
+ "land-of-apps/rails_sample_app_6th_ed" => {
+ description: "Reference implementation of the sample application from Ruby on Rails Tutorial: Learn Web Development with Rails (6th Edition) by Michael Hartl",
},
- 'land-of-apps/Conjur' => {
- description: 'Conjur provides secrets management and application identity for modern infrastructure'
+ "land-of-apps/Conjur" => {
+ description: "Conjur provides secrets management and application identity for modern infrastructure",
},
- 'land-of-apps/discourse' => {
- description: 'Discourse is the 100% open source discussion platform built for the next decade of the Internet'
+ "land-of-apps/discourse" => {
+ description: "Discourse is the 100% open source discussion platform built for the next decade of the Internet",
},
- 'land-of-apps/ifme' => {
- description: 'Free, open source mental health communication web app to share experiences with loved ones'
+ "land-of-apps/ifme" => {
+ description: "Free, open source mental health communication web app to share experiences with loved ones",
},
- 'land-of-apps/refinerycms' => {
- description: 'An open source content management system for Rails'
+ "land-of-apps/refinerycms" => {
+ description: "An open source content management system for Rails",
+ },
+ "land-of-apps/spring-petclinic" => {
+ description: "The canonical sample application for Spring Boot",
},
- 'land-of-apps/spring-petclinic' => {
- description: 'The canonical sample application for Spring Boot'
- }
}.freeze
private_constant :FEATURED_APPS
@@ -52,6 +52,7 @@ class ApplicationsController < ApplicationController
before_action :find_app, only: %i[show component_diagram related_scenarios scenarios_list update mapset delete_form destroy settings update_repository]
before_action :check_to_redirect, only: %i[show]
before_action :find_recent_scans, only: %i[show]
+ before_action :find_preferred_mapsets, only: %i[show]
before_action :find_summary_stats, only: %i[show]
before_action :find_mapset, only: %i[component_diagram mapset related_scenarios scenarios_list]
@@ -64,9 +65,9 @@ class ApplicationsController < ApplicationController
end
def process_scenarios
- render 'processing', locals: {
- paths: @mapset.pending_scenarios.select(:uuid).map { |s| scenario_path s, process: true }
- }
+ render "processing", locals: {
+ paths: @mapset.pending_scenarios.select(:uuid).map { |s| scenario_path s, process: true },
+ }
end
def update
@@ -79,19 +80,19 @@ class ApplicationsController < ApplicationController
end
def destroy
- if(@app.name == params[:application_name])
+ if (@app.name == params[:application_name])
@app.destroy!(current_user)
- render partial: 'deleted'
+ render partial: "deleted"
else
- @app.errors.add :name, 'Application name does not match'
+ @app.errors.add :name, "Application name does not match"
@errors = @app.errors
- render 'delete_form', layout: !request.xhr?
+ render "delete_form", layout: !request.xhr?
end
end
def examples
- render layout: 'grid_full_public'
+ render layout: "grid_full_public"
end
def settings
@@ -103,21 +104,21 @@ class ApplicationsController < ApplicationController
def select_layout
if configuration.show_legacy_teardown?
- 'three_column'
+ "three_column"
else
- 'grid_filters'
+ "grid_filters"
end
end
def key_stats_owner_type
- 'mapset'
+ "mapset"
end
def public_apps
@public_apps ||= search_scope(App, scope: Search::SCOPE_PUBLIC).search(
offset: params[:offset],
optional_columns: %i[scenario_count],
- order: Sequel[:apps][:name]
+ order: Sequel[:apps][:name],
)
end
@@ -148,13 +149,18 @@ class ApplicationsController < ApplicationController
def find_recent_scans
@current_page = begin
- Integer(params[:p], 10)
- rescue ArgumentError
- 1
- end
+ Integer(params[:p], 10)
+ rescue ArgumentError
+ 1
+ end
num_scans = ScannerJob.count_jobs_for_app(@app)
@total_pages = (num_scans / Float(SCANS_PER_PAGE)).ceil
@scans = ScannerJob.most_recent_for_app(@app, limit: SCANS_PER_PAGE, offset: (@current_page - 1) * SCANS_PER_PAGE)
end
+
+ def find_preferred_mapsets
+ @mapsets = @app.mapsets
+ # byebug
+ end
end
diff --git a/app/models/app/show.rb b/app/models/app/show.rb
index ff290f91..4aef6e71 100644
--- a/app/models/app/show.rb
+++ b/app/models/app/show.rb
@@ -17,7 +17,7 @@ class App
end
def mapsets
- @mapsets ||= Mapset.ordered_list(@app)
+ @mapsets ||= Mapset.ordered_list(@app).map { |mapset| mapset.app = self; mapset }
end
def org
diff --git a/app/models/scanner_job.rb b/app/models/scanner_job.rb
index ecd8e23a..3913a58a 100644
--- a/app/models/scanner_job.rb
+++ b/app/models/scanner_job.rb
@@ -15,7 +15,11 @@ class ScannerJob
]
private_constant :LIST_ITEM_COLUMNS
- ListItem = Struct.new(*LIST_ITEM_COLUMNS)
+ ListItem = Struct.new(*LIST_ITEM_COLUMNS) do
+ extend Forwardable
+ include Accessor::ScannerJob
+ end
+
private_constant :ListItem
SummaryItem = Struct.new(
@@ -41,17 +45,17 @@ class ScannerJob
def self.subquery_findings_count
Sequel::Model.db[:check_findings_v]
.select {
- [
- Sequel[:check_findings_v][:scanner_job_id],
- sum(Sequel[:check_findings_v][:findings_count]).cast(:integer).as(:findings_count)
- ]
- }
+ [
+ Sequel[:check_findings_v][:scanner_job_id],
+ sum(Sequel[:check_findings_v][:findings_count]).cast(:integer).as(:findings_count),
+ ]
+ }
.group_by(:scanner_job_id)
.as(:findings)
end
# Retrieve the most recent scanner results for the given user
- def self.most_recent_for_user(user, limit=10, offset=0)
+ def self.most_recent_for_user(user, limit = 10, offset = 0)
most_recent_jobs = DAO::Mapset
.select(Sequel[:scanner_jobs].*, Sequel[:mapsets][:app_id])
.inner_join(:scanner_jobs, mapset_id: :id)
@@ -90,7 +94,7 @@ class ScannerJob
Sequel[:scanner_jobs][:mapset_id],
Sequel[:scanner_jobs][:created_at],
Sequel[:mapsets][:branch],
- Sequel.lit('SUBSTRING(mapsets.commit, 1, 7)').as(:commit),
+ Sequel.lit("SUBSTRING(mapsets.commit, 1, 7)").as(:commit),
Sequel.lit("COALESCE((scanner_jobs.summary->>'numChecks')::integer, 0) as checks_count"),
Sequel.lit("COALESCE((SUM(check_findings_v.findings_count) FILTER (WHERE check_findings_v.status = 'new'))::integer, 0) as new_count"),
Sequel.lit("COALESCE((SUM(check_findings_v.findings_count) FILTER (WHERE check_findings_v.status = 'deferred'))::integer, 0) as deferred_count"),
@@ -108,12 +112,12 @@ class ScannerJob
end
def self.summary_by_category(job_id)
- appmaps_count = DAO::Scanner::Job[job_id].summary['numAppMaps']
+ appmaps_count = DAO::Scanner::Job[job_id].summary["numAppMaps"]
Sequel::Model(:scanner_checks)
.select(
:impact_category,
- Sequel.lit('COUNT(DISTINCT scanner_checks.check_id) as rules_count'),
- Sequel.lit('COUNT(finding_occurrences.*)::integer as findings_count'),
+ Sequel.lit("COUNT(DISTINCT scanner_checks.check_id) as rules_count"),
+ Sequel.lit("COUNT(finding_occurrences.*)::integer as findings_count"),
Sequel.lit('COUNT(finding_occurrences.*) FILTER (WHERE status = \'new\') as new_count'),
Sequel.lit('COUNT(finding_occurrences.*) FILTER (WHERE status = \'deferred\') as deferred_count')
)
@@ -124,15 +128,15 @@ class ScannerJob
.group_by(:impact_category)
.order_by(:impact_category)
.map do |row|
- SummaryItem.new(
- row[:impact_category],
- appmaps_count,
- row[:rules_count],
- row[:rules_count] * appmaps_count - row[:findings_count],
- row[:findings_count],
- row[:new_count],
- row[:deferred_count],
- )
+ SummaryItem.new(
+ row[:impact_category],
+ appmaps_count,
+ row[:rules_count],
+ row[:rules_count] * appmaps_count - row[:findings_count],
+ row[:findings_count],
+ row[:new_count],
+ row[:deferred_count],
+ )
end
end
end
diff --git a/app/views/applications/show.html.haml b/app/views/applications/show.html.haml
index cdd497f5..65cb7f19 100644
--- a/app/views/applications/show.html.haml
+++ b/app/views/applications/show.html.haml
@@ -11,22 +11,22 @@
%img.icon{ src: '/img/gear.svg' }
%section.scan-history
- - if @scans.empty?
+ - if @scans.empty? && @mapsets.empty?
.card{ data: { spec: 'no-data' } }
- %h3.mb-3 Waiting on your first scan...
+ %h3.mb-3 Waiting on your first upload...
%p Looking for a way to get started? Visit our documentation:
%ul
%li
- %a{ href: 'https://appland.com/docs/analysis/getting-started.html' } Getting started
+ %a{ href: 'https://appmap.io/docs/recording-methods.html', target: '_blank' } Recording AppMaps
%li
- %a{ href: 'https://appland.com/docs/analysis/uploading.html' } Uploading findings
+ %a{ href: 'https://appmap.io/docs/openapi.html', target: '_blank' } Generating OpenAPI
%li
- %a{ href: 'https://appland.com/docs/analysis/integrating-with-ci.html' } Integrating with CI
- - else
+ %a{ href: 'https://appmap.io/docs/analysis', target: '_blank' } Runtime Analysis
+ - unless @scans.empty?
= render partial: 'partials/findings_overview'
%h3.subhead Scan history
- @scans.each do |scan|
- %a.card.app{href: scanner_job_path(scan.id), data: { scan_status: scan.new_count == 0 ? 'pass' : 'fail', spec: 'scan' }}
+ %a.card.app{href: scanner_job_path(scan), data: { scan_status: scan.new_count == 0 ? 'pass' : 'fail', spec: 'scan' }}
.stacked-title
.label Branch
.title{data: { spec: 'branch' }}= scan.branch.present? ? scan.branch : 'unknown'
@@ -45,7 +45,7 @@
%li.info.column
.info--key.label Commit
.info--value{data: { spec: 'commit' }}
- = scan.commit.present? ? scan.commit[0..7] : 'unknown'
+ = scan.commit.present? ? scan.commit[0..7] : ' '.html_safe
%li.info.column
.info--key.label Created
.info--value{data: { spec: 'time-created' }}
@@ -61,3 +61,28 @@
%a.page-link{ href: application_path(@app, p: page_num), data: { spec: "page", page: page_num } }= page_num
%li.page-item{ class: ('disabled' if @current_page == @total_pages) }
%a.page-link{ href: application_path(@app, p: @current_page + 1), data: { spec: 'next' } } Next
+
+ - unless @mapsets.empty?
+ %h3.subhead Mapset history
+ - @mapsets.each do |mapset|
+ %a.card.app{href: mapset_path(mapset)}
+ .stacked-title
+ .label Branch
+ .title{data: { spec: 'branch' }}= mapset.branch.present? ? mapset.branch : 'unknown'
+ %ul.metrics
+ %li.info.column
+ .info--key.label Commit
+ .info--value{data: { spec: 'commit' }}
+ = mapset.commit.present? ? mapset.commit[0..7] : ' '.html_safe
+ %li.info.column
+ .info--key.label Environment
+ .info--value{data: { spec: 'environment' }}
+ = mapset.environment || ' '.html_safe
+ %li.info.column
+ .info--key.label Version
+ .info--value{data: { spec: 'version' }}
+ = mapset.version || ' '.html_safe
+ %li.info.column
+ .info--key.label Created
+ .info--value{data: { spec: 'time-created' }}
+ = "#{time_ago_in_words mapset.created_at} ago"
diff --git a/app/views/partials/_findings_overview.html.haml b/app/views/partials/_findings_overview.html.haml
index f0219a56..1676f341 100644
--- a/app/views/partials/_findings_overview.html.haml
+++ b/app/views/partials/_findings_overview.html.haml
@@ -1,6 +1,6 @@
%section.findings-overview
.findings-overview__head
- Trends
+ Analysis Trends
%span.findings-overview__periods
- @time_ranges.each do |days|
%a.findings-overview__period{ class: ('findings-overview__period--active' if @current_time_range == days), href: "?time_range=#{days}", data: { selected: true, spec: 'time-range-select', days: days} }
added function call `controllers#find_preferred_mapsets`
added function call `models#mapsets`
added function call `models.ordered_list`
added function call `models#ordered_mapset_dataset`
added SQL `SELECT *, "mapsets"."name", "mapsets"."branch", "mapsets"."commit", "mapsets"."environment", "mapsets"."version", (SELECT "login" FROM "users" WHERE ("id" = "user_id")), (SELECT count(*) AS "scenario_count" FROM "scenarios" WHERE ("mapset_id" = "mapsets"."id")) FROM "mapsets_preference_ordered" AS "mapsets" WHERE ("mapsets"."app_id" = 1)`
added function call `helpers#mapset_path`
added function call `helpers#base_slug`
added function call `models#org`
spec/system/early_access_spec.rb
diff --git a/app/controllers/applications_controller.rb b/app/controllers/applications_controller.rb
index 62d85043..e476ed52 100644
--- a/app/controllers/applications_controller.rb
+++ b/app/controllers/applications_controller.rb
@@ -13,37 +13,37 @@ class ApplicationsController < ApplicationController
SCANS_PER_PAGE = 25
private_constant :SCANS_PER_PAGE
- TEST_APP_NAME = 'Featured App'
+ TEST_APP_NAME = "Featured App"
public_constant :TEST_APP_NAME
FEATURED_APPS = {
# NOTE: This won't result in AppLand being displayed on the Explore page unless its public flag is set to true.
# It's useful to have this for development, because appmaps of the other featured apps are not as easily available.
- 'appland/AppLand' => {
- description: 'AppLand records your running code, and automatically creates visualizations and data tables that show you exactly how your code works'
+ "appland/AppLand" => {
+ description: "AppLand records your running code, and automatically creates visualizations and data tables that show you exactly how your code works",
},
# For testing
- 'Featured App' => {
- description: 'This app is particularly interesting'
+ "Featured App" => {
+ description: "This app is particularly interesting",
},
- 'land-of-apps/rails_sample_app_6th_ed' => {
- description: 'Reference implementation of the sample application from Ruby on Rails Tutorial: Learn Web Development with Rails (6th Edition) by Michael Hartl'
+ "land-of-apps/rails_sample_app_6th_ed" => {
+ description: "Reference implementation of the sample application from Ruby on Rails Tutorial: Learn Web Development with Rails (6th Edition) by Michael Hartl",
},
- 'land-of-apps/Conjur' => {
- description: 'Conjur provides secrets management and application identity for modern infrastructure'
+ "land-of-apps/Conjur" => {
+ description: "Conjur provides secrets management and application identity for modern infrastructure",
},
- 'land-of-apps/discourse' => {
- description: 'Discourse is the 100% open source discussion platform built for the next decade of the Internet'
+ "land-of-apps/discourse" => {
+ description: "Discourse is the 100% open source discussion platform built for the next decade of the Internet",
},
- 'land-of-apps/ifme' => {
- description: 'Free, open source mental health communication web app to share experiences with loved ones'
+ "land-of-apps/ifme" => {
+ description: "Free, open source mental health communication web app to share experiences with loved ones",
},
- 'land-of-apps/refinerycms' => {
- description: 'An open source content management system for Rails'
+ "land-of-apps/refinerycms" => {
+ description: "An open source content management system for Rails",
+ },
+ "land-of-apps/spring-petclinic" => {
+ description: "The canonical sample application for Spring Boot",
},
- 'land-of-apps/spring-petclinic' => {
- description: 'The canonical sample application for Spring Boot'
- }
}.freeze
private_constant :FEATURED_APPS
@@ -52,6 +52,7 @@ class ApplicationsController < ApplicationController
before_action :find_app, only: %i[show component_diagram related_scenarios scenarios_list update mapset delete_form destroy settings update_repository]
before_action :check_to_redirect, only: %i[show]
before_action :find_recent_scans, only: %i[show]
+ before_action :find_preferred_mapsets, only: %i[show]
before_action :find_summary_stats, only: %i[show]
before_action :find_mapset, only: %i[component_diagram mapset related_scenarios scenarios_list]
@@ -64,9 +65,9 @@ class ApplicationsController < ApplicationController
end
def process_scenarios
- render 'processing', locals: {
- paths: @mapset.pending_scenarios.select(:uuid).map { |s| scenario_path s, process: true }
- }
+ render "processing", locals: {
+ paths: @mapset.pending_scenarios.select(:uuid).map { |s| scenario_path s, process: true },
+ }
end
def update
@@ -79,19 +80,19 @@ class ApplicationsController < ApplicationController
end
def destroy
- if(@app.name == params[:application_name])
+ if (@app.name == params[:application_name])
@app.destroy!(current_user)
- render partial: 'deleted'
+ render partial: "deleted"
else
- @app.errors.add :name, 'Application name does not match'
+ @app.errors.add :name, "Application name does not match"
@errors = @app.errors
- render 'delete_form', layout: !request.xhr?
+ render "delete_form", layout: !request.xhr?
end
end
def examples
- render layout: 'grid_full_public'
+ render layout: "grid_full_public"
end
def settings
@@ -103,21 +104,21 @@ class ApplicationsController < ApplicationController
def select_layout
if configuration.show_legacy_teardown?
- 'three_column'
+ "three_column"
else
- 'grid_filters'
+ "grid_filters"
end
end
def key_stats_owner_type
- 'mapset'
+ "mapset"
end
def public_apps
@public_apps ||= search_scope(App, scope: Search::SCOPE_PUBLIC).search(
offset: params[:offset],
optional_columns: %i[scenario_count],
- order: Sequel[:apps][:name]
+ order: Sequel[:apps][:name],
)
end
@@ -148,13 +149,18 @@ class ApplicationsController < ApplicationController
def find_recent_scans
@current_page = begin
- Integer(params[:p], 10)
- rescue ArgumentError
- 1
- end
+ Integer(params[:p], 10)
+ rescue ArgumentError
+ 1
+ end
num_scans = ScannerJob.count_jobs_for_app(@app)
@total_pages = (num_scans / Float(SCANS_PER_PAGE)).ceil
@scans = ScannerJob.most_recent_for_app(@app, limit: SCANS_PER_PAGE, offset: (@current_page - 1) * SCANS_PER_PAGE)
end
+
+ def find_preferred_mapsets
+ @mapsets = @app.mapsets
+ # byebug
+ end
end
diff --git a/app/models/app/show.rb b/app/models/app/show.rb
index ff290f91..4aef6e71 100644
--- a/app/models/app/show.rb
+++ b/app/models/app/show.rb
@@ -17,7 +17,7 @@ class App
end
def mapsets
- @mapsets ||= Mapset.ordered_list(@app)
+ @mapsets ||= Mapset.ordered_list(@app).map { |mapset| mapset.app = self; mapset }
end
def org
diff --git a/app/models/scanner_job.rb b/app/models/scanner_job.rb
index ecd8e23a..3913a58a 100644
--- a/app/models/scanner_job.rb
+++ b/app/models/scanner_job.rb
@@ -15,7 +15,11 @@ class ScannerJob
]
private_constant :LIST_ITEM_COLUMNS
- ListItem = Struct.new(*LIST_ITEM_COLUMNS)
+ ListItem = Struct.new(*LIST_ITEM_COLUMNS) do
+ extend Forwardable
+ include Accessor::ScannerJob
+ end
+
private_constant :ListItem
SummaryItem = Struct.new(
@@ -41,17 +45,17 @@ class ScannerJob
def self.subquery_findings_count
Sequel::Model.db[:check_findings_v]
.select {
- [
- Sequel[:check_findings_v][:scanner_job_id],
- sum(Sequel[:check_findings_v][:findings_count]).cast(:integer).as(:findings_count)
- ]
- }
+ [
+ Sequel[:check_findings_v][:scanner_job_id],
+ sum(Sequel[:check_findings_v][:findings_count]).cast(:integer).as(:findings_count),
+ ]
+ }
.group_by(:scanner_job_id)
.as(:findings)
end
# Retrieve the most recent scanner results for the given user
- def self.most_recent_for_user(user, limit=10, offset=0)
+ def self.most_recent_for_user(user, limit = 10, offset = 0)
most_recent_jobs = DAO::Mapset
.select(Sequel[:scanner_jobs].*, Sequel[:mapsets][:app_id])
.inner_join(:scanner_jobs, mapset_id: :id)
@@ -90,7 +94,7 @@ class ScannerJob
Sequel[:scanner_jobs][:mapset_id],
Sequel[:scanner_jobs][:created_at],
Sequel[:mapsets][:branch],
- Sequel.lit('SUBSTRING(mapsets.commit, 1, 7)').as(:commit),
+ Sequel.lit("SUBSTRING(mapsets.commit, 1, 7)").as(:commit),
Sequel.lit("COALESCE((scanner_jobs.summary->>'numChecks')::integer, 0) as checks_count"),
Sequel.lit("COALESCE((SUM(check_findings_v.findings_count) FILTER (WHERE check_findings_v.status = 'new'))::integer, 0) as new_count"),
Sequel.lit("COALESCE((SUM(check_findings_v.findings_count) FILTER (WHERE check_findings_v.status = 'deferred'))::integer, 0) as deferred_count"),
@@ -108,12 +112,12 @@ class ScannerJob
end
def self.summary_by_category(job_id)
- appmaps_count = DAO::Scanner::Job[job_id].summary['numAppMaps']
+ appmaps_count = DAO::Scanner::Job[job_id].summary["numAppMaps"]
Sequel::Model(:scanner_checks)
.select(
:impact_category,
- Sequel.lit('COUNT(DISTINCT scanner_checks.check_id) as rules_count'),
- Sequel.lit('COUNT(finding_occurrences.*)::integer as findings_count'),
+ Sequel.lit("COUNT(DISTINCT scanner_checks.check_id) as rules_count"),
+ Sequel.lit("COUNT(finding_occurrences.*)::integer as findings_count"),
Sequel.lit('COUNT(finding_occurrences.*) FILTER (WHERE status = \'new\') as new_count'),
Sequel.lit('COUNT(finding_occurrences.*) FILTER (WHERE status = \'deferred\') as deferred_count')
)
@@ -124,15 +128,15 @@ class ScannerJob
.group_by(:impact_category)
.order_by(:impact_category)
.map do |row|
- SummaryItem.new(
- row[:impact_category],
- appmaps_count,
- row[:rules_count],
- row[:rules_count] * appmaps_count - row[:findings_count],
- row[:findings_count],
- row[:new_count],
- row[:deferred_count],
- )
+ SummaryItem.new(
+ row[:impact_category],
+ appmaps_count,
+ row[:rules_count],
+ row[:rules_count] * appmaps_count - row[:findings_count],
+ row[:findings_count],
+ row[:new_count],
+ row[:deferred_count],
+ )
end
end
end
diff --git a/app/views/applications/show.html.haml b/app/views/applications/show.html.haml
index cdd497f5..65cb7f19 100644
--- a/app/views/applications/show.html.haml
+++ b/app/views/applications/show.html.haml
@@ -11,22 +11,22 @@
%img.icon{ src: '/img/gear.svg' }
%section.scan-history
- - if @scans.empty?
+ - if @scans.empty? && @mapsets.empty?
.card{ data: { spec: 'no-data' } }
- %h3.mb-3 Waiting on your first scan...
+ %h3.mb-3 Waiting on your first upload...
%p Looking for a way to get started? Visit our documentation:
%ul
%li
- %a{ href: 'https://appland.com/docs/analysis/getting-started.html' } Getting started
+ %a{ href: 'https://appmap.io/docs/recording-methods.html', target: '_blank' } Recording AppMaps
%li
- %a{ href: 'https://appland.com/docs/analysis/uploading.html' } Uploading findings
+ %a{ href: 'https://appmap.io/docs/openapi.html', target: '_blank' } Generating OpenAPI
%li
- %a{ href: 'https://appland.com/docs/analysis/integrating-with-ci.html' } Integrating with CI
- - else
+ %a{ href: 'https://appmap.io/docs/analysis', target: '_blank' } Runtime Analysis
+ - unless @scans.empty?
= render partial: 'partials/findings_overview'
%h3.subhead Scan history
- @scans.each do |scan|
- %a.card.app{href: scanner_job_path(scan.id), data: { scan_status: scan.new_count == 0 ? 'pass' : 'fail', spec: 'scan' }}
+ %a.card.app{href: scanner_job_path(scan), data: { scan_status: scan.new_count == 0 ? 'pass' : 'fail', spec: 'scan' }}
.stacked-title
.label Branch
.title{data: { spec: 'branch' }}= scan.branch.present? ? scan.branch : 'unknown'
@@ -45,7 +45,7 @@
%li.info.column
.info--key.label Commit
.info--value{data: { spec: 'commit' }}
- = scan.commit.present? ? scan.commit[0..7] : 'unknown'
+ = scan.commit.present? ? scan.commit[0..7] : ' '.html_safe
%li.info.column
.info--key.label Created
.info--value{data: { spec: 'time-created' }}
@@ -61,3 +61,28 @@
%a.page-link{ href: application_path(@app, p: page_num), data: { spec: "page", page: page_num } }= page_num
%li.page-item{ class: ('disabled' if @current_page == @total_pages) }
%a.page-link{ href: application_path(@app, p: @current_page + 1), data: { spec: 'next' } } Next
+
+ - unless @mapsets.empty?
+ %h3.subhead Mapset history
+ - @mapsets.each do |mapset|
+ %a.card.app{href: mapset_path(mapset)}
+ .stacked-title
+ .label Branch
+ .title{data: { spec: 'branch' }}= mapset.branch.present? ? mapset.branch : 'unknown'
+ %ul.metrics
+ %li.info.column
+ .info--key.label Commit
+ .info--value{data: { spec: 'commit' }}
+ = mapset.commit.present? ? mapset.commit[0..7] : ' '.html_safe
+ %li.info.column
+ .info--key.label Environment
+ .info--value{data: { spec: 'environment' }}
+ = mapset.environment || ' '.html_safe
+ %li.info.column
+ .info--key.label Version
+ .info--value{data: { spec: 'version' }}
+ = mapset.version || ' '.html_safe
+ %li.info.column
+ .info--key.label Created
+ .info--value{data: { spec: 'time-created' }}
+ = "#{time_ago_in_words mapset.created_at} ago"
diff --git a/app/views/partials/_findings_overview.html.haml b/app/views/partials/_findings_overview.html.haml
index f0219a56..1676f341 100644
--- a/app/views/partials/_findings_overview.html.haml
+++ b/app/views/partials/_findings_overview.html.haml
@@ -1,6 +1,6 @@
%section.findings-overview
.findings-overview__head
- Trends
+ Analysis Trends
%span.findings-overview__periods
- @time_ranges.each do |days|
%a.findings-overview__period{ class: ('findings-overview__period--active' if @current_time_range == days), href: "?time_range=#{days}", data: { selected: true, spec: 'time-range-select', days: days} }
diff --git a/app/views/scanner_jobs/_meta.html.haml b/app/views/scanner_jobs/_meta.html.haml
index dfb4a6e0..eb20cdff 100644
--- a/app/views/scanner_jobs/_meta.html.haml
+++ b/app/views/scanner_jobs/_meta.html.haml
@@ -25,6 +25,11 @@
.scan-meta__details
.card.scan-meta__card
%dl.scan-meta__dl
+ .scan-meta__dl-row
+ %dt.scan-meta__dt
+ Scan ID:
+ %dd.scan-meta__dd
+ = @job.id
.scan-meta__dl-row
%dt.scan-meta__dt
Branch:
@@ -32,11 +37,11 @@
= @job.branch
.scan-meta__dl-row
%dt.scan-meta__dt
- Last scanned:
+ Created:
%dd.scan-meta__dd
- = @job.created_at
+ = "#{time_ago_in_words(@job.created_at)} ago"
.scan-meta__dl-row
%dt.scan-meta__dt
- Scan ID:
+ AppMap data:
%dd.scan-meta__dd
- = @job.id
+ = link_to "analysis mapset ##{@job.mapset.id}", mapset_path(@job.mapset)
removed SQL `SELECT "pg_attribute"."attname" AS "name", CAST("pg_attribute"."atttypid" AS integer) AS "oid", CAST("basetype"."oid" AS integer) AS "base_oid", format_type("basetype"."oid", "pg_type"."typtypmod") AS "db_base_type", format_type("pg_type"."oid", "pg_attribute"."atttypmod") AS "db_type", pg_get_expr("pg_attrdef"."adbin", "pg_class"."oid") AS "default", NOT "pg_attribute"."attnotnull" AS "allow_null", COALESCE(("pg_attribute"."attnum" = ANY("pg_index"."indkey")), false) AS "primary_key", "pg_attribute"."attidentity", ("pg_attribute"."attgenerated" != '') AS "generated" FROM "pg_class" INNER JOIN "pg_attribute" ON ("pg_attribute"."attrelid" = "pg_class"."oid") INNER JOIN "pg_type" ON ("pg_type"."oid" = "pg_attribute"."atttypid") LEFT OUTER JOIN "pg_type" AS "basetype" ON ("basetype"."oid" = "pg_type"."typbasetype") LEFT OUTER JOIN "pg_attrdef" ON (("pg_attrdef"."adrelid" = "pg_class"."oid") AND ("pg_attrdef"."adnum" = "pg_attribute"."attnum")) LEFT OUTER JOIN "pg_index" ON (("pg_index"."indrelid" = "pg_class"."oid") AND ("pg_index"."indisprimary" IS TRUE)) WHERE (("pg_attribute"."attisdropped" IS FALSE) AND ("pg_attribute"."attnum" > 0) AND ("pg_class"."oid" = CAST(CAST('"scanner_checks"' AS regclass) AS oid))) ORDER BY "pg_attribute"."attnum"`
2 times: added function call `models#mapset`
2 times: added function call `models#to_model`
2 times: added function call `helpers#mapset_path`
added function call `models#app`
added function call `models#to_model`
2 times: added function call `helpers#base_slug`
2 times: added function call `models#org`
added SQL `SELECT * FROM "orgs" WHERE "id" = 2`
added function call `models#to_model`
removed SQL `SELECT "pg_attribute"."attname" AS "name", CAST("pg_attribute"."atttypid" AS integer) AS "oid", CAST("basetype"."oid" AS integer) AS "base_oid", format_type("basetype"."oid", "pg_type"."typtypmod") AS "db_base_type", format_type("pg_type"."oid", "pg_attribute"."atttypmod") AS "db_type", pg_get_expr("pg_attrdef"."adbin", "pg_class"."oid") AS "default", NOT "pg_attribute"."attnotnull" AS "allow_null", COALESCE(("pg_attribute"."attnum" = ANY("pg_index"."indkey")), false) AS "primary_key", "pg_attribute"."attidentity", ("pg_attribute"."attgenerated" != '') AS "generated" FROM "pg_class" INNER JOIN "pg_attribute" ON ("pg_attribute"."attrelid" = "pg_class"."oid") INNER JOIN "pg_type" ON ("pg_type"."oid" = "pg_attribute"."atttypid") LEFT OUTER JOIN "pg_type" AS "basetype" ON ("basetype"."oid" = "pg_type"."typbasetype") LEFT OUTER JOIN "pg_attrdef" ON (("pg_attrdef"."adrelid" = "pg_class"."oid") AND ("pg_attrdef"."adnum" = "pg_attribute"."attnum")) LEFT OUTER JOIN "pg_index" ON (("pg_index"."indrelid" = "pg_class"."oid") AND ("pg_index"."indisprimary" IS TRUE)) WHERE (("pg_attribute"."attisdropped" IS FALSE) AND ("pg_attribute"."attnum" > 0) AND ("pg_class"."oid" = CAST(CAST('"third_party_integrations"' AS regclass) AS oid))) ORDER BY "pg_attribute"."attnum"`
added function call `controllers#find_preferred_mapsets`
added function call `models#mapsets`
added function call `models.ordered_list`
added function call `models#ordered_mapset_dataset`
added SQL `SELECT *, "mapsets"."name", "mapsets"."branch", "mapsets"."commit", "mapsets"."environment", "mapsets"."version", (SELECT "login" FROM "users" WHERE ("id" = "user_id")), (SELECT count(*) AS "scenario_count" FROM "scenarios" WHERE ("mapset_id" = "mapsets"."id")) FROM "mapsets_preference_ordered" AS "mapsets" WHERE ("mapsets"."app_id" = 1)`
changed SQL `SELECT * FROM (SELECT MAX("scanner_jobs"."id") AS "scanner_job_id", "app_id", 'current' AS "scanner_job_ref_type" FROM "scanner_jobs" INNER JOIN "mapsets" ON ("mapsets"."id" = "scanner_jobs"."mapset_id") INNER JOIN "apps" ON (("apps"."id" = "mapsets"."app_id") AND ("apps"."main_branch" = "mapsets"."branch")) WHERE ("apps"."id" = 43) GROUP BY "app_id") AS "scanner_jobs" LIMIT 1` to SQL `SELECT * FROM (WITH "scanner_job_counts" AS (SELECT "scanner_jobs"."id" AS "scanner_job_id", "sj_range"."app_id", "impact_category", "scanner_job_ref_type", COUNT(DISTINCT scanner_checks.id)::integer as rules_count, COUNT(finding_occurrences.*)::integer as findings_count, (COUNT(finding_occurrences.*) FILTER (WHERE status = 'new'))::integer as new_count, (COUNT(finding_occurrences.*) FILTER (WHERE status = 'deferred'))::integer as deferred_count FROM "scanner_jobs" INNER JOIN (SELECT * FROM (SELECT * FROM (SELECT MAX("scanner_jobs"."id") AS "scanner_job_id", "app_id", 'current' AS "scanner_job_ref_type" FROM "scanner_jobs" INNER JOIN "mapsets" ON ("mapsets"."id" = "scanner_jobs"."mapset_id") INNER JOIN "apps" ON (("apps"."id" = "mapsets"."app_id") AND ("apps"."main_branch" = "mapsets"."branch")) WHERE ("apps"."id" = 1) GROUP BY "app_id") AS "scanner_jobs" UNION (SELECT MAX("scanner_jobs"."id") AS "scanner_job_id", "app_id", 'base' AS "scanner_job_ref_type" FROM "scanner_jobs" INNER JOIN "mapsets" ON ("mapsets"."id" = "scanner_jobs"."mapset_id") INNER JOIN "apps" ON (("apps"."id" = "mapsets"."app_id") AND ("apps"."main_branch" = "mapsets"."branch")) WHERE ((scanner_jobs.created_at <= (NOW() - '604800 SECONDS'::INTERVAL)) AND ("apps"."id" = 1)) GROUP BY "app_id")) AS "t1") AS "sj_range" ON ("sj_range"."scanner_job_id" = "scanner_jobs"."id") INNER JOIN "scanner_checks" ON ("scanner_checks"."job_id" = "scanner_jobs"."id") LEFT JOIN "finding_occurrences" ON (("finding_occurrences"."check_id" = "scanner_checks"."id") AND ("finding_occurrences"."job_id" = "scanner_jobs"."id")) LEFT JOIN "scanner_finding_statuses" ON ("scanner_finding_statuses"."id" = "finding_occurrences"."status_id") WHERE ("impact_category" IS NOT NULL) GROUP BY "scanner_jobs"."id", "impact_category", "scanner_job_ref_type", "sj_range"."app_id" ORDER BY "scanner_jobs"."id", "impact_category", "scanner_job_ref_type", "sj_range"."app_id") SELECT coalesce("current"."impact_category", "base"."impact_category") AS "impact_category", "base"."rules_count"...`
2 times: removed SQL `SELECT * FROM (WITH "scanner_job_counts" AS (SELECT "scanner_jobs"."id" AS "scanner_job_id", "sj_range"."app_id", "impact_category", "scanner_job_ref_type", COUNT(DISTINCT scanner_checks.id)::integer as rules_count, COUNT(finding_occurrences.*)::integer as findings_count, (COUNT(finding_occurrences.*) FILTER (WHERE status = 'new'))::integer as new_count, (COUNT(finding_occurrences.*) FILTER (WHERE status = 'deferred'))::integer as deferred_count FROM "scanner_jobs" INNER JOIN (SELECT * FROM (SELECT * FROM (SELECT MAX("scanner_jobs"."id") AS "scanner_job_id", "app_id", 'current' AS "scanner_job_ref_type" FROM "scanner_jobs" INNER JOIN "mapsets" ON ("mapsets"."id" = "scanner_jobs"."mapset_id") INNER JOIN "apps" ON (("apps"."id" = "mapsets"."app_id") AND ("apps"."main_branch" = "mapsets"."branch")) WHERE ("apps"."id" = 43) GROUP BY "app_id") AS "scanner_jobs" UNION (SELECT MAX("scanner_jobs"."id") AS "scanner_job_id", "app_id", 'base' AS "scanner_job_ref_type" FROM "scanner_jobs" INNER JOIN "mapsets" ON ("mapsets"."id" = "scanner_jobs"."mapset_id") INNER JOIN "apps" ON (("apps"."id" = "mapsets"."app_id") AND ("apps"."main_branch" = "mapsets"."branch")) WHERE ((scanner_jobs.created_at <= (NOW() - '604800 SECONDS'::INTERVAL)) AND ("apps"."id" = 43)) GROUP BY "app_id")) AS "t1") AS "sj_range" ON ("sj_range"."scanner_job_id" = "scanner_jobs"."id") INNER JOIN "scanner_checks" ON ("scanner_checks"."job_id" = "scanner_jobs"."id") LEFT JOIN "finding_occurrences" ON (("finding_occurrences"."check_id" = "scanner_checks"."id") AND ("finding_occurrences"."job_id" = "scanner_jobs"."id")) LEFT JOIN "scanner_finding_statuses" ON ("scanner_finding_statuses"."id" = "finding_occurrences"."status_id") WHERE ("impact_category" IS NOT NULL) GROUP BY "scanner_jobs"."id", "impact_category", "scanner_job_ref_type", "sj_range"."app_id" ORDER BY "scanner_jobs"."id", "impact_category", "scanner_job_ref_type", "sj_range"."app_id") SELECT coalesce("current"."impact_category", "base"."impact_category") AS "impact_category", "base"."rules_coun...`
2 times:
2 times: