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
--------------------------------------------------------------------------------

Post a comment in response:

This account has disabled anonymous posting.
If you don't have an account you can create one now.
HTML doesn't work in the subject.
More info about formatting

If you are unable to use this captcha for any reason, please contact us by email at support@dreamwidth.org