export_boards.pl 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. #!/usr/local/perl-cbt/bin/perl
  2. use Modern::Perl;
  3. use Carp;
  4. use Data::Dumper;
  5. use HTTP::Request;
  6. use JSON;
  7. use LWP::UserAgent;
  8. use MIME::Base64 qw/encode_base64/;
  9. my $BASE_URL = 'https://app.asana.com/api/1.0';
  10. my $ASANA_API_KEY = 'ASANA_PERSONAL_TOKEN';
  11. my $ua = LWP::UserAgent->new();
  12. open my $input_wekan, '<', 'template.json';
  13. my $wekan_json = readline($input_wekan);
  14. close $input_wekan;
  15. my $wekan_board = decode_json($wekan_json);
  16. my %users;
  17. my %users_by_gid;
  18. # get user IDs from template board
  19. foreach my $user ( @{ $wekan_board->{users} } ) {
  20. $users{ $user->{profile}->{fullname} } = $user->{_id};
  21. }
  22. # get list IDs from template (we ended up not using these)
  23. my %lists;
  24. foreach my $list ( @{ $wekan_board->{lists} } ) {
  25. $lists{ $list->{title} } = $list->{_id};
  26. }
  27. my @headers;
  28. push @headers, ( 'Accept', 'application/json' );
  29. push @headers, ( 'Authorization', "Bearer $ASANA_API_KEY" );
  30. my $projects_req = HTTP::Request->new( "GET", "$BASE_URL/projects", \@headers, );
  31. my $projects_res = $ua->request($projects_req);
  32. my $projects = decode_json( $projects_res->content )->{data};
  33. foreach my $project (@$projects) {
  34. say "Project: ".$project->{name};
  35. my $tasks_url =
  36. '/tasks?project='
  37. . $project->{gid}
  38. . '&opt_fields=completed,name,notes,assignee,created_by,memberships.project.name, memberships.section.name,due_on,created_at,custom_fields';
  39. my $tasks_req = HTTP::Request->new( 'GET', "$BASE_URL$tasks_url", \@headers );
  40. my $tasks_res = $ua->request($tasks_req);
  41. my @output_tasks;
  42. my $tasks = decode_json( $tasks_res->content )->{data};
  43. foreach my $task (@$tasks) {
  44. next if $task->{completed};
  45. say ' - '.$task->{name};
  46. my $git_branch;
  47. my $prio;
  48. foreach my $custom ( @{ $task->{custom_fields} } ) {
  49. if ( $custom->{name} eq 'git branch' ) {
  50. $git_branch = $custom->{text_value};
  51. next;
  52. }
  53. # We ended up not importing these.
  54. if ( $custom->{name} eq 'Priority' && defined $custom->{display_value} ) {
  55. $prio =
  56. $custom->{display_value} eq 'High' ? 'fwccC9'
  57. : $custom->{display_value} eq 'Med' ? 'yPnaFa'
  58. : $custom->{display_value} eq 'Low' ? 'W4vMvm'
  59. : 'ML5drH';
  60. next;
  61. }
  62. }
  63. if ( !defined $users_by_gid{ $task->{created_by}->{gid} } ) {
  64. my $user_req =
  65. HTTP::Request->new( 'GET', "$BASE_URL/users/" . $task->{created_by}->{gid},
  66. \@headers );
  67. my $user_res = $ua->request($user_req);
  68. my $user = decode_json( $user_res->content )->{data};
  69. if ( defined $users{ $user->{name} } ) {
  70. $users_by_gid{ $task->{created_by}->{gid} } = $users{ $user->{name} };
  71. }
  72. }
  73. my $creator = $users_by_gid{ $task->{created_by}->{gid} } // undef;
  74. if ( defined $task->{assignee} && !defined $users_by_gid{ $task->{assignee}->{gid} } ) {
  75. my $user_req =
  76. HTTP::Request->new( 'GET', "$BASE_URL/users/" . $task->{assignee}->{gid},
  77. \@headers );
  78. my $user_res = $ua->request($user_req);
  79. my $user = decode_json( $user_res->content )->{data};
  80. if ( defined $users{ $user->{name} } ) {
  81. $users_by_gid{ $task->{assignee}->{gid} } = $users{ $user->{name} };
  82. }
  83. }
  84. my $assignee = defined $task->{assignee} ? $users_by_gid{ $task->{assignee}->{gid} } : undef;
  85. my $list;
  86. foreach my $membership ( @{ $task->{memberships} } ) {
  87. next if $membership->{project}->{name} ne $project->{name};
  88. $list = $membership->{section}->{name};
  89. }
  90. # I was trying to create JSON that I could use on the import screen in Wekan,
  91. # but for bigger boards, it was just *too* hefty, so I took that JSON and used
  92. # APIs to import.
  93. my %output_task = (
  94. swimlaneId => 'As4SNerx4Y4mMnJ8n', # 'Bugs'
  95. sort => 0,
  96. type => 'cardType-card',
  97. archived => JSON::false,
  98. title => $task->{name},
  99. description => $task->{notes},
  100. createdAt => $task->{created_at},
  101. dueAt => defined $task->{due_on} ? $task->{due_on} . 'T22:00:00.000Z' : undef,
  102. customFields => [
  103. {
  104. _id => 'rL8BpFHp5xxSFbDdr',
  105. value => $git_branch,
  106. },
  107. ],
  108. labelIds => [$prio],
  109. listId => $list,
  110. userId => $creator,
  111. assignees => [$assignee],
  112. );
  113. my @final_comments;
  114. my $comments_req =
  115. HTTP::Request->new( 'GET', "$BASE_URL/tasks/" . $task->{gid} . '/stories',
  116. \@headers );
  117. my $comments_res = $ua->request($comments_req);
  118. my $comments = decode_json( $comments_res->content )->{data};
  119. foreach my $comment (@$comments) {
  120. next if $comment->{type} ne 'comment';
  121. if ( !defined $users_by_gid{ $comment->{created_by}->{gid} } ) {
  122. my $user_req =
  123. HTTP::Request->new( 'GET', "$BASE_URL/users/" . $comment->{created_by}->{gid},
  124. \@headers );
  125. my $user_res = $ua->request($user_req);
  126. my $user = decode_json( $user_res->content )->{data};
  127. if ( defined $users{ $user->{name} } ) {
  128. $users_by_gid{ $comment->{created_bye}->{gid} } = $users{ $user->{name} };
  129. }
  130. }
  131. my $commentor = $users_by_gid{ $comment->{created_by}->{gid} };
  132. my %this_comment = (
  133. text => $comment->{text},
  134. createdAt => $comment->{created_at},
  135. userId => $commentor,
  136. );
  137. push @final_comments, \%this_comment;
  138. }
  139. $output_task{comments} = \@final_comments;
  140. my @final_attachments;
  141. my $attachments_req =
  142. HTTP::Request->new( 'GET', "$BASE_URL/tasks/" . $task->{gid} . '/attachments',
  143. \@headers );
  144. my $attachments_res = $ua->request($attachments_req);
  145. my $attachments = decode_json( $attachments_res->content )->{data};
  146. foreach my $attachment (@$attachments) {
  147. my $att_req =
  148. HTTP::Request->new( 'GET', "$BASE_URL/attachments/" . $attachment->{gid},
  149. \@headers );
  150. my $att_res = $ua->request($att_req);
  151. my $att = decode_json( $att_res->content )->{data};
  152. my $file_req=HTTP::Request->new('GET',$att->{download_url});
  153. my $file_res=$ua->request($file_req);
  154. my $file=encode_base64($file_res->content);
  155. my %this_attachment = (
  156. file => $file,
  157. name => $att->{name},
  158. createdAt => $att->{created_at},
  159. );
  160. push @final_attachments, \%this_attachment;
  161. }
  162. $output_task{attachments} = \@final_attachments;
  163. push @output_tasks, \%output_task;
  164. }
  165. my $file_name = $project->{name};
  166. $file_name =~ s/\//_/g;
  167. open my $output_file, '>',$file_name.'_exported.json';
  168. print $output_file encode_json(\@output_tasks);
  169. close $output_file;
  170. }