|
@@ -0,0 +1,178 @@
|
|
|
+#!/usr/local/perl-cbt/bin/perl
|
|
|
+use Modern::Perl;
|
|
|
+use Carp;
|
|
|
+use Data::Dumper;
|
|
|
+use HTTP::Request;
|
|
|
+use JSON;
|
|
|
+use LWP::UserAgent;
|
|
|
+use MIME::Base64 qw/encode_base64/;
|
|
|
+
|
|
|
+my $BASE_URL = 'https://app.asana.com/api/1.0';
|
|
|
+my $ASANA_API_KEY = 'ASANA_PERSONAL_TOKEN';
|
|
|
+my $ua = LWP::UserAgent->new();
|
|
|
+
|
|
|
+open my $input_wekan, '<', 'template.json';
|
|
|
+my $wekan_json = readline($input_wekan);
|
|
|
+close $input_wekan;
|
|
|
+my $wekan_board = decode_json($wekan_json);
|
|
|
+my %users;
|
|
|
+my %users_by_gid;
|
|
|
+# get user IDs from template board
|
|
|
+foreach my $user ( @{ $wekan_board->{users} } ) {
|
|
|
+ $users{ $user->{profile}->{fullname} } = $user->{_id};
|
|
|
+}
|
|
|
+# get list IDs from template (we ended up not using these)
|
|
|
+my %lists;
|
|
|
+foreach my $list ( @{ $wekan_board->{lists} } ) {
|
|
|
+ $lists{ $list->{title} } = $list->{_id};
|
|
|
+}
|
|
|
+
|
|
|
+my @headers;
|
|
|
+push @headers, ( 'Accept', 'application/json' );
|
|
|
+push @headers, ( 'Authorization', "Bearer $ASANA_API_KEY" );
|
|
|
+my $projects_req = HTTP::Request->new( "GET", "$BASE_URL/projects", \@headers, );
|
|
|
+my $projects_res = $ua->request($projects_req);
|
|
|
+my $projects = decode_json( $projects_res->content )->{data};
|
|
|
+foreach my $project (@$projects) {
|
|
|
+ say "Project: ".$project->{name};
|
|
|
+ my $tasks_url =
|
|
|
+ '/tasks?project='
|
|
|
+ . $project->{gid}
|
|
|
+ . '&opt_fields=completed,name,notes,assignee,created_by,memberships.project.name, memberships.section.name,due_on,created_at,custom_fields';
|
|
|
+ my $tasks_req = HTTP::Request->new( 'GET', "$BASE_URL$tasks_url", \@headers );
|
|
|
+ my $tasks_res = $ua->request($tasks_req);
|
|
|
+ my @output_tasks;
|
|
|
+ my $tasks = decode_json( $tasks_res->content )->{data};
|
|
|
+ foreach my $task (@$tasks) {
|
|
|
+ next if $task->{completed};
|
|
|
+ say ' - '.$task->{name};
|
|
|
+ my $git_branch;
|
|
|
+ my $prio;
|
|
|
+ foreach my $custom ( @{ $task->{custom_fields} } ) {
|
|
|
+ if ( $custom->{name} eq 'git branch' ) {
|
|
|
+ $git_branch = $custom->{text_value};
|
|
|
+ next;
|
|
|
+ }
|
|
|
+ # We ended up not importing these.
|
|
|
+ if ( $custom->{name} eq 'Priority' && defined $custom->{display_value} ) {
|
|
|
+ $prio =
|
|
|
+ $custom->{display_value} eq 'High' ? 'fwccC9'
|
|
|
+ : $custom->{display_value} eq 'Med' ? 'yPnaFa'
|
|
|
+ : $custom->{display_value} eq 'Low' ? 'W4vMvm'
|
|
|
+ : 'ML5drH';
|
|
|
+ next;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if ( !defined $users_by_gid{ $task->{created_by}->{gid} } ) {
|
|
|
+ my $user_req =
|
|
|
+ HTTP::Request->new( 'GET', "$BASE_URL/users/" . $task->{created_by}->{gid},
|
|
|
+ \@headers );
|
|
|
+ my $user_res = $ua->request($user_req);
|
|
|
+ my $user = decode_json( $user_res->content )->{data};
|
|
|
+ if ( defined $users{ $user->{name} } ) {
|
|
|
+ $users_by_gid{ $task->{created_by}->{gid} } = $users{ $user->{name} };
|
|
|
+ }
|
|
|
+ }
|
|
|
+ my $creator = $users_by_gid{ $task->{created_by}->{gid} } // undef;
|
|
|
+ if ( defined $task->{assignee} && !defined $users_by_gid{ $task->{assignee}->{gid} } ) {
|
|
|
+ my $user_req =
|
|
|
+ HTTP::Request->new( 'GET', "$BASE_URL/users/" . $task->{assignee}->{gid},
|
|
|
+ \@headers );
|
|
|
+ my $user_res = $ua->request($user_req);
|
|
|
+ my $user = decode_json( $user_res->content )->{data};
|
|
|
+ if ( defined $users{ $user->{name} } ) {
|
|
|
+ $users_by_gid{ $task->{assignee}->{gid} } = $users{ $user->{name} };
|
|
|
+ }
|
|
|
+ }
|
|
|
+ my $assignee = defined $task->{assignee} ? $users_by_gid{ $task->{assignee}->{gid} } : undef;
|
|
|
+ my $list;
|
|
|
+ foreach my $membership ( @{ $task->{memberships} } ) {
|
|
|
+ next if $membership->{project}->{name} ne $project->{name};
|
|
|
+ $list = $membership->{section}->{name};
|
|
|
+ }
|
|
|
+
|
|
|
+ # I was trying to create JSON that I could use on the import screen in Wekan,
|
|
|
+ # but for bigger boards, it was just *too* hefty, so I took that JSON and used
|
|
|
+ # APIs to import.
|
|
|
+ my %output_task = (
|
|
|
+ swimlaneId => 'As4SNerx4Y4mMnJ8n', # 'Bugs'
|
|
|
+ sort => 0,
|
|
|
+ type => 'cardType-card',
|
|
|
+ archived => JSON::false,
|
|
|
+ title => $task->{name},
|
|
|
+ description => $task->{notes},
|
|
|
+ createdAt => $task->{created_at},
|
|
|
+ dueAt => defined $task->{due_on} ? $task->{due_on} . 'T22:00:00.000Z' : undef,
|
|
|
+ customFields => [
|
|
|
+ {
|
|
|
+ _id => 'rL8BpFHp5xxSFbDdr',
|
|
|
+ value => $git_branch,
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ labelIds => [$prio],
|
|
|
+ listId => $list,
|
|
|
+ userId => $creator,
|
|
|
+ assignees => [$assignee],
|
|
|
+ );
|
|
|
+ my @final_comments;
|
|
|
+ my $comments_req =
|
|
|
+ HTTP::Request->new( 'GET', "$BASE_URL/tasks/" . $task->{gid} . '/stories',
|
|
|
+ \@headers );
|
|
|
+ my $comments_res = $ua->request($comments_req);
|
|
|
+ my $comments = decode_json( $comments_res->content )->{data};
|
|
|
+ foreach my $comment (@$comments) {
|
|
|
+ next if $comment->{type} ne 'comment';
|
|
|
+ if ( !defined $users_by_gid{ $comment->{created_by}->{gid} } ) {
|
|
|
+ my $user_req =
|
|
|
+ HTTP::Request->new( 'GET', "$BASE_URL/users/" . $comment->{created_by}->{gid},
|
|
|
+ \@headers );
|
|
|
+ my $user_res = $ua->request($user_req);
|
|
|
+ my $user = decode_json( $user_res->content )->{data};
|
|
|
+ if ( defined $users{ $user->{name} } ) {
|
|
|
+ $users_by_gid{ $comment->{created_bye}->{gid} } = $users{ $user->{name} };
|
|
|
+ }
|
|
|
+ }
|
|
|
+ my $commentor = $users_by_gid{ $comment->{created_by}->{gid} };
|
|
|
+ my %this_comment = (
|
|
|
+ text => $comment->{text},
|
|
|
+ createdAt => $comment->{created_at},
|
|
|
+ userId => $commentor,
|
|
|
+ );
|
|
|
+ push @final_comments, \%this_comment;
|
|
|
+ }
|
|
|
+ $output_task{comments} = \@final_comments;
|
|
|
+
|
|
|
+
|
|
|
+ my @final_attachments;
|
|
|
+ my $attachments_req =
|
|
|
+ HTTP::Request->new( 'GET', "$BASE_URL/tasks/" . $task->{gid} . '/attachments',
|
|
|
+ \@headers );
|
|
|
+ my $attachments_res = $ua->request($attachments_req);
|
|
|
+ my $attachments = decode_json( $attachments_res->content )->{data};
|
|
|
+ foreach my $attachment (@$attachments) {
|
|
|
+ my $att_req =
|
|
|
+ HTTP::Request->new( 'GET', "$BASE_URL/attachments/" . $attachment->{gid},
|
|
|
+ \@headers );
|
|
|
+ my $att_res = $ua->request($att_req);
|
|
|
+ my $att = decode_json( $att_res->content )->{data};
|
|
|
+ my $file_req=HTTP::Request->new('GET',$att->{download_url});
|
|
|
+ my $file_res=$ua->request($file_req);
|
|
|
+ my $file=encode_base64($file_res->content);
|
|
|
+
|
|
|
+ my %this_attachment = (
|
|
|
+ file => $file,
|
|
|
+ name => $att->{name},
|
|
|
+ createdAt => $att->{created_at},
|
|
|
+ );
|
|
|
+ push @final_attachments, \%this_attachment;
|
|
|
+
|
|
|
+ }
|
|
|
+ $output_task{attachments} = \@final_attachments;
|
|
|
+ push @output_tasks, \%output_task;
|
|
|
+ }
|
|
|
+ my $file_name = $project->{name};
|
|
|
+ $file_name =~ s/\//_/g;
|
|
|
+ open my $output_file, '>',$file_name.'_exported.json';
|
|
|
+ print $output_file encode_json(\@output_tasks);
|
|
|
+ close $output_file;
|
|
|
+}
|