##// END OF EJS Templates
file-browser: refactor how we load metadata for file trees....
marcink -
r423:9930e2c8 default
parent child Browse files
Show More
@@ -1103,9 +1103,9 b' def make_map(config):'
1103 conditions={'function': check_repo},
1103 conditions={'function': check_repo},
1104 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1104 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1105
1105
1106 rmap.connect('files_metadata_list_home',
1106 rmap.connect('files_nodetree_full',
1107 '/{repo_name}/metadata_list/{revision}/{f_path}',
1107 '/{repo_name}/nodetree_full/{commit_id}/{f_path}',
1108 controller='files', action='metadata_list',
1108 controller='files', action='nodetree_full',
1109 conditions={'function': check_repo},
1109 conditions={'function': check_repo},
1110 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1110 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1111
1111
@@ -136,10 +136,12 b' class FilesController(BaseRepoController'
136 _namespace = caches.get_repo_namespace_key(namespace_type, repo_name)
136 _namespace = caches.get_repo_namespace_key(namespace_type, repo_name)
137 return caches.get_cache_manager('repo_cache_long', _namespace)
137 return caches.get_cache_manager('repo_cache_long', _namespace)
138
138
139 def _get_tree_at_commit(self, repo_name, commit_id, f_path):
139 def _get_tree_at_commit(self, repo_name, commit_id, f_path,
140 full_load=False, force=False):
140 def _cached_tree():
141 def _cached_tree():
141 log.debug('Generating cached file tree for %s, %s, %s',
142 log.debug('Generating cached file tree for %s, %s, %s',
142 repo_name, commit_id, f_path)
143 repo_name, commit_id, f_path)
144 c.full_load = full_load
143 return render('files/files_browser_tree.html')
145 return render('files/files_browser_tree.html')
144
146
145 cache_manager = self.__get_tree_cache_manager(
147 cache_manager = self.__get_tree_cache_manager(
@@ -148,6 +150,10 b' class FilesController(BaseRepoController'
148 cache_key = caches.compute_key_from_params(
150 cache_key = caches.compute_key_from_params(
149 repo_name, commit_id, f_path)
151 repo_name, commit_id, f_path)
150
152
153 if force:
154 # we want to force recompute of caches
155 cache_manager.remove_value(cache_key)
156
151 return cache_manager.get(cache_key, createfunc=_cached_tree)
157 return cache_manager.get(cache_key, createfunc=_cached_tree)
152
158
153 def _get_nodelist_at_commit(self, repo_name, commit_id, f_path):
159 def _get_nodelist_at_commit(self, repo_name, commit_id, f_path):
@@ -165,22 +171,6 b' class FilesController(BaseRepoController'
165 repo_name, commit_id, f_path)
171 repo_name, commit_id, f_path)
166 return cache_manager.get(cache_key, createfunc=_cached_nodes)
172 return cache_manager.get(cache_key, createfunc=_cached_nodes)
167
173
168 def _get_metadata_at_commit(self, repo_name, commit, dir_node):
169 def _cached_metadata():
170 log.debug('Generating cached metadata for %s, %s, %s',
171 repo_name, commit.raw_id, safe_str(dir_node.path))
172
173 data = ScmModel().get_dirnode_metadata(commit, dir_node)
174 return data
175
176 cache_manager = self.__get_tree_cache_manager(
177 repo_name, caches.FILE_TREE_META)
178
179 cache_key = caches.compute_key_from_params(
180 repo_name, commit.raw_id, safe_str(dir_node.path))
181
182 return cache_manager.get(cache_key, createfunc=_cached_metadata)
183
184 @LoginRequired()
174 @LoginRequired()
185 @HasRepoPermissionAnyDecorator(
175 @HasRepoPermissionAnyDecorator(
186 'repository.read', 'repository.write', 'repository.admin')
176 'repository.read', 'repository.write', 'repository.admin')
@@ -246,6 +236,7 b' class FilesController(BaseRepoController'
246 c.authors = []
236 c.authors = []
247 c.file_tree = self._get_tree_at_commit(
237 c.file_tree = self._get_tree_at_commit(
248 repo_name, c.commit.raw_id, f_path)
238 repo_name, c.commit.raw_id, f_path)
239
249 except RepositoryError as e:
240 except RepositoryError as e:
250 h.flash(safe_str(e), category='error')
241 h.flash(safe_str(e), category='error')
251 raise HTTPNotFound()
242 raise HTTPNotFound()
@@ -1092,23 +1083,32 b' class FilesController(BaseRepoController'
1092 @XHRRequired()
1083 @XHRRequired()
1093 @HasRepoPermissionAnyDecorator(
1084 @HasRepoPermissionAnyDecorator(
1094 'repository.read', 'repository.write', 'repository.admin')
1085 'repository.read', 'repository.write', 'repository.admin')
1095 @jsonify
1086 def nodetree_full(self, repo_name, commit_id, f_path):
1096 def metadata_list(self, repo_name, revision, f_path):
1097 """
1087 """
1098 Returns a json dict that contains commit date, author, revision
1088 Returns rendered html of file tree that contains commit date,
1099 and id for the specified repo, revision and file path
1089 author, revision for the specified combination of
1090 repo, commit_id and file path
1100
1091
1101 :param repo_name: name of the repository
1092 :param repo_name: name of the repository
1102 :param revision: revision of files
1093 :param commit_id: commit_id of file tree
1103 :param f_path: file path of the requested directory
1094 :param f_path: file path of the requested directory
1104 """
1095 """
1105
1096
1106 commit = self.__get_commit_or_redirect(revision, repo_name)
1097 commit = self.__get_commit_or_redirect(commit_id, repo_name)
1107 try:
1098 try:
1108 file_node = commit.get_node(f_path)
1099 dir_node = commit.get_node(f_path)
1109 except RepositoryError as e:
1100 except RepositoryError as e:
1110 return {'error': safe_str(e)}
1101 return 'error {}'.format(safe_str(e))
1102
1103 if dir_node.is_file():
1104 return ''
1111
1105
1112 metadata = self._get_metadata_at_commit(
1106 c.file = dir_node
1113 repo_name, commit, file_node)
1107 c.commit = commit
1114 return {'metadata': metadata}
1108
1109 # using force=True here, make a little trick. We flush the cache and
1110 # compute it using the same key as without full_load, so the fully
1111 # loaded cached tree is now returned instead of partial
1112 return self._get_tree_at_commit(
1113 repo_name, commit.raw_id, dir_node.path, full_load=True,
1114 force=True)
@@ -35,7 +35,7 b" FILE_SEARCH_TREE_META = 'cache_file_sear"
35 SUMMARY_STATS = 'cache_summary_stats'
35 SUMMARY_STATS = 'cache_summary_stats'
36
36
37 # This list of caches gets purged when invalidation happens
37 # This list of caches gets purged when invalidation happens
38 USED_REPO_CACHES = (FILE_TREE, FILE_TREE_META, FILE_TREE_META)
38 USED_REPO_CACHES = (FILE_TREE, FILE_SEARCH_TREE_META)
39
39
40 DEFAULT_CACHE_MANAGER_CONFIG = {
40 DEFAULT_CACHE_MANAGER_CONFIG = {
41 'type': 'memorylru_base',
41 'type': 'memorylru_base',
@@ -4,7 +4,8 b''
4 * DO NOT CHANGE THIS FILE MANUALLY *
4 * DO NOT CHANGE THIS FILE MANUALLY *
5 * *
5 * *
6 * *
6 * *
7 * This file is automatically generated when the app starts up. *
7 * This file is automatically generated when the app starts up with *
8 * generate_js_files = true *
8 * *
9 * *
9 * To add a route here pass jsroute=True to the route definition in the app *
10 * To add a route here pass jsroute=True to the route definition in the app *
10 * *
11 * *
@@ -44,7 +45,7 b' function registerRCRoutes() {'
44 pyroutes.register('files_authors_home', '/%(repo_name)s/authors/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
45 pyroutes.register('files_authors_home', '/%(repo_name)s/authors/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
45 pyroutes.register('files_archive_home', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
46 pyroutes.register('files_archive_home', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
46 pyroutes.register('files_nodelist_home', '/%(repo_name)s/nodelist/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
47 pyroutes.register('files_nodelist_home', '/%(repo_name)s/nodelist/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
47 pyroutes.register('files_metadata_list_home', '/%(repo_name)s/metadata_list/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
48 pyroutes.register('files_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
48 pyroutes.register('summary_home_slash', '/%(repo_name)s/', ['repo_name']);
49 pyroutes.register('summary_home_slash', '/%(repo_name)s/', ['repo_name']);
49 pyroutes.register('summary_home', '/%(repo_name)s', ['repo_name']);
50 pyroutes.register('summary_home', '/%(repo_name)s', ['repo_name']);
50 }
51 }
@@ -91,36 +91,26 b''
91 if (source_page) {
91 if (source_page) {
92 return false;
92 return false;
93 }
93 }
94
95 if ($('#file-tree-wrapper').hasClass('full-load')) {
96 // in case our HTML wrapper has full-load class we don't
97 // trigger the async load of metadata
98 return false;
99 }
100
94 var state = getState('metadata');
101 var state = getState('metadata');
95 var url_data = {
102 var url_data = {
96 'repo_name': templateContext.repo_name,
103 'repo_name': templateContext.repo_name,
97 'revision': state.commit_id,
104 'commit_id': state.commit_id,
98 'f_path': state.f_path
105 'f_path': state.f_path
99 };
106 };
100
107
101 var url = pyroutes.url('files_metadata_list_home', url_data);
108 var url = pyroutes.url('files_nodetree_full', url_data);
102
109
103 metadataRequest = $.ajax({url: url});
110 metadataRequest = $.ajax({url: url});
104
111
105 metadataRequest.done(function(data) {
112 metadataRequest.done(function(data) {
106 var data = data.metadata;
113 $('#file-tree').html(data);
107 var dataLength = data.length;
108 for (var i = 0; i < dataLength; i++) {
109 var rowData = data[i];
110 var name = rowData.name.replace('\\', '\\\\');
111
112 $('td[title="size-' + name + '"]').html(rowData.size);
113 var timeComponent = AgeModule.createTimeComponent(
114 rowData.modified_ts, rowData.modified_at);
115 $('td[title="modified_at-' + name + '"]').html(timeComponent);
116
117 $('td[title="revision-' + name + '"]').html(
118 '<div class="tooltip" title="{0}"><pre>r{1}:{2}</pre></div>'.format(
119 data[i].message, data[i].revision, data[i].short_id));
120 $('td[title="author-' + name + '"]').html(
121 '<span title="{0}">{1}</span>'.format(
122 data[i].author, data[i].user_profile));
123 }
124 timeagoActivate();
114 timeagoActivate();
125 });
115 });
126 metadataRequest.fail(function (data, textStatus, errorThrown) {
116 metadataRequest.fail(function (data, textStatus, errorThrown) {
@@ -138,7 +128,7 b''
138 // used for history, and switch to
128 // used for history, and switch to
139 var initialCommitData = {
129 var initialCommitData = {
140 id: null,
130 id: null,
141 text: "${_("Switch To Commit")}",
131 text: '${_("Switch To Commit")}',
142 type: 'sha',
132 type: 'sha',
143 raw_id: null,
133 raw_id: null,
144 files_url: null
134 files_url: null
@@ -329,5 +319,4 b''
329
319
330 </script>
320 </script>
331
321
332
333 </%def>
322 </%def>
@@ -42,7 +42,9 b''
42 </div>
42 </div>
43 </div>
43 </div>
44 ## file tree is computed from caches, and filled in
44 ## file tree is computed from caches, and filled in
45 <div id="file-tree">
45 ${c.file_tree}
46 ${c.file_tree}
47 </div>
46
48
47 </div>
49 </div>
48
50
@@ -1,4 +1,4 b''
1 <div class="browser-body">
1 <div id="file-tree-wrapper" class="browser-body ${'full-load' if c.full_load else ''}">
2 <table class="code-browser rctable">
2 <table class="code-browser rctable">
3 <thead>
3 <thead>
4 <tr>
4 <tr>
@@ -31,7 +31,7 b''
31 <span class="submodule-dir">
31 <span class="submodule-dir">
32 ${h.link_to_if(
32 ${h.link_to_if(
33 node.url.startswith('http://') or node.url.startswith('https://'),
33 node.url.startswith('http://') or node.url.startswith('https://'),
34 node.name,node.url)}
34 node.name, node.url)}
35 </span>
35 </span>
36 %else:
36 %else:
37 <a href="${h.url('files_home',repo_name=c.repo_name,revision=c.commit.raw_id,f_path=h.safe_unicode(node.path))}" class="pjax-link">
37 <a href="${h.url('files_home',repo_name=c.repo_name,revision=c.commit.raw_id,f_path=h.safe_unicode(node.path))}" class="pjax-link">
@@ -40,12 +40,30 b''
40 %endif
40 %endif
41 </td>
41 </td>
42 %if node.is_file():
42 %if node.is_file():
43 <td class="td-size" title="${'size-%s' % node.name}"></td>
43 <td class="td-size" data-attr-name="size">
44 <td class="td-time" title="${'modified_at-%s' % node.name}">
44 % if c.full_load:
45 <span class="browser-loading">${_('Loading...')}</span>
45 <span data-size="${node.size}">${h.format_byte_size_binary(node.size)}</span>
46 % else:
47 ${_('Loading ...')}
48 % endif
49 </td>
50 <td class="td-time" data-attr-name="modified_at">
51 % if c.full_load:
52 <span data-date="${node.last_commit.date}">${h.age_component(node.last_commit.date)}</span>
53 % endif
46 </td>
54 </td>
47 <td class="td-hash" title="${'revision-%s' % node.name}"></td>
55 <td class="td-hash" data-attr-name="commit_id">
48 <td class="td-user" title="${'author-%s' % node.name}"></td>
56 % if c.full_load:
57 <div class="tooltip" title="${node.last_commit.message}">
58 <pre data-commit-id="${node.last_commit.raw_id}">r${node.last_commit.revision}:${node.last_commit.short_id}</pre>
59 </div>
60 % endif
61 </td>
62 <td class="td-user" data-attr-name="author">
63 % if c.full_load:
64 <span data-author="${node.last_commit.author}" title="${node.last_commit.author}">${h.gravatar_with_user(node.last_commit.author)|n}</span>
65 % endif
66 </td>
49 %else:
67 %else:
50 <td></td>
68 <td></td>
51 <td></td>
69 <td></td>
@@ -57,4 +75,4 b''
57 </tbody>
75 </tbody>
58 <tbody id="tbody_filtered"></tbody>
76 <tbody id="tbody_filtered"></tbody>
59 </table>
77 </table>
60 </div> No newline at end of file
78 </div>
@@ -54,7 +54,7 b' class TestFilesController:'
54
54
55 params = {
55 params = {
56 'repo_name': backend.repo_name,
56 'repo_name': backend.repo_name,
57 'revision': commit.raw_id,
57 'commit_id': commit.raw_id,
58 'date': commit.date
58 'date': commit.date
59 }
59 }
60 assert_dirs_in_response(response, ['docs', 'vcs'], params)
60 assert_dirs_in_response(response, ['docs', 'vcs'], params)
@@ -135,7 +135,7 b' class TestFilesController:'
135 files = ['README.rst']
135 files = ['README.rst']
136 params = {
136 params = {
137 'repo_name': backend.repo_name,
137 'repo_name': backend.repo_name,
138 'revision': commit.raw_id,
138 'commit_id': commit.raw_id,
139 }
139 }
140 assert_dirs_in_response(response, dirs, params)
140 assert_dirs_in_response(response, dirs, params)
141 assert_files_in_response(response, files, params)
141 assert_files_in_response(response, files, params)
@@ -302,31 +302,34 b' class TestFilesController:'
302 url('files_nodelist_home', repo_name=backend.repo_name,
302 url('files_nodelist_home', repo_name=backend.repo_name,
303 f_path='/', revision='tip'), status=400)
303 f_path='/', revision='tip'), status=400)
304
304
305 def test_tree_metadata_list_success(self, backend, xhr_header):
305 def test_nodetree_full_success(self, backend, xhr_header):
306 commit = backend.repo.get_commit(commit_idx=173)
306 commit = backend.repo.get_commit(commit_idx=173)
307 response = self.app.get(
307 response = self.app.get(
308 url('files_metadata_list_home', repo_name=backend.repo_name,
308 url('files_nodetree_full', repo_name=backend.repo_name,
309 f_path='/', revision=commit.raw_id),
309 f_path='/', commit_id=commit.raw_id),
310 extra_environ=xhr_header)
310 extra_environ=xhr_header)
311
311
312 expected_keys = ['author', 'message', 'modified_at', 'modified_ts',
312 assert_response = AssertResponse(response)
313 'name', 'revision', 'short_id', 'size']
314 for filename in response.json.get('metadata'):
315 for key in expected_keys:
316 assert key in filename
317
313
318 def test_tree_metadata_list_if_file(self, backend, xhr_header):
314 for attr in ['data-commit-id', 'data-date', 'data-author']:
315 elements = assert_response.get_elements('[{}]'.format(attr))
316 assert len(elements) > 1
317
318 for element in elements:
319 assert element.get(attr)
320
321 def test_nodetree_full_if_file(self, backend, xhr_header):
319 commit = backend.repo.get_commit(commit_idx=173)
322 commit = backend.repo.get_commit(commit_idx=173)
320 response = self.app.get(
323 response = self.app.get(
321 url('files_metadata_list_home', repo_name=backend.repo_name,
324 url('files_nodetree_full', repo_name=backend.repo_name,
322 f_path='README.rst', revision=commit.raw_id),
325 f_path='README.rst', commit_id=commit.raw_id),
323 extra_environ=xhr_header)
326 extra_environ=xhr_header)
324 assert response.json == {'metadata': []}
327 assert response.body == ''
325
328
326 def test_tree_metadata_list_missing_xhr(self, backend):
329 def test_tree_metadata_list_missing_xhr(self, backend):
327 self.app.get(
330 self.app.get(
328 url('files_metadata_list_home', repo_name=backend.repo_name,
331 url('files_nodetree_full', repo_name=backend.repo_name,
329 f_path='/', revision='tip'), status=400)
332 f_path='/', commit_id='tip'), status=400)
330
333
331 def test_access_empty_repo_redirect_to_summary_with_alert_write_perms(
334 def test_access_empty_repo_redirect_to_summary_with_alert_write_perms(
332 self, app, backend_stub, autologin_regular_user, user_regular,
335 self, app, backend_stub, autologin_regular_user, user_regular,
@@ -917,13 +920,13 b' class TestChangingFiles:'
917
920
918 def assert_files_in_response(response, files, params):
921 def assert_files_in_response(response, files, params):
919 template = (
922 template = (
920 "href='/%(repo_name)s/files/%(revision)s/%(name)s'")
923 'href="/%(repo_name)s/files/%(commit_id)s/%(name)s"')
921 _assert_items_in_response(response, files, template, params)
924 _assert_items_in_response(response, files, template, params)
922
925
923
926
924 def assert_dirs_in_response(response, dirs, params):
927 def assert_dirs_in_response(response, dirs, params):
925 template = (
928 template = (
926 "href='/%(repo_name)s/files/%(revision)s/%(name)s'")
929 'href="/%(repo_name)s/files/%(commit_id)s/%(name)s"')
927 _assert_items_in_response(response, dirs, template, params)
930 _assert_items_in_response(response, dirs, template, params)
928
931
929
932
@@ -171,6 +171,9 b' class AssertResponse(object):'
171 assert len(elements) == 1
171 assert len(elements) == 1
172 return elements[0]
172 return elements[0]
173
173
174 def get_elements(self, css_selector):
175 return self._get_elements(css_selector)
176
174 def _get_elements(self, css_selector):
177 def _get_elements(self, css_selector):
175 doc = fromstring(self.response.body)
178 doc = fromstring(self.response.body)
176 sel = CSSSelector(css_selector)
179 sel = CSSSelector(css_selector)
General Comments 0
You need to be logged in to leave comments. Login now