]> src.twobees.de Git - tampermonkeyscripts.git/blob - AzureDevOpsChangesetCommentSearch.js
0.18: make '@@DEPENDENCIES' blue
[tampermonkeyscripts.git] / AzureDevOpsChangesetCommentSearch.js
1 // ==UserScript==
2 // @name         ADS changeset comment search
3 // @version      0.5
4 // @description  Places a searchbox somewhere to search commit messages
5 // @author       Tobias Sachs
6 // @match        https://ads/*
7 // @grant        GM_addStyle
8 // @updateURL    https://src.twobees.de/?p=tampermonkeyscripts.git;a=blob_plain;f=AzureDevOpsChangesetCommentSearch.js;hb=HEAD
9 // @downloadURL  https://src.twobees.de/?p=tampermonkeyscripts.git;a=blob_plain;f=AzureDevOpsChangesetCommentSearch.js;hb=HEAD
10 // ==/UserScript==
11
12 // 0.5: - fix size of search box
13 // 0.4: - improve position of search box
14 // 0.3: - fix abort search
15 //      - filter reuslts by committers name
16 //      - no need for an observer
17 //      - use GM_addStyle to improve readability
18 //      - "Escape" stops search and removes resultstab
19
20 /* jshint esversion:6 */
21 (function() {
22     'use strict';
23
24     let searchDelayTimerId;
25     let pollUrl = 'https://ads/HeBa/Entwicklung/_apis/tfvc/changesets?maxCommentLength=500?$top=1000?searchCriteria.itemPath=$/Entwicklung/HEAD';
26     let skipParm = '&$skip=';
27     let changeSetUrl = `/HeBa/Entwicklung/_versionControl/changeset/`;
28
29     let totalReceived = 0;
30     let matchesFound = 0;
31     let searchTerm = "";
32
33     GM_addStyle(`#ts_cs_search {
34                     position: absolute;
35                     left: 50%;
36                     top: 0px;
37                     height: auto;
38                     z-index: 9999;
39                     transform: translateX(-50%);
40                  }`);
41     GM_addStyle(`.ts_match {
42                      background: orange;
43                  }`);
44     GM_addStyle(`.ts_found {
45                      padding: 2px;
46                  }`);
47     GM_addStyle(`.ts_found a {
48                      text-decoration: none;
49                  }`);
50     GM_addStyle(`#ts_searchResults{
51                      max-height: 500px;
52                      background:#CCCCCC;
53                      overflow: auto;
54                  }`);
55
56     let searchDelayed = function(){
57         if (searchDelayTimerId) {
58             clearTimeout(searchDelayTimerId);
59         }
60
61         searchDelayTimerId = setTimeout(
62             () => {
63                 searchDelayTimerId = undefined;
64                 startSearching();
65             },
66             200);
67     };
68
69     let pressEscapeToAbort = function(e) {
70         if (e.key == "Escape"){
71             document.getElementById('ts_searchBox').value = "";
72             startSearching(); // will abort search and clean results
73         }
74     };
75
76     let requestNext = function(skipCnt, currentSearch){
77         let xhr = new XMLHttpRequest();
78         xhr.onload = () => searchItems(xhr, currentSearch);
79         xhr.onerror = function (e){
80             console.warning(`poll error: ${e.type}: ${e.loaded} bytes transferred\n` + JSON.stringify(e));
81         };
82         xhr.open('GET', pollUrl + skipParm + skipCnt, true);
83         xhr.send();
84     };
85
86     let searchItems = function(xhr, searchStr){
87         if (xhr.status !== 200){
88             console.info("poll failed: " + xhr.statusText);
89             return;
90         }
91
92         if (searchStr !== searchTerm)
93         {
94             console.info("Searchterm changed while searching to " + searchTerm);
95             return;
96         }
97
98         let sp = searchStr.split(/(von:|from:)/i);
99         let searchName = undefined;
100         let search = searchStr;
101         if (sp.length == 3){
102             search = sp[0].trim();
103             searchName = sp[2].trim();
104         }
105
106         let resultDiv = document.getElementById('ts_searchResults');
107         let statsSpan = document.getElementById('ts_searchStats');
108
109         var d = JSON.parse(xhr.responseText);
110         let regex = undefined;
111         let regexAuthor = undefined;
112         if (search){
113             regex = new RegExp("("+search+")", 'gi');
114         }
115         if (searchName){
116             regexAuthor = new RegExp("("+searchName+")", 'i');
117         }
118         totalReceived += d.value.length;
119         for (let e in d.value){
120             let cs = d.value[e];
121             if (regex !== undefined && regexAuthor !== undefined)
122             {
123                 if (!(cs.comment && regex.test(cs.comment) && regexAuthor.test(cs.author.displayName)))
124                 {
125                     continue;
126                 }
127             }
128             else
129             {
130                 if ((
131                        (cs.comment && regex !== undefined && regex.test(cs.comment))
132                     || (regexAuthor !== undefined && regexAuthor.test(cs.author.displayName))
133                     ) == false){
134                     continue;
135                 }
136             }
137             matchesFound++;
138             let comment;
139             let author;
140             if (regex){
141                 comment = cs.comment.replace(regex, `<span class="ts_match">$1</span>`);
142             }
143             else
144             {
145                 comment = cs.comment;
146             }
147             if (regexAuthor){
148                 author = cs.author.displayName.replace(regexAuthor, `<span class="ts_match">$1</span>`);
149             }
150             else{
151                 author = cs.author.displayName;
152             }
153
154
155             let item = `<div class='ts_found'>
156                                <a href='${changeSetUrl}${cs.changesetId}'>
157                                   ${cs.changesetId}: ${comment}  (${author})
158                                </a>
159                             </div>`;
160             resultDiv.innerHTML += item;
161     }
162     statsSpan.innerHTML = "matches: " + matchesFound +" searched comments: " + totalReceived;
163     if (d.value.length <= 0)
164     {
165         statsSpan.innerHTML += " -- search done.";
166         return;
167     }
168
169     requestNext(totalReceived, searchStr);
170 };
171
172  let startSearching = function (){
173     searchTerm = document.getElementById('ts_searchBox').value;
174     let results = document.getElementById('ts_searchResults');
175     totalReceived = 0;
176     matchesFound = 0;
177     results.innerHTML = "";
178     document.getElementById('ts_searchStats').innerHTML = "";
179
180     if (searchTerm.length < 3){ return; }
181
182     console.debug("new search term: '" + searchTerm+"'");
183     requestNext(0, searchTerm);
184 };
185
186 let addSearch = () => {
187     if (window.location.href.includes("_apis")){ return; }
188
189     let search = document.getElementById('ts_cs_search');
190     if (search){ return; }
191
192     console.debug("adding searchbox");
193     let html = `<div id="ts_cs_search">
194                         <input id="ts_searchBox" style="width: 200px;" placeholder="searchstring [von:Name]" />
195                         <span id="ts_searchStats"></span>
196                         <div id="ts_searchResults"></div>
197                     </div>`;
198
199         document.body.insertAdjacentHTML('afterbegin', html);
200         let s = document.getElementById('ts_searchBox');
201         s.addEventListener("keyup", searchDelayed);
202         document.body.addEventListener("keyup", pressEscapeToAbort);
203
204     };
205
206 addSearch();
207 })();