BigW Consortium Gitlab

importer.rb 10.1 KB
Newer Older
1 2 3
module Gitlab
  module GoogleCodeImport
    class Importer
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
      attr_reader :project, :repo, :closed_statuses

      NICE_LABEL_COLOR_HASH =
        {
          'Status: New'        => '#428bca',
          'Status: Accepted'   => '#5cb85c',
          'Status: Started'    => '#8e44ad',
          'Priority: Critical' => '#ffcfcf',
          'Priority: High'     => '#deffcf',
          'Priority: Medium'   => '#fff5cc',
          'Priority: Low'      => '#cfe9ff',
          'Type: Defect'       => '#d9534f',
          'Type: Enhancement'  => '#44ad8e',
          'Type: Task'         => '#4b6dd0',
          'Type: Review'       => '#8e44ad',
          'Type: Other'        => '#7f8c8d'
        }.freeze
21 22 23

      def initialize(project)
        @project = project
24 25 26 27

        import_data = project.import_data.try(:data)
        repo_data = import_data["repo"] if import_data
        @repo = GoogleCodeImport::Repository.new(repo_data)
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46

        @closed_statuses = []
        @known_labels = Set.new
      end

      def execute
        return true unless repo.valid?

        import_status_labels

        import_labels

        import_issues

        true
      end

      private

47 48
      def user_map
        @user_map ||= begin
49
          user_map = Hash.new do |hash, user|
50 51 52
            # Replace ... by \.\.\., so `johnsm...@gmail.com` isn't autolinked.
            Client.mask_email(user).sub("...", "\\.\\.\\.")
          end
53

54 55
          import_data = project.import_data.try(:data)
          stored_user_map = import_data["user_map"] if import_data
56 57 58 59 60 61
          user_map.update(stored_user_map) if stored_user_map

          user_map
        end
      end

62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
      def import_status_labels
        repo.raw_data["issuesConfig"]["statuses"].each do |status|
          closed = !status["meansOpen"]
          @closed_statuses << status["status"] if closed

          name = nice_status_name(status["status"])
          create_label(name)
          @known_labels << name
        end
      end

      def import_labels
        repo.raw_data["issuesConfig"]["labels"].each do |label|
          name = nice_label_name(label["label"])
          create_label(name)
          @known_labels << name
        end
      end

      def import_issues
82
        return unless repo.issues
83

84
        while raw_issue = repo.issues.shift
85
          author  = user_map[raw_issue["author"]["name"]]
86
          date    = DateTime.parse(raw_issue["published"]).to_formatted_s(:long)
87 88 89 90

          comments = raw_issue["comments"]["items"]
          issue_comment = comments.shift

91
          content     = format_content(issue_comment["content"])
92
          attachments = format_attachments(raw_issue["id"], 0, issue_comment["attachments"])
93 94

          body = format_issue_body(author, date, content, attachments)
95
          labels = import_issue_labels(raw_issue)
96

97
          assignee_id = nil
98
          if raw_issue.key?("owner")
99 100 101 102 103 104 105 106 107 108 109
            username = user_map[raw_issue["owner"]["name"]]

            if username.start_with?("@")
              username = username[1..-1]

              if user = User.find_by(username: username)
                assignee_id = user.id
              end
            end
          end

110
          issue = Issue.create!(
111 112 113 114 115 116 117
            iid:          raw_issue['id'],
            project_id:   project.id,
            title:        raw_issue['title'],
            description:  body,
            author_id:    project.creator_id,
            assignee_ids: [assignee_id],
            state:        raw_issue['state'] == 'closed' ? 'closed' : 'opened'
118
          )
119

120
          issue_labels = ::LabelsFinder.new(nil, project_id: project.id, title: labels).execute(skip_authorization: true)
121
          issue.update_attribute(:label_ids, issue_labels.pluck(:id))
122

123 124 125 126
          import_issue_comments(issue, comments)
        end
      end

127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
      def import_issue_labels(raw_issue)
        labels = []

        raw_issue["labels"].each do |label|
          name = nice_label_name(label)
          labels << name

          unless @known_labels.include?(name)
            create_label(name)
            @known_labels << name
          end
        end

        labels << nice_status_name(raw_issue["status"])
        labels
      end

144
      def import_issue_comments(issue, comments)
145 146
        Note.transaction do
          while raw_comment = comments.shift
147
            next if raw_comment.key?("deletedBy")
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165

            content     = format_content(raw_comment["content"])
            updates     = format_updates(raw_comment["updates"])
            attachments = format_attachments(issue.iid, raw_comment["id"], raw_comment["attachments"])

            next if content.blank? && updates.blank? && attachments.blank?

            author  = user_map[raw_comment["author"]["name"]]
            date    = DateTime.parse(raw_comment["published"]).to_formatted_s(:long)

            body = format_issue_comment_body(
              raw_comment["id"],
              author,
              date,
              content,
              updates,
              attachments
            )
166

167 168 169 170 171 172 173 174 175
            # Needs to match order of `comment_columns` below.
            Note.create!(
              project_id:     project.id,
              noteable_type:  "Issue",
              noteable_id:    issue.id,
              author_id:      project.creator_id,
              note:           body
            )
          end
176 177 178 179
        end
      end

      def nice_label_color(name)
180 181 182 183 184 185 186 187 188 189 190 191 192
        NICE_LABEL_COLOR_HASH[name] ||
          case name
          when /\AComponent:/
            '#fff39e'
          when /\AOpSys:/
            '#e2e2e2'
          when /\AMilestone:/
            '#fee3ff'
          when *closed_statuses.map { |s| nice_status_name(s) }
            '#cfcfcf'
          else
            '#e2e2e2'
          end
193 194 195 196 197 198 199 200 201 202 203
      end

      def nice_label_name(name)
        name.sub("-", ": ")
      end

      def nice_status_name(name)
        "Status: #{name}"
      end

      def linkify_issues(s)
204 205 206
        s = s.gsub(/([Ii]ssue) ([0-9]+)/, '\1 #\2')
        s = s.gsub(/([Cc]omment) #([0-9]+)/, '\1 \2')
        s
207 208 209
      end

      def escape_for_markdown(s)
210 211 212 213 214
        # No headings and lists
        s = s.gsub(/^#/, "\\#")
        s = s.gsub(/^-/, "\\-")

        # No inline code
215
        s = s.gsub("`", "\\`")
216 217

        # Carriage returns make me sad
218
        s = s.delete("\r")
219 220

        # Markdown ignores single newlines, but we need them as <br />.
221
        s = s.gsub("\n", "  \n")
222

223 224 225 226
        s
      end

      def create_label(name)
227
        params = { name: name, color: nice_label_color(name) }
228
        ::Labels::FindOrCreateService.new(nil, project, params).execute(skip_authorization: true)
229 230 231 232 233 234 235 236 237
      end

      def format_content(raw_content)
        linkify_issues(escape_for_markdown(raw_content))
      end

      def format_updates(raw_updates)
        updates = []

238
        if raw_updates.key?("status")
239 240 241
          updates << "*Status: #{raw_updates["status"]}*"
        end

242
        if raw_updates.key?("owner")
243
          updates << "*Owner: #{user_map[raw_updates["owner"]]}*"
244 245
        end

246
        if raw_updates.key?("cc")
247 248
          cc = raw_updates["cc"].map do |l|
            deleted = l.start_with?("-")
249
            l = l[1..-1] if deleted
250
            l = user_map[l]
251 252 253 254 255 256 257
            l = "~~#{l}~~" if deleted
            l
          end

          updates << "*Cc: #{cc.join(", ")}*"
        end

258
        if raw_updates.key?("labels")
259 260
          labels = raw_updates["labels"].map do |l|
            deleted = l.start_with?("-")
261 262 263 264 265 266 267 268 269
            l = l[1..-1] if deleted
            l = nice_label_name(l)
            l = "~~#{l}~~" if deleted
            l
          end

          updates << "*Labels: #{labels.join(", ")}*"
        end

270
        if raw_updates.key?("mergedInto")
271 272 273
          updates << "*Merged into: ##{raw_updates["mergedInto"]}*"
        end

274
        if raw_updates.key?("blockedOn")
275
          blocked_ons = raw_updates["blockedOn"].map do |raw_blocked_on|
276
            format_blocking_updates(raw_blocked_on)
277
          end
278

279 280 281
          updates << "*Blocked on: #{blocked_ons.join(", ")}*"
        end

282
        if raw_updates.key?("blocking")
283
          blockings = raw_updates["blocking"].map do |raw_blocked_on|
284
            format_blocking_updates(raw_blocked_on)
285
          end
286

287 288 289 290 291 292
          updates << "*Blocking: #{blockings.join(", ")}*"
        end

        updates
      end

293 294 295 296 297 298 299 300 301 302
      def format_blocking_updates(raw_blocked_on)
        name, id = raw_blocked_on.split(":", 2)

        deleted = name.start_with?("-")
        name = name[1..-1] if deleted

        text =
          if name == project.import_source
            "##{id}"
          else
303
            "#{project.namespace.full_path}/#{name}##{id}"
304 305 306 307 308
          end
        text = "~~#{text}~~" if deleted
        text
      end

309 310 311 312 313 314
      def format_attachments(issue_id, comment_id, raw_attachments)
        return [] unless raw_attachments

        raw_attachments.map do |attachment|
          next if attachment["isDeleted"]

315 316
          filename = attachment["fileName"]
          link = "https://storage.googleapis.com/google-code-attachments/#{@repo.name}/issue-#{issue_id}/comment-#{comment_id}/#{filename}"
317

318
          text = "[#{filename}](#{link})"
319
          text = "!#{text}" if filename =~ /\.(png|jpg|jpeg|gif|bmp|tiff)\z/i
320
          text
321 322
        end.compact
      end
323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348

      def format_issue_comment_body(id, author, date, content, updates, attachments)
        body = []
        body << "*Comment #{id} by #{author} on #{date}*"
        body << "---"

        if content.blank?
          content = "*(No comment has been entered for this change)*"
        end
        body << content

        if updates.any?
          body << "---"
          body += updates
        end

        if attachments.any?
          body << "---"
          body += attachments
        end

        body.join("\n\n")
      end

      def format_issue_body(author, date, content, attachments)
        body = []
349
        body << "*By #{author} on #{date} (imported from Google Code)*"
350 351 352 353 354 355 356 357 358 359 360 361 362 363
        body << "---"

        if content.blank?
          content = "*(No description has been entered for this issue)*"
        end
        body << content

        if attachments.any?
          body << "---"
          body += attachments
        end

        body.join("\n\n")
      end
364 365 366
    end
  end
end