add code to fetch individual reviews and wrap the results
Wrap the raw json with a data class that has properties so we don't have to spread knowledge of the JSON format all over. The request and response parsing code is taken from the elections repo "owner.py" module. Change-Id: I5013e7c89f550d385beb99cdc6f2ae2f1af38d64 Signed-off-by: Doug Hellmann <doug@doughellmann.com>
This commit is contained in:
parent
26bd017da1
commit
11d790f5d4
48
goal_tools/apis.py
Normal file
48
goal_tools/apis.py
Normal file
@ -0,0 +1,48 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import json
|
||||
import logging
|
||||
|
||||
import requests
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def requester(url, params={}, headers={}):
|
||||
"""A requests wrapper to consistently retry HTTPS queries"""
|
||||
|
||||
# Try up to 3 times
|
||||
retry = requests.Session()
|
||||
retry.mount("https://", requests.adapters.HTTPAdapter(max_retries=3))
|
||||
return retry.get(url=url, params=params, headers=headers)
|
||||
|
||||
|
||||
def decode_json(raw):
|
||||
"""Trap JSON decoding failures and provide more detailed errors"""
|
||||
|
||||
# Gerrit's REST API prepends a JSON-breaker to avoid XSS vulnerabilities
|
||||
if raw.text.startswith(")]}'"):
|
||||
trimmed = raw.text[4:]
|
||||
else:
|
||||
trimmed = raw.text
|
||||
|
||||
# Try to decode and bail with much detail if it fails
|
||||
try:
|
||||
decoded = json.loads(trimmed)
|
||||
except Exception:
|
||||
LOG.error(
|
||||
'\nrequest returned %s error to query:\n\n %s\n'
|
||||
'\nwith detail:\n\n %s\n',
|
||||
raw, raw.url, trimmed)
|
||||
raise
|
||||
return decoded
|
@ -10,12 +10,19 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import collections
|
||||
import datetime
|
||||
import fileinput
|
||||
import logging
|
||||
import urllib.parse
|
||||
|
||||
from goal_tools import apis
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
# The base URL to Gerrit REST API
|
||||
GERRIT_API_URL = 'https://review.openstack.org/'
|
||||
|
||||
|
||||
def parse_review_lists(filenames):
|
||||
"""Generator that produces review IDs as strings.
|
||||
@ -42,3 +49,57 @@ def parse_review_lists(filenames):
|
||||
else:
|
||||
# https://review.openstack.org/555353/
|
||||
yield parsed.path.lstrip('/').partition('/')[0]
|
||||
|
||||
|
||||
def query_gerrit(method, params={}):
|
||||
"""Query the Gerrit REST API"""
|
||||
url = GERRIT_API_URL + method
|
||||
LOG.debug('fetching %s', url)
|
||||
raw = apis.requester(
|
||||
url, params=params,
|
||||
headers={'Accept': 'application/json'})
|
||||
return apis.decode_json(raw)
|
||||
|
||||
|
||||
def _to_datetime(s):
|
||||
# Ignore the trailing decimal seconds.
|
||||
s = s.rpartition('.')[0]
|
||||
return datetime.datetime.strptime(s, '%Y-%m-%d %H:%M:%S')
|
||||
|
||||
|
||||
Participant = collections.namedtuple(
|
||||
'Participant', ['role', 'name', 'email', 'date'])
|
||||
|
||||
|
||||
class Review:
|
||||
|
||||
def __init__(self, id, data):
|
||||
self._id = id
|
||||
self._data = data
|
||||
|
||||
@property
|
||||
def url(self):
|
||||
return GERRIT_API_URL + self._id + '/'
|
||||
|
||||
@property
|
||||
def created(self):
|
||||
return _to_datetime(self._data['created'])
|
||||
|
||||
@property
|
||||
def participants(self):
|
||||
yield self.owner
|
||||
|
||||
@property
|
||||
def owner(self):
|
||||
owner = self._data['owner']
|
||||
return Participant(
|
||||
'owner',
|
||||
owner['name'],
|
||||
owner['email'],
|
||||
self.created,
|
||||
)
|
||||
|
||||
|
||||
def fetch_review(review_id):
|
||||
data = query_gerrit('changes/' + review_id + '/detail')
|
||||
return Review(review_id, data)
|
||||
|
550
goal_tools/tests/data/55535.json
Normal file
550
goal_tools/tests/data/55535.json
Normal file
@ -0,0 +1,550 @@
|
||||
{
|
||||
"id": "openstack%2Fblazar-dashboard~master~Icbecf09ff90d7b5e563df27fe1d8db2438ac6c4a",
|
||||
"project": "openstack/blazar-dashboard",
|
||||
"branch": "master",
|
||||
"topic": "requirements-stop-syncing",
|
||||
"hashtags": [],
|
||||
"change_id": "Icbecf09ff90d7b5e563df27fe1d8db2438ac6c4a",
|
||||
"subject": "add lower-constraints job",
|
||||
"status": "NEW",
|
||||
"created": "2018-03-22 16:05:45.000000000",
|
||||
"updated": "2018-04-26 06:32:01.000000000",
|
||||
"submit_type": "MERGE_IF_NECESSARY",
|
||||
"mergeable": true,
|
||||
"submittable": false,
|
||||
"insertions": 88,
|
||||
"deletions": 1,
|
||||
"_number": 555353,
|
||||
"owner": {
|
||||
"_account_id": 2472,
|
||||
"name": "Doug Hellmann",
|
||||
"email": "doug@doughellmann.com",
|
||||
"username": "doug-hellmann"
|
||||
},
|
||||
"labels": {
|
||||
"Verified": {
|
||||
"recommended": {
|
||||
"_account_id": 22348,
|
||||
"name": "Zuul",
|
||||
"username": "zuul"
|
||||
},
|
||||
"all": [
|
||||
{
|
||||
"_account_id": 9414,
|
||||
"name": "zhongshengping",
|
||||
"email": "chdzsp@163.com",
|
||||
"username": "ZhongShengping"
|
||||
},
|
||||
{
|
||||
"_account_id": 2472,
|
||||
"name": "Doug Hellmann",
|
||||
"email": "doug@doughellmann.com",
|
||||
"username": "doug-hellmann"
|
||||
},
|
||||
{
|
||||
"value": 1,
|
||||
"date": "2018-04-26 03:06:40.000000000",
|
||||
"_account_id": 22348,
|
||||
"name": "Zuul",
|
||||
"username": "zuul"
|
||||
},
|
||||
{
|
||||
"_account_id": 8878,
|
||||
"name": "Masahito Muroi",
|
||||
"email": "muroi.masahito@lab.ntt.co.jp",
|
||||
"username": "masa"
|
||||
},
|
||||
{
|
||||
"_account_id": 26297,
|
||||
"name": "kaka",
|
||||
"email": "huang.zhiping@99cloud.net",
|
||||
"username": "huang.zhiping"
|
||||
},
|
||||
{
|
||||
"_account_id": 841,
|
||||
"name": "Akihiro Motoki",
|
||||
"email": "amotoki@gmail.com",
|
||||
"username": "amotoki"
|
||||
},
|
||||
{
|
||||
"_account_id": 23840,
|
||||
"name": "Hiroaki Kobayashi",
|
||||
"email": "kobayashi.hiroaki@lab.ntt.co.jp",
|
||||
"username": "hiro-kobayashi"
|
||||
}
|
||||
],
|
||||
"values": {
|
||||
"-2": "Fails",
|
||||
"-1": "Doesn\u0027t seem to work",
|
||||
" 0": "No score",
|
||||
"+1": "Works for me",
|
||||
"+2": "Verified"
|
||||
},
|
||||
"value": 1,
|
||||
"default_value": 0
|
||||
},
|
||||
"Code-Review": {
|
||||
"approved": {
|
||||
"_account_id": 23840,
|
||||
"name": "Hiroaki Kobayashi",
|
||||
"email": "kobayashi.hiroaki@lab.ntt.co.jp",
|
||||
"username": "hiro-kobayashi"
|
||||
},
|
||||
"all": [
|
||||
{
|
||||
"value": 1,
|
||||
"date": "2018-04-23 03:17:42.000000000",
|
||||
"_account_id": 9414,
|
||||
"name": "zhongshengping",
|
||||
"email": "chdzsp@163.com",
|
||||
"username": "ZhongShengping"
|
||||
},
|
||||
{
|
||||
"value": 0,
|
||||
"_account_id": 2472,
|
||||
"name": "Doug Hellmann",
|
||||
"email": "doug@doughellmann.com",
|
||||
"username": "doug-hellmann"
|
||||
},
|
||||
{
|
||||
"value": 0,
|
||||
"_account_id": 22348,
|
||||
"name": "Zuul",
|
||||
"username": "zuul"
|
||||
},
|
||||
{
|
||||
"value": 2,
|
||||
"date": "2018-04-24 10:18:51.000000000",
|
||||
"_account_id": 8878,
|
||||
"name": "Masahito Muroi",
|
||||
"email": "muroi.masahito@lab.ntt.co.jp",
|
||||
"username": "masa"
|
||||
},
|
||||
{
|
||||
"value": 0,
|
||||
"_account_id": 26297,
|
||||
"name": "kaka",
|
||||
"email": "huang.zhiping@99cloud.net",
|
||||
"username": "huang.zhiping"
|
||||
},
|
||||
{
|
||||
"value": 1,
|
||||
"date": "2018-04-22 02:44:33.000000000",
|
||||
"_account_id": 841,
|
||||
"name": "Akihiro Motoki",
|
||||
"email": "amotoki@gmail.com",
|
||||
"username": "amotoki"
|
||||
},
|
||||
{
|
||||
"value": 2,
|
||||
"date": "2018-04-24 01:32:26.000000000",
|
||||
"_account_id": 23840,
|
||||
"name": "Hiroaki Kobayashi",
|
||||
"email": "kobayashi.hiroaki@lab.ntt.co.jp",
|
||||
"username": "hiro-kobayashi"
|
||||
}
|
||||
],
|
||||
"values": {
|
||||
"-2": "Do not merge",
|
||||
"-1": "This patch needs further work before it can be merged",
|
||||
" 0": "No score",
|
||||
"+1": "Looks good to me, but someone else must approve",
|
||||
"+2": "Looks good to me (core reviewer)"
|
||||
},
|
||||
"default_value": 0
|
||||
},
|
||||
"Workflow": {
|
||||
"approved": {
|
||||
"_account_id": 8878,
|
||||
"name": "Masahito Muroi",
|
||||
"email": "muroi.masahito@lab.ntt.co.jp",
|
||||
"username": "masa"
|
||||
},
|
||||
"all": [
|
||||
{
|
||||
"_account_id": 9414,
|
||||
"name": "zhongshengping",
|
||||
"email": "chdzsp@163.com",
|
||||
"username": "ZhongShengping"
|
||||
},
|
||||
{
|
||||
"value": 0,
|
||||
"_account_id": 2472,
|
||||
"name": "Doug Hellmann",
|
||||
"email": "doug@doughellmann.com",
|
||||
"username": "doug-hellmann"
|
||||
},
|
||||
{
|
||||
"_account_id": 22348,
|
||||
"name": "Zuul",
|
||||
"username": "zuul"
|
||||
},
|
||||
{
|
||||
"value": 1,
|
||||
"date": "2018-04-26 06:32:01.000000000",
|
||||
"_account_id": 8878,
|
||||
"name": "Masahito Muroi",
|
||||
"email": "muroi.masahito@lab.ntt.co.jp",
|
||||
"username": "masa"
|
||||
},
|
||||
{
|
||||
"_account_id": 26297,
|
||||
"name": "kaka",
|
||||
"email": "huang.zhiping@99cloud.net",
|
||||
"username": "huang.zhiping"
|
||||
},
|
||||
{
|
||||
"_account_id": 841,
|
||||
"name": "Akihiro Motoki",
|
||||
"email": "amotoki@gmail.com",
|
||||
"username": "amotoki"
|
||||
},
|
||||
{
|
||||
"value": 0,
|
||||
"_account_id": 23840,
|
||||
"name": "Hiroaki Kobayashi",
|
||||
"email": "kobayashi.hiroaki@lab.ntt.co.jp",
|
||||
"username": "hiro-kobayashi"
|
||||
}
|
||||
],
|
||||
"values": {
|
||||
"-1": "Work in progress",
|
||||
" 0": "Ready for reviews",
|
||||
"+1": "Approved"
|
||||
},
|
||||
"default_value": 0
|
||||
}
|
||||
},
|
||||
"permitted_labels": {},
|
||||
"removable_reviewers": [],
|
||||
"reviewers": {
|
||||
"CC": [
|
||||
{
|
||||
"_account_id": 2472,
|
||||
"name": "Doug Hellmann",
|
||||
"email": "doug@doughellmann.com",
|
||||
"username": "doug-hellmann"
|
||||
}
|
||||
],
|
||||
"REVIEWER": [
|
||||
{
|
||||
"_account_id": 841,
|
||||
"name": "Akihiro Motoki",
|
||||
"email": "amotoki@gmail.com",
|
||||
"username": "amotoki"
|
||||
},
|
||||
{
|
||||
"_account_id": 8878,
|
||||
"name": "Masahito Muroi",
|
||||
"email": "muroi.masahito@lab.ntt.co.jp",
|
||||
"username": "masa"
|
||||
},
|
||||
{
|
||||
"_account_id": 9414,
|
||||
"name": "zhongshengping",
|
||||
"email": "chdzsp@163.com",
|
||||
"username": "ZhongShengping"
|
||||
},
|
||||
{
|
||||
"_account_id": 22348,
|
||||
"name": "Zuul",
|
||||
"username": "zuul"
|
||||
},
|
||||
{
|
||||
"_account_id": 23840,
|
||||
"name": "Hiroaki Kobayashi",
|
||||
"email": "kobayashi.hiroaki@lab.ntt.co.jp",
|
||||
"username": "hiro-kobayashi"
|
||||
},
|
||||
{
|
||||
"_account_id": 26297,
|
||||
"name": "kaka",
|
||||
"email": "huang.zhiping@99cloud.net",
|
||||
"username": "huang.zhiping"
|
||||
}
|
||||
]
|
||||
},
|
||||
"reviewer_updates": [],
|
||||
"messages": [
|
||||
{
|
||||
"id": "df7087c5_9e5659ce",
|
||||
"author": {
|
||||
"_account_id": 2472,
|
||||
"name": "Doug Hellmann",
|
||||
"email": "doug@doughellmann.com",
|
||||
"username": "doug-hellmann"
|
||||
},
|
||||
"date": "2018-03-22 16:05:45.000000000",
|
||||
"message": "Uploaded patch set 1.",
|
||||
"_revision_number": 1
|
||||
},
|
||||
{
|
||||
"id": "df7087c5_b2efd11b",
|
||||
"author": {
|
||||
"_account_id": 22348,
|
||||
"name": "Zuul",
|
||||
"username": "zuul"
|
||||
},
|
||||
"date": "2018-03-22 16:31:55.000000000",
|
||||
"message": "Patch Set 1: Verified-1\n\nBuild failed (check pipeline). For information on how to proceed, see\nhttp://docs.openstack.org/infra/manual/developers.html#automated-testing\n\n\n- openstack-tox-pep8 http://logs.openstack.org/53/555353/1/check/openstack-tox-pep8/341dced/ : SUCCESS in 5m 12s\n- openstack-tox-py27 http://logs.openstack.org/53/555353/1/check/openstack-tox-py27/51d2274/ : SUCCESS in 5m 04s\n- openstack-tox-py35 http://logs.openstack.org/53/555353/1/check/openstack-tox-py35/8749020/ : SUCCESS in 5m 05s\n- build-openstack-sphinx-docs http://logs.openstack.org/53/555353/1/check/build-openstack-sphinx-docs/c5f3add/html/ : SUCCESS in 4m 00s\n- openstack-tox-lower-constraints http://logs.openstack.org/53/555353/1/check/openstack-tox-lower-constraints/5f9282b/ : FAILURE in 5m 13s",
|
||||
"_revision_number": 1
|
||||
},
|
||||
{
|
||||
"id": "df7087c5_325181da",
|
||||
"author": {
|
||||
"_account_id": 2472,
|
||||
"name": "Doug Hellmann",
|
||||
"email": "doug@doughellmann.com",
|
||||
"username": "doug-hellmann"
|
||||
},
|
||||
"date": "2018-03-22 16:33:45.000000000",
|
||||
"message": "Patch Set 1:\n\nIt looks like this repo is going to need a special job variant that installs horizon.",
|
||||
"_revision_number": 1
|
||||
},
|
||||
{
|
||||
"id": "bf659307_4452f63c",
|
||||
"author": {
|
||||
"_account_id": 8878,
|
||||
"name": "Masahito Muroi",
|
||||
"email": "muroi.masahito@lab.ntt.co.jp",
|
||||
"username": "masa"
|
||||
},
|
||||
"date": "2018-04-02 08:31:55.000000000",
|
||||
"message": "Patch Set 1:\n\nHow do other dashboard repositories handle the situation where horizon is required? Only this repo needs a special job?",
|
||||
"_revision_number": 1
|
||||
},
|
||||
{
|
||||
"id": "bf659307_03e0d1f9",
|
||||
"author": {
|
||||
"_account_id": 23840,
|
||||
"name": "Hiroaki Kobayashi",
|
||||
"email": "kobayashi.hiroaki@lab.ntt.co.jp",
|
||||
"username": "hiro-kobayashi"
|
||||
},
|
||||
"date": "2018-04-10 06:07:47.000000000",
|
||||
"message": "Patch Set 2: Patch Set 1 was rebased",
|
||||
"_revision_number": 2
|
||||
},
|
||||
{
|
||||
"id": "bf659307_43656944",
|
||||
"author": {
|
||||
"_account_id": 22348,
|
||||
"name": "Zuul",
|
||||
"username": "zuul"
|
||||
},
|
||||
"date": "2018-04-10 06:24:11.000000000",
|
||||
"message": "Patch Set 2: Verified-1\n\nBuild failed (check pipeline). For information on how to proceed, see\nhttp://docs.openstack.org/infra/manual/developers.html#automated-testing\n\n\n- openstack-tox-pep8 http://logs.openstack.org/53/555353/2/check/openstack-tox-pep8/c4b9713/ : SUCCESS in 5m 18s\n- openstack-tox-py27 http://logs.openstack.org/53/555353/2/check/openstack-tox-py27/e821d02/ : SUCCESS in 5m 22s\n- requirements-check http://logs.openstack.org/53/555353/2/check/requirements-check/b7a4041/ : FAILURE in 3m 18s\n- openstack-tox-py35 http://logs.openstack.org/53/555353/2/check/openstack-tox-py35/f1a84ef/ : SUCCESS in 4m 59s\n- build-openstack-sphinx-docs http://logs.openstack.org/53/555353/2/check/build-openstack-sphinx-docs/2bd9664/html/ : SUCCESS in 6m 22s\n- openstack-tox-lower-constraints http://logs.openstack.org/53/555353/2/check/openstack-tox-lower-constraints/95d4f67/ : SUCCESS in 4m 11s",
|
||||
"_revision_number": 2
|
||||
},
|
||||
{
|
||||
"id": "bf659307_b1d7e44f",
|
||||
"author": {
|
||||
"_account_id": 26297,
|
||||
"name": "kaka",
|
||||
"email": "huang.zhiping@99cloud.net",
|
||||
"username": "huang.zhiping"
|
||||
},
|
||||
"date": "2018-04-10 12:24:36.000000000",
|
||||
"message": "Patch Set 2: Code-Review+1",
|
||||
"_revision_number": 2
|
||||
},
|
||||
{
|
||||
"id": "9f6a8fd7_851fd7b5",
|
||||
"author": {
|
||||
"_account_id": 2472,
|
||||
"name": "Doug Hellmann",
|
||||
"email": "doug@doughellmann.com",
|
||||
"username": "doug-hellmann"
|
||||
},
|
||||
"date": "2018-04-20 20:29:17.000000000",
|
||||
"message": "Uploaded patch set 3.",
|
||||
"_revision_number": 3
|
||||
},
|
||||
{
|
||||
"id": "9f6a8fd7_b8fa3eba",
|
||||
"author": {
|
||||
"_account_id": 22348,
|
||||
"name": "Zuul",
|
||||
"username": "zuul"
|
||||
},
|
||||
"date": "2018-04-20 20:55:30.000000000",
|
||||
"message": "Patch Set 3: Verified-1\n\nBuild failed (check pipeline). For information on how to proceed, see\nhttp://docs.openstack.org/infra/manual/developers.html#automated-testing\n\n\n- openstack-tox-pep8 http://logs.openstack.org/53/555353/3/check/openstack-tox-pep8/262ed55/ : SUCCESS in 6m 06s\n- openstack-tox-py27 http://logs.openstack.org/53/555353/3/check/openstack-tox-py27/c69f570/ : SUCCESS in 5m 11s\n- requirements-check http://logs.openstack.org/53/555353/3/check/requirements-check/24e8e67/ : SUCCESS in 3m 02s\n- openstack-tox-py35 http://logs.openstack.org/53/555353/3/check/openstack-tox-py35/78cc5d4/ : SUCCESS in 6m 20s\n- build-openstack-sphinx-docs http://logs.openstack.org/53/555353/3/check/build-openstack-sphinx-docs/4eac6f4/html/ : SUCCESS in 4m 33s\n- openstack-tox-lower-constraints http://logs.openstack.org/53/555353/3/check/openstack-tox-lower-constraints/eb48f89/ : FAILURE in 3m 21s",
|
||||
"_revision_number": 3
|
||||
},
|
||||
{
|
||||
"id": "9f6a8fd7_cc2f2f86",
|
||||
"author": {
|
||||
"_account_id": 841,
|
||||
"name": "Akihiro Motoki",
|
||||
"email": "amotoki@gmail.com",
|
||||
"username": "amotoki"
|
||||
},
|
||||
"date": "2018-04-22 02:25:01.000000000",
|
||||
"message": "Patch Set 3:\n\nl-c job fails as there is no blazarcient 1.0.0 on PyPI.",
|
||||
"_revision_number": 3
|
||||
},
|
||||
{
|
||||
"id": "9f6a8fd7_4c59df32",
|
||||
"author": {
|
||||
"_account_id": 841,
|
||||
"name": "Akihiro Motoki",
|
||||
"email": "amotoki@gmail.com",
|
||||
"username": "amotoki"
|
||||
},
|
||||
"date": "2018-04-22 02:28:56.000000000",
|
||||
"message": "Uploaded patch set 4.",
|
||||
"_revision_number": 4
|
||||
},
|
||||
{
|
||||
"id": "9f6a8fd7_6c7703a2",
|
||||
"author": {
|
||||
"_account_id": 22348,
|
||||
"name": "Zuul",
|
||||
"username": "zuul"
|
||||
},
|
||||
"date": "2018-04-22 02:39:16.000000000",
|
||||
"message": "Patch Set 4: Verified+1\n\nBuild succeeded (check pipeline).\n\n- openstack-tox-pep8 http://logs.openstack.org/53/555353/4/check/openstack-tox-pep8/a417873/ : SUCCESS in 4m 32s\n- openstack-tox-py27 http://logs.openstack.org/53/555353/4/check/openstack-tox-py27/7e6f704/ : SUCCESS in 5m 57s\n- requirements-check http://logs.openstack.org/53/555353/4/check/requirements-check/b1ed799/ : SUCCESS in 2m 46s\n- openstack-tox-py35 http://logs.openstack.org/53/555353/4/check/openstack-tox-py35/2b52b85/ : SUCCESS in 5m 02s\n- build-openstack-sphinx-docs http://logs.openstack.org/53/555353/4/check/build-openstack-sphinx-docs/f8955d6/html/ : SUCCESS in 3m 23s\n- openstack-tox-lower-constraints http://logs.openstack.org/53/555353/4/check/openstack-tox-lower-constraints/5887317/ : SUCCESS in 4m 47s",
|
||||
"_revision_number": 4
|
||||
},
|
||||
{
|
||||
"id": "9f6a8fd7_6c9ce3bf",
|
||||
"author": {
|
||||
"_account_id": 841,
|
||||
"name": "Akihiro Motoki",
|
||||
"email": "amotoki@gmail.com",
|
||||
"username": "amotoki"
|
||||
},
|
||||
"date": "2018-04-22 02:44:33.000000000",
|
||||
"message": "Patch Set 4: Code-Review+1\n\nI am fine with this from my knowledge on this.",
|
||||
"_revision_number": 4
|
||||
},
|
||||
{
|
||||
"id": "9f6a8fd7_0caf627d",
|
||||
"author": {
|
||||
"_account_id": 9414,
|
||||
"name": "zhongshengping",
|
||||
"email": "chdzsp@163.com",
|
||||
"username": "ZhongShengping"
|
||||
},
|
||||
"date": "2018-04-23 03:17:42.000000000",
|
||||
"message": "Patch Set 4: Code-Review+1",
|
||||
"_revision_number": 4
|
||||
},
|
||||
{
|
||||
"id": "9f6a8fd7_9576fc6f",
|
||||
"author": {
|
||||
"_account_id": 23840,
|
||||
"name": "Hiroaki Kobayashi",
|
||||
"email": "kobayashi.hiroaki@lab.ntt.co.jp",
|
||||
"username": "hiro-kobayashi"
|
||||
},
|
||||
"date": "2018-04-24 01:32:26.000000000",
|
||||
"message": "Patch Set 4: Code-Review+2",
|
||||
"_revision_number": 4
|
||||
},
|
||||
{
|
||||
"id": "9f6a8fd7_6e9112f1",
|
||||
"author": {
|
||||
"_account_id": 8878,
|
||||
"name": "Masahito Muroi",
|
||||
"email": "muroi.masahito@lab.ntt.co.jp",
|
||||
"username": "masa"
|
||||
},
|
||||
"date": "2018-04-24 10:18:51.000000000",
|
||||
"message": "Patch Set 4: Workflow+1 Code-Review+2",
|
||||
"_revision_number": 4
|
||||
},
|
||||
{
|
||||
"id": "9f6a8fd7_8e8ca6d7",
|
||||
"author": {
|
||||
"_account_id": 22348,
|
||||
"name": "Zuul",
|
||||
"username": "zuul"
|
||||
},
|
||||
"date": "2018-04-24 10:19:23.000000000",
|
||||
"message": "Patch Set 4: -Verified\n\nStarting gate jobs.",
|
||||
"_revision_number": 4
|
||||
},
|
||||
{
|
||||
"id": "9f6a8fd7_31029b0b",
|
||||
"author": {
|
||||
"_account_id": 22348,
|
||||
"name": "Zuul",
|
||||
"username": "zuul"
|
||||
},
|
||||
"date": "2018-04-24 10:30:45.000000000",
|
||||
"message": "Patch Set 4: Verified-2\n\nBuild failed (gate pipeline). For information on how to proceed, see\nhttp://docs.openstack.org/infra/manual/developers.html#automated-testing\n\n\n- openstack-tox-pep8 http://logs.openstack.org/53/555353/4/gate/openstack-tox-pep8/c8de9ef/ : SUCCESS in 5m 04s\n- openstack-tox-py27 http://logs.openstack.org/53/555353/4/gate/openstack-tox-py27/2b6f268/ : FAILURE in 5m 09s\n- requirements-check http://logs.openstack.org/53/555353/4/gate/requirements-check/8ff01b9/ : SUCCESS in 4m 51s\n- openstack-tox-py35 http://logs.openstack.org/53/555353/4/gate/openstack-tox-py35/3879fd8/ : FAILURE in 5m 56s\n- build-openstack-sphinx-docs http://logs.openstack.org/53/555353/4/gate/build-openstack-sphinx-docs/fd2b88c/html/ : SUCCESS in 4m 09s\n- openstack-tox-lower-constraints http://logs.openstack.org/53/555353/4/gate/openstack-tox-lower-constraints/121deb2/ : FAILURE in 5m 36s",
|
||||
"_revision_number": 4
|
||||
},
|
||||
{
|
||||
"id": "9f6a8fd7_91acb09f",
|
||||
"author": {
|
||||
"_account_id": 23840,
|
||||
"name": "Hiroaki Kobayashi",
|
||||
"email": "kobayashi.hiroaki@lab.ntt.co.jp",
|
||||
"username": "hiro-kobayashi"
|
||||
},
|
||||
"date": "2018-04-25 07:58:51.000000000",
|
||||
"message": "Patch Set 4:\n\nrecheck",
|
||||
"_revision_number": 4
|
||||
},
|
||||
{
|
||||
"id": "9f6a8fd7_51f7f88b",
|
||||
"author": {
|
||||
"_account_id": 22348,
|
||||
"name": "Zuul",
|
||||
"username": "zuul"
|
||||
},
|
||||
"date": "2018-04-25 08:06:19.000000000",
|
||||
"message": "Patch Set 4: Verified-1\n\nBuild failed (check pipeline). For information on how to proceed, see\nhttp://docs.openstack.org/infra/manual/developers.html#automated-testing\n\n\n- openstack-tox-pep8 http://logs.openstack.org/53/555353/4/check/openstack-tox-pep8/4153204/ : SUCCESS in 5m 55s\n- openstack-tox-py27 http://logs.openstack.org/53/555353/4/check/openstack-tox-py27/3e7f01f/ : FAILURE in 5m 01s\n- requirements-check http://logs.openstack.org/53/555353/4/check/requirements-check/cd10778/ : SUCCESS in 3m 43s\n- openstack-tox-py35 http://logs.openstack.org/53/555353/4/check/openstack-tox-py35/ce4b440/ : FAILURE in 5m 10s\n- build-openstack-sphinx-docs http://logs.openstack.org/53/555353/4/check/build-openstack-sphinx-docs/8fee641/html/ : SUCCESS in 5m 15s\n- openstack-tox-lower-constraints http://logs.openstack.org/53/555353/4/check/openstack-tox-lower-constraints/9609359/ : FAILURE in 5m 47s",
|
||||
"_revision_number": 4
|
||||
},
|
||||
{
|
||||
"id": "9f6a8fd7_bf973bec",
|
||||
"author": {
|
||||
"_account_id": 841,
|
||||
"name": "Akihiro Motoki",
|
||||
"email": "amotoki@gmail.com",
|
||||
"username": "amotoki"
|
||||
},
|
||||
"date": "2018-04-25 09:35:55.000000000",
|
||||
"message": "Patch Set 4:\n\nunit test failures is due to the horizon planned change. As announced before [0], horizon test helper is no longer set up by default after Rocky-1 [1]. Dashboard tests which still depends on mox must declare use_mox\u003dTrue.\n\n[0] http://lists.openstack.org/pipermail/openstack-dev/2018-March/128486.html\n[1] http://lists.openstack.org/pipermail/openstack-dev/2018-April/129648.html",
|
||||
"_revision_number": 4
|
||||
},
|
||||
{
|
||||
"id": "9f6a8fd7_3c5b22f6",
|
||||
"author": {
|
||||
"_account_id": 23840,
|
||||
"name": "Hiroaki Kobayashi",
|
||||
"email": "kobayashi.hiroaki@lab.ntt.co.jp",
|
||||
"username": "hiro-kobayashi"
|
||||
},
|
||||
"date": "2018-04-26 02:59:34.000000000",
|
||||
"message": "Patch Set 5: Patch Set 4 was rebased",
|
||||
"_revision_number": 5
|
||||
},
|
||||
{
|
||||
"id": "9f6a8fd7_5cd5b61a",
|
||||
"author": {
|
||||
"_account_id": 22348,
|
||||
"name": "Zuul",
|
||||
"username": "zuul"
|
||||
},
|
||||
"date": "2018-04-26 03:06:40.000000000",
|
||||
"message": "Patch Set 5: Verified+1\n\nBuild succeeded (check pipeline).\n\n- openstack-tox-pep8 http://logs.openstack.org/53/555353/5/check/openstack-tox-pep8/18c1cc5/ : SUCCESS in 5m 26s\n- openstack-tox-py27 http://logs.openstack.org/53/555353/5/check/openstack-tox-py27/ea97804/ : SUCCESS in 4m 56s\n- requirements-check http://logs.openstack.org/53/555353/5/check/requirements-check/04dff5c/ : SUCCESS in 3m 05s\n- openstack-tox-py35 http://logs.openstack.org/53/555353/5/check/openstack-tox-py35/e961aef/ : SUCCESS in 5m 08s\n- build-openstack-sphinx-docs http://logs.openstack.org/53/555353/5/check/build-openstack-sphinx-docs/fa408bc/html/ : SUCCESS in 4m 04s\n- openstack-tox-lower-constraints http://logs.openstack.org/53/555353/5/check/openstack-tox-lower-constraints/16d68c9/ : SUCCESS in 5m 18s",
|
||||
"_revision_number": 5
|
||||
},
|
||||
{
|
||||
"id": "9f6a8fd7_bab17246",
|
||||
"author": {
|
||||
"_account_id": 8878,
|
||||
"name": "Masahito Muroi",
|
||||
"email": "muroi.masahito@lab.ntt.co.jp",
|
||||
"username": "masa"
|
||||
},
|
||||
"date": "2018-04-26 06:32:01.000000000",
|
||||
"message": "Patch Set 5: Workflow+1",
|
||||
"_revision_number": 5
|
||||
}
|
||||
]
|
||||
}
|
320
goal_tools/tests/data/561507.json
Normal file
320
goal_tools/tests/data/561507.json
Normal file
@ -0,0 +1,320 @@
|
||||
{
|
||||
"id": "openstack%2Freleases~master~I08015ec6c5e0a0f3cd552c2b5a077c0fb86df148",
|
||||
"project": "openstack/releases",
|
||||
"branch": "master",
|
||||
"hashtags": [],
|
||||
"change_id": "I08015ec6c5e0a0f3cd552c2b5a077c0fb86df148",
|
||||
"subject": "Change storlets release model to cycle-with-milestones",
|
||||
"status": "MERGED",
|
||||
"created": "2018-04-16 04:50:17.000000000",
|
||||
"updated": "2018-04-19 12:57:36.000000000",
|
||||
"submitted": "2018-04-19 12:57:36.000000000",
|
||||
"submittable": false,
|
||||
"insertions": 6,
|
||||
"deletions": 1,
|
||||
"_number": 561507,
|
||||
"owner": {
|
||||
"_account_id": 4608,
|
||||
"name": "Kota Tsuyuzaki",
|
||||
"email": "tsuyuzaki.kota@lab.ntt.co.jp",
|
||||
"username": "tsuyuzaki-kota"
|
||||
},
|
||||
"labels": {
|
||||
"Verified": {
|
||||
"approved": {
|
||||
"_account_id": 22348,
|
||||
"name": "Zuul",
|
||||
"username": "zuul"
|
||||
},
|
||||
"all": [
|
||||
{
|
||||
"value": 0,
|
||||
"_account_id": 2472,
|
||||
"name": "Doug Hellmann",
|
||||
"email": "doug@doughellmann.com",
|
||||
"username": "doug-hellmann"
|
||||
},
|
||||
{
|
||||
"value": 0,
|
||||
"_account_id": 9816,
|
||||
"name": "Takashi Kajinami",
|
||||
"email": "kajinamit@nttdata.co.jp",
|
||||
"username": "kajinamit"
|
||||
},
|
||||
{
|
||||
"value": 2,
|
||||
"date": "2018-04-19 12:57:35.000000000",
|
||||
"_account_id": 22348,
|
||||
"name": "Zuul",
|
||||
"username": "zuul"
|
||||
},
|
||||
{
|
||||
"value": 0,
|
||||
"_account_id": 308,
|
||||
"name": "Thierry Carrez",
|
||||
"email": "thierry@openstack.org",
|
||||
"username": "ttx"
|
||||
}
|
||||
],
|
||||
"values": {
|
||||
"-2": "Fails",
|
||||
"-1": "Doesn\u0027t seem to work",
|
||||
" 0": "No score",
|
||||
"+1": "Works for me",
|
||||
"+2": "Verified"
|
||||
},
|
||||
"default_value": 0
|
||||
},
|
||||
"Code-Review": {
|
||||
"approved": {
|
||||
"_account_id": 2472,
|
||||
"name": "Doug Hellmann",
|
||||
"email": "doug@doughellmann.com",
|
||||
"username": "doug-hellmann"
|
||||
},
|
||||
"all": [
|
||||
{
|
||||
"value": 2,
|
||||
"date": "2018-04-16 14:45:28.000000000",
|
||||
"_account_id": 2472,
|
||||
"name": "Doug Hellmann",
|
||||
"email": "doug@doughellmann.com",
|
||||
"username": "doug-hellmann"
|
||||
},
|
||||
{
|
||||
"value": 1,
|
||||
"date": "2018-04-16 07:13:17.000000000",
|
||||
"_account_id": 9816,
|
||||
"name": "Takashi Kajinami",
|
||||
"email": "kajinamit@nttdata.co.jp",
|
||||
"username": "kajinamit"
|
||||
},
|
||||
{
|
||||
"value": 0,
|
||||
"_account_id": 22348,
|
||||
"name": "Zuul",
|
||||
"username": "zuul"
|
||||
},
|
||||
{
|
||||
"value": 2,
|
||||
"date": "2018-04-18 10:27:59.000000000",
|
||||
"_account_id": 308,
|
||||
"name": "Thierry Carrez",
|
||||
"email": "thierry@openstack.org",
|
||||
"username": "ttx"
|
||||
}
|
||||
],
|
||||
"values": {
|
||||
"-2": "Do not merge",
|
||||
"-1": "This patch needs further work before it can be merged",
|
||||
" 0": "No score",
|
||||
"+1": "Looks good to me, but someone else must approve",
|
||||
"+2": "Looks good to me (core reviewer)"
|
||||
},
|
||||
"default_value": 0
|
||||
},
|
||||
"Workflow": {
|
||||
"approved": {
|
||||
"_account_id": 2472,
|
||||
"name": "Doug Hellmann",
|
||||
"email": "doug@doughellmann.com",
|
||||
"username": "doug-hellmann"
|
||||
},
|
||||
"all": [
|
||||
{
|
||||
"value": 1,
|
||||
"date": "2018-04-19 12:49:44.000000000",
|
||||
"_account_id": 2472,
|
||||
"name": "Doug Hellmann",
|
||||
"email": "doug@doughellmann.com",
|
||||
"username": "doug-hellmann"
|
||||
},
|
||||
{
|
||||
"value": 0,
|
||||
"_account_id": 9816,
|
||||
"name": "Takashi Kajinami",
|
||||
"email": "kajinamit@nttdata.co.jp",
|
||||
"username": "kajinamit"
|
||||
},
|
||||
{
|
||||
"value": 0,
|
||||
"_account_id": 22348,
|
||||
"name": "Zuul",
|
||||
"username": "zuul"
|
||||
},
|
||||
{
|
||||
"value": 0,
|
||||
"_account_id": 308,
|
||||
"name": "Thierry Carrez",
|
||||
"email": "thierry@openstack.org",
|
||||
"username": "ttx"
|
||||
}
|
||||
],
|
||||
"values": {
|
||||
"-1": "Work in progress",
|
||||
" 0": "Ready for reviews",
|
||||
"+1": "Approved"
|
||||
},
|
||||
"default_value": 0
|
||||
}
|
||||
},
|
||||
"permitted_labels": {},
|
||||
"removable_reviewers": [],
|
||||
"reviewers": {
|
||||
"REVIEWER": [
|
||||
{
|
||||
"_account_id": 308,
|
||||
"name": "Thierry Carrez",
|
||||
"email": "thierry@openstack.org",
|
||||
"username": "ttx"
|
||||
},
|
||||
{
|
||||
"_account_id": 2472,
|
||||
"name": "Doug Hellmann",
|
||||
"email": "doug@doughellmann.com",
|
||||
"username": "doug-hellmann"
|
||||
},
|
||||
{
|
||||
"_account_id": 9816,
|
||||
"name": "Takashi Kajinami",
|
||||
"email": "kajinamit@nttdata.co.jp",
|
||||
"username": "kajinamit"
|
||||
},
|
||||
{
|
||||
"_account_id": 22348,
|
||||
"name": "Zuul",
|
||||
"username": "zuul"
|
||||
}
|
||||
]
|
||||
},
|
||||
"reviewer_updates": [],
|
||||
"messages": [
|
||||
{
|
||||
"id": "9f6a8fd7_80b757d9",
|
||||
"author": {
|
||||
"_account_id": 4608,
|
||||
"name": "Kota Tsuyuzaki",
|
||||
"email": "tsuyuzaki.kota@lab.ntt.co.jp",
|
||||
"username": "tsuyuzaki-kota"
|
||||
},
|
||||
"date": "2018-04-16 04:50:17.000000000",
|
||||
"message": "Uploaded patch set 1.",
|
||||
"_revision_number": 1
|
||||
},
|
||||
{
|
||||
"id": "9f6a8fd7_8005177d",
|
||||
"author": {
|
||||
"_account_id": 22348,
|
||||
"name": "Zuul",
|
||||
"username": "zuul"
|
||||
},
|
||||
"date": "2018-04-16 04:57:28.000000000",
|
||||
"message": "Patch Set 1: Verified-1\n\nBuild failed (check pipeline). For information on how to proceed, see\nhttp://docs.openstack.org/infra/manual/developers.html#automated-testing\n\n\n- openstack-tox-validate http://logs.openstack.org/07/561507/1/check/openstack-tox-validate/5722934/ : FAILURE in 3m 01s\n- releases-tox-list-changes http://logs.openstack.org/07/561507/1/check/releases-tox-list-changes/61376fd/ : SUCCESS in 2m 30s\n- build-openstack-sphinx-docs http://logs.openstack.org/07/561507/1/check/build-openstack-sphinx-docs/db4fb75/html/ : SUCCESS in 6m 19s",
|
||||
"_revision_number": 1
|
||||
},
|
||||
{
|
||||
"id": "9f6a8fd7_e0ef33ae",
|
||||
"author": {
|
||||
"_account_id": 4608,
|
||||
"name": "Kota Tsuyuzaki",
|
||||
"email": "tsuyuzaki.kota@lab.ntt.co.jp",
|
||||
"username": "tsuyuzaki-kota"
|
||||
},
|
||||
"date": "2018-04-16 05:00:21.000000000",
|
||||
"message": "Uploaded patch set 2.",
|
||||
"_revision_number": 2
|
||||
},
|
||||
{
|
||||
"id": "9f6a8fd7_400dbf8e",
|
||||
"author": {
|
||||
"_account_id": 22348,
|
||||
"name": "Zuul",
|
||||
"username": "zuul"
|
||||
},
|
||||
"date": "2018-04-16 05:06:06.000000000",
|
||||
"message": "Patch Set 2: Verified+1\n\nBuild succeeded (check pipeline).\n\n- openstack-tox-validate http://logs.openstack.org/07/561507/2/check/openstack-tox-validate/95549e4/ : SUCCESS in 3m 09s\n- releases-tox-list-changes http://logs.openstack.org/07/561507/2/check/releases-tox-list-changes/09f1182/ : SUCCESS in 3m 33s\n- build-openstack-sphinx-docs http://logs.openstack.org/07/561507/2/check/build-openstack-sphinx-docs/2e6c15e/html/ : SUCCESS in 5m 16s",
|
||||
"_revision_number": 2
|
||||
},
|
||||
{
|
||||
"id": "9f6a8fd7_9688fd71",
|
||||
"author": {
|
||||
"_account_id": 9816,
|
||||
"name": "Takashi Kajinami",
|
||||
"email": "kajinamit@nttdata.co.jp",
|
||||
"username": "kajinamit"
|
||||
},
|
||||
"date": "2018-04-16 07:13:17.000000000",
|
||||
"message": "Patch Set 2: Code-Review+1\n\nLGTM.\n\nCurrently, Swift, on which storlets depend on, follows cycle-with-intermediary.\nBut I know that they also have releases based on milestone, so I think that having milestone based release at least can work for us.",
|
||||
"_revision_number": 2
|
||||
},
|
||||
{
|
||||
"id": "9f6a8fd7_3824730d",
|
||||
"author": {
|
||||
"_account_id": 2472,
|
||||
"name": "Doug Hellmann",
|
||||
"email": "doug@doughellmann.com",
|
||||
"username": "doug-hellmann"
|
||||
},
|
||||
"date": "2018-04-16 14:45:28.000000000",
|
||||
"message": "Patch Set 2: Code-Review+2\n\nThis looks good. The deadline for the first milestone is in a few days, so I will leave this open in case you want to update the SHA to refer to a newer commit.",
|
||||
"_revision_number": 2
|
||||
},
|
||||
{
|
||||
"id": "9f6a8fd7_9741970e",
|
||||
"author": {
|
||||
"_account_id": 308,
|
||||
"name": "Thierry Carrez",
|
||||
"email": "thierry@openstack.org",
|
||||
"username": "ttx"
|
||||
},
|
||||
"date": "2018-04-18 10:27:59.000000000",
|
||||
"message": "Patch Set 2: Code-Review+2\n\nLooking good, leaving it open until Thursday for last-minute SHA updates.",
|
||||
"_revision_number": 2
|
||||
},
|
||||
{
|
||||
"id": "9f6a8fd7_5318d3d1",
|
||||
"author": {
|
||||
"_account_id": 2472,
|
||||
"name": "Doug Hellmann",
|
||||
"email": "doug@doughellmann.com",
|
||||
"username": "doug-hellmann"
|
||||
},
|
||||
"date": "2018-04-19 12:49:44.000000000",
|
||||
"message": "Patch Set 2: Workflow+1",
|
||||
"_revision_number": 2
|
||||
},
|
||||
{
|
||||
"id": "9f6a8fd7_f30ec78b",
|
||||
"author": {
|
||||
"_account_id": 22348,
|
||||
"name": "Zuul",
|
||||
"username": "zuul"
|
||||
},
|
||||
"date": "2018-04-19 12:49:56.000000000",
|
||||
"message": "Patch Set 2: -Verified\n\nStarting gate jobs.",
|
||||
"_revision_number": 2
|
||||
},
|
||||
{
|
||||
"id": "9f6a8fd7_93550b20",
|
||||
"author": {
|
||||
"_account_id": 22348,
|
||||
"name": "Zuul",
|
||||
"username": "zuul"
|
||||
},
|
||||
"date": "2018-04-19 12:57:35.000000000",
|
||||
"message": "Patch Set 2: Verified+2\n\nBuild succeeded (gate pipeline).\n\n- openstack-tox-validate http://logs.openstack.org/07/561507/2/gate/openstack-tox-validate/c2390b6/ : SUCCESS in 4m 49s\n- build-openstack-sphinx-docs http://logs.openstack.org/07/561507/2/gate/build-openstack-sphinx-docs/f632774/html/ : SUCCESS in 6m 13s",
|
||||
"_revision_number": 2
|
||||
},
|
||||
{
|
||||
"id": "9f6a8fd7_3332ff11",
|
||||
"author": {
|
||||
"_account_id": 22348,
|
||||
"name": "Zuul",
|
||||
"username": "zuul"
|
||||
},
|
||||
"date": "2018-04-19 12:57:36.000000000",
|
||||
"message": "Change has been successfully merged by Zuul",
|
||||
"_revision_number": 2
|
||||
}
|
||||
]
|
||||
}
|
@ -10,7 +10,10 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import datetime
|
||||
import json
|
||||
import os.path
|
||||
import pkgutil
|
||||
import textwrap
|
||||
|
||||
from goal_tools import gerrit
|
||||
@ -63,3 +66,32 @@ class TestParseReviewLists(base.TestCase):
|
||||
expected = ['561507', '555353']
|
||||
actual = list(gerrit.parse_review_lists([self.ids_name]))
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
|
||||
class TestReview(base.TestCase):
|
||||
|
||||
_data = json.loads(
|
||||
pkgutil.get_data('goal_tools.tests', 'data/55535.json').decode('utf-8')
|
||||
)
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.rev = gerrit.Review('55535', self._data)
|
||||
|
||||
def test_url(self):
|
||||
self.assertEqual('https://review.openstack.org/55535/', self.rev.url)
|
||||
|
||||
def test_created(self):
|
||||
self.assertEqual(
|
||||
datetime.datetime(2018, 3, 22, 16, 5, 45),
|
||||
self.rev.created,
|
||||
)
|
||||
|
||||
def test_owner(self):
|
||||
owner = self.rev.owner
|
||||
self.assertEqual('Doug Hellmann', owner.name)
|
||||
self.assertEqual('doug@doughellmann.com', owner.email)
|
||||
self.assertEqual(
|
||||
datetime.datetime(2018, 3, 22, 16, 5, 45),
|
||||
owner.date,
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user