fu: Close-up of Fu, bringing a scoop of water to her mouth (Default)
fu ([personal profile] fu) wrote in [site community profile] changelog2011-11-25 02:56 pm

[dw-free] import queue viewer admin tool

[commit: http://hg.dwscoalition.org/dw-free/rev/40273080754d]

http://bugs.dwscoalition.org/show_bug.cgi?id=4067

Admin tool for a rough overview of the import queue. Includes a drill-down
to a more detailed per-user view.

Patch by [personal profile] fu.

Files modified:
  • cgi-bin/DW/Controller/Admin/Importer.pm
  • cgi-bin/DW/Logic/Importer.pm
  • views/admin/importer.tt
  • views/admin/importer.tt.text
  • views/admin/importer/detail.tt
  • views/admin/importer/detail.tt.text
--------------------------------------------------------------------------------
diff -r a286d2a201aa -r 40273080754d cgi-bin/DW/Controller/Admin/Importer.pm
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cgi-bin/DW/Controller/Admin/Importer.pm	Fri Nov 25 22:51:19 2011 +0800
@@ -0,0 +1,94 @@
+#!/usr/bin/perl
+#
+# DW::Controller::Importer
+#
+# This controller is to view details about the import queue
+#
+# Authors:
+#      Afuna <coder.dw@afunamatata.com>
+#
+# Copyright (c) 2011 by Dreamwidth Studios, LLC.
+#
+# This program is free software; you may redistribute it and/or modify it under
+# the same terms as Perl itself. For a copy of the license, please reference
+# 'perldoc perlartistic' or 'perldoc perlgpl'.
+#
+
+package DW::Controller::Importer;
+
+use strict;
+
+use DW::Controller;
+use DW::Routing;
+use DW::Template;
+use DW::Controller::Admin;
+
+use DW::Logic::Importer;
+
+DW::Routing->register_string( "/admin/importer/index", \&index_controller );
+DW::Routing->register_string( "/admin/importer/details/index", \&detail_controller );
+
+DW::Controller::Admin->register_admin_page( '/',
+    path => 'importer/',
+    ml_scope => '/admin/importer.tt',
+    privs => [ 'siteadmin:theschwartz' ]
+);
+
+sub index_controller {
+    my ( $ok, $rv ) = controller( privcheck => [ "siteadmin:theschwartz" ] );
+    return $rv unless $ok;
+
+    my $vars = {};
+    my $sclient = LJ::theschwartz();
+    my @joblist = $sclient->list_jobs( { funcname => [qw(   DW::Worker::ContentImporter::LiveJournal::Bio
+                                DW::Worker::ContentImporter::LiveJournal::Tags
+                                DW::Worker::ContentImporter::LiveJournal::Entries
+                                DW::Worker::ContentImporter::LiveJournal::Comments
+                                DW::Worker::ContentImporter::LiveJournal::Userpics
+                                DW::Worker::ContentImporter::LiveJournal::Friends
+                                DW::Worker::ContentImporter::LiveJournal::FriendGroups
+                                DW::Worker::ContentImporter::LiveJournal::Verify
+                            )] } );
+
+    my @latest;
+    my @jobs;
+    foreach my $job ( @joblist ) {
+        my $funcname = $job->funcname;
+        my $arg = $job->arg;
+        my $u = LJ::load_userid( $arg->{userid} );
+        my $latest_id = DW::Logic::Importer->get_import_data_for_user( $u )->[0]->[0];
+
+        push @latest, [ $u, $latest_id ];
+
+        push @jobs, { type => $funcname,
+                      user => $u->ljuser_display,
+                      username => $u->username,
+                      importid => { job => $arg->{import_data_id}, latest => $latest_id },
+                    };
+    }
+    $vars->{jobs} = \@jobs;
+
+    return DW::Template->render_template( 'admin/importer.tt', $vars );
+}
+
+sub detail_controller {
+    my ( $ok, $rv ) = controller( privcheck => [ "siteadmin:theschwartz" ] );
+    return $rv unless $ok;
+
+    my $get = DW::Request->get->get_args;
+    my $user = $get->{user};
+
+    my $u = LJ::load_user( $user );
+    return error_ml( "error.invaliduser" ) unless $u;
+
+    my $import_items = DW::Logic::Importer->get_queued_imports( $u );
+    my $vars = { username => $u->ljuser_display, import_items => $import_items };
+
+    if ( scalar keys %{$import_items||{}} > 1 ) {
+        $vars->{errmsg} = ".error.toomanypending";
+    }
+
+    return DW::Template->render_template( 'admin/importer/detail.tt', $vars );
+}
+
+1;
diff -r a286d2a201aa -r 40273080754d cgi-bin/DW/Logic/Importer.pm
--- a/cgi-bin/DW/Logic/Importer.pm	Wed Nov 23 21:40:13 2011 +0800
+++ b/cgi-bin/DW/Logic/Importer.pm	Fri Nov 25 22:51:19 2011 +0800
@@ -21,6 +21,41 @@
 use DW::Pay;
 use Storable;
 
+=head1 API
+
+=head2 C<<DW::Logic::Importer->get_import_data( $u, id1, [ id2, id3 ] )>>
+
+Get import data for this user for all provided import ids
+
+=cut
+sub get_import_data {
+    my ( $class, $u, @ids ) = @_;
+
+    my $dbh = LJ::get_db_writer()
+        or die "No database.";
+
+    my $qs = join ",", map { "?" } @ids;
+
+    # FIXME: memcache this
+    my $imports = $dbh->selectall_arrayref(
+        "SELECT import_data_id, hostname, username, usejournal, password_md5, options FROM import_data WHERE userid = ? AND import_data_id IN ( $qs ) " .
+        "ORDER BY import_data_id ASC",
+        undef, $u->id, @ids
+    );
+
+    foreach my $import ( @{$imports||[]} ) {
+        $import->[5] = Storable::thaw( $import->[5] ) || {}
+            if $import->[5];
+    }
+
+    return $imports;
+}
+
+=head2 C<<DW::Logic::Importer->get_import_data_for_user>>
+
+Get the latest import data for this user
+
+=cut
 sub get_import_data_for_user {
     my ( $class, $u ) = @_;
 
@@ -41,35 +76,91 @@
     return $imports;
 }
 
+=head2 C<<DW::Logic::Importer->get_import_items( $u, id1, [ id2, id3... ] )>>
+
+Get import items for this user for all provided import ids.
+
+=cut
+sub get_import_items {
+    my ( $class, $u, @importids ) = @_;
+
+    my $dbh = LJ::get_db_writer()
+        or die "No database.";
+
+    my $qs = join ",", map { "?" } @importids;
+
+    my $import_items = $dbh->selectall_arrayref(
+        "SELECT import_data_id, item, status, created, last_touch FROM import_items WHERE userid = ? AND import_data_id IN ( $qs )",
+        undef, $u->id, @importids
+    );
+
+    my %ret;
+    foreach my $import_item ( @$import_items ) {
+        my ( $id, $item, $status, $created, $last_touch ) = @$import_item;
+        $ret{$id}->{$item} = {
+            status => $status,
+            created => $created,
+            last_touch => $last_touch,
+        };
+    }
+
+    return \%ret;
+}
+
+=head2 C<<DW::Logic::Importer->get_import_items_for_user( $u, id1, [ id2, id3... ] )>>
+
+Get latest import item for this user. Includes import data.
+
+=cut
+
 sub get_import_items_for_user {
     my ( $class, $u ) = @_;
 
     my $imports = DW::Logic::Importer->get_import_data_for_user( $u );
     my %items;
-    my $dbh = LJ::get_db_writer()
-        or die "No database.";
 
     my $has_items = 0;
     foreach my $import ( @$imports ) {
         my ( $importid, $host, $username, $usejournal, $password ) = @$import;
-        $items{$importid} = { host => $host, user => $username, pw => $password, usejournal => $usejournal, items => {} };
+        $items{$importid} = {
+                    host => $host,
+                    user => $username,
+                    pw => $password,
+                    usejournal => $usejournal,
+                    items => DW::Logic::Importer->get_import_items( $u, $importid )->{$importid}, };
 
-        my $import_items = $dbh->selectall_arrayref(
-            'SELECT item, status, created, last_touch FROM import_items WHERE userid = ? AND import_data_id = ?',
-            undef, $u->id, $importid
-        );
-
-        foreach my $import_item ( @$import_items ) {
-            $items{$importid}->{items}->{$import_item->[0]} = {
-                status => $import_item->[1], created => $import_item->[2], last_touch => $import_item->[3]
-            };
-            $has_items = 1 if $import_item->[0];
-        }
+        $has_items = 1 if scalar keys %{ $items{$importid}->{items} };
     }
 
     return $has_items ? \%items : {};
 }
 
+=head2 C<<DW::Logic::Importer->get_queued_imports( $u )>>
+
+Get a list of imports that have yet to be processed. May be in the schwartz queue, and thus running soon
+or else in the import queue, and have yet to be put into the schwartz queue. The latter may not run if they
+are duplicates of something in the schwartz queue.
+
+=cut
+
+sub get_queued_imports {
+    my ( $class, $u ) = @_;
+
+    return {} unless $u;
+
+    # the latest import the user has queued
+    my $latestimport = DW::Logic::Importer->get_import_data_for_user( $u );
+
+    # the latest job that's currently running
+    # should be <= $latestimport
+    my $runningjob = $u->prop( "import_job" );
+
+    return {} unless $latestimport && $runningjob;
+
+    return DW::Logic::Importer->get_import_items( $u, $runningjob .. $latestimport->[0]->[0] );
+}
+
+
 sub set_import_data_for_user {
     my ( $class, $u, %opts ) = @_;
 
diff -r a286d2a201aa -r 40273080754d views/admin/importer.tt
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/views/admin/importer.tt	Fri Nov 25 22:51:19 2011 +0800
@@ -0,0 +1,35 @@
+[%# admin/importer.tt
+
+Admin page for the importer queue
+
+Authors:
+    Afuna <coder.dw@afunamatata.com>
+
+This program is free software; you may redistribute it and/or modify it under
+the same terms as Perl itself.  For a copy of the license, please reference
+'perldoc perlartistic' or 'perldoc perlgpl'.
+%]
+[%- sections.title = '.title' | ml -%]
+
+<h2>[% '.theschwartz.header' | ml %]</h2>
+<p>[% '.theschwartz.intro' | ml %]</p>
+<p>[% '.theschwartz.queue' | ml( num = jobs.size ) %]</p>
+
+
+[%- IF jobs.size > 0 -%]
+<table>
+<thead><tr><th>Order</th><th>User</th><th>Job Type</th><th>Job Import ID</th><th>Latest Queued ID</th><th>Details</th></tr>
+<tbody>
+    [%- FOREACH job = jobs -%]
+    <tr>
+        <td>[% loop.count %]</td>
+        <td>[% job.user %]</td>
+        <td>[% job.type %]</td>
+        <td>[% job.importid.job %]</td>
+        <td>[% job.importid.latest %]</td>
+        <td><a href="[% site.root %]/admin/importer/details/?user=[% job.username %]">Details</a></td>
+    </tr>
+    [%- END -%]
+</tbody>
+</table>
+[%- END -%]
diff -r a286d2a201aa -r 40273080754d views/admin/importer.tt.text
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/views/admin/importer.tt.text	Fri Nov 25 22:51:19 2011 +0800
@@ -0,0 +1,12 @@
+;; -*- coding: utf-8 -*-
+.admin.link=Importer Queue
+
+.admin.text=View importer queue status
+
+.theschwartz.header=TheSchwartz Queue
+
+.theschwartz.intro=This is a list of jobs currently in the (schwartz) job queue, but does not include jobs that are waiting for other things to complete (e.g., comment imports that are still importing comments).
+
+.theschwartz.queue=There [[?num|is|are]] [[num]] [[?num|item|items]] in the queue.
+
+.title=Importer Queue
diff -r a286d2a201aa -r 40273080754d views/admin/importer/detail.tt
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/views/admin/importer/detail.tt	Fri Nov 25 22:51:19 2011 +0800
@@ -0,0 +1,46 @@
+[%# admin/importer/detail.tt
+
+Details for pending imports for a user
+
+Authors:
+    Afuna <coder.dw@afunamatata.com>
+
+This program is free software; you may redistribute it and/or modify it under
+the same terms as Perl itself.  For a copy of the license, please reference
+'perldoc perlartistic' or 'perldoc perlgpl'.
+%]
+[%- sections.title = '.title' | ml -%]
+
+[%- USE date -%]
+
+Pending imports for [% username %].
+
+[% IF errmsg %]
+<div class='error-box'>
+    <p>[% errmsg | ml %]</p>
+</div>
+[% END %]
+
+[%- IF import_items.size > 0 -%]
+    [%- FOREACH import_id = import_items.keys.sort -%]
+    <h3>Import #[% import_id %]</h3>
+    <table>
+    <thead><tr>
+        <th>Item</th>
+        <th>Status</th>
+        <th>Created</th>
+        <th>Last Touch</th>
+    </tr></thead>
+    <tbody>
+    [%- FOREACH item = import_items.$import_id.pairs -%]
+    <tr>
+        <td>[% item.key %]</td>
+        <td>[% item.value.status %]</td>
+        <td>[% date.format( item.value.created ) %]</td>
+        <td>[% item.value.last_touch ? date.format( item.value.last_touch ) : "-" %]</td>
+    </tr>
+    [%- END -%]
+    </tbody>
+    </table>
+    [%- END %]
+[%- END -%]
diff -r a286d2a201aa -r 40273080754d views/admin/importer/detail.tt.text
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/views/admin/importer/detail.tt.text	Fri Nov 25 22:51:19 2011 +0800
@@ -0,0 +1,4 @@
+;; -*- coding: utf-8 -*-
+.error.toomanypending=There are too many pending imports. *Probably* what is happening here is that the user's import is already in the schwartz async queue where we can't look it up easily, but then they started a new import. The old import will still keep running, and the new import will show up as aborted.
+
+.title=Pending Import Details
--------------------------------------------------------------------------------