Merge pull request #436 from abf/rosa-build:435-activity-statistics
#435: Activity statistics
This commit is contained in:
commit
5fb74d90bc
Binary file not shown.
After Width: | Height: | Size: 6.7 KiB |
Binary file not shown.
After Width: | Height: | Size: 2.5 KiB |
|
@ -0,0 +1,150 @@
|
|||
RosaABF.controller 'StatisticsController', ['$scope', '$http', '$filter', ($scope, $http, $filter) ->
|
||||
|
||||
$scope.range = 'last_30_days'
|
||||
$scope.range_start = $('#range_start').attr('value')
|
||||
$scope.range_end = $('#range_end').attr('value')
|
||||
$scope.loading = true
|
||||
$scope.statistics = {}
|
||||
$scope.statistics_path = '/statistics'
|
||||
|
||||
$scope.colors = [
|
||||
'56, 132, 158',
|
||||
'77, 169, 68',
|
||||
'241, 128, 73',
|
||||
'174, 199, 232',
|
||||
'255, 187, 120',
|
||||
'152, 223, 138',
|
||||
'214, 39, 40',
|
||||
'31, 119, 180'
|
||||
]
|
||||
$scope.charts = {}
|
||||
|
||||
|
||||
$scope.init = ->
|
||||
$('#range-form .date_picker').datepicker
|
||||
'dateFormat': 'yy-mm-dd'
|
||||
maxDate: 0
|
||||
minDate: -366
|
||||
showButtonPanel: true
|
||||
|
||||
$scope.rangeChange()
|
||||
true
|
||||
|
||||
$scope.prepareRange = ->
|
||||
range_start = new Date($scope.range_start)
|
||||
range_end = new Date($scope.range_end)
|
||||
|
||||
if range_start > range_end
|
||||
tmp = $scope.range_start
|
||||
$scope.range_start = $scope.range_end
|
||||
$scope.range_end = tmp
|
||||
|
||||
|
||||
$scope.rangeChange = ->
|
||||
$scope.loading = true
|
||||
$scope.statistics = {}
|
||||
|
||||
$scope.prepareRange()
|
||||
$('.doughnut-legend').remove()
|
||||
|
||||
params =
|
||||
range: $scope.range
|
||||
range_start: $scope.range_start
|
||||
range_end: $scope.range_end
|
||||
format: 'json'
|
||||
|
||||
$http.get($scope.statistics_path, params: params).success (results) ->
|
||||
$scope.statistics = results
|
||||
|
||||
$scope.loading = false
|
||||
|
||||
# BuildLists
|
||||
if $scope.statistics.build_lists
|
||||
$scope.initBuildListsChart()
|
||||
|
||||
# PullRequests
|
||||
if $scope.statistics.pull_requests
|
||||
$scope.initPullRequestsChart()
|
||||
|
||||
# Issues
|
||||
if $scope.statistics.issues
|
||||
$scope.initIssuesChart()
|
||||
|
||||
.error (data, status, headers, config) ->
|
||||
console.log 'error:'
|
||||
$scope.loading = false
|
||||
|
||||
$scope.dateChart = (id, collections) ->
|
||||
new_collections = $.grep collections, ( c ) ->
|
||||
return c
|
||||
|
||||
if collections.length == new_collections.length
|
||||
$scope.charts[id].destroy() if $scope.charts[id]
|
||||
|
||||
points = collections[0]
|
||||
factor = points.length // 45 + 1
|
||||
|
||||
tooltipTitles = []
|
||||
labels = _.map points, (d, index) ->
|
||||
x = d.x
|
||||
format =
|
||||
if $scope.statistics.unit == 'hour'
|
||||
# input date should have format: 'yyyy-MM-ddTHH:mm:ssZ'
|
||||
x = x.replace(/\s/, 'T') + 'Z'
|
||||
'HH:mm'
|
||||
else
|
||||
'yyyy-MM-dd'
|
||||
x = $filter('date')(x, format)
|
||||
tooltipTitles.push x
|
||||
if index %% factor == 0
|
||||
x
|
||||
else
|
||||
''
|
||||
|
||||
datasets = _.map collections, (collection, index) ->
|
||||
data = _.map collection, (d) ->
|
||||
d.y
|
||||
|
||||
dataset =
|
||||
fillColor: "rgba(#{ $scope.colors[index] }, 0.5)"
|
||||
strokeColor: "rgba(#{ $scope.colors[index] }, 1)"
|
||||
pointColor: "rgba(#{ $scope.colors[index] }, 1)"
|
||||
pointStrokeColor: "#fff"
|
||||
data: data
|
||||
|
||||
data =
|
||||
datasets: datasets
|
||||
# We display only limited count of labels on X axis, but tooltips should have titles
|
||||
# See: Chart.js "Added by avokhmin"
|
||||
labels: labels
|
||||
tooltipTitles: tooltipTitles
|
||||
|
||||
options =
|
||||
responsive: true
|
||||
|
||||
context = $(id)[0].getContext('2d')
|
||||
$scope.charts[id] = new Chart(context).Line(data, options)
|
||||
|
||||
$scope.initBuildListsChart = ->
|
||||
$scope.dateChart '#build_lists_chart', [
|
||||
$scope.statistics.build_lists.build_started,
|
||||
$scope.statistics.build_lists.success,
|
||||
$scope.statistics.build_lists.build_error,
|
||||
$scope.statistics.build_lists.build_published
|
||||
]
|
||||
|
||||
$scope.initPullRequestsChart = ->
|
||||
$scope.dateChart '#pull_requests_chart', [
|
||||
$scope.statistics.pull_requests.open,
|
||||
$scope.statistics.pull_requests.closed,
|
||||
$scope.statistics.pull_requests.approved
|
||||
]
|
||||
|
||||
$scope.initIssuesChart = ->
|
||||
$scope.dateChart '#issues_chart', [
|
||||
$scope.statistics.issues.open,
|
||||
$scope.statistics.issues.closed,
|
||||
$scope.statistics.issues.approved
|
||||
]
|
||||
|
||||
]
|
File diff suppressed because it is too large
Load Diff
|
@ -6,3 +6,4 @@
|
|||
@import "design/custom";
|
||||
@import "design/build_lists_monitoring";
|
||||
@import "design/profile";
|
||||
@import "design/statistics";
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
#manage-statistics {
|
||||
.graph-key-color1 { background-color: #38849e; }
|
||||
.graph-key-color2 { background-color: #4da944; }
|
||||
.graph-key-color3 { background-color: #f18049; }
|
||||
.graph-key-color4 { background-color: #aec7e8; }
|
||||
.graph-key-color5 { background-color: #ffbb78; }
|
||||
|
||||
.graph-wrapper h3 span {
|
||||
display: inline-block;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
margin-left: 15px;
|
||||
}
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
class ApplicationController < ActionController::Base
|
||||
AIRBRAKE_IGNORE = [ActionController::InvalidAuthenticityToken,
|
||||
AbstractController::ActionNotFound]
|
||||
AIRBRAKE_IGNORE = [
|
||||
ActionController::InvalidAuthenticityToken,
|
||||
AbstractController::ActionNotFound
|
||||
]
|
||||
|
||||
protect_from_forgery
|
||||
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
class StatisticsController < ApplicationController
|
||||
|
||||
RANGES = [
|
||||
RANGE_TWENTY_FOUR_HOURS = 'twenty_four_hours',
|
||||
RANGE_LAST_7_DAYS = 'last_7_days',
|
||||
RANGE_LAST_30_DAYS = 'last_30_days',
|
||||
RANGE_LAST_60_DAYS = 'last_60_days',
|
||||
RANGE_LAST_90_DAYS = 'last_90_days',
|
||||
RANGE_LAST_180_DAYS = 'last_180_days',
|
||||
RANGE_LAST_YEAR = 'last_year',
|
||||
RANGE_CUSTOM = 'custom',
|
||||
]
|
||||
|
||||
def index
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.json do
|
||||
init_variables
|
||||
render json: StatisticPresenter.new(range_start: @range_start, range_end: @range_end, unit: @unit)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def init_variables
|
||||
case params[:range]
|
||||
when RANGE_TWENTY_FOUR_HOURS
|
||||
@range_end = Time.now.utc
|
||||
@range_start = @range_end - 1.day
|
||||
@unit = :hour
|
||||
when RANGE_LAST_7_DAYS
|
||||
@range_end = Date.today
|
||||
@range_start = @range_end - 7.days
|
||||
@unit = :day
|
||||
when RANGE_LAST_30_DAYS
|
||||
@range_end = Date.today
|
||||
@range_start = @range_end - 30.days
|
||||
@unit = :day
|
||||
when RANGE_LAST_60_DAYS
|
||||
@range_end = Date.today
|
||||
@range_start = @range_end - 30.days
|
||||
@unit = :day
|
||||
when RANGE_LAST_90_DAYS
|
||||
@range_end = Date.today
|
||||
@range_start = @range_end - 90.days
|
||||
@unit = :day
|
||||
when RANGE_LAST_180_DAYS
|
||||
@range_end = Date.today
|
||||
@range_start = @range_end - 180.days
|
||||
@unit = :month
|
||||
when RANGE_LAST_YEAR
|
||||
@range_end = Date.today
|
||||
@range_start = @range_end - 1.year
|
||||
@unit = :month
|
||||
when RANGE_CUSTOM
|
||||
@range_start = Time.parse(params[:range_start]).utc
|
||||
@range_end = Time.parse(params[:range_end]).utc
|
||||
diff = @range_end - @range_start
|
||||
@unit =
|
||||
if diff <= 24.hours
|
||||
:hour
|
||||
elsif diff <= 90.days
|
||||
:day
|
||||
else
|
||||
:month
|
||||
end
|
||||
else
|
||||
raise ActiveRecord::RecordNotFound
|
||||
end
|
||||
rescue ArgumentError
|
||||
raise ActiveRecord::RecordNotFound
|
||||
end
|
||||
|
||||
end
|
|
@ -0,0 +1,9 @@
|
|||
module StatisticsHelper
|
||||
|
||||
def statistics_range_options
|
||||
options_for_select(
|
||||
StatisticsController::RANGES.map { |r| [I18n.t(r, scope: 'statistics.helper.period'), r] }
|
||||
)
|
||||
end
|
||||
|
||||
end
|
|
@ -3,10 +3,20 @@ module BuildListObserver
|
|||
|
||||
included do
|
||||
before_update :update_average_build_time
|
||||
before_update :update_statistic
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def update_statistic
|
||||
Statistic.statsd_increment(
|
||||
activity_at: Time.now,
|
||||
key: "build_list.#{status}",
|
||||
project_id: project_id,
|
||||
user_id: user_id,
|
||||
) if status_changed?
|
||||
end
|
||||
|
||||
def update_average_build_time
|
||||
if status_changed?
|
||||
self.started_at = Time.now if status == self.class::BUILD_STARTED
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
class Statistic < ActiveRecord::Base
|
||||
belongs_to :user
|
||||
belongs_to :project
|
||||
|
||||
validates :user_id,
|
||||
uniqueness: { scope: [:project_id, :key, :activity_at] },
|
||||
presence: true
|
||||
|
||||
validates :email,
|
||||
presence: true
|
||||
|
||||
validates :project_id,
|
||||
presence: true
|
||||
|
||||
validates :project_name_with_owner,
|
||||
presence: true
|
||||
|
||||
validates :key,
|
||||
presence: true
|
||||
|
||||
validates :counter,
|
||||
presence: true
|
||||
|
||||
validates :activity_at,
|
||||
presence: true
|
||||
|
||||
attr_accessible :user_id,
|
||||
:email,
|
||||
:project_id,
|
||||
:project_name_with_owner,
|
||||
:key,
|
||||
:counter,
|
||||
:activity_at
|
||||
|
||||
scope :for_period, -> (start_date, end_date) { where(activity_at: (start_date..end_date)) }
|
||||
|
||||
scope :build_lists_started, -> { where(key: "build_list.#{BuildList::BUILD_STARTED}") }
|
||||
scope :build_lists_success, -> { where(key: "build_list.#{BuildList::SUCCESS}") }
|
||||
scope :build_lists_error, -> { where(key: "build_list.#{BuildList::BUILD_ERROR}") }
|
||||
scope :build_lists_published, -> { where(key: "build_list.#{BuildList::BUILD_PUBLISHED}") }
|
||||
|
||||
|
||||
|
||||
def self.now_statsd_increment(activity_at: nil, user_id: nil, project_id: nil, key: nil)
|
||||
# Truncates a DateTime to the minute
|
||||
activity_at = activity_at.utc.change(min: 0)
|
||||
user = User.find user_id
|
||||
project = Project.find project_id
|
||||
Statistic.create(
|
||||
user_id: user_id,
|
||||
email: user.email,
|
||||
project_id: project_id,
|
||||
project_name_with_owner: project.name_with_owner,
|
||||
key: key,
|
||||
activity_at: activity_at
|
||||
)
|
||||
ensure
|
||||
Statistic.where(
|
||||
user_id: user_id,
|
||||
project_id: project_id,
|
||||
key: key,
|
||||
activity_at: activity_at
|
||||
).update_all('counter = counter + 1')
|
||||
end
|
||||
|
||||
# TODO: remove later
|
||||
def self.fill_in_build_lists
|
||||
BuildList.find_each do |bl|
|
||||
Statistic.now_statsd_increment({
|
||||
activity_at: bl.created_at,
|
||||
key: "build_list.#{BuildList::BUILD_STARTED}",
|
||||
project_id: bl.project_id,
|
||||
user_id: bl.user_id,
|
||||
})
|
||||
Statistic.now_statsd_increment({
|
||||
activity_at: bl.updated_at,
|
||||
key: "build_list.#{bl.status}",
|
||||
project_id: bl.project_id,
|
||||
user_id: bl.user_id,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
def self.statsd_increment(options = {})
|
||||
Statistic.perform_later(:middle, :now_statsd_increment, options)
|
||||
end
|
||||
|
||||
end
|
|
@ -0,0 +1,72 @@
|
|||
class StatisticPresenter < ApplicationPresenter
|
||||
|
||||
attr_accessor :range_start, :range_end, :unit
|
||||
|
||||
def initialize(range_start: nil, range_end: nil, unit: nil)
|
||||
@range_start = range_start
|
||||
@range_end = range_end
|
||||
@unit = unit
|
||||
end
|
||||
|
||||
def as_json(options = nil)
|
||||
{
|
||||
build_lists: {
|
||||
build_started: prepare_collection(build_lists_started),
|
||||
success: prepare_collection(build_lists_success),
|
||||
build_error: prepare_collection(build_lists_error),
|
||||
build_published: prepare_collection(build_lists_published),
|
||||
|
||||
build_started_count: build_lists_started.sum(&:count),
|
||||
success_count: build_lists_success.sum(&:count),
|
||||
build_error_count: build_lists_error.sum(&:count),
|
||||
build_published_count: build_lists_published.sum(&:count),
|
||||
}
|
||||
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def scope
|
||||
@scope ||= Statistic.for_period(range_start, range_end)
|
||||
end
|
||||
|
||||
def build_lists
|
||||
@build_lists ||= scope.
|
||||
select("SUM(counter) as count, date_trunc('#{ unit }', activity_at) as activity_at").
|
||||
group("date_trunc('#{ unit }', activity_at)").order('activity_at')
|
||||
end
|
||||
|
||||
def build_lists_started
|
||||
@build_lists_started ||= build_lists.build_lists_started.to_a
|
||||
end
|
||||
|
||||
def build_lists_success
|
||||
@build_lists_success ||= build_lists.build_lists_success.to_a
|
||||
end
|
||||
|
||||
def build_lists_error
|
||||
@build_lists_error ||= build_lists.build_lists_error.to_a
|
||||
end
|
||||
|
||||
def build_lists_published
|
||||
@build_lists_published ||= build_lists.build_lists_published.to_a
|
||||
end
|
||||
|
||||
def prepare_collection(items)
|
||||
data = []
|
||||
to = range_start
|
||||
while to <= range_end
|
||||
from = to - 1.send(unit)
|
||||
y = items.find{ |i| i.activity_at > from && i.activity_at <= to }.try(:count)
|
||||
data << { x: from.strftime(format), y: y || 0 }
|
||||
to += 1.send(unit)
|
||||
end
|
||||
data
|
||||
end
|
||||
|
||||
def format
|
||||
@format ||= unit == :hour ? '%F %H:00:00' : '%F'
|
||||
end
|
||||
|
||||
end
|
|
@ -0,0 +1,55 @@
|
|||
.row
|
||||
.span8
|
||||
.graph-wrapper
|
||||
%h3
|
||||
%span.graph-key-color1
|
||||
= t('.build_started_title')
|
||||
%span.graph-key-color2
|
||||
= t('.success_title')
|
||||
%span.graph-key-color3
|
||||
= t('.build_error_title')
|
||||
%span.graph-key-color4
|
||||
= t('.build_published_title')
|
||||
.centered.graph-loading{ ng_show: 'loading' }
|
||||
= image_tag 'loading-large.gif'
|
||||
.no-data{ ng_hide: 'loading || statistics.build_lists' }
|
||||
= t('.no_data')
|
||||
%canvas#build_lists_chart{ ng_show: 'statistics.build_lists' }
|
||||
|
||||
.span3
|
||||
.panel-wrapper
|
||||
%h3
|
||||
= t('.total_build_started')
|
||||
.panel-data
|
||||
= image_tag 'loading-small.gif', ng_show: 'loading'
|
||||
.no-data{ ng_hide: 'loading || statistics.build_lists.build_started_count >= 0' }
|
||||
= t('.no_data')
|
||||
{{ statistics.build_lists.build_started_count | number }}
|
||||
|
||||
.panel-wrapper
|
||||
%h3
|
||||
= t('.total_success')
|
||||
.panel-data
|
||||
= image_tag 'loading-small.gif', ng_show: 'loading'
|
||||
.no-data{ ng_hide: 'loading || statistics.build_lists.success_count >= 0' }
|
||||
= t('.no_data')
|
||||
{{ statistics.build_lists.success_count | number }}
|
||||
|
||||
.panel-wrapper
|
||||
%h3
|
||||
= t('.total_build_error')
|
||||
.panel-data
|
||||
= image_tag 'loading-small.gif', ng_show: 'loading'
|
||||
.no-data{ ng_hide: 'loading || statistics.build_lists.build_error_count >= 0' }
|
||||
= t('.no_data')
|
||||
{{ statistics.build_lists.build_error_count | number }}
|
||||
|
||||
.panel-wrapper
|
||||
%h3
|
||||
= t('.total_build_published')
|
||||
.panel-data
|
||||
= image_tag 'loading-small.gif', ng_show: 'loading'
|
||||
.no-data{ ng_hide: 'loading || statistics.build_lists.build_published_count >= 0' }
|
||||
= t('.no_data')
|
||||
{{ statistics.build_lists.build_published_count | number }}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
.row
|
||||
.span11.flash_notify
|
||||
|
||||
%h3.text-info
|
||||
= t('.header')
|
||||
|
||||
= form_tag '#', class: 'form-inline alert alert-info centered', id: 'range-form' do
|
||||
%label.control-label
|
||||
= t('.range_label')
|
||||
= select_tag 'range', statistics_range_options, id: 'range_select', class: 'select input-medium', ng_model: 'range', ng_change: 'rangeChange()'
|
||||
%span{ ng_show: "range == 'custom'" }
|
||||
= text_field_tag :range_start, Date.today - 1.month, class: 'date_picker input-medium', placeholder: t('.range_start_placeholder'), ng_model: 'range_start', ng_change: 'rangeChange()', readonly: true
|
||||
= t('.range_separator')
|
||||
= text_field_tag :range_end, Date.today, class: 'date_picker input-medium', placeholder: t('.range_end_placeholder'), ng_model: 'range_end', ng_change: 'rangeChange()', readonly: true
|
|
@ -0,0 +1,7 @@
|
|||
- set_meta_tags title: t('.header')
|
||||
|
||||
#manage-statistics{ ng_controller: 'StatisticsController', ng_init: 'init()' }
|
||||
|
||||
= render 'filter'
|
||||
= render 'build_lists'
|
||||
|
|
@ -9,6 +9,7 @@ en:
|
|||
build_lists: Task monitoring
|
||||
groups: Groups
|
||||
advisories: Advisories
|
||||
statistics: Statistics
|
||||
bottom_menu:
|
||||
copyright: ROSA Lab © %{year}
|
||||
about: About the company
|
||||
|
|
|
@ -9,6 +9,7 @@ ru:
|
|||
build_lists: Мониторинг задач
|
||||
groups: Группы
|
||||
advisories: Бюллетени
|
||||
statistics: Статистика
|
||||
bottom_menu:
|
||||
copyright: ROSA Лаб. © %{year}
|
||||
about: О компании
|
||||
|
@ -47,7 +48,7 @@ ru:
|
|||
wiki: Вики
|
||||
platform_menu:
|
||||
settings: Настройки
|
||||
admins_menu_header: Центр управления
|
||||
admins_menu_header: ЦУ
|
||||
admins_menu:
|
||||
users: Пользователи
|
||||
register_requests: Инвайты
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
en:
|
||||
statistics:
|
||||
index:
|
||||
header: "Statistics"
|
||||
|
||||
filter:
|
||||
header: "Statistics"
|
||||
range_label: "Display data for"
|
||||
range_start_placeholder: "Select a start date"
|
||||
range_separator: " and "
|
||||
range_end_placeholder: "Select an end date"
|
||||
no_data: "No data available"
|
||||
|
||||
build_lists:
|
||||
build_started_title: "Build started"
|
||||
success_title: "Build complete"
|
||||
build_error_title: "Build error"
|
||||
build_published_title: "Build has been published"
|
||||
|
||||
total_build_started: "Total build started"
|
||||
total_success: "Total build complete"
|
||||
total_build_error: "Total build error"
|
||||
total_build_published: "Total build has been published"
|
||||
|
||||
helper:
|
||||
period:
|
||||
twenty_four_hours: "the last 24 hours"
|
||||
last_7_days: "the last 7 days"
|
||||
last_30_days: "the last 30 days"
|
||||
last_60_days: "the last 60 days"
|
||||
last_90_days: "the last 90 days"
|
||||
last_180_days: "the last 6 months"
|
||||
last_year: "the last year"
|
||||
custom: "custom"
|
|
@ -0,0 +1,34 @@
|
|||
ru:
|
||||
statistics:
|
||||
index:
|
||||
header: "Статистика"
|
||||
|
||||
filter:
|
||||
header: "Статистика"
|
||||
range_label: "Показать данные за"
|
||||
range_start_placeholder: "Выберите начальную дату"
|
||||
range_separator: " и "
|
||||
range_end_placeholder: "Выберите конечную дату"
|
||||
no_data: "Нет данных"
|
||||
|
||||
build_lists:
|
||||
build_started_title: "Cобирается"
|
||||
success_title: "Cобрано"
|
||||
build_error_title: "Ошибка сборки"
|
||||
build_published_title: "Опубликовано"
|
||||
|
||||
total_build_started: "Всего собирается"
|
||||
total_success: "Всего собрано"
|
||||
total_build_error: "Всего ошибок сборки"
|
||||
total_build_published: "Всего опубликовано"
|
||||
|
||||
helper:
|
||||
period:
|
||||
twenty_four_hours: "последние 24 часа"
|
||||
last_7_days: "последние 7 дней"
|
||||
last_30_days: "последние 30 дней"
|
||||
last_60_days: "последние 60 дней"
|
||||
last_90_days: "последние 90 дней"
|
||||
last_180_days: "последние 6 месяцев"
|
||||
last_year: "последний год"
|
||||
custom: "интервал"
|
|
@ -13,6 +13,7 @@ Rosa::Application.routes.draw do
|
|||
match '/sitemap.xml.gz' => 'sitemap#show', via: [:get, :post, :head], as: :sitemap
|
||||
match '/robots.txt' => 'sitemap#robots', via: [:get, :post, :head], as: :robots
|
||||
|
||||
resources :statistics, only: [:index]
|
||||
resource :contact, only: [:new, :create, :sended] do
|
||||
get '/' => 'contacts#new'
|
||||
get :sended
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
class CreateStatistics < ActiveRecord::Migration
|
||||
def up
|
||||
create_table :statistics do |t|
|
||||
t.integer :user_id, null: false
|
||||
t.string :email, null: false
|
||||
t.integer :project_id, null: false
|
||||
t.string :project_name_with_owner, null: false
|
||||
t.string :key, null: false
|
||||
t.integer :counter, null: false, default: 0
|
||||
t.datetime :activity_at, null: false
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
|
||||
add_index :statistics, :user_id
|
||||
add_index :statistics, :project_id
|
||||
add_index :statistics, :key
|
||||
add_index :statistics, [:user_id, :key, :activity_at]
|
||||
add_index :statistics, [:project_id, :key, :activity_at]
|
||||
add_index :statistics, [:key, :activity_at]
|
||||
add_index :statistics, :activity_at
|
||||
|
||||
add_index :statistics, [:user_id, :project_id, :key, :activity_at], unique: true,
|
||||
name: 'index_statistics_on_all_keys'
|
||||
end
|
||||
|
||||
def down
|
||||
drop_table :statistics
|
||||
end
|
||||
|
||||
end
|
224
db/schema.rb
224
db/schema.rb
|
@ -11,7 +11,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 20140924203530) do
|
||||
ActiveRecord::Schema.define(version: 20141006182907) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
|
@ -31,8 +31,8 @@ ActiveRecord::Schema.define(version: 20140924203530) do
|
|||
t.text "description", default: ""
|
||||
t.text "references", default: ""
|
||||
t.text "update_type", default: ""
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["advisory_id"], :name => "index_advisories_on_advisory_id", :unique => true
|
||||
t.index ["update_type"], :name => "index_advisories_on_update_type"
|
||||
end
|
||||
|
@ -90,8 +90,8 @@ ActiveRecord::Schema.define(version: 20140924203530) do
|
|||
t.string "version"
|
||||
t.string "release"
|
||||
t.string "package_type"
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.boolean "actual", default: false
|
||||
t.string "sha1"
|
||||
t.integer "epoch"
|
||||
|
@ -151,6 +151,31 @@ ActiveRecord::Schema.define(version: 20140924203530) do
|
|||
t.index ["project_id"], :name => "index_build_lists_on_project_id"
|
||||
end
|
||||
|
||||
create_table "projects", force: true do |t|
|
||||
t.string "name"
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.integer "owner_id"
|
||||
t.string "owner_type"
|
||||
t.string "visibility", default: "open"
|
||||
t.text "description"
|
||||
t.string "ancestry"
|
||||
t.boolean "has_issues", default: true
|
||||
t.string "srpm_file_name"
|
||||
t.integer "srpm_file_size"
|
||||
t.datetime "srpm_updated_at"
|
||||
t.string "srpm_content_type"
|
||||
t.boolean "has_wiki", default: false
|
||||
t.string "default_branch", default: "master"
|
||||
t.boolean "is_package", default: true, null: false
|
||||
t.integer "maintainer_id"
|
||||
t.boolean "publish_i686_into_x86_64", default: false
|
||||
t.string "owner_uname", null: false
|
||||
t.boolean "architecture_dependent", default: false, null: false
|
||||
t.integer "autostart_status"
|
||||
t.index ["name", "owner_id", "owner_type"], :name => "index_projects_on_name_and_owner_id_and_owner_type", :unique => true, :case_sensitive => false
|
||||
end
|
||||
|
||||
create_table "build_scripts", force: true do |t|
|
||||
t.integer "project_id", null: false
|
||||
t.string "treeish", null: false
|
||||
|
@ -160,6 +185,8 @@ ActiveRecord::Schema.define(version: 20140924203530) do
|
|||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.index ["project_id", "treeish"], :name => "index_build_scripts_on_project_id_and_treeish", :unique => true
|
||||
t.index ["project_id"], :name => "fk__build_scripts_project_id"
|
||||
t.foreign_key ["project_id"], "projects", ["id"], :on_update => :no_action, :on_delete => :no_action, :name => "fk_build_scripts_project_id"
|
||||
end
|
||||
|
||||
create_table "comments", force: true do |t|
|
||||
|
@ -195,8 +222,6 @@ ActiveRecord::Schema.define(version: 20140924203530) do
|
|||
t.text "message"
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.index ["eventable_id", "eventable_type"], :name => "index_event_logs_on_eventable_id_and_eventable_type"
|
||||
t.index ["user_id"], :name => "index_event_logs_on_user_id"
|
||||
end
|
||||
|
||||
create_table "flash_notifies", force: true do |t|
|
||||
|
@ -204,8 +229,8 @@ ActiveRecord::Schema.define(version: 20140924203530) do
|
|||
t.text "body_en", null: false
|
||||
t.string "status", null: false
|
||||
t.boolean "published", default: true, null: false
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
end
|
||||
|
||||
create_table "groups", force: true do |t|
|
||||
|
@ -225,8 +250,8 @@ ActiveRecord::Schema.define(version: 20140924203530) do
|
|||
t.text "data"
|
||||
t.integer "project_id"
|
||||
t.string "name"
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
end
|
||||
|
||||
create_table "issues", force: true do |t|
|
||||
|
@ -251,8 +276,8 @@ ActiveRecord::Schema.define(version: 20140924203530) do
|
|||
t.string "key_id", null: false
|
||||
t.integer "user_id", null: false
|
||||
t.integer "repository_id", null: false
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["repository_id"], :name => "index_key_pairs_on_repository_id", :unique => true
|
||||
end
|
||||
|
||||
|
@ -261,16 +286,16 @@ ActiveRecord::Schema.define(version: 20140924203530) do
|
|||
t.integer "user_id", null: false
|
||||
t.string "key_id", null: false
|
||||
t.text "public", null: false
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["repository_id"], :name => "index_key_pairs_backup_on_repository_id", :unique => true
|
||||
end
|
||||
|
||||
create_table "labelings", force: true do |t|
|
||||
t.integer "label_id", null: false
|
||||
t.integer "issue_id"
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["issue_id"], :name => "index_labelings_on_issue_id"
|
||||
end
|
||||
|
||||
|
@ -278,16 +303,16 @@ ActiveRecord::Schema.define(version: 20140924203530) do
|
|||
t.string "name", null: false
|
||||
t.string "color", null: false
|
||||
t.integer "project_id"
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["project_id"], :name => "index_labels_on_project_id"
|
||||
end
|
||||
|
||||
create_table "mass_builds", force: true do |t|
|
||||
t.integer "build_for_platform_id", null: false
|
||||
t.string "name"
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.string "arch_names"
|
||||
t.integer "user_id"
|
||||
t.integer "build_lists_count", default: 0, null: false
|
||||
|
@ -310,6 +335,46 @@ ActiveRecord::Schema.define(version: 20140924203530) do
|
|||
t.integer "status", default: 2000, null: false
|
||||
end
|
||||
|
||||
create_table "users", force: true do |t|
|
||||
t.string "name"
|
||||
t.string "email", default: "", null: false
|
||||
t.string "encrypted_password", limit: 128, default: "", null: false
|
||||
t.string "reset_password_token"
|
||||
t.datetime "reset_password_sent_at"
|
||||
t.datetime "remember_created_at"
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.text "ssh_key"
|
||||
t.string "uname"
|
||||
t.string "role"
|
||||
t.string "language", default: "en"
|
||||
t.integer "own_projects_count", default: 0, null: false
|
||||
t.text "professional_experience"
|
||||
t.string "site"
|
||||
t.string "company"
|
||||
t.string "location"
|
||||
t.string "avatar_file_name"
|
||||
t.string "avatar_content_type"
|
||||
t.integer "avatar_file_size"
|
||||
t.datetime "avatar_updated_at"
|
||||
t.integer "failed_attempts", default: 0
|
||||
t.string "unlock_token"
|
||||
t.datetime "locked_at"
|
||||
t.string "confirmation_token"
|
||||
t.datetime "confirmed_at"
|
||||
t.datetime "confirmation_sent_at"
|
||||
t.string "authentication_token"
|
||||
t.integer "build_priority", default: 50
|
||||
t.boolean "sound_notifications", default: true
|
||||
t.boolean "hide_email", default: true, null: false
|
||||
t.index ["authentication_token"], :name => "index_users_on_authentication_token"
|
||||
t.index ["confirmation_token"], :name => "index_users_on_confirmation_token", :unique => true
|
||||
t.index ["email"], :name => "index_users_on_email", :unique => true
|
||||
t.index ["reset_password_token"], :name => "index_users_on_reset_password_token", :unique => true
|
||||
t.index ["uname"], :name => "index_users_on_uname", :unique => true
|
||||
t.index ["unlock_token"], :name => "index_users_on_unlock_token", :unique => true
|
||||
end
|
||||
|
||||
create_table "node_instructions", force: true do |t|
|
||||
t.integer "user_id", null: false
|
||||
t.text "encrypted_instruction", null: false
|
||||
|
@ -317,6 +382,8 @@ ActiveRecord::Schema.define(version: 20140924203530) do
|
|||
t.string "status"
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.index ["user_id"], :name => "fk__node_instructions_user_id"
|
||||
t.foreign_key ["user_id"], "users", ["id"], :on_update => :no_action, :on_delete => :no_action, :name => "fk_node_instructions_user_id"
|
||||
end
|
||||
|
||||
create_table "platform_arch_settings", force: true do |t|
|
||||
|
@ -324,8 +391,8 @@ ActiveRecord::Schema.define(version: 20140924203530) do
|
|||
t.integer "arch_id", null: false
|
||||
t.integer "time_living", null: false
|
||||
t.boolean "default"
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["platform_id", "arch_id"], :name => "index_platform_arch_settings_on_platform_id_and_arch_id", :unique => true
|
||||
end
|
||||
|
||||
|
@ -340,7 +407,7 @@ ActiveRecord::Schema.define(version: 20140924203530) do
|
|||
t.string "owner_type"
|
||||
t.string "visibility", default: "open", null: false
|
||||
t.string "platform_type", default: "main", null: false
|
||||
t.string "distrib_type"
|
||||
t.string "distrib_type", null: false
|
||||
t.integer "status"
|
||||
t.datetime "last_regenerated_at"
|
||||
t.integer "last_regenerated_status"
|
||||
|
@ -398,8 +465,8 @@ ActiveRecord::Schema.define(version: 20140924203530) do
|
|||
t.integer "build_count", default: 0, null: false
|
||||
t.integer "arch_id", null: false
|
||||
t.integer "project_id", null: false
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["project_id", "arch_id"], :name => "index_project_statistics_on_project_id_and_arch_id", :unique => true
|
||||
end
|
||||
|
||||
|
@ -409,8 +476,8 @@ ActiveRecord::Schema.define(version: 20140924203530) do
|
|||
t.string "sha1"
|
||||
t.string "tag_name"
|
||||
t.integer "format_id"
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
end
|
||||
|
||||
create_table "project_to_repositories", force: true do |t|
|
||||
|
@ -422,31 +489,6 @@ ActiveRecord::Schema.define(version: 20140924203530) do
|
|||
t.index ["repository_id", "project_id"], :name => "index_project_to_repositories_on_repository_id_and_project_id", :unique => true
|
||||
end
|
||||
|
||||
create_table "projects", force: true do |t|
|
||||
t.string "name"
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.integer "owner_id"
|
||||
t.string "owner_type"
|
||||
t.string "visibility", default: "open"
|
||||
t.text "description"
|
||||
t.string "ancestry"
|
||||
t.boolean "has_issues", default: true
|
||||
t.boolean "has_wiki", default: false
|
||||
t.string "srpm_file_name"
|
||||
t.string "srpm_content_type"
|
||||
t.integer "srpm_file_size"
|
||||
t.datetime "srpm_updated_at"
|
||||
t.string "default_branch", default: "master"
|
||||
t.boolean "is_package", default: true, null: false
|
||||
t.integer "maintainer_id"
|
||||
t.boolean "publish_i686_into_x86_64", default: false
|
||||
t.string "owner_uname", null: false
|
||||
t.boolean "architecture_dependent", default: false, null: false
|
||||
t.integer "autostart_status"
|
||||
t.index ["name", "owner_id", "owner_type"], :name => "index_projects_on_name_and_owner_id_and_owner_type", :unique => true, :case_sensitive => false
|
||||
end
|
||||
|
||||
create_table "pull_requests", force: true do |t|
|
||||
t.integer "issue_id", null: false
|
||||
t.integer "to_project_id", null: false
|
||||
|
@ -455,9 +497,9 @@ ActiveRecord::Schema.define(version: 20140924203530) do
|
|||
t.string "from_ref", null: false
|
||||
t.string "from_project_owner_uname"
|
||||
t.string "from_project_name"
|
||||
t.index ["from_project_id"], :name => "index_pull_requests_on_from_project_id"
|
||||
t.index ["from_project_id"], :name => "index_pull_requests_on_head_project_id"
|
||||
t.index ["issue_id"], :name => "index_pull_requests_on_issue_id"
|
||||
t.index ["to_project_id"], :name => "index_pull_requests_on_to_project_id"
|
||||
t.index ["to_project_id"], :name => "index_pull_requests_on_base_project_id"
|
||||
end
|
||||
|
||||
create_table "register_requests", force: true do |t|
|
||||
|
@ -505,8 +547,8 @@ ActiveRecord::Schema.define(version: 20140924203530) do
|
|||
t.integer "status", default: 0
|
||||
t.datetime "last_regenerated_at"
|
||||
t.integer "last_regenerated_status"
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.string "last_regenerated_log_sha1"
|
||||
t.index ["repository_id", "platform_id"], :name => "index_repository_statuses_on_repository_id_and_platform_id", :unique => true
|
||||
end
|
||||
|
@ -533,12 +575,32 @@ ActiveRecord::Schema.define(version: 20140924203530) do
|
|||
t.text "key", null: false
|
||||
t.string "fingerprint", null: false
|
||||
t.integer "user_id", null: false
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["fingerprint"], :name => "index_ssh_keys_on_fingerprint", :unique => true
|
||||
t.index ["user_id"], :name => "index_ssh_keys_on_user_id"
|
||||
end
|
||||
|
||||
create_table "statistics", force: true do |t|
|
||||
t.integer "user_id", null: false
|
||||
t.string "email", null: false
|
||||
t.integer "project_id", null: false
|
||||
t.string "project_name_with_owner", null: false
|
||||
t.string "key", null: false
|
||||
t.integer "counter", default: 0, null: false
|
||||
t.datetime "activity_at", null: false
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.index ["activity_at"], :name => "index_statistics_on_activity_at"
|
||||
t.index ["key", "activity_at"], :name => "index_statistics_on_key_and_activity_at"
|
||||
t.index ["key"], :name => "index_statistics_on_key"
|
||||
t.index ["project_id", "key", "activity_at"], :name => "index_statistics_on_project_id_and_key_and_activity_at"
|
||||
t.index ["project_id"], :name => "index_statistics_on_project_id"
|
||||
t.index ["user_id", "key", "activity_at"], :name => "index_statistics_on_user_id_and_key_and_activity_at"
|
||||
t.index ["user_id", "project_id", "key", "activity_at"], :name => "index_statistics_on_all_keys", :unique => true
|
||||
t.index ["user_id"], :name => "index_statistics_on_user_id"
|
||||
end
|
||||
|
||||
create_table "subscribes", force: true do |t|
|
||||
t.string "subscribeable_type"
|
||||
t.integer "user_id"
|
||||
|
@ -557,50 +619,10 @@ ActiveRecord::Schema.define(version: 20140924203530) do
|
|||
t.string "status", default: "active"
|
||||
t.text "description"
|
||||
t.string "authentication_token", null: false
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["authentication_token"], :name => "index_tokens_on_authentication_token", :unique => true
|
||||
t.index ["subject_id", "subject_type"], :name => "index_tokens_on_subject_id_and_subject_type"
|
||||
end
|
||||
|
||||
create_table "users", force: true do |t|
|
||||
t.string "name"
|
||||
t.string "email", default: "", null: false
|
||||
t.string "encrypted_password", default: "", null: false
|
||||
t.string "reset_password_token"
|
||||
t.datetime "reset_password_sent_at"
|
||||
t.datetime "remember_created_at"
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.text "ssh_key"
|
||||
t.string "uname"
|
||||
t.string "role"
|
||||
t.string "language", default: "en"
|
||||
t.integer "own_projects_count", default: 0, null: false
|
||||
t.string "confirmation_token"
|
||||
t.datetime "confirmed_at"
|
||||
t.datetime "confirmation_sent_at"
|
||||
t.text "professional_experience"
|
||||
t.string "site"
|
||||
t.string "company"
|
||||
t.string "location"
|
||||
t.string "avatar_file_name"
|
||||
t.string "avatar_content_type"
|
||||
t.integer "avatar_file_size"
|
||||
t.datetime "avatar_updated_at"
|
||||
t.integer "failed_attempts", default: 0
|
||||
t.string "unlock_token"
|
||||
t.datetime "locked_at"
|
||||
t.string "authentication_token"
|
||||
t.integer "build_priority", default: 50
|
||||
t.boolean "sound_notifications", default: true
|
||||
t.boolean "hide_email", default: true, null: false
|
||||
t.index ["authentication_token"], :name => "index_users_on_authentication_token"
|
||||
t.index ["confirmation_token"], :name => "index_users_on_confirmation_token", :unique => true
|
||||
t.index ["email"], :name => "index_users_on_email", :unique => true
|
||||
t.index ["reset_password_token"], :name => "index_users_on_reset_password_token", :unique => true
|
||||
t.index ["uname"], :name => "index_users_on_uname", :unique => true
|
||||
t.index ["unlock_token"], :name => "index_users_on_unlock_token", :unique => true
|
||||
end
|
||||
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue