##// END OF EJS Templates
repository-groups: use lazy loaded admin dashboard
marcink -
r3623:58c253a3 default
parent child Browse files
Show More
@@ -0,0 +1,54 b''
1 # -*- coding: utf-8 -*-
2
3 import logging
4
5 from alembic.migration import MigrationContext
6 from alembic.operations import Operations
7 from sqlalchemy import String, Column
8 from sqlalchemy.sql import text
9
10 from rhodecode.lib.dbmigrate.versions import _reset_base
11 from rhodecode.model import meta, init_model_encryption
12 from rhodecode.model.db import RepoGroup
13
14
15 log = logging.getLogger(__name__)
16
17
18 def upgrade(migrate_engine):
19 """
20 Upgrade operations go here.
21 Don't create your own engine; bind migrate_engine to your metadata
22 """
23 _reset_base(migrate_engine)
24 from rhodecode.lib.dbmigrate.schema import db_4_16_0_2
25
26 init_model_encryption(db_4_16_0_2)
27
28 context = MigrationContext.configure(migrate_engine.connect())
29 op = Operations(context)
30
31 repo_group = db_4_16_0_2.RepoGroup.__table__
32
33 with op.batch_alter_table(repo_group.name) as batch_op:
34 batch_op.add_column(
35 Column("repo_group_name_hash", String(1024), nullable=True, unique=False))
36
37 _generate_repo_group_name_hashes(db_4_16_0_2, op, meta.Session)
38
39
40 def downgrade(migrate_engine):
41 pass
42
43
44 def _generate_repo_group_name_hashes(models, op, session):
45 repo_groups = models.RepoGroup.get_all()
46 for repo_group in repo_groups:
47 print(repo_group.group_name)
48 hash_ = RepoGroup.hash_repo_group_name(repo_group.group_name)
49 params = {'hash': hash_, 'id': repo_group.group_id}
50 query = text(
51 'UPDATE groups SET repo_group_name_hash = :hash'
52 ' WHERE group_id = :id').bindparams(**params)
53 op.execute(query)
54 session().commit()
@@ -0,0 +1,39 b''
1 # -*- coding: utf-8 -*-
2
3 import logging
4
5 from alembic.migration import MigrationContext
6 from alembic.operations import Operations
7
8 from rhodecode.lib.dbmigrate.versions import _reset_base
9 from rhodecode.model import init_model_encryption
10
11
12 log = logging.getLogger(__name__)
13
14
15 def upgrade(migrate_engine):
16 """
17 Upgrade operations go here.
18 Don't create your own engine; bind migrate_engine to your metadata
19 """
20 _reset_base(migrate_engine)
21 from rhodecode.lib.dbmigrate.schema import db_4_16_0_2
22
23 init_model_encryption(db_4_16_0_2)
24
25 context = MigrationContext.configure(migrate_engine.connect())
26 op = Operations(context)
27
28 repo_group = db_4_16_0_2.RepoGroup.__table__
29
30 with op.batch_alter_table(repo_group.name) as batch_op:
31 batch_op.alter_column("repo_group_name_hash", nullable=False)
32
33
34 def downgrade(migrate_engine):
35 pass
36
37
38 def _generate_repo_group_name_hashes(models, op, session):
39 pass
@@ -45,7 +45,7 b' PYRAMID_SETTINGS = {}'
45 EXTENSIONS = {}
45 EXTENSIONS = {}
46
46
47 __version__ = ('.'.join((str(each) for each in VERSION[:3])))
47 __version__ = ('.'.join((str(each) for each in VERSION[:3])))
48 __dbversion__ = 95 # defines current db version for migrations
48 __dbversion__ = 97 # defines current db version for migrations
49 __platform__ = platform.system()
49 __platform__ = platform.system()
50 __license__ = 'AGPLv3, and Commercial License'
50 __license__ = 'AGPLv3, and Commercial License'
51 __author__ = 'RhodeCode GmbH'
51 __author__ = 'RhodeCode GmbH'
@@ -424,6 +424,10 b' def admin_routes(config):'
424 pattern='/repo_groups')
424 pattern='/repo_groups')
425
425
426 config.add_route(
426 config.add_route(
427 name='repo_groups_data',
428 pattern='/repo_groups_data')
429
430 config.add_route(
427 name='repo_group_new',
431 name='repo_group_new',
428 pattern='/repo_group/new')
432 pattern='/repo_group/new')
429
433
@@ -23,11 +23,11 b' import pytest'
23
23
24 from rhodecode.apps._base import ADMIN_PREFIX
24 from rhodecode.apps._base import ADMIN_PREFIX
25 from rhodecode.lib import helpers as h
25 from rhodecode.lib import helpers as h
26 from rhodecode.model.db import Repository, UserRepoToPerm, User
26 from rhodecode.model.db import Repository, UserRepoToPerm, User, RepoGroup
27 from rhodecode.model.meta import Session
27 from rhodecode.model.meta import Session
28 from rhodecode.model.repo_group import RepoGroupModel
28 from rhodecode.model.repo_group import RepoGroupModel
29 from rhodecode.tests import (
29 from rhodecode.tests import (
30 assert_session_flash, TEST_USER_REGULAR_LOGIN, TESTS_TMP_PATH, TestController)
30 assert_session_flash, TEST_USER_REGULAR_LOGIN, TESTS_TMP_PATH)
31 from rhodecode.tests.fixture import Fixture
31 from rhodecode.tests.fixture import Fixture
32
32
33 fixture = Fixture()
33 fixture = Fixture()
@@ -38,6 +38,7 b' def route_path(name, params=None, **kwar'
38
38
39 base_url = {
39 base_url = {
40 'repo_groups': ADMIN_PREFIX + '/repo_groups',
40 'repo_groups': ADMIN_PREFIX + '/repo_groups',
41 'repo_groups_data': ADMIN_PREFIX + '/repo_groups_data',
41 'repo_group_new': ADMIN_PREFIX + '/repo_group/new',
42 'repo_group_new': ADMIN_PREFIX + '/repo_group/new',
42 'repo_group_create': ADMIN_PREFIX + '/repo_group/create',
43 'repo_group_create': ADMIN_PREFIX + '/repo_group/create',
43
44
@@ -59,13 +60,30 b' def _get_permission_for_user(user, repo)'
59
60
60 @pytest.mark.usefixtures("app")
61 @pytest.mark.usefixtures("app")
61 class TestAdminRepositoryGroups(object):
62 class TestAdminRepositoryGroups(object):
63
62 def test_show_repo_groups(self, autologin_user):
64 def test_show_repo_groups(self, autologin_user):
63 response = self.app.get(route_path('repo_groups'))
65 self.app.get(route_path('repo_groups'))
64 response.mustcontain('data: []')
66
67 def test_show_repo_groups_data(self, autologin_user, xhr_header):
68 response = self.app.get(route_path(
69 'repo_groups_data'), extra_environ=xhr_header)
70
71 all_repo_groups = RepoGroup.query().count()
72 assert response.json['recordsTotal'] == all_repo_groups
65
73
66 def test_show_repo_groups_after_creating_group(self, autologin_user):
74 def test_show_repo_groups_data_filtered(self, autologin_user, xhr_header):
75 response = self.app.get(route_path(
76 'repo_groups_data', params={'search[value]': 'empty_search'}),
77 extra_environ=xhr_header)
78
79 all_repo_groups = RepoGroup.query().count()
80 assert response.json['recordsTotal'] == all_repo_groups
81 assert response.json['recordsFiltered'] == 0
82
83 def test_show_repo_groups_after_creating_group(self, autologin_user, xhr_header):
67 fixture.create_repo_group('test_repo_group')
84 fixture.create_repo_group('test_repo_group')
68 response = self.app.get(route_path('repo_groups'))
85 response = self.app.get(route_path(
86 'repo_groups_data'), extra_environ=xhr_header)
69 response.mustcontain('"name_raw": "test_repo_group"')
87 response.mustcontain('"name_raw": "test_repo_group"')
70 fixture.destroy_repo_group('test_repo_group')
88 fixture.destroy_repo_group('test_repo_group')
71
89
@@ -17,7 +17,7 b''
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20 import datetime
21 import logging
21 import logging
22 import formencode
22 import formencode
23 import formencode.htmlfill
23 import formencode.htmlfill
@@ -30,16 +30,16 b' from pyramid.response import Response'
30 from rhodecode import events
30 from rhodecode import events
31 from rhodecode.apps._base import BaseAppView, DataGridAppView
31 from rhodecode.apps._base import BaseAppView, DataGridAppView
32
32
33 from rhodecode.lib.ext_json import json
34 from rhodecode.lib.auth import (
33 from rhodecode.lib.auth import (
35 LoginRequired, CSRFRequired, NotAnonymous,
34 LoginRequired, CSRFRequired, NotAnonymous,
36 HasPermissionAny, HasRepoGroupPermissionAny)
35 HasPermissionAny, HasRepoGroupPermissionAny)
37 from rhodecode.lib import helpers as h, audit_logger
36 from rhodecode.lib import helpers as h, audit_logger
38 from rhodecode.lib.utils2 import safe_int, safe_unicode
37 from rhodecode.lib.utils2 import safe_int, safe_unicode, datetime_to_time
39 from rhodecode.model.forms import RepoGroupForm
38 from rhodecode.model.forms import RepoGroupForm
40 from rhodecode.model.repo_group import RepoGroupModel
39 from rhodecode.model.repo_group import RepoGroupModel
41 from rhodecode.model.scm import RepoGroupList
40 from rhodecode.model.scm import RepoGroupList
42 from rhodecode.model.db import Session, RepoGroup
41 from rhodecode.model.db import (
42 or_, count, func, in_filter_generator, Session, RepoGroup, User, Repository)
43
43
44 log = logging.getLogger(__name__)
44 log = logging.getLogger(__name__)
45
45
@@ -88,22 +88,168 b' class AdminRepoGroupsView(BaseAppView, D'
88 return False
88 return False
89 return False
89 return False
90
90
91 # permission check in data loading of
92 # `repo_group_list_data` via RepoGroupList
91 @LoginRequired()
93 @LoginRequired()
92 @NotAnonymous()
94 @NotAnonymous()
93 # perms check inside
94 @view_config(
95 @view_config(
95 route_name='repo_groups', request_method='GET',
96 route_name='repo_groups', request_method='GET',
96 renderer='rhodecode:templates/admin/repo_groups/repo_groups.mako')
97 renderer='rhodecode:templates/admin/repo_groups/repo_groups.mako')
97 def repo_group_list(self):
98 def repo_group_list(self):
98 c = self.load_default_context()
99 c = self.load_default_context()
100 return self._get_template_context(c)
99
101
100 repo_group_list = RepoGroup.get_all_repo_groups()
102 # permission check inside
101 repo_group_list_acl = RepoGroupList(
103 @LoginRequired()
102 repo_group_list, perm_set=['group.admin'])
104 @NotAnonymous()
103 repo_group_data = RepoGroupModel().get_repo_groups_as_dict(
105 @view_config(
104 repo_group_list=repo_group_list_acl, admin=True)
106 route_name='repo_groups_data', request_method='GET',
105 c.data = json.dumps(repo_group_data)
107 renderer='json_ext', xhr=True)
106 return self._get_template_context(c)
108 def repo_group_list_data(self):
109 self.load_default_context()
110 column_map = {
111 'name_raw': 'group_name_hash',
112 'desc': 'group_description',
113 'last_change_raw': 'updated_on',
114 'top_level_repos': 'repos_total',
115 'owner': 'user_username',
116 }
117 draw, start, limit = self._extract_chunk(self.request)
118 search_q, order_by, order_dir = self._extract_ordering(
119 self.request, column_map=column_map)
120
121 _render = self.request.get_partial_renderer(
122 'rhodecode:templates/data_table/_dt_elements.mako')
123 c = _render.get_call_context()
124
125 def quick_menu(repo_group_name):
126 return _render('quick_repo_group_menu', repo_group_name)
127
128 def repo_group_lnk(repo_group_name):
129 return _render('repo_group_name', repo_group_name)
130
131 def last_change(last_change):
132 if isinstance(last_change, datetime.datetime) and not last_change.tzinfo:
133 delta = datetime.timedelta(
134 seconds=(datetime.datetime.now() - datetime.datetime.utcnow()).seconds)
135 last_change = last_change + delta
136 return _render("last_change", last_change)
137
138 def desc(desc, personal):
139 return _render(
140 'repo_group_desc', desc, personal, c.visual.stylify_metatags)
141
142 def repo_group_actions(repo_group_id, repo_group_name, gr_count):
143 return _render(
144 'repo_group_actions', repo_group_id, repo_group_name, gr_count)
145
146 def user_profile(username):
147 return _render('user_profile', username)
148
149 auth_repo_group_list = RepoGroupList(
150 RepoGroup.query().all(), perm_set=['group.admin'])
151
152 allowed_ids = [-1]
153 for repo_group in auth_repo_group_list:
154 allowed_ids.append(repo_group.group_id)
155
156 repo_groups_data_total_count = RepoGroup.query()\
157 .filter(or_(
158 # generate multiple IN to fix limitation problems
159 *in_filter_generator(RepoGroup.group_id, allowed_ids)
160 )) \
161 .count()
162
163 repo_groups_data_total_inactive_count = RepoGroup.query()\
164 .filter(RepoGroup.group_id.in_(allowed_ids))\
165 .count()
166
167 repo_count = count(Repository.repo_id)
168 base_q = Session.query(
169 RepoGroup.group_name,
170 RepoGroup.group_name_hash,
171 RepoGroup.group_description,
172 RepoGroup.group_id,
173 RepoGroup.personal,
174 RepoGroup.updated_on,
175 User,
176 repo_count.label('repos_count')
177 ) \
178 .filter(or_(
179 # generate multiple IN to fix limitation problems
180 *in_filter_generator(RepoGroup.group_id, allowed_ids)
181 )) \
182 .outerjoin(Repository) \
183 .join(User, User.user_id == RepoGroup.user_id) \
184 .group_by(RepoGroup, User)
185
186 if search_q:
187 like_expression = u'%{}%'.format(safe_unicode(search_q))
188 base_q = base_q.filter(or_(
189 RepoGroup.group_name.ilike(like_expression),
190 ))
191
192 repo_groups_data_total_filtered_count = base_q.count()
193 # the inactive isn't really used, but we still make it same as other data grids
194 # which use inactive (users,user groups)
195 repo_groups_data_total_filtered_inactive_count = repo_groups_data_total_filtered_count
196
197 sort_defined = False
198 if order_by == 'group_name':
199 sort_col = func.lower(RepoGroup.group_name)
200 sort_defined = True
201 elif order_by == 'repos_total':
202 sort_col = repo_count
203 sort_defined = True
204 elif order_by == 'user_username':
205 sort_col = User.username
206 else:
207 sort_col = getattr(RepoGroup, order_by, None)
208
209 if sort_defined or sort_col:
210 if order_dir == 'asc':
211 sort_col = sort_col.asc()
212 else:
213 sort_col = sort_col.desc()
214
215 base_q = base_q.order_by(sort_col)
216 base_q = base_q.offset(start).limit(limit)
217
218 # authenticated access to user groups
219 auth_repo_group_list = base_q.all()
220
221 repo_groups_data = []
222 for repo_gr in auth_repo_group_list:
223 row = {
224 "menu": quick_menu(repo_gr.group_name),
225 "name": repo_group_lnk(repo_gr.group_name),
226 "name_raw": repo_gr.group_name,
227 "last_change": last_change(repo_gr.updated_on),
228 "last_change_raw": datetime_to_time(repo_gr.updated_on),
229
230 "last_changeset": "",
231 "last_changeset_raw": "",
232
233 "desc": desc(repo_gr.group_description, repo_gr.personal),
234 "owner": user_profile(repo_gr.User.username),
235 "top_level_repos": repo_gr.repos_count,
236 "action": repo_group_actions(
237 repo_gr.group_id, repo_gr.group_name, repo_gr.repos_count),
238
239 }
240
241 repo_groups_data.append(row)
242
243 data = ({
244 'draw': draw,
245 'data': repo_groups_data,
246 'recordsTotal': repo_groups_data_total_count,
247 'recordsTotalInactive': repo_groups_data_total_inactive_count,
248 'recordsFiltered': repo_groups_data_total_filtered_count,
249 'recordsFilteredInactive': repo_groups_data_total_filtered_inactive_count,
250 })
251
252 return data
107
253
108 @LoginRequired()
254 @LoginRequired()
109 @NotAnonymous()
255 @NotAnonymous()
@@ -25,6 +25,7 b' Database Models for RhodeCode Enterprise'
25 import re
25 import re
26 import os
26 import os
27 import time
27 import time
28 import string
28 import hashlib
29 import hashlib
29 import logging
30 import logging
30 import datetime
31 import datetime
@@ -50,6 +51,7 b' from sqlalchemy.dialects.mysql import LO'
50 from zope.cachedescriptors.property import Lazy as LazyProperty
51 from zope.cachedescriptors.property import Lazy as LazyProperty
51 from pyramid import compat
52 from pyramid import compat
52 from pyramid.threadlocal import get_current_request
53 from pyramid.threadlocal import get_current_request
54 from webhelpers.text import collapse, remove_formatting
53
55
54 from rhodecode.translation import _
56 from rhodecode.translation import _
55 from rhodecode.lib.vcs import get_vcs_instance
57 from rhodecode.lib.vcs import get_vcs_instance
@@ -2469,7 +2471,8 b' class RepoGroup(Base, BaseModel):'
2469 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2471 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2470
2472
2471 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2473 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2472 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2474 _group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2475 group_name_hash = Column("repo_group_name_hash", String(1024), nullable=False, unique=False)
2473 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2476 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2474 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2477 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2475 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2478 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
@@ -2492,6 +2495,15 b' class RepoGroup(Base, BaseModel):'
2492 return u"<%s('id:%s:%s')>" % (
2495 return u"<%s('id:%s:%s')>" % (
2493 self.__class__.__name__, self.group_id, self.group_name)
2496 self.__class__.__name__, self.group_id, self.group_name)
2494
2497
2498 @hybrid_property
2499 def group_name(self):
2500 return self._group_name
2501
2502 @group_name.setter
2503 def group_name(self, value):
2504 self._group_name = value
2505 self.group_name_hash = self.hash_repo_group_name(value)
2506
2495 @validates('group_parent_id')
2507 @validates('group_parent_id')
2496 def validate_group_parent_id(self, key, val):
2508 def validate_group_parent_id(self, key, val):
2497 """
2509 """
@@ -2508,6 +2520,18 b' class RepoGroup(Base, BaseModel):'
2508 return h.escape(self.group_description)
2520 return h.escape(self.group_description)
2509
2521
2510 @classmethod
2522 @classmethod
2523 def hash_repo_group_name(cls, repo_group_name):
2524 val = remove_formatting(repo_group_name)
2525 val = safe_str(val).lower()
2526 chars = []
2527 for c in val:
2528 if c not in string.ascii_letters:
2529 c = str(ord(c))
2530 chars.append(c)
2531
2532 return ''.join(chars)
2533
2534 @classmethod
2511 def _generate_choice(cls, repo_group):
2535 def _generate_choice(cls, repo_group):
2512 from webhelpers.html import literal as _literal
2536 from webhelpers.html import literal as _literal
2513 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2537 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
@@ -2770,6 +2794,13 b' class RepoGroup(Base, BaseModel):'
2770 }
2794 }
2771 return data
2795 return data
2772
2796
2797 def get_dict(self):
2798 # Since we transformed `group_name` to a hybrid property, we need to
2799 # keep compatibility with the code which uses `group_name` field.
2800 result = super(RepoGroup, self).get_dict()
2801 result['group_name'] = result.pop('_group_name', None)
2802 return result
2803
2773
2804
2774 class Permission(Base, BaseModel):
2805 class Permission(Base, BaseModel):
2775 __tablename__ = 'permissions'
2806 __tablename__ = 'permissions'
@@ -10,7 +10,7 b''
10
10
11 <%def name="breadcrumbs_links()">
11 <%def name="breadcrumbs_links()">
12 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
12 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
13 ${h.link_to(_('Admin'),h.route_path('admin_home'))} &raquo; <span id="repo_group_count">0</span> ${_('repository groups')}
13 ${h.link_to(_('Admin'),h.route_path('admin_home'))} &raquo; <span id="repo_group_count">0</span>
14 </%def>
14 </%def>
15
15
16 <%def name="menu_bar_nav()">
16 <%def name="menu_bar_nav()">
@@ -36,15 +36,32 b''
36
36
37 <script>
37 <script>
38 $(document).ready(function() {
38 $(document).ready(function() {
39
39 var $repoGroupsListTable = $('#group_list_table');
40 var get_datatable_count = function(){
41 var api = $('#group_list_table').dataTable().api();
42 $('#repo_group_count').text(api.page.info().recordsDisplay);
43 };
44
40
45 // repo group list
41 // repo group list
46 $('#group_list_table').DataTable({
42 $repoGroupsListTable.DataTable({
47 data: ${c.data|n},
43 processing: true,
44 serverSide: true,
45 ajax: {
46 "url": "${h.route_path('repo_groups_data')}",
47 "dataSrc": function (json) {
48 var filteredCount = json.recordsFiltered;
49 var filteredInactiveCount = json.recordsFilteredInactive;
50 var totalInactive = json.recordsTotalInactive;
51 var total = json.recordsTotal;
52
53 var _text = _gettext(
54 "{0} of {1} repository groups").format(
55 filteredCount, total);
56
57 if (total === filteredCount) {
58 _text = _gettext("{0} repository groups").format(total);
59 }
60 $('#repo_group_count').text(_text);
61 return json.data;
62 },
63 },
64
48 dom: 'rtp',
65 dom: 'rtp',
49 pageLength: ${c.visual.admin_grid_items},
66 pageLength: ${c.visual.admin_grid_items},
50 order: [[ 0, "asc" ]],
67 order: [[ 0, "asc" ]],
@@ -62,36 +79,34 b''
62 { data: {"_": "owner",
79 { data: {"_": "owner",
63 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" },
80 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" },
64 { data: {"_": "action",
81 { data: {"_": "action",
65 "sort": "action"}, title: "${_('Action')}", className: "td-action" }
82 "sort": "action"}, title: "${_('Action')}", className: "td-action", orderable: false }
66 ],
83 ],
67 language: {
84 language: {
68 paginate: DEFAULT_GRID_PAGINATION,
85 paginate: DEFAULT_GRID_PAGINATION,
86 sProcessing: _gettext('loading...'),
69 emptyTable: _gettext("No repository groups available yet.")
87 emptyTable: _gettext("No repository groups available yet.")
70 },
88 },
71 "initComplete": function( settings, json ) {
72 get_datatable_count();
73 quick_repo_menu();
74 }
75 });
89 });
76
90
77 // update the counter when doing search
91 $repoGroupsListTable.on('xhr.dt', function(e, settings, json, xhr){
78 $('#group_list_table').on( 'search.dt', function (e,settings) {
92 $repoGroupsListTable.css('opacity', 1);
79 get_datatable_count();
93 });
94
95 $repoGroupsListTable.on('preXhr.dt', function(e, settings, data){
96 $repoGroupsListTable.css('opacity', 0.3);
80 });
97 });
81
98
82 // filter, filter both grids
99 // filter
83 $('#q_filter').on( 'keyup', function () {
100 $('#q_filter').on('keyup',
101 $.debounce(250, function() {
102 $repoGroupsListTable.DataTable().search(
103 $('#q_filter').val()
104 ).draw();
105 })
106 );
107 });
84
108
85 var repo_group_api = $('#group_list_table').dataTable().api();
109 </script>
86 repo_group_api
87 .columns(0)
88 .search(this.value)
89 .draw();
90 });
91
110
92 // refilter table if page load via back button
93 $("#q_filter").trigger('keyup');
94 });
95 </script>
96 </%def>
111 </%def>
97
112
@@ -66,7 +66,7 b''
66 { data: {"_": "state",
66 { data: {"_": "state",
67 "sort": "state"}, title: "${_('State')}", className: "td-tags td-state" },
67 "sort": "state"}, title: "${_('State')}", className: "td-tags td-state" },
68 { data: {"_": "action",
68 { data: {"_": "action",
69 "sort": "action"}, title: "${_('Action')}", className: "td-action" }
69 "sort": "action"}, title: "${_('Action')}", className: "td-action", orderable: false }
70 ],
70 ],
71 language: {
71 language: {
72 paginate: DEFAULT_GRID_PAGINATION,
72 paginate: DEFAULT_GRID_PAGINATION,
@@ -34,6 +34,8 b' def route_path(name, params=None, **kwar'
34 ADMIN_PREFIX + '/repos',
34 ADMIN_PREFIX + '/repos',
35 'repo_groups':
35 'repo_groups':
36 ADMIN_PREFIX + '/repo_groups',
36 ADMIN_PREFIX + '/repo_groups',
37 'repo_groups_data':
38 ADMIN_PREFIX + '/repo_groups_data',
37 'user_groups':
39 'user_groups':
38 ADMIN_PREFIX + '/user_groups',
40 ADMIN_PREFIX + '/user_groups',
39 'user_groups_data':
41 'user_groups_data':
@@ -67,8 +69,9 b' class TestAdminDelegatedUser(TestControl'
67 response = self.app.get(route_path('repos'), status=200)
69 response = self.app.get(route_path('repos'), status=200)
68 response.mustcontain('data: []')
70 response.mustcontain('data: []')
69
71
70 response = self.app.get(route_path('repo_groups'), status=200)
72 response = self.app.get(route_path('repo_groups_data'),
71 response.mustcontain('data: []')
73 status=200, extra_environ=xhr_header)
74 assert response.json['data'] == []
72
75
73 response = self.app.get(route_path('user_groups_data'),
76 response = self.app.get(route_path('user_groups_data'),
74 status=200, extra_environ=xhr_header)
77 status=200, extra_environ=xhr_header)
@@ -102,7 +105,8 b' class TestAdminDelegatedUser(TestControl'
102 response = self.app.get(route_path('repos'), status=200)
105 response = self.app.get(route_path('repos'), status=200)
103 response.mustcontain('"name_raw": "{}"'.format(repo_name))
106 response.mustcontain('"name_raw": "{}"'.format(repo_name))
104
107
105 response = self.app.get(route_path('repo_groups'), status=200)
108 response = self.app.get(route_path('repo_groups_data'),
109 extra_environ=xhr_header, status=200)
106 response.mustcontain('"name_raw": "{}"'.format(repo_group_name))
110 response.mustcontain('"name_raw": "{}"'.format(repo_group_name))
107
111
108 response = self.app.get(route_path('user_groups_data'),
112 response = self.app.get(route_path('user_groups_data'),
@@ -144,7 +148,8 b' class TestAdminDelegatedUser(TestControl'
144 response = self.app.get(route_path('repos'), status=200)
148 response = self.app.get(route_path('repos'), status=200)
145 response.mustcontain('"name_raw": "{}"'.format(repo_name))
149 response.mustcontain('"name_raw": "{}"'.format(repo_name))
146
150
147 response = self.app.get(route_path('repo_groups'), status=200)
151 response = self.app.get(route_path('repo_groups_data'),
152 extra_environ=xhr_header, status=200)
148 response.mustcontain('"name_raw": "{}"'.format(repo_group_name))
153 response.mustcontain('"name_raw": "{}"'.format(repo_group_name))
149
154
150 response = self.app.get(route_path('user_groups_data'),
155 response = self.app.get(route_path('user_groups_data'),
General Comments 0
You need to be logged in to leave comments. Login now