Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,11 @@ bundle exec rails runner "pwd = SecureRandom.alphanumeric(16); u = User.create!(
bundle exec rails sync:assets[scan_output.applications-backup.csv]

# Run these after full assets ingest is complete.
bundle exec rails sync:cleanup_folder_assets
bundle exec rails duplicates:detect
bundle exec rails sync:post_ingest
```

`sync:post_ingest` runs cleanup_folder_assets, folders:backfill_counts, and duplicates:detect.


In some zsh shells with nomatch turned on, escaping the brackets in this command may be necessary. Alternatively, quote the entire task portion of the command, or add "setopt +o nomatch" to your ~/.zshrc profile to prevent zsh from requiring bracket escaping in Rails commands.

Expand Down
18 changes: 18 additions & 0 deletions app/assets/stylesheets/application.css.scss
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,24 @@ div.wunderbaum div.wb-header span.wb-col.wb-active {
vertical-align: -0.15em;
}

div.wunderbaum .wb-title .wb-badge {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 18px;
height: 18px;
font-size: 11px;
font-weight: 600;
padding: 0 6px;
border-radius: 999px;
background-color: #3b82f6;
color: #ffffff;
margin-left: 6px;
line-height: 1;
position: relative;
top: -2px;
}

.dashboard-title {
font-weight: 400;
}
10 changes: 10 additions & 0 deletions app/javascript/controllers/wunderbaum_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,16 @@ export default class extends Controller {

if (isFolder) {
titleElem.textContent = node.title || "";

const count = node.data?.descendant_assets_count;
if (count != null) {
const badge = document.createElement("span");
badge.className = "wb-badge";
badge.textContent = count;
badge.title = `${count} assets`;
badge.style.marginLeft = "6px";
titleElem.appendChild(badge);
}
} else {
titleElem.innerHTML =
`<a href="${node.data.url}" class="asset-link" target="_blank" rel="noopener" data-turbo="false">${node.title}</a>`;
Expand Down
28 changes: 28 additions & 0 deletions app/models/isilon_asset.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ class IsilonAsset < ApplicationRecord
before_validation :set_default_migration_status, on: :create
before_validation :sync_volume_from_parent

after_create :increment_folder_counts
after_destroy :decrement_folder_counts
after_update :handle_folder_move

def full_path_with_volume
volume_name = parent_folder&.volume&.name
return isilon_path unless volume_name.present?
Expand All @@ -22,6 +26,30 @@ def full_path_with_volume
"/#{volume_name}#{path}".gsub(%r{//+}, "/")
end

def increment_folder_counts
update_ancestors(parent_folder_id, 1)
end

def decrement_folder_counts
update_ancestors(parent_folder_id, -1)
end

def handle_folder_move
if saved_change_to_parent_folder_id?
old_id, new_id = saved_change_to_parent_folder_id
update_ancestors(old_id, -1) if old_id
update_ancestors(new_id, 1) if new_id
end
end

def update_ancestors(folder_id, delta)
folder = IsilonFolder.find_by(id: folder_id)
while folder
folder.increment!(:descendant_assets_count, delta)
folder = folder.parent_folder
end
end

private

def set_default_migration_status
Expand Down
6 changes: 5 additions & 1 deletion app/serializers/isilon_folder_serializer.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
class IsilonFolderSerializer < ActiveModel::Serializer
attributes :title, :full_path, :folder, :id, :lazy,
:assigned_to_id, :assigned_to,
:assigned_to_id, :assigned_to, :descendant_assets_count,
:parent_folder_id, :path, :key, :notes

def title
Expand Down Expand Up @@ -28,6 +28,10 @@ def assigned_to
object.assigned_to&.name.to_s.presence || "Unassigned"
end

def descendant_assets_count
return nil unless object.is_a?(IsilonFolder)
object.descendant_assets_count
end

def path
return [] if object.respond_to?(:parent_folder_id) && object.parent_folder_id.nil?
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
class AddDescendantAssetsCountToIsilonFolders < ActiveRecord::Migration[7.2]
def change
add_column :isilon_folders, :descendant_assets_count, :integer, default: 0, null: false
add_index :isilon_folders, :descendant_assets_count
end
end
5 changes: 4 additions & 1 deletion db/schema.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions lib/tasks/backfill_folder_counts.rake
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace :folders do
desc "Backfill descendant_assets_count"
task backfill_counts: :environment do
IsilonFolder.find_each do |folder|
ids = [ folder.id ] + folder.descendant_folders.map(&:id)
count = IsilonAsset.where(parent_folder_id: ids).count
folder.update_column(:descendant_assets_count, count)
end
end
end
20 changes: 20 additions & 0 deletions lib/tasks/isilon_import.rake
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,26 @@ namespace :sync do
puts "Deleted #{count} asset(s)"
end

desc "Post-ingest housekeeping: cleanup folder-assets, backfill folder counts, detect duplicates"
task :post_ingest, [ :volume_name ] => :environment do |_t, args|
args.with_defaults(volume_name: nil)

volume_name = args[:volume_name]

puts "Running post-ingest housekeeping..."

Rake::Task["sync:cleanup_folder_assets"].reenable
Rake::Task["sync:cleanup_folder_assets"].invoke(volume_name)

Rake::Task["folders:backfill_counts"].reenable
Rake::Task["folders:backfill_counts"].invoke

Rake::Task["duplicates:detect"].reenable
Rake::Task["duplicates:detect"].invoke

puts "Post-ingest housekeeping complete."
end

desc "Export TIFF rule matches without updating migration_status"
task :tiffs_export, [ :output_path, :volume_name ] => :environment do |_t, args|
args.with_defaults(output_path: nil, volume_name: nil)
Expand Down
21 changes: 21 additions & 0 deletions spec/models/isilon_asset_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,25 @@
expect(asset.volume).to eq(volume)
end
end

describe "descendant_assets_count" do
let!(:root) { create(:isilon_folder) }
let!(:child) { create(:isilon_folder, parent_folder: root) }

it "increments counts up the tree when asset is created" do
expect {
create(:isilon_asset, parent_folder: child)
}.to change { child.reload.descendant_assets_count }.by(1)
.and change { root.reload.descendant_assets_count }.by(1)
end

it "decrements counts when asset is destroyed" do
asset = create(:isilon_asset, parent_folder: child)

expect {
asset.destroy
}.to change { child.reload.descendant_assets_count }.by(-1)
.and change { root.reload.descendant_assets_count }.by(-1)
end
end
end
12 changes: 12 additions & 0 deletions spec/requests/volumes/file_tree_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,18 @@ def parsed
ids = body.map { |h| h["id"] }
expect(ids).to include(root.id)
end

let!(:folder) { create(:isilon_folder, volume: volume) }

it "returns correct descendant_assets_count" do
create_list(:isilon_asset, 3, parent_folder: folder)

get file_tree_volume_path(volume, format: :json)

json = JSON.parse(response.body)
match = json.find { |h| h["id"] == folder.id }
expect(match["descendant_assets_count"]).to eq(3)
end
end

describe "GET /volumes/:id/file_tree_assets" do
Expand Down
6 changes: 6 additions & 0 deletions spec/serializers/isilon_folder_serializer_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,10 @@

expect(serialized[:notes]).to eq("Serializer note")
end

it "includes descendant_assets_count for folders" do
folder = create(:isilon_folder, descendant_assets_count: 5)
json = IsilonFolderSerializer.new(folder).as_json
expect(json[:descendant_assets_count]).to eq(5)
end
end