##// 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 1103 conditions={'function': check_repo},
1104 1104 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1105 1105
1106 rmap.connect('files_metadata_list_home',
1107 '/{repo_name}/metadata_list/{revision}/{f_path}',
1108 controller='files', action='metadata_list',
1106 rmap.connect('files_nodetree_full',
1107 '/{repo_name}/nodetree_full/{commit_id}/{f_path}',
1108 controller='files', action='nodetree_full',
1109 1109 conditions={'function': check_repo},
1110 1110 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1111 1111
@@ -136,10 +136,12 b' class FilesController(BaseRepoController'
136 136 _namespace = caches.get_repo_namespace_key(namespace_type, repo_name)
137 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 141 def _cached_tree():
141 142 log.debug('Generating cached file tree for %s, %s, %s',
142 143 repo_name, commit_id, f_path)
144 c.full_load = full_load
143 145 return render('files/files_browser_tree.html')
144 146
145 147 cache_manager = self.__get_tree_cache_manager(
@@ -148,6 +150,10 b' class FilesController(BaseRepoController'
148 150 cache_key = caches.compute_key_from_params(
149 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 157 return cache_manager.get(cache_key, createfunc=_cached_tree)
152 158
153 159 def _get_nodelist_at_commit(self, repo_name, commit_id, f_path):
@@ -165,22 +171,6 b' class FilesController(BaseRepoController'
165 171 repo_name, commit_id, f_path)
166 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 174 @LoginRequired()
185 175 @HasRepoPermissionAnyDecorator(
186 176 'repository.read', 'repository.write', 'repository.admin')
@@ -246,6 +236,7 b' class FilesController(BaseRepoController'
246 236 c.authors = []
247 237 c.file_tree = self._get_tree_at_commit(
248 238 repo_name, c.commit.raw_id, f_path)
239
249 240 except RepositoryError as e:
250 241 h.flash(safe_str(e), category='error')
251 242 raise HTTPNotFound()
@@ -1092,23 +1083,32 b' class FilesController(BaseRepoController'
1092 1083 @XHRRequired()
1093 1084 @HasRepoPermissionAnyDecorator(
1094 1085 'repository.read', 'repository.write', 'repository.admin')
1095 @jsonify
1096 def metadata_list(self, repo_name, revision, f_path):
1086 def nodetree_full(self, repo_name, commit_id, f_path):
1097 1087 """
1098 Returns a json dict that contains commit date, author, revision
1099 and id for the specified repo, revision and file path
1088 Returns rendered html of file tree that contains commit date,
1089 author, revision for the specified combination of
1090 repo, commit_id and file path
1100 1091
1101 1092 :param repo_name: name of the repository
1102 :param revision: revision of files
1093 :param commit_id: commit_id of file tree
1103 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 1098 try:
1108 file_node = commit.get_node(f_path)
1099 dir_node = commit.get_node(f_path)
1109 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(
1113 repo_name, commit, file_node)
1114 return {'metadata': metadata}
1106 c.file = dir_node
1107 c.commit = commit
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 35 SUMMARY_STATS = 'cache_summary_stats'
36 36
37 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 40 DEFAULT_CACHE_MANAGER_CONFIG = {
41 41 'type': 'memorylru_base',
@@ -4,7 +4,8 b''
4 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 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 45 pyroutes.register('files_authors_home', '/%(repo_name)s/authors/%(revision)s/%(f_path)s', ['repo_name', 'revision', 'f_path']);
45 46 pyroutes.register('files_archive_home', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
46 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 49 pyroutes.register('summary_home_slash', '/%(repo_name)s/', ['repo_name']);
49 50 pyroutes.register('summary_home', '/%(repo_name)s', ['repo_name']);
50 51 }
@@ -91,36 +91,26 b''
91 91 if (source_page) {
92 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 101 var state = getState('metadata');
95 102 var url_data = {
96 103 'repo_name': templateContext.repo_name,
97 'revision': state.commit_id,
104 'commit_id': state.commit_id,
98 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 110 metadataRequest = $.ajax({url: url});
104 111
105 112 metadataRequest.done(function(data) {
106 var data = data.metadata;
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 }
113 $('#file-tree').html(data);
124 114 timeagoActivate();
125 115 });
126 116 metadataRequest.fail(function (data, textStatus, errorThrown) {
@@ -138,7 +128,7 b''
138 128 // used for history, and switch to
139 129 var initialCommitData = {
140 130 id: null,
141 text: "${_("Switch To Commit")}",
131 text: '${_("Switch To Commit")}',
142 132 type: 'sha',
143 133 raw_id: null,
144 134 files_url: null
@@ -329,5 +319,4 b''
329 319
330 320 </script>
331 321
332
333 322 </%def>
@@ -42,7 +42,9 b''
42 42 </div>
43 43 </div>
44 44 ## file tree is computed from caches, and filled in
45 <div id="file-tree">
45 46 ${c.file_tree}
47 </div>
46 48
47 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 2 <table class="code-browser rctable">
3 3 <thead>
4 4 <tr>
@@ -40,12 +40,30 b''
40 40 %endif
41 41 </td>
42 42 %if node.is_file():
43 <td class="td-size" title="${'size-%s' % node.name}"></td>
44 <td class="td-time" title="${'modified_at-%s' % node.name}">
45 <span class="browser-loading">${_('Loading...')}</span>
43 <td class="td-size" data-attr-name="size">
44 % if c.full_load:
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 54 </td>
47 <td class="td-hash" title="${'revision-%s' % node.name}"></td>
48 <td class="td-user" title="${'author-%s' % node.name}"></td>
55 <td class="td-hash" data-attr-name="commit_id">
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 67 %else:
50 68 <td></td>
51 69 <td></td>
@@ -57,4 +75,4 b''
57 75 </tbody>
58 76 <tbody id="tbody_filtered"></tbody>
59 77 </table>
60 </div> No newline at end of file
78 </div>
@@ -54,7 +54,7 b' class TestFilesController:'
54 54
55 55 params = {
56 56 'repo_name': backend.repo_name,
57 'revision': commit.raw_id,
57 'commit_id': commit.raw_id,
58 58 'date': commit.date
59 59 }
60 60 assert_dirs_in_response(response, ['docs', 'vcs'], params)
@@ -135,7 +135,7 b' class TestFilesController:'
135 135 files = ['README.rst']
136 136 params = {
137 137 'repo_name': backend.repo_name,
138 'revision': commit.raw_id,
138 'commit_id': commit.raw_id,
139 139 }
140 140 assert_dirs_in_response(response, dirs, params)
141 141 assert_files_in_response(response, files, params)
@@ -302,31 +302,34 b' class TestFilesController:'
302 302 url('files_nodelist_home', repo_name=backend.repo_name,
303 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 306 commit = backend.repo.get_commit(commit_idx=173)
307 307 response = self.app.get(
308 url('files_metadata_list_home', repo_name=backend.repo_name,
309 f_path='/', revision=commit.raw_id),
308 url('files_nodetree_full', repo_name=backend.repo_name,
309 f_path='/', commit_id=commit.raw_id),
310 310 extra_environ=xhr_header)
311 311
312 expected_keys = ['author', 'message', 'modified_at', 'modified_ts',
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
312 assert_response = AssertResponse(response)
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 322 commit = backend.repo.get_commit(commit_idx=173)
320 323 response = self.app.get(
321 url('files_metadata_list_home', repo_name=backend.repo_name,
322 f_path='README.rst', revision=commit.raw_id),
324 url('files_nodetree_full', repo_name=backend.repo_name,
325 f_path='README.rst', commit_id=commit.raw_id),
323 326 extra_environ=xhr_header)
324 assert response.json == {'metadata': []}
327 assert response.body == ''
325 328
326 329 def test_tree_metadata_list_missing_xhr(self, backend):
327 330 self.app.get(
328 url('files_metadata_list_home', repo_name=backend.repo_name,
329 f_path='/', revision='tip'), status=400)
331 url('files_nodetree_full', repo_name=backend.repo_name,
332 f_path='/', commit_id='tip'), status=400)
330 333
331 334 def test_access_empty_repo_redirect_to_summary_with_alert_write_perms(
332 335 self, app, backend_stub, autologin_regular_user, user_regular,
@@ -917,13 +920,13 b' class TestChangingFiles:'
917 920
918 921 def assert_files_in_response(response, files, params):
919 922 template = (
920 "href='/%(repo_name)s/files/%(revision)s/%(name)s'")
923 'href="/%(repo_name)s/files/%(commit_id)s/%(name)s"')
921 924 _assert_items_in_response(response, files, template, params)
922 925
923 926
924 927 def assert_dirs_in_response(response, dirs, params):
925 928 template = (
926 "href='/%(repo_name)s/files/%(revision)s/%(name)s'")
929 'href="/%(repo_name)s/files/%(commit_id)s/%(name)s"')
927 930 _assert_items_in_response(response, dirs, template, params)
928 931
929 932
@@ -171,6 +171,9 b' class AssertResponse(object):'
171 171 assert len(elements) == 1
172 172 return elements[0]
173 173
174 def get_elements(self, css_selector):
175 return self._get_elements(css_selector)
176
174 177 def _get_elements(self, css_selector):
175 178 doc = fromstring(self.response.body)
176 179 sel = CSSSelector(css_selector)
General Comments 0
You need to be logged in to leave comments. Login now