This is an automated email from the ASF dual-hosted git repository.

humbedooh pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/kibble.git

commit 810f3d2484f40af146c4bd4043de1ea886bef589
Author: Daniel Gruno <humbed...@apache.org>
AuthorDate: Fri Mar 2 16:35:42 2018 +0100

    Initial stab at forum statistics
---
 api/pages/forum/actors.py     | 244 +++++++++++++++++++++++++++++
 api/pages/forum/creators.py   | 181 ++++++++++++++++++++++
 api/pages/forum/issues.py     | 258 +++++++++++++++++++++++++++++++
 api/pages/forum/responders.py | 182 ++++++++++++++++++++++
 api/pages/forum/top-count.py  | 170 ++++++++++++++++++++
 api/pages/forum/top.py        | 159 +++++++++++++++++++
 api/pages/forum/trends.py     | 351 ++++++++++++++++++++++++++++++++++++++++++
 7 files changed, 1545 insertions(+)

diff --git a/api/pages/forum/actors.py b/api/pages/forum/actors.py
new file mode 100644
index 0000000..345f59a
--- /dev/null
+++ b/api/pages/forum/actors.py
@@ -0,0 +1,244 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+########################################################################
+# OPENAPI-URI: /api/forum/actors
+########################################################################
+# get:
+#   responses:
+#     '200':
+#       content:
+#         application/json:
+#           schema:
+#             $ref: '#/components/schemas/Timeseries'
+#       description: 200 Response
+#     default:
+#       content:
+#         application/json:
+#           schema:
+#             $ref: '#/components/schemas/Error'
+#       description: unexpected error
+#   security:
+#   - cookieAuth: []
+#   summary: Shows timeseries of no. of people opening/closing issues over time
+# post:
+#   requestBody:
+#     content:
+#       application/json:
+#         schema:
+#           $ref: '#/components/schemas/defaultWidgetArgs'
+#   responses:
+#     '200':
+#       content:
+#         application/json:
+#           schema:
+#             $ref: '#/components/schemas/Timeseries'
+#       description: 200 Response
+#     default:
+#       content:
+#         application/json:
+#           schema:
+#             $ref: '#/components/schemas/Error'
+#       description: unexpected error
+#   security:
+#   - cookieAuth: []
+#   summary: Shows timeseries of no. of people opening topics or replying to 
them.
+# 
+########################################################################
+
+
+
+
+
+"""
+This is the forum actors stats page for Kibble
+"""
+
+import json
+import time
+import hashlib
+
+def run(API, environ, indata, session):
+    
+    # We need to be logged in for this!
+    if not session.user:
+        raise API.exception(403, "You must be logged in to use this API 
endpoint! %s")
+    
+    now = time.time()
+    
+    # First, fetch the view if we have such a thing enabled
+    viewList = []
+    if indata.get('view'):
+        viewList = session.getView(indata.get('view'))
+    if indata.get('subfilter'):
+        viewList = session.subFilter(indata.get('subfilter'), view = viewList) 
+    
+    
+    dateTo = indata.get('to', int(time.time()))
+    dateFrom = indata.get('from', dateTo - (86400*30*6)) # Default to a 6 
month span
+        
+    interval = indata.get('interval', 'month')
+    
+    
+    ####################################################################
+    ####################################################################
+    dOrg = session.user['defaultOrganisation'] or "apache"
+    query = {
+                'query': {
+                    'bool': {
+                        'must': [
+                            {'range':
+                                {
+                                    'created': {
+                                        'from': dateFrom,
+                                        'to': dateTo
+                                    }
+                                }
+                            },
+                            {
+                                'term': {
+                                    'organisation': dOrg
+                                }
+                            }
+                        ]
+                    }
+                }
+            }
+    # Source-specific or view-specific??
+    if indata.get('source'):
+        query['query']['bool']['must'].append({'term': {'sourceID': 
indata.get('source')}})
+    elif viewList:
+        query['query']['bool']['must'].append({'terms': {'sourceID': 
viewList}})
+    if indata.get('email'):
+        query['query']['bool']['should'] = [{'term': {'issueCreator': 
indata.get('email')}}]
+    
+    # Get timeseries for this period
+    query['aggs'] = {
+            'per_interval': {
+                'date_histogram': {
+                    'field': 'createdDate',
+                    'interval': interval
+                },
+                'aggs': {
+                    'by_user': {
+                        'cardinality': {
+                            'field': 'creator'
+                        }
+                    }
+                }
+            }
+        }
+    
+    res = session.DB.ES.search(
+            index=session.DB.dbname,
+            doc_type="forum_post",
+            size = 0,
+            body = query
+        )
+    
+    timeseries = {}
+
+    for bucket in res['aggregations']['per_interval']['buckets']:
+        ts = int(bucket['key'] / 1000)
+        ccount = bucket['by_user']['value']
+        timeseries[ts] = {
+            'date': ts,
+            'topic responders': ccount,
+            'topic creators': 0
+        }
+        
+    
+    ####################################################################
+    ####################################################################
+    dOrg = session.user['defaultOrganisation'] or "apache"
+    query = {
+                'query': {
+                    'bool': {
+                        'must': [
+                            {'range':
+                                {
+                                    'created': {
+                                        'from': dateFrom,
+                                        'to': dateTo
+                                    }
+                                }
+                            },
+                            {
+                                'term': {
+                                    'organisation': dOrg
+                                }
+                            }
+                        ]
+                    }
+                }
+            }
+    # Source-specific or view-specific??
+    if indata.get('source'):
+        query['query']['bool']['must'].append({'term': {'sourceID': 
indata.get('source')}})
+    elif viewList:
+        query['query']['bool']['must'].append({'terms': {'sourceID': 
viewList}})
+    if indata.get('email'):
+        query['query']['bool']['should'] = [{'term': {'creator': 
indata.get('email')}}]
+    
+    # Get timeseries for this period
+    query['aggs'] = {
+            'per_interval': {
+                'date_histogram': {
+                    'field': 'createdDate',
+                    'interval': interval
+                },
+                'aggs': {
+                    'by_user': {
+                        'cardinality': {
+                            'field': 'creator'
+                        }
+                    }
+                }
+            }
+        }
+    
+    res = session.DB.ES.search(
+            index=session.DB.dbname,
+            doc_type="forum_topic",
+            size = 0,
+            body = query
+        )
+
+    for bucket in res['aggregations']['per_interval']['buckets']:
+        ts = int(bucket['key'] / 1000)
+        ccount = bucket['by_user']['value']
+        if ts in timeseries:
+            timeseries[ts]['topic creators'] = ccount
+        else:
+            timeseries[ts] = {
+                'date': ts,
+                'topic creators': 0,
+                'topic responders': ccount
+            }
+    
+    ts = []
+    for x, el in timeseries.items():
+        ts.append(el)
+        
+    JSON_OUT = {
+        'timeseries': ts,
+        'okay': True,
+        'responseTime': time.time() - now,
+        'widgetType': {
+            'chartType': 'bar'
+        }
+    }
+    yield json.dumps(JSON_OUT)
diff --git a/api/pages/forum/creators.py b/api/pages/forum/creators.py
new file mode 100644
index 0000000..dc6a6c6
--- /dev/null
+++ b/api/pages/forum/creators.py
@@ -0,0 +1,181 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+########################################################################
+# OPENAPI-URI: /api/forum/creators
+########################################################################
+# get:
+#   responses:
+#     '200':
+#       content:
+#         application/json:
+#           schema:
+#             $ref: '#/components/schemas/CommitterList'
+#       description: 200 Response
+#     default:
+#       content:
+#         application/json:
+#           schema:
+#             $ref: '#/components/schemas/Error'
+#       description: unexpected error
+#   security:
+#   - cookieAuth: []
+#   summary: Shows the top N of issue openers
+# post:
+#   requestBody:
+#     content:
+#       application/json:
+#         schema:
+#           $ref: '#/components/schemas/defaultWidgetArgs'
+#   responses:
+#     '200':
+#       content:
+#         application/json:
+#           schema:
+#             $ref: '#/components/schemas/CommitterList'
+#       description: 200 Response
+#     default:
+#       content:
+#         application/json:
+#           schema:
+#             $ref: '#/components/schemas/Error'
+#       description: unexpected error
+#   security:
+#   - cookieAuth: []
+#   summary: Shows the top N of forum topic creators
+# 
+########################################################################
+
+
+
+
+
+"""
+This is the TopN issue openers list renderer for Kibble
+"""
+
+import json
+import time
+import hashlib
+
+def run(API, environ, indata, session):
+    
+    # We need to be logged in for this!
+    if not session.user:
+        raise API.exception(403, "You must be logged in to use this API 
endpoint! %s")
+    
+    now = time.time()
+    
+    # First, fetch the view if we have such a thing enabled
+    viewList = []
+    if indata.get('view'):
+        viewList = session.getView(indata.get('view'))
+    if indata.get('subfilter'):
+        viewList = session.subFilter(indata.get('subfilter'), view = viewList) 
+    
+    
+    dateTo = indata.get('to', int(time.time()))
+    dateFrom = indata.get('from', dateTo - (86400*30*6)) # Default to a 6 
month span
+    
+    interval = indata.get('interval', 'month')
+    xtitle = None
+    
+    ####################################################################
+    ####################################################################
+    dOrg = session.user['defaultOrganisation'] or "apache"
+    query = {
+                'query': {
+                    'bool': {
+                        'must': [
+                            {'range':
+                                {
+                                    'created': {
+                                        'from': dateFrom,
+                                        'to': dateTo
+                                    }
+                                }
+                            },
+                            {
+                                'term': {
+                                    'organisation': dOrg
+                                }
+                            }
+                        ]
+                    }
+                }
+            }
+    # Source-specific or view-specific??
+    if indata.get('source'):
+        query['query']['bool']['must'].append({'term': {'sourceID': 
indata.get('source')}})
+    elif viewList:
+        query['query']['bool']['must'].append({'terms': {'sourceID': 
viewList}})
+    if indata.get('email'):
+        query['query']['bool']['must'].append({'term': {'creator': 
indata.get('email')}})
+        xtitle = "People opening issues solved by %s" % indata.get('email')
+    
+    # Get top 25 committers this period
+    query['aggs'] = {
+            'committers': {
+                'terms': {
+                    'field': 'creator',
+                    'size': 25
+                },
+                'aggs': {
+                
+            }
+        }        
+    }
+    res = session.DB.ES.search(
+            index=session.DB.dbname,
+            doc_type="forum_topic",
+            size = 0,
+            body = query
+        )
+
+    people = {}
+    for bucket in res['aggregations']['committers']['buckets']:
+        email = bucket['key']
+        count = bucket['doc_count']
+        sha = email
+        if session.DB.ES.exists(index=session.DB.dbname,doc_type="person",id = 
sha):
+            pres = session.DB.ES.get(
+                index=session.DB.dbname,
+                doc_type="person",
+                id = email
+                )
+            person = pres['_source']
+            person['name'] = person.get('name', 'unknown')
+            people[email] = person
+            people[email]['gravatar'] = hashlib.md5(person.get('email', 
'unknown').encode('utf-8')).hexdigest()
+            people[email]['count'] = count
+        
+    topN = []
+    for email, person in people.items():
+        topN.append(person)
+    topN = sorted(topN, key = lambda x: x['count'], reverse = True)
+    JSON_OUT = {
+        'topN': {
+            'denoter': 'topics created',
+            'items': topN,
+        },
+        'okay': True,
+        'responseTime': time.time() - now,
+        'widgetType': {
+            'chartType': 'bar',
+            'title': xtitle
+        }
+    }
+    yield json.dumps(JSON_OUT)
diff --git a/api/pages/forum/issues.py b/api/pages/forum/issues.py
new file mode 100644
index 0000000..a485bfa
--- /dev/null
+++ b/api/pages/forum/issues.py
@@ -0,0 +1,258 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+########################################################################
+# OPENAPI-URI: /api/forum/issues
+########################################################################
+# get:
+#   responses:
+#     '200':
+#       content:
+#         application/json:
+#           schema:
+#             $ref: '#/components/schemas/Timeseries'
+#       description: 200 Response
+#     default:
+#       content:
+#         application/json:
+#           schema:
+#             $ref: '#/components/schemas/Error'
+#       description: unexpected error
+#   security:
+#   - cookieAuth: []
+#   summary: Shows timeseries of issues opened/closed over time
+# post:
+#   requestBody:
+#     content:
+#       application/json:
+#         schema:
+#           $ref: '#/components/schemas/defaultWidgetArgs'
+#   responses:
+#     '200':
+#       content:
+#         application/json:
+#           schema:
+#             $ref: '#/components/schemas/Timeseries'
+#       description: 200 Response
+#     default:
+#       content:
+#         application/json:
+#           schema:
+#             $ref: '#/components/schemas/Error'
+#       description: unexpected error
+#   security:
+#   - cookieAuth: []
+#   summary: Shows timeseries of forum topics opened/responded-to over time
+# 
+########################################################################
+
+
+
+
+
+"""
+This is the forum timeseries renderer for Kibble
+"""
+
+import json
+import time
+import hashlib
+
+# This creates an empty timeseries object with
+# all categories initialized as 0 opened, 0 closed.
+def makeTS(dist):
+    ts = {}
+    for k in dist:
+        ts[k + ' topics'] = 0
+        ts[k + ' replies'] = 0
+    return ts
+
+def run(API, environ, indata, session):
+    
+    # We need to be logged in for this!
+    if not session.user:
+        raise API.exception(403, "You must be logged in to use this API 
endpoint! %s")
+    
+    now = time.time()
+    
+    # First, fetch the view if we have such a thing enabled
+    viewList = []
+    if indata.get('view'):
+        viewList = session.getView(indata.get('view'))
+    if indata.get('subfilter'):
+        viewList = session.subFilter(indata.get('subfilter'), view = viewList) 
+    
+    
+    dateTo = indata.get('to', int(time.time()))
+    dateFrom = indata.get('from', dateTo - (86400*30*6)) # Default to a 6 
month span
+    
+    interval = indata.get('interval', 'month')
+    
+    # By default, we lump generic forums and question/answer (like SO, askbot) 
together as one
+    distinct = {
+        'forum': ['discourse', 'stackoverflow', 'askbot']
+    }
+    
+    # If requested, we split them into two
+    if indata.get('distinguish', False):
+        distinct = {
+            'forum':        ['discourse'],
+            'question bank': ['stackoverflow', 'askbot']
+        }
+    
+    timeseries = {}
+    
+    # For each category and the issue types that go along with that,
+    # grab opened and closed over time.
+    for iType, iValues in distinct.items():
+        ####################################################################
+        # ISSUES OPENED                                                    #
+        ####################################################################
+        dOrg = session.user['defaultOrganisation'] or "apache"
+        query = {
+                    'query': {
+                        'bool': {
+                            'must': [
+                                {'range':
+                                    {
+                                        'created': {
+                                            'from': dateFrom,
+                                            'to': dateTo
+                                        }
+                                    }
+                                },
+                                {
+                                    'term': {
+                                        'organisation': dOrg
+                                    }
+                                },
+                                {
+                                    'terms': {
+                                        'type': iValues
+                                    }
+                                }
+                            ]
+                        }
+                    }
+                }
+        # Source-specific or view-specific??
+        if indata.get('source'):
+            query['query']['bool']['must'].append({'term': {'sourceID': 
indata.get('source')}})
+        elif viewList:
+            query['query']['bool']['must'].append({'terms': {'sourceID': 
viewList}})
+        if indata.get('email'):
+            query['query']['bool']['must'].append({'term': {'creator': 
indata.get('email')}})
+        
+        # Get number of opened ones, this period
+        query['aggs'] = {
+                'commits': {
+                    'date_histogram': {
+                        'field': 'createdDate',
+                        'interval': interval
+                    }                
+                }
+            }
+        res = session.DB.ES.search(
+                index=session.DB.dbname,
+                doc_type="forum_topic",
+                size = 0,
+                body = query
+            )
+        
+        for bucket in res['aggregations']['commits']['buckets']:
+            ts = int(bucket['key'] / 1000)
+            count = bucket['doc_count']
+            timeseries[ts] = timeseries.get(ts, makeTS(distinct))
+            timeseries[ts][iType + ' topics'] = timeseries[ts].get(iType + ' 
topics', 0) + count
+            
+        
+        ####################################################################
+        # ISSUES CLOSED                                                    #
+        ####################################################################
+        dOrg = session.user['defaultOrganisation'] or "apache"
+        query = {
+                    'query': {
+                        'bool': {
+                            'must': [
+                                {'range':
+                                    {
+                                        'created': {
+                                            'from': dateFrom,
+                                            'to': dateTo
+                                        }
+                                    }
+                                },
+                                {
+                                    'term': {
+                                        'organisation': dOrg
+                                    }
+                                },
+                                {
+                                    'terms': {
+                                        'type': iValues
+                                    }
+                                }
+                            ]
+                        }
+                    }
+                }
+        if viewList:
+            query['query']['bool']['must'].append({'terms': {'sourceID': 
viewList}})
+        if indata.get('source'):
+            query['query']['bool']['must'].append({'term': {'sourceID': 
indata.get('source')}})
+        if indata.get('email'):
+            query['query']['bool']['must'].append({'term': {'creator': 
indata.get('email')}})
+        
+        # Get number of closed ones, this period
+        query['aggs'] = {
+                'commits': {
+                    'date_histogram': {
+                        'field': 'createdDate',
+                        'interval': interval
+                    }                
+                }
+            }
+        res = session.DB.ES.search(
+                index=session.DB.dbname,
+                doc_type="forum_post",
+                size = 0,
+                body = query
+            )
+        
+        for bucket in res['aggregations']['commits']['buckets']:
+            ts = int(bucket['key'] / 1000)
+            count = bucket['doc_count']
+            timeseries[ts] = timeseries.get(ts, makeTS(distinct))
+            timeseries[ts][iType + ' replies'] = timeseries[ts].get(iType + ' 
replies', 0) + count
+        
+    ts = []
+    for k, v in timeseries.items():
+        v['date'] = k
+        ts.append(v)
+        
+    
+    JSON_OUT = {
+        'widgetType': {
+            'chartType': 'line',  # Recommendation for the UI
+            'nofill': True
+        },
+        'timeseries': ts,
+        'interval': interval,
+        'okay': True,
+        'distinguishable': True,
+        'responseTime': time.time() - now
+    }
+    yield json.dumps(JSON_OUT)
diff --git a/api/pages/forum/responders.py b/api/pages/forum/responders.py
new file mode 100644
index 0000000..6c12ca2
--- /dev/null
+++ b/api/pages/forum/responders.py
@@ -0,0 +1,182 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+########################################################################
+# OPENAPI-URI: /api/forum/responders
+########################################################################
+# get:
+#   responses:
+#     '200':
+#       content:
+#         application/json:
+#           schema:
+#             $ref: '#/components/schemas/CommitterList'
+#       description: 200 Response
+#     default:
+#       content:
+#         application/json:
+#           schema:
+#             $ref: '#/components/schemas/Error'
+#       description: unexpected error
+#   security:
+#   - cookieAuth: []
+#   summary: Shows the top N of issue closers
+# post:
+#   requestBody:
+#     content:
+#       application/json:
+#         schema:
+#           $ref: '#/components/schemas/defaultWidgetArgs'
+#   responses:
+#     '200':
+#       content:
+#         application/json:
+#           schema:
+#             $ref: '#/components/schemas/CommitterList'
+#       description: 200 Response
+#     default:
+#       content:
+#         application/json:
+#           schema:
+#             $ref: '#/components/schemas/Error'
+#       description: unexpected error
+#   security:
+#   - cookieAuth: []
+#   summary: Shows the top N of issue closers
+# 
+########################################################################
+
+
+
+
+
+"""
+This is the TopN forum posters list renderer for Kibble
+"""
+
+import json
+import time
+import hashlib
+
+def run(API, environ, indata, session):
+    
+    # We need to be logged in for this!
+    if not session.user:
+        raise API.exception(403, "You must be logged in to use this API 
endpoint! %s")
+    
+    now = time.time()
+    
+    # First, fetch the view if we have such a thing enabled
+    viewList = []
+    if indata.get('view'):
+        viewList = session.getView(indata.get('view'))
+    if indata.get('subfilter'):
+        viewList = session.subFilter(indata.get('subfilter'), view = viewList) 
+    
+    
+    dateTo = indata.get('to', int(time.time()))
+    dateFrom = indata.get('from', dateTo - (86400*30*6)) # Default to a 6 
month span
+    
+    interval = indata.get('interval', 'month')
+    xtitle = None
+    
+    
+    ####################################################################
+    ####################################################################
+    dOrg = session.user['defaultOrganisation'] or "apache"
+    query = {
+                'query': {
+                    'bool': {
+                        'must': [
+                            {'range':
+                                {
+                                    'created': {
+                                        'from': dateFrom,
+                                        'to': dateTo
+                                    }
+                                }
+                            },
+                            {
+                                'term': {
+                                    'organisation': dOrg
+                                }
+                            }
+                        ]
+                    }
+                }
+            }
+    # Source-specific or view-specific??
+    if indata.get('source'):
+        query['query']['bool']['must'].append({'term': {'sourceID': 
indata.get('source')}})
+    elif viewList:
+        query['query']['bool']['must'].append({'terms': {'sourceID': 
viewList}})
+    if indata.get('email'):
+        query['query']['bool']['must'].append({'term': {'creator': 
indata.get('email')}})
+        xTitle = "People closing %s's issues" % indata.get('email')
+    
+    # Get top 25 committers this period
+    query['aggs'] = {
+            'committers': {
+                'terms': {
+                    'field': 'creator',
+                    'size': 25
+                },
+                'aggs': {
+                
+            }
+        }        
+    }
+    res = session.DB.ES.search(
+            index=session.DB.dbname,
+            doc_type="forum_post",
+            size = 0,
+            body = query
+        )
+
+    people = {}
+    for bucket in res['aggregations']['committers']['buckets']:
+        email = bucket['key']
+        count = bucket['doc_count']
+        sha = email
+        if session.DB.ES.exists(index=session.DB.dbname,doc_type="person",id = 
sha):
+            pres = session.DB.ES.get(
+                index=session.DB.dbname,
+                doc_type="person",
+                id = email
+                )
+            person = pres['_source']
+            person['name'] = person.get('name', 'unknown')
+            people[email] = person
+            people[email]['gravatar'] = hashlib.md5(person.get('email', 
'unknown').encode('utf-8')).hexdigest()
+            people[email]['count'] = count
+        
+    topN = []
+    for email, person in people.items():
+        topN.append(person)
+    topN = sorted(topN, key = lambda x: x['count'], reverse = True)
+    JSON_OUT = {
+        'topN': {
+            'denoter': 'replies posted',
+            'items': topN,
+        },
+        'okay': True,
+        'responseTime': time.time() - now,
+        'widgetType': {
+            'chartType': 'bar',
+            'title': xtitle
+        }
+    }
+    yield json.dumps(JSON_OUT)
diff --git a/api/pages/forum/top-count.py b/api/pages/forum/top-count.py
new file mode 100644
index 0000000..58d345c
--- /dev/null
+++ b/api/pages/forum/top-count.py
@@ -0,0 +1,170 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+########################################################################
+# OPENAPI-URI: /api/forum/top-count
+########################################################################
+# get:
+#   responses:
+#     '200':
+#       content:
+#         application/json:
+#           schema:
+#             $ref: '#/components/schemas/Timeseries'
+#       description: 200 Response
+#     default:
+#       content:
+#         application/json:
+#           schema:
+#             $ref: '#/components/schemas/Error'
+#       description: unexpected error
+#   security:
+#   - cookieAuth: []
+#   summary: Shows top 25 issue trackers by issues
+# post:
+#   requestBody:
+#     content:
+#       application/json:
+#         schema:
+#           $ref: '#/components/schemas/defaultWidgetArgs'
+#   responses:
+#     '200':
+#       content:
+#         application/json:
+#           schema:
+#             $ref: '#/components/schemas/Timeseries'
+#       description: 200 Response
+#     default:
+#       content:
+#         application/json:
+#           schema:
+#             $ref: '#/components/schemas/Error'
+#       description: unexpected error
+#   security:
+#   - cookieAuth: []
+#   summary: Shows top 25 forums by interactions
+# 
+########################################################################
+
+
+
+
+
+"""
+This is the TopN repos by commits list renderer for Kibble
+"""
+
+import json
+import time
+import re
+
+def run(API, environ, indata, session):
+    
+    # We need to be logged in for this!
+    if not session.user:
+        raise API.exception(403, "You must be logged in to use this API 
endpoint! %s")
+    
+    now = time.time()
+    
+    # First, fetch the view if we have such a thing enabled
+    viewList = []
+    if indata.get('view'):
+        viewList = session.getView(indata.get('view'))
+    if indata.get('subfilter'):
+        viewList = session.subFilter(indata.get('subfilter'), view = viewList) 
+    
+    
+    dateTo = indata.get('to', int(time.time()))
+    dateFrom = indata.get('from', dateTo - (86400*30*6)) # Default to a 6 
month span
+    
+    ####################################################################
+    ####################################################################
+    dOrg = session.user['defaultOrganisation'] or "apache"
+    query = {
+                'query': {
+                    'bool': {
+                        'must': [
+                            {'range':
+                                {
+                                    'created': {
+                                        'from': dateFrom,
+                                        'to': dateTo
+                                    }
+                                }
+                            },
+                            {
+                                'term': {
+                                    'organisation': dOrg
+                                }
+                            }
+                        ]
+                    }
+                }
+            }
+    # Source-specific or view-specific??
+    if indata.get('source'):
+        query['query']['bool']['must'].append({'term': {'sourceID': 
indata.get('source')}})
+    elif viewList:
+        query['query']['bool']['must'].append({'terms': {'sourceID': 
viewList}})
+    if indata.get('email'):
+        query['query']['bool']['should'] = [
+            {'term': {'creator': indata.get('email')}}
+        ]
+    
+    
+    # Get top 25 committers this period
+    query['aggs'] = {
+            'by_repo': {
+                'terms': {
+                    'field': 'sourceID',
+                    'size': 5000
+                }                
+            }
+        }
+    res = session.DB.ES.search(
+            index=session.DB.dbname,
+            doc_type="forum_post",
+            size = 0,
+            body = query
+        )
+    
+    toprepos = []
+    for bucket in res['aggregations']['by_repo']['buckets']:
+        ID = bucket['key']
+        if session.DB.ES.exists(index=session.DB.dbname, doc_type="source", id 
= ID):
+            it = session.DB.ES.get(index=session.DB.dbname, doc_type="source", 
id = ID)['_source']
+            repo = re.sub(r".+/([^/]+)$", r"\1", it['sourceURL'])
+            count = bucket['doc_count']
+            toprepos.append([repo, count])
+        
+    toprepos = sorted(toprepos, key = lambda x: x[1], reverse = True)
+    top = toprepos[0:24]
+    if len(toprepos) > 25:
+        count = 0
+        for repo in toprepos[25:]:
+            count += repo[1]
+        top.append(["Other forums", count])
+    
+    tophash = {}
+    for v in top:
+        tophash[v[0]] = v[1]
+        
+    JSON_OUT = {
+        'counts': tophash,
+        'okay': True,
+        'responseTime': time.time() - now,
+    }
+    yield json.dumps(JSON_OUT)
diff --git a/api/pages/forum/top.py b/api/pages/forum/top.py
new file mode 100644
index 0000000..c29c28f
--- /dev/null
+++ b/api/pages/forum/top.py
@@ -0,0 +1,159 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+########################################################################
+# OPENAPI-URI: /api/forum/top
+########################################################################
+# get:
+#   responses:
+#     '200':
+#       content:
+#         application/json:
+#           schema:
+#             $ref: '#/components/schemas/TopList'
+#       description: 200 Response
+#     default:
+#       content:
+#         application/json:
+#           schema:
+#             $ref: '#/components/schemas/Error'
+#       description: unexpected error
+#   security:
+#   - cookieAuth: []
+#   summary: Shows the top N issues by interactions
+# post:
+#   requestBody:
+#     content:
+#       application/json:
+#         schema:
+#           $ref: '#/components/schemas/defaultWidgetArgs'
+#   responses:
+#     '200':
+#       content:
+#         application/json:
+#           schema:
+#             $ref: '#/components/schemas/TopList'
+#       description: 200 Response
+#     default:
+#       content:
+#         application/json:
+#           schema:
+#             $ref: '#/components/schemas/Error'
+#       description: unexpected error
+#   security:
+#   - cookieAuth: []
+#   summary: Shows the top N topics by interactions
+# 
+########################################################################
+
+
+
+
+
+"""
+This is the issue actors stats page for Kibble
+"""
+
+import json
+import time
+import hashlib
+
+def run(API, environ, indata, session):
+    
+    # We need to be logged in for this!
+    if not session.user:
+        raise API.exception(403, "You must be logged in to use this API 
endpoint! %s")
+    
+    now = time.time()
+    
+    # First, fetch the view if we have such a thing enabled
+    viewList = []
+    if indata.get('view'):
+        viewList = session.getView(indata.get('view'))
+    if indata.get('subfilter'):
+        viewList = session.subFilter(indata.get('subfilter'), view = viewList) 
+    
+    
+    dateTo = indata.get('to', int(time.time()))
+    dateFrom = indata.get('from', dateTo - (86400*30*6)) # Default to a 6 
month span
+    
+    interval = indata.get('interval', 'month')
+    
+    
+    ####################################################################
+    ####################################################################
+    dOrg = session.user['defaultOrganisation'] or "apache"
+    query = {
+                'query': {
+                    'bool': {
+                        'must': [
+                            {'range':
+                                {
+                                    'created': {
+                                        'from': dateFrom,
+                                        'to': dateTo
+                                    }
+                                }
+                            },
+                            {
+                                'term': {
+                                    'organisation': dOrg
+                                }
+                            }
+                        ]
+                    }
+                },
+                'sort': {
+                    'posts': 'desc'
+                }
+            }
+    # Source-specific or view-specific??
+    if indata.get('source'):
+        query['query']['bool']['must'].append({'term': {'sourceID': 
indata.get('source')}})
+    elif viewList:
+        query['query']['bool']['must'].append({'terms': {'sourceID': 
viewList}})
+    if indata.get('email'):
+        query['query']['bool']['should'] = [{'term': {'creator': 
indata.get('email')}}]
+    
+    res = session.DB.ES.search(
+            index=session.DB.dbname,
+            doc_type="forum_topic",
+            size = 25,
+            body = query
+        )
+    top = []
+    for bucket in res['hits']['hits']:
+        doc = bucket['_source']
+        doc['source'] = doc.get('url', '#')
+        doc['name'] = doc.get('type', 'unknown')
+        doc['subject'] = doc.get('title')
+        doc['count'] = doc.get('posts', 0)
+        top.append(doc)
+    
+        
+    JSON_OUT = {
+        'topN': {
+            'denoter': 'interactions',
+            'icon': 'bug',
+            'items': top
+        },
+        'okay': True,
+        'responseTime': time.time() - now,
+        'widgetType': {
+            'chartType': 'line'
+        }
+    }
+    yield json.dumps(JSON_OUT)
diff --git a/api/pages/forum/trends.py b/api/pages/forum/trends.py
new file mode 100644
index 0000000..542029a
--- /dev/null
+++ b/api/pages/forum/trends.py
@@ -0,0 +1,351 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+########################################################################
+# OPENAPI-URI: /api/forum/trends
+########################################################################
+# get:
+#   responses:
+#     '200':
+#       content:
+#         application/json:
+#           schema:
+#             $ref: '#/components/schemas/Trend'
+#       description: 200 Response
+#     default:
+#       content:
+#         application/json:
+#           schema:
+#             $ref: '#/components/schemas/Error'
+#       description: unexpected error
+#   security:
+#   - cookieAuth: []
+#   summary: Shows trend data for a set of issue trackers over a given period 
of time
+# post:
+#   requestBody:
+#     content:
+#       application/json:
+#         schema:
+#           $ref: '#/components/schemas/defaultWidgetArgs'
+#   responses:
+#     '200':
+#       content:
+#         application/json:
+#           schema:
+#             $ref: '#/components/schemas/Trend'
+#       description: 200 Response
+#     default:
+#       content:
+#         application/json:
+#           schema:
+#             $ref: '#/components/schemas/Error'
+#       description: unexpected error
+#   security:
+#   - cookieAuth: []
+#   summary: Shows trend data for a set of forums over a given period of time
+# 
+########################################################################
+
+
+
+
+
+"""
+This is the forum trends renderer for Kibble
+"""
+
+import json
+import time
+
+def run(API, environ, indata, session):
+    
+    # We need to be logged in for this!
+    if not session.user:
+        raise API.exception(403, "You must be logged in to use this API 
endpoint! %s")
+    
+    now = time.time()
+    
+    # First, fetch the view if we have such a thing enabled
+    viewList = []
+    if indata.get('view'):
+        viewList = session.getView(indata.get('view'))
+    if indata.get('subfilter'):
+        viewList = session.subFilter(indata.get('subfilter'), view = viewList) 
+    
+    
+    dateTo = indata.get('to', int(time.time()))
+    dateFrom = indata.get('from', dateTo - (86400*30*6)) # Default to a 6 
month span
+    if dateFrom < 0:
+        dateFrom = 0
+    dateYonder = dateFrom - (dateTo - dateFrom)
+    
+    
+    dOrg = session.user['defaultOrganisation'] or "apache"
+    
+    ####################################################################
+    # We start by doing all the queries for THIS period.               #
+    # Then we reset the query, and change date to yonder-->from        #
+    # and rerun the same queries.                                      #
+    ####################################################################
+    query = {
+                'query': {
+                    'bool': {
+                        'must': [
+                            {'range':
+                                {
+                                    'created': {
+                                        'from': dateFrom,
+                                        'to': dateTo
+                                    }
+                                }
+                            },
+                            {
+                                'term': {
+                                    'organisation': dOrg
+                                }
+                            }
+                        ]
+                    }
+                }
+            }
+    # Source-specific or view-specific??
+    if indata.get('source'):
+        query['query']['bool']['must'].append({'term': {'sourceID': 
indata.get('source')}})
+    elif viewList:
+        query['query']['bool']['must'].append({'terms': {'sourceID': 
viewList}})
+    
+    # Get number of issues created, this period
+    res = session.DB.ES.count(
+            index=session.DB.dbname,
+            doc_type="forum_topic",
+            body = query
+        )
+    no_issues_created = res['count']
+    
+    
+    # Get number of open/close, this period
+    query['aggs'] = {
+            'opener': {
+                'cardinality': {
+                    'field': 'creator'
+                }
+            }
+        }
+    res = session.DB.ES.search(
+            index=session.DB.dbname,
+            doc_type="forum_topic",
+            size = 0,
+            body = query
+        )
+    no_creators = res['aggregations']['opener']['value']
+    
+    
+    # REPLIERS
+    
+    query = {
+                'query': {
+                    'bool': {
+                        'must': [
+                            {'range':
+                                {
+                                    'created': {
+                                        'from': dateFrom,
+                                        'to': dateTo
+                                    }
+                                }
+                            },
+                            {
+                                'term': {
+                                    'organisation': dOrg
+                                }
+                            }
+                        ]
+                    }
+                }
+            }
+    # Source-specific or view-specific??
+    if indata.get('source'):
+        query['query']['bool']['must'].append({'term': {'sourceID': 
indata.get('source')}})
+    elif viewList:
+        query['query']['bool']['must'].append({'terms': {'sourceID': 
viewList}})
+
+    
+    # Get number of issues created, this period
+    res = session.DB.ES.count(
+            index=session.DB.dbname,
+            doc_type="forum_post",
+            body = query
+        )
+    no_issues_closed = res['count']
+    
+    
+    # Get number of open/close, this period
+    query['aggs'] = {
+            'closer': {
+                'cardinality': {
+                    'field': 'creator'
+                }
+            }
+        }
+    res = session.DB.ES.search(
+            index=session.DB.dbname,
+            doc_type="forum_post",
+            size = 0,
+            body = query
+        )
+    no_closers = res['aggregations']['closer']['value']
+    
+    
+    ####################################################################
+    # Change to PRIOR SPAN                                             #
+    ####################################################################
+    query = {
+                'query': {
+                    'bool': {
+                        'must': [
+                            {'range':
+                                {
+                                    'created': {
+                                        'from': dateYonder,
+                                        'to': dateFrom-1
+                                    }
+                                }
+                            },
+                            {
+                                'term': {
+                                    'organisation': dOrg
+                                }
+                            }
+                        ]
+                    }
+                }
+            }
+    
+    if indata.get('source'):
+        query['query']['bool']['must'].append({'term': {'sourceID': 
indata.get('source')}})
+    elif viewList:
+        query['query']['bool']['must'].append({'terms': {'sourceID': 
viewList}})
+    
+    
+    # Get number of issues, this period
+    res = session.DB.ES.count(
+            index=session.DB.dbname,
+            doc_type="forum_topic",
+            body = query
+        )
+    no_issues_created_before = res['count']
+    
+    # Get number of committers, this period
+    query['aggs'] = {
+            'opener': {
+                'cardinality': {
+                    'field': 'creator'
+                }
+            }
+        }
+    res = session.DB.ES.search(
+            index=session.DB.dbname,
+            doc_type="forum_topic",
+            size = 0,
+            body = query
+        )
+    no_creators_before = res['aggregations']['opener']['value']
+    
+    
+    
+    # REPLIERS
+    
+    query = {
+                'query': {
+                    'bool': {
+                        'must': [
+                            {'range':
+                                {
+                                    'created': {
+                                        'from': dateYonder,
+                                        'to': dateFrom-1
+                                    }
+                                }
+                            },
+                            {
+                                'term': {
+                                    'organisation': dOrg
+                                }
+                            }
+                        ]
+                    }
+                }
+            }
+    if indata.get('source'):
+        query['query']['bool']['must'].append({'term': {'sourceID': 
indata.get('source')}})
+    elif viewList:
+        query['query']['bool']['must'].append({'terms': {'sourceID': 
viewList}})
+    
+    # Get number of issues created, this period
+    res = session.DB.ES.count(
+            index=session.DB.dbname,
+            doc_type="forum_post",
+            body = query
+        )
+    no_issues_closed_before = res['count']
+    
+    
+    # Get number of open/close, this period
+    query['aggs'] = {
+            'closer': {
+                'cardinality': {
+                    'field': "creator"
+                }
+            }
+        }
+    res = session.DB.ES.search(
+            index=session.DB.dbname,
+            doc_type="forum_post",
+            size = 0,
+            body = query
+        )
+    no_closers_before = res['aggregations']['closer']['value']
+    print(res)
+    
+    trends = {
+        "created": {
+            'before': no_issues_created_before,
+            'after': no_issues_created,
+            'title': "Topics started this period"
+        },
+        "authors": {
+            'before': no_creators_before,
+            'after': no_creators,
+            'title': "People starting topics this period"
+        },
+        "closed": {
+            'before': no_issues_closed_before,
+            'after': no_issues_closed,
+            'title': "Replies this period"
+        },
+        "closers": {
+            'before': no_closers_before,
+            'after': no_closers,
+            'title': "People replying this period"
+        }
+    }
+    
+    JSON_OUT = {
+        'trends': trends,
+        'okay': True,
+        'responseTime': time.time() - now
+    }
+    yield json.dumps(JSON_OUT)

-- 
To stop receiving notification emails like this one, please contact
humbed...@apache.org.

Reply via email to