Merge Request #38

Merged
softwarepublico/colab!38
Created by Gustavo Jaruga Cruz

Gitlab data

Make sure you have gitlab plugin active and properly configurated PROXIED_APPS: gitlab: upstream: 'http://localhost:8090/gitlab/' private_token: ''

You'll need a valid administrator private_token to validate import_proxy_data It might take a while if the upstream server is external depending on the delay

Assignee: Alexandre Barbosa
Milestone: None

Merged by Sergio Oliveira

Source branch has been removed
Commits (12)
3 participants
colab/accounts/tests/test_user.py
... ... @@ -5,7 +5,6 @@ Objective: Test parameters, and behavior.
5 5 from colab.accounts.models import User
6 6 from django.test import TestCase, Client
7 7  
8   -
9 8 class UserTest(TestCase):
10 9  
11 10 def setUp(self):
... ...
colab/proxy/gitlab/data_api.py
1 1 import json
2 2 import urllib
3 3 import urllib2
  4 +import logging
4 5  
5 6 from dateutil.parser import parse
6 7  
7 8 from django.conf import settings
8 9 from django.db.models.fields import DateTimeField
9 10  
10   -from colab.proxy.gitlab.models import GitlabProject
  11 +from colab.proxy.gitlab.models import (GitlabProject, GitlabMergeRequest,
  12 + GitlabComment, GitlabIssue)
11 13 from colab.proxy.utils.proxy_data_api import ProxyDataAPI
12 14  
  15 +LOGGER = logging.getLogger('colab.plugin.gitlab')
  16 +
13 17  
14 18 class GitlabDataAPI(ProxyDataAPI):
15 19  
... ... @@ -25,42 +29,175 @@ class GitlabDataAPI(ProxyDataAPI):
25 29  
26 30 return u'{}{}?{}'.format(upstream, path, params)
27 31  
28   - def fetchProjects(self):
  32 + def get_json_data(self, api_url, page, pages=1000):
  33 + url = self.get_request_url(api_url, per_page=pages,
  34 + page=page)
  35 +
  36 + try:
  37 + data = urllib2.urlopen(url, timeout=10)
  38 + json_data = json.load(data)
  39 + except urllib2.URLError:
  40 + LOGGER.exception("Connection timeout: " + url)
  41 + json_data = []
  42 +
  43 + return json_data
  44 +
  45 + def fill_object_data(self, element, _object):
  46 + for field in _object._meta.fields:
  47 + try:
2
  • 9fe63c7bd60deeb55e409a1d7dd173f5?s=40&d=identicon
    Sergio Oliveira @seocam

    Why such a big block of code is inside a try for KeyError?

    Choose File ...   File name...
    Cancel
  • C6b14af78e51fba6beb90142971240cc?s=40&d=identicon
    Gustavo Jaruga Cruz @darksshades

    You highlighted the wrong code. Every attribution of "value = element[somevalue]" in this function has a chance of not existing and give a KeyError. This functions tries to generalize the attributions of the GitlabData expecting the the json data to be the same name, which happens 80% of the time. Others specific attributions are made outside this funcion.

    Choose File ...   File name...
    Cancel
  48 + if field.name == "user":
  49 + _object.update_user(
  50 + element["author"]["username"])
  51 + continue
  52 + if field.name == "project":
  53 + _object.project_id = element["project_id"]
  54 + continue
  55 +
  56 + if isinstance(field, DateTimeField):
  57 + value = parse(element[field.name])
  58 + else:
  59 + value = element[field.name]
  60 +
  61 + setattr(_object, field.name, value)
  62 + except KeyError:
  63 + continue
  64 +
  65 + return _object
  66 +
  67 + def fetch_projects(self):
29 68 page = 1
30 69 projects = []
31 70  
32   - # Iterates throughout all projects pages
33   - while(True):
34   - url = self.get_request_url('/api/v3/projects/all',
35   - per_page=100,
36   - page=page)
37   - data = urllib2.urlopen(url)
38   - json_data = json.load(data)
  71 + while True:
3
  • 9fe63c7bd60deeb55e409a1d7dd173f5?s=40&d=identicon
    Sergio Oliveira @seocam

    Isn't it a bit dangerous? If the server doesn't send the parameter you are expecting you might end-up in an infinite loop. Isn't there a why to calculate the number of pages before hand?

    Choose File ...   File name...
    Cancel
  • 9fe63c7bd60deeb55e409a1d7dd173f5?s=40&d=identicon
    Sergio Oliveira @seocam

    The same applies for all loops while True loops in this file.

    Choose File ...   File name...
    Cancel
  • C6b14af78e51fba6beb90142971240cc?s=40&d=identicon
    Gustavo Jaruga Cruz @darksshades

    GitlabAPI doesn't provide any way to know how many pages there are... But when you input a nonexistent page or the parser fails, it returns an empty list and breaks the While.

    Choose File ...   File name...
    Cancel
  72 + json_data = self.get_json_data('/api/v3/projects/all', page)
  73 + page = page + 1
39 74  
40   - if len(json_data) == 0:
  75 + if not len(json_data):
41 76 break
42 77  
43   - page = page + 1
44   -
45 78 for element in json_data:
46 79 project = GitlabProject()
47   -
48   - for field in GitlabProject._meta.fields:
49   - if isinstance(field, DateTimeField):
50   - value = parse(element[field.name])
51   - else:
52   - value = element[field.name]
53   -
54   - setattr(project, field.name, value)
55   -
  80 + self.fill_object_data(element, project)
2
  • 9fe63c7bd60deeb55e409a1d7dd173f5?s=40&d=identicon
    Sergio Oliveira @seocam

    What about GitlabProject(**element)? You would need to thread element to be in the right format but it might be a good idea. Perhaps a class method GitlabProject.from_api.

    Choose File ...   File name...
    Cancel
  • C6b14af78e51fba6beb90142971240cc?s=40&d=identicon
    Gustavo Jaruga Cruz @darksshades

    We didn't understand.... the function fill_object_data is used to fill the generic fields of GitlabProject, and any code bellow that are used to fill the specific ones.

    Choose File ...   File name...
    Cancel
56 81 projects.append(project)
57 82  
58 83 return projects
59 84  
  85 + def fetch_merge_request(self, projects):
  86 + all_merge_request = []
  87 +
  88 + for project in projects:
  89 + page = 1
  90 + while True:
  91 + url = '/api/v3/projects/{}/merge_requests'.format(project.id)
  92 + json_data_mr = self.get_json_data(url, page)
  93 + page = page + 1
  94 +
  95 + if len(json_data_mr) == 0:
  96 + break
  97 +
  98 + for element in json_data_mr:
  99 + single_merge_request = GitlabMergeRequest()
  100 + self.fill_object_data(element, single_merge_request)
  101 + all_merge_request.append(single_merge_request)
  102 +
  103 + return all_merge_request
  104 +
  105 + def fetch_issue(self, projects):
  106 + all_issues = []
  107 +
  108 + for project in projects:
  109 + page = 1
  110 + while True:
  111 + url = '/api/v3/projects/{}/issues'.format(project.id)
  112 + json_data_issue = self.get_json_data(url, page)
  113 + page = page + 1
  114 +
  115 + if len(json_data_issue) == 0:
  116 + break
  117 +
  118 + for element in json_data_issue:
  119 + single_issue = GitlabIssue()
  120 + self.fill_object_data(element, single_issue)
  121 + all_issues.append(single_issue)
  122 +
  123 + return all_issues
  124 +
  125 + def fetch_comments(self):
  126 + all_comments = []
  127 + all_comments.extend(self.fetch_comments_MR())
  128 + all_comments.extend(self.fetch_comments_issues())
  129 +
  130 + return all_comments
  131 +
  132 + def fetch_comments_MR(self):
  133 + all_comments = []
  134 + all_merge_requests = GitlabMergeRequest.objects.all()
  135 +
  136 + for merge_request in all_merge_requests:
  137 + page = 1
  138 + while True:
  139 + url = '/api/v3/projects/{}/merge_requests/{}/notes'.format(
  140 + merge_request.project_id, merge_request.id)
  141 + json_data_mr = self.get_json_data(url, page)
  142 + page = page + 1
  143 +
  144 + if len(json_data_mr) == 0:
  145 + break
  146 +
  147 + for element in json_data_mr:
  148 + single_comment = GitlabComment()
  149 + self.fill_object_data(element, single_comment)
  150 + single_comment.project = merge_request.project
  151 + single_comment.issue_comment = False
  152 + single_comment.parent_id = merge_request.id
  153 + all_comments.append(single_comment)
  154 +
  155 + return all_comments
  156 +
  157 + def fetch_comments_issues(self):
  158 + all_comments = []
  159 + all_issues = GitlabIssue.objects.all()
  160 +
  161 + for issue in all_issues:
  162 + page = 1
  163 + while True:
  164 + url = '/api/v3/projects/{}/issues/{}/notes'.format(
  165 + issue.project_id, issue.id)
  166 + json_data_mr = self.get_json_data(url, page)
  167 + page = page + 1
  168 +
  169 + if len(json_data_mr) == 0:
  170 + break
  171 +
  172 + for element in json_data_mr:
  173 + single_comment = GitlabComment()
  174 + self.fill_object_data(element, single_comment)
  175 + single_comment.project = issue.project
  176 + single_comment.issue_comment = True
  177 + single_comment.parent_id = issue.id
  178 + all_comments.append(single_comment)
  179 +
  180 + return all_comments
  181 +
60 182 def fetch_data(self):
61   - data = self.fetchProjects()
  183 + LOGGER.info("Importing Projects")
  184 + projects = self.fetch_projects()
  185 + for datum in projects:
  186 + datum.save()
  187 +
  188 + LOGGER.info("Importing Merge Requests")
  189 + merge_request_list = self.fetch_merge_request(projects)
  190 + for datum in merge_request_list:
  191 + datum.save()
  192 +
  193 + LOGGER.info("Importing Issues")
  194 + issue_list = self.fetch_issue(projects)
  195 + for datum in issue_list:
  196 + datum.save()
62 197  
63   - for datum in data:
  198 + LOGGER.info("Importing Comments")
  199 + comments_list = self.fetch_comments()
  200 + for datum in comments_list:
64 201 datum.save()
65 202  
66 203 @property
... ...
colab/proxy/gitlab/migrations/0002_auto_20150205_1220.py
... ... @@ -0,0 +1,69 @@
  1 +# -*- coding: utf-8 -*-
  2 +from __future__ import unicode_literals
  3 +
  4 +from django.db import models, migrations
  5 +import django.db.models.deletion
  6 +from django.conf import settings
  7 +
  8 +
  9 +class Migration(migrations.Migration):
  10 +
  11 + dependencies = [
  12 + migrations.swappable_dependency(settings.AUTH_USER_MODEL),
  13 + ('gitlab', '0001_initial'),
  14 + ]
  15 +
  16 + operations = [
  17 + migrations.CreateModel(
  18 + name='GitlabComment',
  19 + fields=[
  20 + ('id', models.IntegerField(serialize=False, primary_key=True)),
  21 + ('body', models.TextField()),
  22 + ('created_at', models.DateTimeField(blank=True)),
  23 + ('user', models.ForeignKey(on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, null=True)),
  24 + ],
  25 + options={
  26 + 'verbose_name': 'Gitlab Comments',
  27 + 'verbose_name_plural': 'Gitlab Comments',
  28 + },
  29 + bases=(models.Model,),
  30 + ),
  31 + migrations.CreateModel(
  32 + name='GitlabIssue',
  33 + fields=[
  34 + ('id', models.IntegerField(serialize=False, primary_key=True)),
  35 + ('title', models.TextField()),
  36 + ('description', models.TextField()),
  37 + ('state', models.TextField()),
  38 + ('project', models.ForeignKey(on_delete=django.db.models.deletion.SET_NULL, to='gitlab.GitlabProject', null=True)),
  39 + ('user', models.ForeignKey(on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, null=True)),
  40 + ],
  41 + options={
  42 + 'verbose_name': 'Gitlab Collaboration',
  43 + 'verbose_name_plural': 'Gitlab Collaborations',
  44 + },
  45 + bases=(models.Model,),
  46 + ),
  47 + migrations.CreateModel(
  48 + name='GitlabMergeRequest',
  49 + fields=[
  50 + ('id', models.IntegerField(serialize=False, primary_key=True)),
  51 + ('target_branch', models.TextField()),
  52 + ('source_branch', models.TextField()),
  53 + ('description', models.TextField()),
  54 + ('title', models.TextField()),
  55 + ('state', models.TextField()),
  56 + ('project', models.ForeignKey(on_delete=django.db.models.deletion.SET_NULL, to='gitlab.GitlabProject', null=True)),
  57 + ('user', models.ForeignKey(on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, null=True)),
  58 + ],
  59 + options={
  60 + 'verbose_name': 'Gitlab Merge Request',
  61 + 'verbose_name_plural': 'Gitlab Merge Requests',
  62 + },
  63 + bases=(models.Model,),
  64 + ),
  65 + migrations.AlterModelOptions(
  66 + name='gitlabproject',
  67 + options={'verbose_name': 'Gitlab Project', 'verbose_name_plural': 'Gitlab Projects'},
  68 + ),
  69 + ]
... ...
colab/proxy/gitlab/migrations/0003_auto_20150211_1203.py
... ... @@ -0,0 +1,61 @@
  1 +# -*- coding: utf-8 -*-
  2 +from __future__ import unicode_literals
  3 +
  4 +from django.db import models, migrations
  5 +import django.db.models.deletion
  6 +
  7 +
  8 +class Migration(migrations.Migration):
  9 +
  10 + dependencies = [
  11 + ('gitlab', '0002_auto_20150205_1220'),
  12 + ]
  13 +
  14 + operations = [
  15 + migrations.AlterModelOptions(
  16 + name='gitlabissue',
  17 + options={'verbose_name': 'Gitlab Issue', 'verbose_name_plural': 'Gitlab Issues'},
  18 + ),
  19 + migrations.AddField(
  20 + model_name='gitlabcomment',
  21 + name='issue_comment',
  22 + field=models.BooleanField(default=True),
  23 + preserve_default=True,
  24 + ),
  25 + migrations.AddField(
  26 + model_name='gitlabcomment',
  27 + name='parent_id',
  28 + field=models.IntegerField(null=True),
  29 + preserve_default=True,
  30 + ),
  31 + migrations.AddField(
  32 + model_name='gitlabcomment',
  33 + name='project',
  34 + field=models.ForeignKey(on_delete=django.db.models.deletion.SET_NULL, to='gitlab.GitlabProject', null=True),
  35 + preserve_default=True,
  36 + ),
  37 + migrations.AddField(
  38 + model_name='gitlabissue',
  39 + name='created_at',
  40 + field=models.DateTimeField(null=True, blank=True),
  41 + preserve_default=True,
  42 + ),
  43 + migrations.AddField(
  44 + model_name='gitlabmergerequest',
  45 + name='created_at',
  46 + field=models.DateTimeField(null=True, blank=True),
  47 + preserve_default=True,
  48 + ),
  49 + migrations.AddField(
  50 + model_name='gitlabproject',
  51 + name='path_with_namespace',
  52 + field=models.TextField(null=True, blank=True),
  53 + preserve_default=True,
  54 + ),
  55 + migrations.AlterField(
  56 + model_name='gitlabcomment',
  57 + name='created_at',
  58 + field=models.DateTimeField(null=True, blank=True),
  59 + preserve_default=True,
  60 + ),
  61 + ]
... ...
colab/proxy/gitlab/models.py
1 1 from django.db import models
2 2 from django.utils.translation import ugettext_lazy as _
  3 +from colab.proxy.utils.models import Collaboration
  4 +from hitcounter.models import HitCounterModelMixin
3 5  
4 6  
5   -class GitlabProject(models.Model):
  7 +class GitlabProject(models.Model, HitCounterModelMixin):
6 8  
7 9 id = models.IntegerField(primary_key=True)
8 10 description = models.TextField()
... ... @@ -11,7 +13,131 @@ class GitlabProject(models.Model):
11 13 name_with_namespace = models.TextField()
12 14 created_at = models.DateTimeField(blank=True)
13 15 last_activity_at = models.DateTimeField(blank=True)
  16 + path_with_namespace = models.TextField(blank=True, null=True)
  17 +
  18 + @property
  19 + def url(self):
  20 + return u'/gitlab/{}'.format(self.path_with_namespace)
14 21  
15 22 class Meta:
16 23 verbose_name = _('Gitlab Project')
17 24 verbose_name_plural = _('Gitlab Projects')
  25 +
  26 +
  27 +class GitlabMergeRequest(Collaboration):
  28 +
  29 + id = models.IntegerField(primary_key=True)
  30 + target_branch = models.TextField()
  31 + source_branch = models.TextField()
  32 + project = models.ForeignKey(GitlabProject, null=True,
  33 + on_delete=models.SET_NULL)
  34 + description = models.TextField()
  35 + title = models.TextField()
  36 + state = models.TextField()
  37 + created_at = models.DateTimeField(blank=True, null=True)
  38 +
  39 + @property
  40 + def modified(self):
  41 + return self.created_at
  42 +
  43 + @property
  44 + def tag(self):
  45 + return self.state
  46 +
  47 + type = u'merge_request'
  48 + icon_name = u'file'
  49 +
  50 + @property
  51 + def url(self):
  52 + return u'/gitlab/{}/merge_requests/{}'.format(
  53 + self.project.path_with_namespace, self.id)
  54 +
  55 + def get_author(self):
  56 + return self.user
  57 +
  58 + class Meta:
  59 + verbose_name = _('Gitlab Merge Request')
  60 + verbose_name_plural = _('Gitlab Merge Requests')
  61 +
  62 +
  63 +class GitlabIssue(Collaboration):
  64 +
  65 + id = models.IntegerField(primary_key=True)
  66 + project = models.ForeignKey(GitlabProject, null=True,
  67 + on_delete=models.SET_NULL)
  68 + title = models.TextField()
  69 + description = models.TextField()
  70 +
  71 + state = models.TextField()
  72 + created_at = models.DateTimeField(blank=True, null=True)
  73 +
  74 + icon_name = u'align-right'
  75 + type = u'gitlab_issue'
  76 +
  77 + @property
  78 + def modified(self):
  79 + return self.created_at
  80 +
  81 + @property
  82 + def url(self):
  83 + return u'/gitlab/{}/issues/{}'.format(
  84 + self.project.path_with_namespace, self.id)
  85 +
  86 + class Meta:
  87 + verbose_name = _('Gitlab Issue')
  88 + verbose_name_plural = _('Gitlab Issues')
  89 +
  90 +
  91 +class GitlabComment(Collaboration):
  92 +
  93 + id = models.IntegerField(primary_key=True)
  94 + body = models.TextField()
  95 + created_at = models.DateTimeField(blank=True, null=True)
  96 + issue_comment = models.BooleanField(default=True)
  97 +
  98 + project = models.ForeignKey(GitlabProject, null=True,
  99 + on_delete=models.SET_NULL)
  100 +
  101 + parent_id = models.IntegerField(null=True)
  102 + type = u'comment'
  103 +
  104 + @property
  105 + def modified(self):
  106 + return self.created_at
  107 +
  108 + @property
  109 + def title(self):
  110 + if self.issue_comment:
  111 + issue = GitlabIssue.objects.get(id=self.parent_id)
  112 + return issue.title
  113 + else:
  114 + merge_request = GitlabMergeRequest.objects.get(id=self.parent_id)
  115 + return merge_request.title
  116 +
  117 + icon_name = u'align-right'
  118 +
  119 + @property
  120 + def description(self):
  121 + return self.body
  122 +
  123 + @property
  124 + def tag(self):
  125 + if self.issue_comment:
  126 + issue = GitlabIssue.objects.get(id=self.parent_id)
  127 + return issue.state
  128 + else:
  129 + merge_request = GitlabMergeRequest.objects.get(id=self.parent_id)
  130 + return merge_request.state
  131 +
  132 + @property
  133 + def url(self):
  134 + if self.issue_comment:
  135 + return u'/gitlab/{}/issues/{}#notes_{}'.format(
  136 + self.project.path_with_namespace, self.parent_id, self.id)
  137 + else:
  138 + return u'/gitlab/{}/merge_requests/{}#notes_{}'.format(
  139 + self.project.path_with_namespace, self.parent_id, self.id)
  140 +
  141 + class Meta:
  142 + verbose_name = _('Gitlab Comments')
  143 + verbose_name_plural = _('Gitlab Comments')
... ...
colab/proxy/gitlab/search_indexes.py
... ... @@ -0,0 +1,120 @@
  1 +# -*- coding: utf-8 -*-
  2 +
  3 +import string
  4 +
  5 +from haystack import indexes
  6 +from haystack.utils import log as logging
  7 +
  8 +from .models import (GitlabProject, GitlabMergeRequest,
  9 + GitlabIssue, GitlabComment)
  10 +
  11 +
  12 +logger = logging.getLogger('haystack')
  13 +
  14 +# The string maketrans always return a string encoded with latin1
  15 +# http://stackoverflow.com/questions/1324067/how-do-i-get-str-translate-to-work-with-unicode-strings
  16 +table = string.maketrans(
  17 + string.punctuation,
  18 + '.' * len(string.punctuation)
  19 +).decode('latin1')
  20 +
  21 +
  22 +class GitlabProjectIndex(indexes.SearchIndex, indexes.Indexable):
  23 + text = indexes.CharField(document=True, use_template=False, stored=False)
  24 + title = indexes.CharField(model_attr='name')
  25 + description = indexes.CharField(model_attr='description', null=True)
  26 + tag = indexes.CharField()
  27 + url = indexes.CharField(model_attr='url', indexed=False)
  28 + icon_name = indexes.CharField()
  29 + type = indexes.CharField()
  30 + created = indexes.DateTimeField(model_attr='created_at', null=True)
  31 +
  32 + def prepare_tag(self, obj):
  33 + return "{}".format(obj.name_with_namespace.split('/')[0].strip())
  34 +
  35 + def prepare_icon_name(self, obj):
  36 + return u'file'
  37 +
  38 + def get_ful_name(self):
  39 + self.objs.name
  40 +
  41 + def get_model(self):
  42 + return GitlabProject
  43 +
  44 + def prepare_type(self, obj):
  45 + return u'gitlab'
  46 +
  47 +
  48 +class GitlabMergeRequestIndex(indexes.SearchIndex, indexes.Indexable):
  49 +
  50 + text = indexes.CharField(document=True, use_template=False, stored=False)
  51 + title = indexes.CharField(model_attr='title')
  52 + description = indexes.CharField(model_attr='description')
  53 + tag = indexes.CharField(model_attr='state')
  54 + url = indexes.CharField(model_attr='url', indexed=False)
  55 + icon_name = indexes.CharField()
  56 + type = indexes.CharField(model_attr='type')
  57 +
  58 + modified_by = indexes.CharField(model_attr='modified_by', null=True)
  59 + modified_by_url = indexes.CharField(model_attr='modified_by_url',
  60 + null=True)
  61 + modified = indexes.DateTimeField(model_attr='created_at', null=True)
  62 +
  63 + def get_model(self):
  64 + return GitlabMergeRequest
  65 +
  66 + def prepare_icon_name(self, obj):
  67 + return u'file'
  68 +
  69 + def prepare_type(self, obj):
  70 + return u'merge_request'
  71 +
  72 +
  73 +class GitlabIssueIndex(indexes.SearchIndex, indexes.Indexable):
  74 +
  75 + text = indexes.CharField(document=True, use_template=False, stored=False)
  76 + title = indexes.CharField(model_attr='title')
  77 + description = indexes.CharField(model_attr='description')
  78 + tag = indexes.CharField(model_attr='state')
  79 + url = indexes.CharField(model_attr='url', indexed=False)
  80 + icon_name = indexes.CharField()
  81 + type = indexes.CharField(model_attr='type')
  82 +
  83 + modified_by = indexes.CharField(model_attr='modified_by', null=True)
  84 + modified_by_url = indexes.CharField(model_attr='modified_by_url',
  85 + null=True)
  86 + modified = indexes.DateTimeField(model_attr='created_at', null=True)
  87 +
  88 + def get_model(self):
  89 + return GitlabIssue
  90 +
  91 + def prepare_icon_name(self, obj):
  92 + return u'align-right'
  93 +
  94 + def prepare_type(self, obj):
  95 + return u'merge_request'
  96 +
  97 +
  98 +class GitlabCommentIndex(indexes.SearchIndex, indexes.Indexable):
  99 +
  100 + text = indexes.CharField(document=True, use_template=False, stored=False)
  101 + title = indexes.CharField(model_attr='title')
  102 + description = indexes.CharField(model_attr='description')
  103 + tag = indexes.CharField()
  104 + url = indexes.CharField(model_attr='url', indexed=False)
  105 + icon_name = indexes.CharField()
  106 + type = indexes.CharField(model_attr='type')
  107 +
  108 + modified_by = indexes.CharField(model_attr='modified_by', null=True)
  109 + modified_by_url = indexes.CharField(model_attr='modified_by_url',
  110 + null=True)
  111 + modified = indexes.DateTimeField(model_attr='created_at', null=True)
  112 +
  113 + def get_model(self):
  114 + return GitlabComment
  115 +
  116 + def prepare_icon_name(self, obj):
  117 + return u'align-right'
  118 +
  119 + def prepare_tag(self, obj):
  120 + return obj.tag
... ...
colab/proxy/gitlab/tests/__init__.py
colab/proxy/gitlab/tests/test_gitlab.py
... ... @@ -0,0 +1,114 @@
  1 +"""
  2 +Test User class.
  3 +Objective: Test parameters, and behavior.
  4 +"""
  5 +from datetime import datetime
  6 +
  7 +
  8 +from django.test import TestCase, Client
  9 +from colab.accounts.models import User
  10 +from colab.proxy.gitlab.models import GitlabProject, \
  11 + GitlabIssue, GitlabComment, GitlabMergeRequest
  12 +
  13 +
  14 +class GitlabTest(TestCase):
  15 +
  16 + def setUp(self):
  17 + self.user = self.create_user()
  18 + self.client = Client()
  19 + self.create_gitlab_data()
  20 +
  21 + super(GitlabTest, self).setUp()
  22 +
  23 + def tearDown(self):
  24 + pass
  25 +
  26 + def test_data_integrity(self):
  27 + self.assertEqual(GitlabProject.objects.all().count(), 1)
  28 + self.assertEqual(GitlabMergeRequest.objects.all().count(), 1)
  29 + self.assertEqual(GitlabIssue.objects.all().count(), 1)
  30 + self.assertEqual(GitlabComment.objects.all().count(), 2)
  31 +
  32 + def test_project_url(self):
  33 + self.assertEqual(GitlabProject.objects.get(id=1).url,
  34 + '/gitlab/softwarepublico/colab')
  35 +
  36 + def test_merge_request_url(self):
  37 + self.assertEqual(GitlabMergeRequest.objects.get(id=1).url,
  38 + '/gitlab/softwarepublico/colab/merge_requests/1')
  39 +
  40 + def test_issue_url(self):
  41 + self.assertEqual(GitlabIssue.objects.get(id=1).url,
  42 + '/gitlab/softwarepublico/colab/issues/1')
  43 +
  44 + def test_comment_on_mr_url(self):
  45 + url = '/gitlab/softwarepublico/colab/merge_requests/1#notes_1'
  46 + self.assertEqual(GitlabComment.objects.get(id=1).url, url)
  47 +
  48 + def test_comment_on_issue_url(self):
  49 + self.assertEqual(GitlabComment.objects.get(id=2).url,
  50 + '/gitlab/softwarepublico/colab/issues/1#notes_2')
  51 +
  52 + def create_gitlab_data(self):
  53 + g = GitlabProject()
  54 + g.id = 1
  55 + g.name = "colab"
  56 + g.name_with_namespace = "Software Public / Colab"
  57 + g.path_with_namespace = "softwarepublico/colab"
  58 + g.created_at = datetime.now()
  59 + g.last_activity_at = datetime.now()
  60 + g.save()
  61 +
  62 + mr = GitlabMergeRequest()
  63 + mr.id = 1
  64 + mr.project = g
  65 + mr.title = "Include plugin support"
  66 + mr.description = "Merge request for plugin support"
  67 + mr.state = "Closed"
  68 + mr.created_at = datetime.now()
  69 + mr.update_user(self.user.username)
  70 + mr.save()
  71 +
  72 + i = GitlabIssue()
  73 + i.id = 1
  74 + i.project = g
  75 + i.title = "Issue for colab"
  76 + i.description = "Issue reported to colab"
  77 + i.created_at = datetime.now()
  78 + i.state = "Open"
  79 + i.update_user(self.user.username)
  80 + i.save()
  81 +
  82 + c1 = GitlabComment()
  83 + c1.id = 1
  84 + c1.parent_id = mr.id
  85 + c1.project = g
  86 + c1.body = "Comment to merge request"
  87 + c1.created_at = datetime.now()
  88 + c1.issue_comment = False
  89 + c1.update_user(self.user.username)
  90 + c1.save()
  91 +
  92 + c2 = GitlabComment()
  93 + c2.id = 2
  94 + c2.parent_id = i.id
  95 + c2.project = g
  96 + c2.body = "Comment to issue"
  97 + c2.created_at = datetime.now()
  98 + c2.issue_comment = True
  99 + c2.update_user(self.user.username)
  100 + c2.save()
  101 +
  102 + def create_user(self):
  103 + user = User()
  104 + user.username = "USERtestCoLaB"
  105 + user.set_password("123colab4")
  106 + user.email = "usertest@colab.com.br"
  107 + user.id = 1
  108 + user.twitter = "usertestcolab"
  109 + user.facebook = "usertestcolab"
  110 + user.first_name = "USERtestCoLaB"
  111 + user.last_name = "COLAB"
  112 + user.save()
  113 +
  114 + return user
... ...
tests/settings.yaml
... ... @@ -63,10 +63,10 @@ ROBOTS_NOINDEX: false
63 63 # RAVEN_DSN: 'http://public:secret@example.com/1'
64 64  
65 65 ### Colab proxied apps
66   -# PROXIED_APPS:
67   -# gitlab:
68   -# upstream: 'http://localhost:8090/gitlab/'
69   -# private_token: ''
  66 +PROXIED_APPS:
  67 + gitlab:
  68 + upstream: 'http://localhost:8090/gitlab/'
  69 + private_token: ''
70 70 # trac:
71 71 # upstream: 'http://localhost:5000/trac/'
72 72  
... ...
    9fe63c7bd60deeb55e409a1d7dd173f5?s=40&d=identicon
    Sergio Oliveira started a discussion on the diff
    last updated by Gustavo Jaruga Cruz
    colab/proxy/gitlab/data_api.py
      32 + def get_json_data(self, api_url, page, pages=1000):
      33 + url = self.get_request_url(api_url, per_page=pages,
      34 + page=page)
      35 +
      36 + try:
      37 + data = urllib2.urlopen(url, timeout=10)
      38 + json_data = json.load(data)
      39 + except urllib2.URLError:
      40 + LOGGER.exception("Connection timeout: " + url)
      41 + json_data = []
      42 +
      43 + return json_data
      44 +
      45 + def fill_object_data(self, element, _object):
      46 + for field in _object._meta.fields:
      47 + try:
    2
    • 9fe63c7bd60deeb55e409a1d7dd173f5?s=40&d=identicon
      Sergio Oliveira @seocam

      Why such a big block of code is inside a try for KeyError?

      Choose File ...   File name...
      Cancel
    • C6b14af78e51fba6beb90142971240cc?s=40&d=identicon
      Gustavo Jaruga Cruz @darksshades

      You highlighted the wrong code. Every attribution of "value = element[somevalue]" in this function has a chance of not existing and give a KeyError. This functions tries to generalize the attributions of the GitlabData expecting the the json data to be the same name, which happens 80% of the time. Others specific attributions are made outside this funcion.

      Choose File ...   File name...
      Cancel
    9fe63c7bd60deeb55e409a1d7dd173f5?s=40&d=identicon
    Sergio Oliveira started a discussion on the diff
    last updated by Gustavo Jaruga Cruz
    colab/proxy/gitlab/data_api.py
      63 + continue
      64 +
      65 + return _object
      66 +
      67 + def fetch_projects(self):
    29 68 page = 1
    30 69 projects = []
    31 70  
    32   - # Iterates throughout all projects pages
    33   - while(True):
    34   - url = self.get_request_url('/api/v3/projects/all',
    35   - per_page=100,
    36   - page=page)
    37   - data = urllib2.urlopen(url)
    38   - json_data = json.load(data)
      71 + while True:
    3
    • 9fe63c7bd60deeb55e409a1d7dd173f5?s=40&d=identicon
      Sergio Oliveira @seocam

      Isn't it a bit dangerous? If the server doesn't send the parameter you are expecting you might end-up in an infinite loop. Isn't there a why to calculate the number of pages before hand?

      Choose File ...   File name...
      Cancel
    • 9fe63c7bd60deeb55e409a1d7dd173f5?s=40&d=identicon
      Sergio Oliveira @seocam

      The same applies for all loops while True loops in this file.

      Choose File ...   File name...
      Cancel
    • C6b14af78e51fba6beb90142971240cc?s=40&d=identicon
      Gustavo Jaruga Cruz @darksshades

      GitlabAPI doesn't provide any way to know how many pages there are... But when you input a nonexistent page or the parser fails, it returns an empty list and breaks the While.

      Choose File ...   File name...
      Cancel
    9fe63c7bd60deeb55e409a1d7dd173f5?s=40&d=identicon
    Sergio Oliveira started a discussion on the diff
    last updated by Gustavo Jaruga Cruz
    colab/proxy/gitlab/data_api.py
    41 76 break
    42 77  
    43   - page = page + 1
    44   -
    45 78 for element in json_data:
    46 79 project = GitlabProject()
    47   -
    48   - for field in GitlabProject._meta.fields:
    49   - if isinstance(field, DateTimeField):
    50   - value = parse(element[field.name])
    51   - else:
    52   - value = element[field.name]
    53   -
    54   - setattr(project, field.name, value)
    55   -
      80 + self.fill_object_data(element, project)
    2
    • 9fe63c7bd60deeb55e409a1d7dd173f5?s=40&d=identicon
      Sergio Oliveira @seocam

      What about GitlabProject(**element)? You would need to thread element to be in the right format but it might be a good idea. Perhaps a class method GitlabProject.from_api.

      Choose File ...   File name...
      Cancel
    • C6b14af78e51fba6beb90142971240cc?s=40&d=identicon
      Gustavo Jaruga Cruz @darksshades

      We didn't understand.... the function fill_object_data is used to fill the generic fields of GitlabProject, and any code bellow that are used to fill the specific ones.

      Choose File ...   File name...
      Cancel
  • 9fe63c7bd60deeb55e409a1d7dd173f5?s=40&d=identicon
    Sergio Oliveira @seocam

    Great progress here! What about the peer reviews?

    Choose File ...   File name...
    Cancel