32 |
33 |
34 | At the [Jenkins UX SIG Meeting](https://www.youtube.com/watch?v=F1ISpA7K0YA) on 28. April 2021, there was a live demo of the first beta version (1.0.3-beta).
35 |
36 |
37 |
38 |
83 |
84 |
85 |
86 | # About The Project
87 |
88 | Many software teams have changed their development processes to lightweight pull requests.
89 | Changes to the software are packed into such a pull request,
90 | which is then manually reviewed and also automatically built in the CI/CD server.
91 | In Jenkins, however, these pull requests are not "first-class citizens"; the results of a pull request
92 | are currently simulated via branches.
93 |
94 | For developers, this representation is insufficient: instead of having Git diffs, tests, static analysis, etc.
95 | as an overview in the overall project, a filtered representation of these results on the changes actually made
96 | would be much more helpful.
97 |
98 | This plugin offers a possibility to display and aggregate the results (in the form of individual views) of a pull
99 | request in a configurable dashboard. Views can only be accessed or displayed if the corresponding plugin fulfils
100 | certain requirements and already provides a view.
101 |
102 | ## Built With
103 |
104 | * [Muuri](https://github.com/haltu/muuri) wrapped in [Jenkins Muuri.js API Plugin](https://github.com/jenkinsci/muuri-api-plugin)
105 | * [Select2](https://select2.org) wrapped in [Jenkins Select2.js API Plugin](https://github.com/jenkinsci/select2-api-plugin)
106 |
107 | # Getting Started
108 |
109 | ## Prerequisites
110 |
111 | Currently, only **multibranch pipelines** projects are supported to use this plugin. Therefore, you have to
112 | install the corresponding Jenkins plugin [Multibranch: Pipeline](https://plugins.jenkins.io/workflow-multibranch/)
113 | and connect to own of your SCM Repositories to use the **Pull Request Monitoring** Jenkins plugin.
114 |
115 | ## Provide a portlet
116 |
117 | This plugin relies on other plugins to provide a view that aggregates and provides delta metrics for a pull request.
118 |
119 | ### The code behind
120 |
121 | The [MonitorPortlet](src/main/java/io/jenkins/plugins/monitoring/MonitorPortlet.java) class defines the base class
122 | for each portlet that is to be displayed. In order to register the portlet for the plugin,
123 | a factory class is required, which must be provided with the annotation `@Extension`.
124 | The factory has to extend the [MonitorPortletFactory](src/main/java/io/jenkins/plugins/monitoring/MonitorPortletFactory.java)
125 | abstract class, receives the current `Run,?>`, delivers a set of `MonitorPortlets` and defines a `displayedName`,
126 | which appears as `optgroup` in the dashboard in the dropdown list of all available portlets and their corresponding
127 | factory.
128 |
129 | 
130 |
131 | #### One Instance Of One Portlet
132 |
133 | Normally, one plugin delivers one portlet. A minimal example could look as follows:
134 |
135 | ```java
136 | /**
137 | * An example Monitor Portlet implementation with one portlet delivered by factory.
138 | */
139 | public class DemoPortlet extends MonitorPortlet {
140 | private final Run, ?> build;
141 |
142 | /**
143 | * Create a new {@link DemoPortlet}.
144 | *
145 | * @param run
146 | * the {@link Run}
147 | */
148 | public DemoPortlet(Run, ?> run) {
149 | this.build = run;
150 | }
151 |
152 | /**
153 | * Defines the title of portlet. It will be shown in the
154 | * upper left corner of the portlet.
155 | *
156 | * @return
157 | * the title as string.
158 | */
159 | @Override
160 | public String getTitle() {
161 | return "Demo Portlet";
162 | }
163 |
164 | /**
165 | * Defines the id for the portlet.
166 | *
167 | * @return
168 | * the id.
169 | */
170 | @Override
171 | public String getId() {
172 | return "demo-portlet-id";
173 | }
174 |
175 | /**
176 | * Defines whether the portlet is shown per default in the user dashboard or not.
177 | *
178 | * @return
179 | * true if portlet should be shown, false else.
180 | */
181 | @Override
182 | public boolean isDefault() {
183 | return true;
184 | }
185 |
186 | /**
187 | * Defines the preferred width of the portlet. It's
188 | * possible to override the default width by user.
189 | *
190 | * @return
191 | * the width as int. (range from 100 to 1000)
192 | */
193 | @Override
194 | public int getPreferredWidth() {
195 | return 300;
196 | }
197 |
198 | /**
199 | * Defines the preferred height of the portlet. It's
200 | * possible to override the default height by user.
201 | *
202 | * @return
203 | * the height as int. (range from 100 to 1000)
204 | */
205 | @Override
206 | public int getPreferredHeight() {
207 | return 200;
208 | }
209 |
210 | /**
211 | * Defines the icon, which will be shown in the dropdown of
212 | * all available portlets in the dashboard.
213 | *
214 | * @return
215 | * the icon url as {@link java.util.Optional} of string, or an
216 | * empty Optional, if a default icon should be added.
217 | */
218 | @Override
219 | public Optional getIconUrl() {
220 | return Optional.of("");
221 | }
222 |
223 | /**
224 | * Defines a link to a detail view, if its needed. Links the title
225 | * of the portlet to this url.
226 | *
227 | * @return
228 | * {@link java.util.Optional} of the url, or an empty Optional,
229 | * if no link should be provided by portlet.
230 | */
231 | @Override
232 | public Optional getDetailViewUrl() {
233 | return Optional.of("");
234 | }
235 |
236 | /**
237 | * Creates a new {@link ExamplePortletFactory}.
238 | */
239 | @Extension
240 | public static class ExamplePortletFactory extends MonitorPortletFactory {
241 | @Override
242 | public Collection getPortlets(Run, ?> build) {
243 | return Collections.singleton(new DemoPortlet(build));
244 | }
245 |
246 | @Override
247 | public String getDisplayName() {
248 | return "Demo Portlet Factory";
249 | }
250 | }
251 | }
252 | ```
253 |
254 | > ⭕ **Null Check for used actions**
255 | >
256 | > Since an empty dashboard is always added by default, it is possible that the method `getPortlets(Run)` will
257 | > be called (e.g. open the dashboard of actual run) even though the corresponding run may not be finished.
258 | > It is possible that actions have not yet been added to the run.
259 | > It is therefore advisable to perform a null check on the actions of the run required by your portlet and return
260 | > an empty list if necessary.
261 | > (Example: code-coverage-api)
262 |
263 | #### Multiple Instances Of One Portlet
264 |
265 | The factory can also deliver several portlets of one class.
266 |
267 | > ⚠️ **Unique portlet ID**:
268 | >
269 | > The id must be unique. Please make sure that the id is related to the plugin so that there are no conflicts with
270 | > other plugins. It is recommended to use the artifact id of the plugin or parts of it as prefix.
271 | > If several portlets of the same class are created in the factory, it must be ensured that the ID is always unique
272 | > for each portlet!
273 |
274 | Here is an example of a factory that delivers two instances of a class:
275 |
276 | ```java
277 | /**
278 | * An example Monitor Portlet implementation with multiple portlets delivered by factory.
279 | */
280 | public class DemoPortlet extends MonitorPortlet {
281 | private final Run, ?> run;
282 | private final String id;
283 |
284 | /**
285 | * Create a new {@link DemoPortlet}.
286 | *
287 | * @param run
288 | * the {@link Run}
289 | * @param id
290 | * the id.
291 | */
292 | public DemoPortlet(Run, ?> run, String id) {
293 | this.run = run;
294 | this.id = id;
295 | }
296 |
297 | @Override
298 | public String getTitle() {
299 | return "Demo Portlet " + getId();
300 | }
301 |
302 | @Override
303 | public String getId() {
304 | return id;
305 | }
306 |
307 | // other interface methods, see example above.
308 |
309 | /**
310 | * Creates a new {@link ExamplePortletFactory}.
311 | */
312 | @Extension
313 | public static class ExamplePortletFactory extends MonitorPortletFactory {
314 | @Override
315 | public Collection getPortlets(Run, ?> build) {
316 | List portlets = new ArrayList<>();
317 | portlets.add(new DemoPortlet(build, "example-portlet-first"));
318 | portlets.add(new DemoPortlet(build, "example-portlet-second"));
319 | return monitors;
320 | }
321 |
322 | @Override
323 | public String getDisplayName() {
324 | return "Demo Portlet Factory";
325 | }
326 | }
327 | }
328 | ```
329 |
330 | ### The corresponding jelly file
331 |
332 | Each portlet have to have a corresponding `monitor.jelly` file, which is responsible for the content of the
333 | plugins portlet delivered on the dashboard later. Therefore you have to create a new `monitor.jelly` file
334 | in the directory, which corresponds to the `MonitorView` class.
335 |
336 | **Example**:
337 | The code behind is defined in `src/main/java/io/jenkins/plugins/sample/DemoPortlet.java`. The related sources
338 | (e.g. the `monitor.jelly` file) have to be defined in
339 | `src/main/resources/io/jenkins/plugins/sample/DemoPortlet/monitory.jelly`.
340 |
341 | Now the portlet, which can be added later in the dashboard, can be filled individually.
342 | Of course, all obligatory functions of the Jelly files, such as [JEXL](https://commons.apache.org/proper/commons-jexl/)
343 | calls, can be used. To do this, please refer to the official Jenkins documentation.
344 | A minimal example:
345 |
346 | ```xml
347 |
348 |
349 |
350 |
351 |
Portlet content goes here!
352 |
353 |
354 | ```
355 |
356 | # Usage Of Monitoring
357 |
358 | ## Introduction
359 | This plugin offers the following monitoring options:
360 |
361 | * project level: [MonitoringMultibranchProjectAction](src/main/java/io/jenkins/plugins/monitoring/MonitoringMultibranchProjectAction.java)
362 | * build level: [MonitoringBuildAction](src/main/java/io/jenkins/plugins/monitoring/MonitoringBuildAction.java)
363 |
364 | ## Project Level
365 |
366 | The monitoring on the project level is very basic and is limited to the summary of all open pull requests of the associated SCM.
367 | From here, you can access the corresponding monitoring dashboards, view the pull request information and navigate
368 | to the respective pull request in the repository.
369 |
370 | 
371 |
372 | ## Build Level
373 |
374 | The monitoring at build level is the core of the plugin and offers the possibility
375 | to observe various delta metrics of a pull request provided by other portlets.
376 |
377 | The dashboard is only added to those builds whose branch
378 | [SCMHead](https://javadoc.jenkins.io/plugin/scm-api/jenkins/scm/api/SCMHead.html) of the parent job is an instance of
379 | [ChangeRequestSCMHead](https://javadoc.jenkins.io/plugin/scm-api/jenkins/scm/api/mixin/ChangeRequestSCMHead.html).
380 | Otherwise, the corresponding [MonitoringBuildAction](src/main/java/io/jenkins/plugins/monitoring/MonitoringBuildAction.java)
381 | will not be added and no dashboard will be provided for the build.
382 | For more details, please refer to the logging in the console output of the respective build.
383 |
384 | ### Persistence & Permissions
385 |
386 | The configuration, which is set via the Jenkinsfile or via the Dahsboard, is saved per user as a
387 | `hudson.model.UserProperty` and is browser independent. The plugin therefore requires that you are logged in
388 | and have the appropriate permissions. Otherwise, the action will not be displayed!
389 |
390 | ### Default Dashboard
391 |
392 | > 💡 **Reference build search**:
393 | >
394 | > The [git-forensics-api](https://www.jenkins.io/doc/pipeline/steps/git-forensics/) plugin allows to find a reference build for a pull request build.
395 | > It is highly recommended using this plugin and
396 | > to search for the reference build in the pipeline with `discoverGitReferenceBuild()` (or configure it via the Jenkins UI).
397 | > For more information please visit the [plugin page](https://www.jenkins.io/doc/pipeline/steps/git-forensics/) or have a
398 | > look into an example [pipeline](etc/Jenkinsfile).
399 |
400 | To start with the default dashboard, you have to do nothing. If the run is part of a pull request, the corresponding
401 | [MonitoringDefaultAction](src/main/java/io/jenkins/plugins/monitoring/MonitoringDefaultAction.java) is automatically
402 | added to the `Run` and later available on the sidepanel of the run.
403 |
404 | Now you are able to add portlets to the dashboard, change the layout or remove it again.
405 | The configuration will be saved for each project per user. If you want to save the configuration permanently,
406 | it is best to copy and paste it into the Jenkinsfile and overwrite the default dashboard and use a custom dashboard.
407 |
408 | > 🧩 **Default Portlets for Dashboard**:
409 | >
410 | > Since version 1.7.0, the portlets can decide whether they should be displayed by default or not in the
411 | > user dashboard. So when you start with the default dashboard, all available portlets
412 | > that are marked as default are automatically loaded into your dashboard.
413 |
414 | ### Custom Dashboard
415 |
416 | The other way to add a dashboard is to set the configuration of the dashboard via the Jenkinsfile to pre-define
417 | your dashboard. It is recommended to add the monitoring at the end of your pipeline, to ensure that other
418 | plugins such as static code analysis are performed first and that the actions that may be required are
419 | available:
420 |
421 | ```text
422 | stage ('Pull Request Monitoring - Dashboard Configuration') {
423 | monitoring (
424 | '''
425 | [
426 | {
427 | // Minimal usage of one portlet
428 | "id": "portlet-id"
429 | },
430 | {
431 | // Feel free to customize the portlets
432 | "id": "another-portlet-id",
433 | "width": 200,
434 | "height": 100,
435 | "color": "#FF5733"
436 | }
437 | ]
438 | '''
439 | )
440 | }
441 | ```
442 |
443 | Therefore, the monitoring stage expects a JSONArray of JSONObjects. To validate your
444 | configured json, you could use a [JSON Schema Validator](https://www.jsonschemavalidator.net) and the
445 | corresponding [JSON schema](src/main/resources/schema.json) used for this plugin.
446 | Each JSONObject needs at least the `id` of the portlet to add. For `width` and `height`, the default
447 | `preferredWidth` and `preferredHeight` of the `MonitorPortlet` is used. `#000000` is used as default `color`.
448 |
449 | The dashboard will be added to your `Run` and the pre-defined monitor should be available.
450 |
451 | > 🔥 **Duplicate or missing portlet IDs**:
452 | >
453 | > Duplicates will be removed as well as missing portlet ids. The `Run` will not fail unless
454 | > the provided json does not follow the scheme!
455 |
456 | If there are unavailable portlets (e.g. corresponding plugin is uninstalled) in stored user configuration,
457 | and the dashboard tries to load this portlet, an alert will be displayed with the ids of the unavailable portlets:
458 |
459 | 
460 |
461 | ### Settings
462 |
463 | Under the settings of each `Run`, various things can be tracked:
464 |
465 | 1. Are there changes of the pre-defined dashboard since the last build?
466 |
467 | 2. The current activated source of configuration (Default or User-specific).
468 | * Default means Jenkinsfile if `monitoring` is provided, else all available default portlets.
469 |
470 | * User-specific means the local changes of dashboard.
471 |
472 | 3. Configuration synced with the default one? If needed, you can synchronize the
473 | actual configuration with the default one.
474 |
475 | 4. The actual configuration
476 |
477 | 5. The default configuration.
478 |
479 | > ❗**Prioritising of the different configurations**:
480 | >
481 | > By default, all available default portlets are loaded. If there is a user-specific configuration for one
482 | > dashboard (per project), the user-specific configuration will be loaded per default. If you sync the current
483 | > configuration with the default one, the user-specific configuration for current dashboard will be deleted, and the
484 | > default configuration will be loaded. Deletion cannot be undone!
485 |
486 | 
487 |
488 | # Available Portlets
489 |
490 | If your plugin is not in the list but provides a portlet, feel free to add it with a pull request!
491 |
492 | | | Plugin | Number of delivered portlets | Portlet ID | Default? |
493 | | --- | --- | --- | --- | ---|
494 | |  | [Pull Request Monitoring](https://plugins.jenkins.io/pull-request-monitoring/) | 2 | `first-demo-portlet` `second-demo-portlet` | ❌
495 | |  | [Warnings Next Generation](https://plugins.jenkins.io/warnings-ng/) | ≥ 100 | depends on [tool](https://github.com/jenkinsci/warnings-ng-plugin/blob/master/SUPPORTED-FORMATS.md) | ✅ |
496 | |  | [Code Coverage API](https://plugins.jenkins.io/code-coverage-api/) | 1 | `code-coverage` | ✅
497 |
498 | # Demo
499 |
500 | For the demo (v1.3.0-beta) I added two recorder (javadoc and pmd) of the
501 | [Warnings Ng Plugin](https://plugins.jenkins.io/warnings-ng/) to the pipeline and added both as
502 | default portlet to the `monitoring`. After the run finished, the portlets will be shown in
503 | the dashboard.
504 |
505 | 
506 |
507 | # Roadmap
508 |
509 | See the [open issues](https://github.com/jenkinsci/pull-request-monitoring-plugin/issues)
510 | for a list of proposed features (and known issues).
511 |
512 | # Contributing
513 |
514 | Contributions are what make the open source community such an amazing place to be learn,
515 | inspire, and create. Any contributions you make are **greatly appreciated**.
516 |
517 | 1. Fork the Project
518 | 2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`)
519 | 3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`)
520 | 4. Push to the Branch (`git push origin feature/AmazingFeature`)
521 | 5. Open a Pull Request
522 |
523 | # License
524 |
525 | Distributed under the MIT License. See [LICENSE](LICENSE) for more information.
526 |
527 | # Credits
528 |
529 | The following icons, which are used by this plugin
530 |
531 | * [Line Graph Icon 32x32 px](src/main/webapp/icons/line-graph-32x32.png)
532 | * [Line Graph Icon 64x64 px](src/main/webapp/icons/line-graph-64x64.png)
533 |
534 | made by [Freepik](https://www.freepik.com) from [Flaticon](https://www.flaticon.com/).
535 |
536 | # Contact
537 |
538 | Simon Symhoven - post@simon-symhoven.de
539 |
540 | Project Link: [https://github.com/jenkinsci/pull-request-monitoring-plugin](https://github.com/jenkinsci/pull-request-monitoring-plugin)
--------------------------------------------------------------------------------
/codacy/checkstyle.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
--------------------------------------------------------------------------------
/etc/Jenkinsfile:
--------------------------------------------------------------------------------
1 | node {
2 |
3 | stage ('Checkout') {
4 | checkout scm
5 | }
6 |
7 | stage ('Git mining') {
8 | discoverGitReferenceBuild()
9 | mineRepository()
10 | }
11 |
12 | stage ('Build and Static Analysis') {
13 | withMaven {
14 | sh 'mvn clean verify'
15 | }
16 |
17 | recordIssues tools: [javaDoc(), java()], aggregatingResults: 'true', id: 'java', name: 'Java'
18 | recordIssues tool: errorProne(), healthy: 1, unhealthy: 20
19 | recordIssues tools: [checkStyle(pattern: 'target/checkstyle-result.xml'),
20 | spotBugs(pattern: 'target/spotbugsXml.xml'),
21 | pmdParser(pattern: 'target/pmd.xml'),
22 | taskScanner(highTags:'FIXME', normalTags:'TODO', includePattern: '**/*.java', excludePattern: 'target/**/*')],
23 | qualityGates: [[threshold: 1, type: 'TOTAL', unstable: true]]
24 | recordIssues tool: mavenConsole()
25 | }
26 |
27 |
28 | stage ('Line and Branch Coverage') {
29 | withMaven {
30 | sh 'mvn jacoco:prepare-agent test jacoco:report -Dmaven.test.failure.ignore'
31 | }
32 | publishCoverage adapters: [jacocoAdapter('target/site/jacoco/jacoco.xml')],
33 | calculateDiffForChangeRequests: true
34 | }
35 |
36 | /*
37 | stage ('Pull Request Monitoring - Dashboard Configuration') {
38 | monitoring (
39 | '''
40 | [
41 | {
42 | "id": "first-demo-portlet",
43 | "width": 400,
44 | "height": 400,
45 | "color": "#FF5733"
46 | }
47 | ]
48 | '''
49 | )
50 | }
51 | */
52 | }
53 |
--------------------------------------------------------------------------------
/etc/images/add-portlet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/pull-request-monitoring-plugin/d602c91131a8112eef8cc9d00aee013f4443a307/etc/images/add-portlet.png
--------------------------------------------------------------------------------
/etc/images/demo-1.3.0-beta.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/pull-request-monitoring-plugin/d602c91131a8112eef8cc9d00aee013f4443a307/etc/images/demo-1.3.0-beta.gif
--------------------------------------------------------------------------------
/etc/images/example-monitor-overview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/pull-request-monitoring-plugin/d602c91131a8112eef8cc9d00aee013f4443a307/etc/images/example-monitor-overview.png
--------------------------------------------------------------------------------
/etc/images/missing-portlet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/pull-request-monitoring-plugin/d602c91131a8112eef8cc9d00aee013f4443a307/etc/images/missing-portlet.png
--------------------------------------------------------------------------------
/etc/images/portlets/code-coverage-api.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/pull-request-monitoring-plugin/d602c91131a8112eef8cc9d00aee013f4443a307/etc/images/portlets/code-coverage-api.png
--------------------------------------------------------------------------------
/etc/images/portlets/warnings-next-generation.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/pull-request-monitoring-plugin/d602c91131a8112eef8cc9d00aee013f4443a307/etc/images/portlets/warnings-next-generation.png
--------------------------------------------------------------------------------
/etc/images/settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/pull-request-monitoring-plugin/d602c91131a8112eef8cc9d00aee013f4443a307/etc/images/settings.png
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 |
5 |
6 | org.jenkins-ci.plugins
7 | plugin
8 | 4.87
9 |
10 |
11 |
12 | io.jenkins.plugins
13 | pull-request-monitoring
14 | hpi
15 | Pull Request Monitoring
16 | ${changelist}
17 | https://github.com/jenkinsci/${project.artifactId}-plugin
18 |
19 |
20 | 999999-SNAPSHOT
21 | 2.414
22 | ${jenkins.baseline}.3
23 | jenkinsci/${project.artifactId}-plugin
24 |
25 | 0.9.5-3
26 | 4.0.13-8
27 | 1.5.1
28 | 0.22.0
29 |
30 |
31 |
32 |
33 |
34 | io.jenkins.tools.bom
35 | bom-${jenkins.baseline}.x
36 | 2982.vdce2153031a_0
37 | pom
38 | import
39 |
40 |
41 |
42 |
43 |
44 |
45 | MIT license
46 | All source code is under the MIT license.
47 |
48 |
49 |
50 |
51 |
52 | Simon Symhoven
53 | simonsymhoven
54 | post@simon-symhoven.de
55 |
56 |
57 |
58 |
59 | scm:git:https://github.com/${gitHubRepo}.git
60 | scm:git:git@github.com:${gitHubRepo}.git
61 | https://github.com/${gitHubRepo}
62 | ${scmTag}
63 |
64 |
65 |
66 |
67 | io.jenkins.plugins
68 | plugin-util-api
69 |
70 |
71 | io.jenkins.plugins
72 | bootstrap5-api
73 |
74 |
75 | io.jenkins.plugins
76 | font-awesome-api
77 |
78 |
79 | io.jenkins.plugins
80 | jquery3-api
81 |
82 |
83 | io.jenkins.plugins
84 | muuri-api
85 | ${muuri-api.version}
86 |
87 |
88 | io.jenkins.plugins
89 | echarts-api
90 |
91 |
92 | io.jenkins.plugins
93 | select2-api
94 | ${select2-api.version}
95 |
96 |
97 | io.jenkins.plugins
98 | forensics-api
99 |
100 |
101 | org.everit.json
102 | org.everit.json.schema
103 | ${json-schema.version}
104 |
105 |
106 | json
107 | org.json
108 |
109 |
110 |
111 |
112 | org.jenkins-ci.plugins
113 | scm-api
114 |
115 |
116 | org.commonmark
117 | commonmark
118 | ${commonmark.version}
119 |
120 |
121 | io.jenkins.plugins
122 | commons-lang3-api
123 |
124 |
125 |
126 |
127 | org.jenkins-ci.plugins.workflow
128 | workflow-multibranch
129 |
130 |
131 | org.jenkins-ci.plugins.workflow
132 | workflow-api
133 |
134 |
135 | org.jenkins-ci.plugins.workflow
136 | workflow-job
137 |
138 |
139 |
140 |
141 | org.jenkins-ci.plugins.workflow
142 | workflow-basic-steps
143 | test
144 |
145 |
146 | org.jenkins-ci.plugins
147 | pipeline-stage-step
148 | test
149 |
150 |
151 | org.jenkins-ci.plugins
152 | scm-api
153 | tests
154 | test
155 |
156 |
157 | io.jenkins.plugins
158 | commons-text-api
159 | test
160 |
161 |
162 |
163 |
164 |
165 |
166 | maven-dependency-plugin
167 | 3.8.0
168 |
169 |
170 | org.jacoco
171 | jacoco-maven-plugin
172 |
173 |
174 | io/jenkins/plugins/**/*
175 |
176 |
177 | **/*Action*
178 | **/*Factory*
179 | **/*Property*
180 | **/*Portlet*
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 | repo.jenkins-ci.org
190 | https://repo.jenkins-ci.org/public/
191 |
192 |
193 |
194 |
195 |
196 | repo.jenkins-ci.org
197 | https://repo.jenkins-ci.org/public/
198 |
199 |
200 |
201 |
202 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/monitoring/DemoPortlet.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.monitoring;
2 |
3 | import hudson.Extension;
4 | import hudson.model.Run;
5 |
6 | import java.util.ArrayList;
7 | import java.util.Collection;
8 | import java.util.List;
9 |
10 | /**
11 | * An example of {@link MonitorPortlet}.
12 | */
13 | public class DemoPortlet extends MonitorPortlet {
14 | private final String id;
15 | private final String title;
16 |
17 | /**
18 | * Creates a new {@link DemoPortlet}.
19 | *
20 | * @param title
21 | * the title of the portlet.
22 | *
23 | * @param id
24 | * the id of the portlet.
25 | */
26 | public DemoPortlet(final String title, final String id) {
27 | super();
28 | this.id = id;
29 | this.title = title;
30 | }
31 |
32 | @Override
33 | public String getTitle() {
34 | return title;
35 | }
36 |
37 | @Override
38 | public String getId() {
39 | return id;
40 | }
41 |
42 | @Override
43 | public int getPreferredWidth() {
44 | return 300;
45 | }
46 |
47 | @Override
48 | public int getPreferredHeight() {
49 | return 200;
50 | }
51 |
52 | /**
53 | * An example of {@link MonitorPortletFactory}.
54 | */
55 | @Extension
56 | public static class ExampleMonitorFactory extends MonitorPortletFactory {
57 |
58 | @Override
59 | public Collection getPortlets(final Run, ?> build) {
60 | List portlets = new ArrayList<>();
61 | portlets.add(new DemoPortlet("Good First Portlet", "first-demo-portlet"));
62 | portlets.add(new DemoPortlet("Another Portlet", "second-demo-portlet"));
63 | return portlets;
64 | }
65 |
66 | @Override
67 | public String getDisplayName() {
68 | return "Pull Request Monitoring (Demo)";
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/monitoring/Monitor.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.monitoring;
2 |
3 | import com.google.common.collect.ImmutableSet;
4 | import edu.umd.cs.findbugs.annotations.NonNull;
5 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
6 | import hudson.Extension;
7 | import hudson.model.Run;
8 | import hudson.model.TaskListener;
9 | import io.jenkins.plugins.monitoring.util.PortletUtils;
10 | import io.jenkins.plugins.monitoring.util.PullRequestUtils;
11 | import org.apache.commons.collections.CollectionUtils;
12 | import org.apache.commons.lang.StringUtils;
13 | import org.jenkinsci.plugins.workflow.steps.*;
14 | import org.json.JSONArray;
15 | import org.json.JSONObject;
16 | import org.kohsuke.stapler.DataBoundConstructor;
17 |
18 | import java.io.IOException;
19 | import java.io.Serializable;
20 | import java.util.ArrayList;
21 | import java.util.List;
22 | import java.util.Objects;
23 | import java.util.Set;
24 | import java.util.stream.Collectors;
25 |
26 | /**
27 | * This {@link Step} is responsible for the configuration of the monitoring dashboard
28 | * via the corresponding Jenkinsfile.
29 | *
30 | * @author Simon Symhoven
31 | */
32 | public final class Monitor extends Step implements Serializable {
33 | private static final long serialVersionUID = -1329798203887148860L;
34 | private String portlets;
35 |
36 | /**
37 | * Creates a new instance of {@link Monitor}.
38 | *
39 | * @param portlets
40 | * the monitor configuration as json array string.
41 | */
42 | @DataBoundConstructor
43 | public Monitor(final String portlets) {
44 | super();
45 | this.portlets = portlets;
46 | }
47 |
48 | private void setPortlets(String portlets) {
49 | this.portlets = portlets;
50 | }
51 |
52 | public String getPortlets() {
53 | return portlets;
54 | }
55 |
56 | @Override
57 | public StepExecution start(final StepContext stepContext) {
58 | return new Execution(stepContext, this);
59 | }
60 |
61 | /**
62 | * The {@link Execution} routine for the monitoring step.
63 | */
64 | @SuppressFBWarnings(value = "THROWS_METHOD_THROWS_CLAUSE_BASIC_EXCEPTION")
65 | static class Execution extends SynchronousStepExecution {
66 |
67 | private static final long serialVersionUID = 1300005476208035751L;
68 | private final Monitor monitor;
69 |
70 | Execution(final StepContext stepContext, final Monitor monitor) {
71 | super(stepContext);
72 | this.monitor = monitor;
73 | }
74 |
75 | @Override
76 | public Void run() throws IOException, InterruptedException {
77 | final Run, ?> run = getContext().get(Run.class);
78 |
79 | if (run == null) {
80 | log("[Monitor] Run not found!");
81 | return null;
82 | }
83 |
84 | if (!PortletUtils.isValidConfiguration(monitor.getPortlets())) {
85 | log("[Monitor] Portlet Configuration is invalid!");
86 | return null;
87 | }
88 |
89 | JSONArray portlets = new JSONArray(monitor.getPortlets());
90 | log("[Monitor] Portlet Configuration: " + portlets.toString(3));
91 |
92 | List classes = PortletUtils.getAvailablePortlets(run)
93 | .stream()
94 | .map(MonitorPortlet::getId)
95 | .collect(Collectors.toList());
96 |
97 | log("[Monitor] Available portlets: ["
98 | + StringUtils.join(classes, ", ") + "]");
99 |
100 | List usedPortlets = new ArrayList<>();
101 |
102 | for (Object o : portlets) {
103 | if (o instanceof JSONObject) {
104 | JSONObject portlet = (JSONObject) o;
105 | String id = portlet.getString("id");
106 |
107 | if (usedPortlets.contains(id)) {
108 | log("[Monitor] Portlet with ID '" + id
109 | + "' already defined in list of portlets. Skip adding this portlet!");
110 | }
111 | else {
112 | usedPortlets.add(id);
113 | }
114 | }
115 | }
116 |
117 | List missedPortletIds = new ArrayList(CollectionUtils.removeAll(usedPortlets, classes));
118 |
119 | if (!missedPortletIds.isEmpty()) {
120 | log("[Monitor] Can't find the following portlets "
121 | + missedPortletIds + " in list of available portlets! Will remove from current configuration.");
122 |
123 | JSONArray cleanedPortlets = new JSONArray();
124 | for (Object o : portlets) {
125 | JSONObject portlet = (JSONObject) o;
126 | if (!missedPortletIds.contains(portlet.getString("id"))) {
127 | cleanedPortlets.put(portlet);
128 | }
129 | }
130 |
131 | monitor.setPortlets(cleanedPortlets.toString(3));
132 |
133 | log("[Monitor] Cleaned Portlets: " + cleanedPortlets.toString(3));
134 | }
135 |
136 | if (PullRequestUtils.isPullRequest(run.getParent())) {
137 | log("[Monitor] Build is part of a pull request. Add 'MonitoringCustomAction' now.");
138 |
139 | run.addAction(new MonitoringCustomAction(monitor.getPortlets()));
140 | }
141 | else {
142 | log("[Monitor] Build is not part of a pull request. Skip adding 'MonitoringCustomAction'.");
143 | }
144 |
145 | return null;
146 | }
147 |
148 | private void log(String message) throws IOException, InterruptedException {
149 | Objects.requireNonNull(getContext().get(TaskListener.class)).getLogger()
150 | .println(message);
151 | }
152 |
153 | }
154 |
155 | /**
156 | * A {@link Descriptor} for the monitoring step.
157 | */
158 | @Extension
159 | public static class Descriptor extends StepDescriptor {
160 |
161 | @Override
162 | public Set extends Class>> getRequiredContext() {
163 | return ImmutableSet.of(TaskListener.class, Run.class);
164 | }
165 |
166 | @Override
167 | public String getFunctionName() {
168 | return "monitoring";
169 | }
170 |
171 | @Override
172 | @NonNull
173 | public String getDisplayName() {
174 | return Messages.Step_DisplayName();
175 | }
176 | }
177 |
178 | }
179 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/monitoring/MonitorConfigurationProperty.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.monitoring;
2 |
3 | import edu.umd.cs.findbugs.annotations.NonNull;
4 | import hudson.Extension;
5 | import hudson.model.Saveable;
6 | import hudson.model.User;
7 | import hudson.model.UserProperty;
8 | import hudson.model.UserPropertyDescriptor;
9 |
10 | import java.io.IOException;
11 | import java.util.ArrayList;
12 | import java.util.Collection;
13 | import java.util.List;
14 | import java.util.Optional;
15 | import java.util.logging.Level;
16 | import java.util.logging.Logger;
17 |
18 | /**
19 | * A {@link UserProperty} to store the json configuration per user as property.
20 | *
21 | * @author Simon Symhoven
22 | */
23 | public class MonitorConfigurationProperty extends UserProperty implements Saveable {
24 | private static final Logger LOGGER = Logger.getLogger(MonitorConfigurationProperty.class.getName());
25 | private final Collection configurations;
26 |
27 | /**
28 | * The id for the default configuration.
29 | */
30 | public static final String DEFAULT_ID = "default";
31 |
32 | /**
33 | * Creates a new {@link MonitorConfigurationProperty}.
34 | *
35 | * @param configurations
36 | * the list of configurations to add to the {@link MonitorConfigurationProperty}.
37 | */
38 | public MonitorConfigurationProperty(final List configurations) {
39 | super();
40 | this.configurations = configurations;
41 | }
42 |
43 | public Collection getConfigurations() {
44 | return configurations;
45 | }
46 |
47 | /**
48 | * Get a {@link MonitorConfiguration} by its id.
49 | *
50 | * @param id
51 | * the id of the {@link MonitorConfiguration} to get.
52 | *
53 | * @return
54 | * the {@link MonitorConfiguration} or default if id does not exist on {@link MonitorConfigurationProperty}.
55 | */
56 | public MonitorConfiguration getConfiguration(final String id) {
57 | return getConfigurations().stream()
58 | .filter(monitorConfiguration -> monitorConfiguration.getId().equals(id))
59 | .findFirst()
60 | .orElse(getConfigurations()
61 | .stream()
62 | .filter(monitorConfiguration -> monitorConfiguration.getId().equals(DEFAULT_ID))
63 | .findFirst().get());
64 | }
65 |
66 | /**
67 | * Updates an existing {@link MonitorConfiguration}.
68 | *
69 | * @param id
70 | * the id of the {@link MonitorConfiguration} to update.
71 | *
72 | * @param config
73 | * the config string to update.
74 | */
75 | public void createOrUpdateConfiguration(final String id, final String config) {
76 | Optional property = getConfigurations().stream()
77 | .filter(monitorConfiguration -> monitorConfiguration.getId().equals(id))
78 | .findFirst();
79 |
80 | if (property.isPresent()) {
81 | property.get().setConfig(config);
82 | }
83 | else {
84 | getConfigurations().add(new MonitorConfiguration(id, config));
85 | }
86 |
87 | save();
88 | }
89 |
90 | /**
91 | * Removes a configuration from configurations list.
92 | *
93 | * @param id
94 | * the id of configuration to remove.
95 | */
96 | public void removeConfiguration(final String id) {
97 | getConfigurations().remove(getConfiguration(id));
98 | save();
99 | }
100 |
101 | /**
102 | * Gets the {@link MonitorConfigurationProperty} for current user.
103 | *
104 | * @return
105 | * the {@link MonitorConfigurationProperty} as {@link Optional}.
106 | */
107 | public static Optional forCurrentUser() {
108 | final User current = User.current();
109 | return current == null ? Optional.empty() : Optional.ofNullable(current.getProperty(MonitorConfigurationProperty.class));
110 | }
111 |
112 | @Override
113 | public void save() {
114 | try {
115 | user.save();
116 | }
117 | catch (IOException exception) {
118 | LOGGER.log(Level.SEVERE, "User could not be saved: ", exception);
119 | }
120 | }
121 |
122 | /**
123 | * The property class to store. Each {@link MonitorConfiguration} has an id and a config (json string).
124 | */
125 | public static class MonitorConfiguration {
126 |
127 | private final String id;
128 | private String config;
129 |
130 | /**
131 | * Creates a {@link MonitorConfiguration}.
132 | *
133 | * @param id
134 | * the id of the {@link MonitorConfiguration}.
135 | *
136 | * @param config
137 | * the config of the {@link MonitorConfiguration}.
138 | */
139 | public MonitorConfiguration(final String id, final String config) {
140 | this.id = id;
141 | this.config = config;
142 | }
143 |
144 | public String getId() {
145 | return id;
146 | }
147 |
148 | public String getConfig() {
149 | return config;
150 | }
151 |
152 | public void setConfig(final String config) {
153 | this.config = config;
154 | }
155 |
156 | }
157 |
158 | /**
159 | * A {@link UserPropertyDescriptor} for the {@link MonitorConfigurationProperty}.
160 | */
161 | @Extension
162 | public static class MonitorPropertyDescriptor extends UserPropertyDescriptor {
163 |
164 | @Override
165 | public UserProperty newInstance(final User user) {
166 | return new MonitorConfigurationProperty(new ArrayList<>());
167 | }
168 |
169 | @NonNull
170 | @Override
171 | public String getDisplayName() {
172 | return Messages.Property_DisplayName();
173 | }
174 |
175 | }
176 |
177 | }
178 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/monitoring/MonitorPortlet.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.monitoring;
2 |
3 | import java.util.Optional;
4 |
5 | /**
6 | *
Defines a single portlet for the pull request monitoring dashboard.
7 | *
8 | *
The id must be unique. Please make sure that the id is related to the plugin so that
9 | * there are no conflicts with other plugins. It is recommended to use the artifact id
10 | * of the plugin or parts of it as prefix. If several portlets of the same class are created in the
11 | * {@link MonitorPortletFactory}, it must be ensured that {@link #getId()} is always unique for each portlet!
12 | *
13 | * @since 1.6.0
14 | * @author Simon Symhoven
15 | */
16 | public abstract class MonitorPortlet {
17 |
18 | /**
19 | * Defines the title to be shown.
20 | *
21 | * @return
22 | * the title.
23 | */
24 | public abstract String getTitle();
25 |
26 | /**
27 | * Defines the id for the portlet.
28 | *
29 | * @return
30 | * the id.
31 | */
32 | public abstract String getId();
33 |
34 | /**
35 | * Defines whether the portlet is shown per default in the dashboard or not.
36 | * The functionality for this is not yet given and the flag is ignored, but the API for it is
37 | * already prepared.
38 | *
39 | * @return
40 | * true if portlet should be shown, false else.
41 | *
42 | * @since 1.6.0
43 | */
44 | public boolean isDefault() {
45 | return false;
46 | }
47 |
48 | /**
49 | * Defines the preferred width of the portlet.
50 | *
51 | * @return
52 | * the width in pixels.
53 | */
54 | public abstract int getPreferredWidth();
55 |
56 | /**
57 | * Defines the preferred height of the portlet.
58 | *
59 | * @return
60 | * the height in pixels.
61 | */
62 | public abstract int getPreferredHeight();
63 |
64 | /**
65 | * Defines the icon to show in the dropdown list of available portlets.
66 | *
67 | * @return
68 | * the app relative icon url depending on ${resURL} (/static/cache-id),
69 | * or {@code Optional.empty()}, if a default icon should be added.
70 | * Supported file formats (depending on browser): jpeg, gif, png, apng, svg, bmp, bmp ico, png ico.
71 | */
72 | public Optional getIconUrl() {
73 | return Optional.empty();
74 | }
75 |
76 | /**
77 | * Defines the relative url to a detail view of the portlet.
78 | *
79 | * @return
80 | * the relative link to the detail view depending on the current build url,
81 | * e.g. current build url: "http://localhost:8080/jenkins/job/job-name/view/change-requests/job/PR-1/1/",
82 | * then the relative url could be "pull-request-monitoring".
83 | * or {@code Optional.empty()}, if no link should be added to portlet.
84 | */
85 | public Optional getDetailViewUrl() {
86 | return Optional.empty();
87 | }
88 |
89 | /**
90 | * {@inheritDoc}
91 | */
92 | @Override
93 | public String toString() {
94 | return "MonitorPortlet{'" + getId() + "'}";
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/monitoring/MonitorPortletFactory.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.monitoring;
2 |
3 | import hudson.ExtensionPoint;
4 | import hudson.model.Run;
5 |
6 | import java.util.Collection;
7 |
8 | /**
9 | *
Defines the {@link ExtensionPoint} to register one or more new pull request monitoring portlets.
10 | *
11 | *
The {@link #getDisplayName()} is shown in a dropdown list as optgroup. The children of the optgroup
12 | * are the registered portlets of {@link #getPortlets(Run)}.
13 | *
14 | *
Since an empty dashboard is always added by default, it is possible that the method {@link #getPortlets(Run)} will
15 | * be called even though the current run may not be finished. It is therefore advisable to perform a null check on
16 | * the actions of the run required by your portlet and return an empty list if necessary.
17 | * (Example: code-coverage-api)
18 | *
19 | * @since 1.6.0
20 | * @author Simon Symhoven
21 | */
22 | public abstract class MonitorPortletFactory implements ExtensionPoint {
23 |
24 | /**
25 | * Get a collection of {@link MonitorPortlet} to display.
26 | *
27 | * @param build
28 | * the reference {@link Run}.
29 | *
30 | * @return
31 | * a collection of {@link MonitorPortlet}.
32 | */
33 | public abstract Collection getPortlets(Run, ?> build);
34 |
35 | /**
36 | * Defines the name of the factory.
37 | *
38 | * @return
39 | * the name to display for the factory.
40 | */
41 | public abstract String getDisplayName();
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/monitoring/MonitoringCustomAction.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.monitoring;
2 |
3 | import hudson.model.InvisibleAction;
4 | import hudson.model.Run;
5 |
6 | /**
7 | * This action is added to {@link Run}, if configuration is set in the Jenkinsfile. Therefore the portlets of this
8 | * action {@link MonitoringCustomAction} overwrites the one from the {@link MonitoringDefaultAction} added by the
9 | * {@link MonitoringDefaultActionFactory}.
10 | *
11 | * @author Simon Symhoven
12 | */
13 | public class MonitoringCustomAction extends InvisibleAction {
14 | private final String portlets;
15 |
16 | /**
17 | * Creates a new instance of {@link MonitoringCustomAction}.
18 | *
19 | * @param portlets
20 | * the portlets as json array string to be add.
21 | */
22 | public MonitoringCustomAction(final String portlets) {
23 | super();
24 | this.portlets = portlets;
25 | }
26 |
27 | public String getPortlets() {
28 | return portlets;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/monitoring/MonitoringDefaultAction.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.monitoring;
2 |
3 | import com.fasterxml.jackson.core.JsonProcessingException;
4 | import com.fasterxml.jackson.databind.JsonNode;
5 | import com.fasterxml.jackson.databind.ObjectMapper;
6 | import edu.hm.hafner.util.FilteredLog;
7 | import hudson.model.Run;
8 | import io.jenkins.plugins.forensics.reference.ReferenceFinder;
9 | import io.jenkins.plugins.monitoring.util.PortletUtils;
10 | import j2html.tags.DomContent;
11 | import jenkins.model.Jenkins;
12 | import jenkins.model.RunAction2;
13 | import jenkins.scm.api.metadata.ContributorMetadataAction;
14 | import jenkins.scm.api.metadata.ObjectMetadataAction;
15 | import jenkins.scm.api.mixin.ChangeRequestSCMHead2;
16 | import org.apache.commons.collections.CollectionUtils;
17 | import org.apache.commons.lang3.StringUtils;
18 | import org.commonmark.node.Node;
19 | import org.commonmark.parser.Parser;
20 | import org.commonmark.renderer.html.HtmlRenderer;
21 | import org.jenkinsci.plugins.workflow.multibranch.BranchJobProperty;
22 | import org.json.JSONArray;
23 | import org.json.JSONObject;
24 | import org.kohsuke.stapler.StaplerProxy;
25 | import org.kohsuke.stapler.bind.JavaScriptMethod;
26 |
27 | import java.util.ArrayList;
28 | import java.util.List;
29 | import java.util.Optional;
30 | import java.util.logging.Level;
31 | import java.util.logging.Logger;
32 | import java.util.stream.Collectors;
33 |
34 | import static j2html.TagCreator.*;
35 |
36 | /**
37 | * This action displays a link on the side panel of a {@link Run}. The action is only displayed if the parent job
38 | * is a pull request.
39 | * The action is responsible to render the summary via its associated 'summary.jelly' view and render the
40 | * main plugin page, where the user can configure the dashboard with all supported plugins via its associated
41 | * 'index.jelly view.
42 | *
43 | * @author Simon Symhoven
44 | */
45 | public class MonitoringDefaultAction implements RunAction2, StaplerProxy {
46 | private static final Logger LOGGER = Logger.getLogger(MonitoringDefaultAction.class.getName());
47 | private transient Run, ?> run;
48 |
49 | /**
50 | * Creates a new instance of {@link MonitoringDefaultAction}.
51 | *
52 | * @param run
53 | * the run that owns this action.
54 | */
55 | public MonitoringDefaultAction(final Run, ?> run) {
56 | this.run = run;
57 | }
58 |
59 | @Override
60 | public String getIconFileName() {
61 | return MonitoringMultibranchProjectAction.getIconSmall();
62 | }
63 |
64 | @Override
65 | public String getDisplayName() {
66 | return String.format("%s '%s'", Messages.BuildAction_Name(), getRun().getDisplayName());
67 | }
68 |
69 | @Override
70 | public String getUrlName() {
71 | return MonitoringMultibranchProjectAction.getURI();
72 | }
73 |
74 | @Override
75 | public void onAttached(final Run, ?> build) {
76 | this.run = build;
77 | }
78 |
79 | @Override
80 | public void onLoad(final Run, ?> build) {
81 | this.run = build;
82 | }
83 |
84 | public Run, ?> getRun() {
85 | return run;
86 | }
87 |
88 | public String getPortlets() {
89 | return PortletUtils.getDefaultPortletsAsConfiguration(getRun());
90 | }
91 |
92 | /**
93 | * Get the {@link ObjectMetadataAction} for the current build.
94 | *
95 | * @return
96 | * the {@link ObjectMetadataAction}.
97 | */
98 | public Optional getObjectMetadataAction() {
99 | return Optional.ofNullable(getRun().getParent().getProperty(BranchJobProperty.class)
100 | .getBranch().getAction(ObjectMetadataAction.class));
101 | }
102 |
103 | /**
104 | * Get the {@link ContributorMetadataAction} for the current build.
105 | *
106 | * @return
107 | * the {@link ContributorMetadataAction}.
108 | */
109 | public Optional getContributorMetadataAction() {
110 | return Optional.ofNullable(getRun().getParent().getProperty(BranchJobProperty.class)
111 | .getBranch().getAction(ContributorMetadataAction.class));
112 | }
113 |
114 | /**
115 | * Get the {@link ChangeRequestSCMHead2} for a specific {@link Run}.
116 | *
117 | * @return
118 | * the {@link ChangeRequestSCMHead2} of run.
119 | */
120 | public ChangeRequestSCMHead2 getScmHead() {
121 | return (ChangeRequestSCMHead2) getRun().getParent().getProperty(BranchJobProperty.class).getBranch().getHead();
122 | }
123 |
124 | /**
125 | * Creates the heading for the pull request metadata accordion.
126 | *
127 | * @return
128 | * the title as html element.
129 | */
130 | public String getPullRequestMetadataTitle() {
131 |
132 | Optional contributorMetadataAction = getContributorMetadataAction();
133 |
134 | return span(join(
135 | strong(iffElse(contributorMetadataAction.isPresent(),
136 | getContributorMetadataAction().get().getContributor(),
137 | "unknown")), "wants to merge",
138 | strong(getScmHead().getOriginName()), "into",
139 | strong(getScmHead().getTarget().getName()))).render();
140 | }
141 |
142 | /**
143 | * Creates the reference build link for the pull request metadata title.
144 | *
145 | * @return
146 | * the reference build as html element.
147 | */
148 | public String getPullRequestReferenceBuildDescription() {
149 | Optional> referenceBuild = getReferenceBuild();
150 |
151 | String url = referenceBuild.map(value -> Jenkins.get().getRootUrl() + value.getUrl()).orElse("#");
152 | String name = referenceBuild.map(Run::getDisplayName).orElse("#?");
153 |
154 | String target = getScmHead().getTarget().getName();
155 |
156 | DomContent reference = join("Reference Build:", a().withId("reference-build-link")
157 | .withHref(url)
158 | .withText(target + " " + name));
159 |
160 | return span(reference).render();
161 | }
162 |
163 | /**
164 | * Get the reference build to current build.
165 | *
166 | * @return
167 | * the reference build as {@link Optional}.
168 | */
169 | private Optional> getReferenceBuild() {
170 | FilteredLog log = new FilteredLog("");
171 | ReferenceFinder referenceFinder = new ReferenceFinder();
172 | return referenceFinder.findReference(getRun(), log);
173 | }
174 |
175 | /**
176 | * Creates the body for the pull request metadata accordion.
177 | *
178 | * @return
179 | * the body as html element.
180 | */
181 | public String getPullRequestMetadataBody() {
182 | if (!getObjectMetadataAction().isPresent()) {
183 | return span(i("No metadata found.")).render();
184 | }
185 |
186 | String description = getObjectMetadataAction().get().getObjectDescription();
187 |
188 | if (StringUtils.isEmpty(description)) {
189 | return span(i("No description provided.")).render();
190 | }
191 |
192 | Parser parser = Parser.builder().build();
193 | Node document = parser.parse(description);
194 | HtmlRenderer renderer = HtmlRenderer.builder().build();
195 |
196 | return renderer.render(document);
197 | }
198 |
199 | /**
200 | * Get the project it based on the current {@link Run}.
201 | *
202 | * @return
203 | * the display name of the current project as id.
204 | */
205 | public String getConfigurationId() {
206 | String id = getRun().getParent().getParent().getDisplayName();
207 | return StringUtils.toRootLowerCase(id).replaceAll(" ", "-");
208 | }
209 |
210 | /**
211 | * Sets the default for {@link MonitorConfigurationProperty}.
212 | */
213 | private void setDefaultMonitorConfiguration() {
214 | MonitorConfigurationProperty
215 | .forCurrentUser()
216 | .ifPresent(monitorConfigurationProperty -> monitorConfigurationProperty
217 | .createOrUpdateConfiguration(MonitorConfigurationProperty.DEFAULT_ID, resolvePortlets()));
218 | }
219 |
220 | /**
221 | * Get all portlets, which are not available anymore.
222 | *
223 | * @return
224 | * a list of all unavailable portlet ids.
225 | */
226 | @SuppressWarnings("unchecked")
227 | public List getUnavailablePortlets() {
228 | JSONArray portlets = new JSONArray(getConfiguration());
229 |
230 | List usedPlugins = new ArrayList<>();
231 |
232 | for (Object o : portlets) {
233 | JSONObject portlet = (JSONObject) o;
234 | usedPlugins.add(portlet.getString("id"));
235 | }
236 |
237 | List availablePlugins = PortletUtils.getAvailablePortlets(getRun())
238 | .stream().map(MonitorPortlet::getId).collect(Collectors.toList());
239 | return new ArrayList(CollectionUtils.removeAll(usedPlugins, availablePlugins));
240 | }
241 |
242 | /**
243 | * Checks if there are changes in the configuration since the last build.
244 | *
245 | * @return
246 | * true if both configurations are equal, else false.
247 | */
248 | public boolean hasChanges() {
249 | MonitoringCustomAction action = getRun().getAction(MonitoringCustomAction.class);
250 |
251 | if (action == null) {
252 | return false;
253 | }
254 |
255 | Run, ?> previous = getRun().getPreviousBuild();
256 |
257 | if (previous == null) {
258 | return false;
259 | }
260 |
261 | MonitoringCustomAction prevAction = previous.getAction(MonitoringCustomAction.class);
262 |
263 | if (prevAction == null) {
264 | return false;
265 | }
266 |
267 | return !areJsonNodesEquals(action.getPortlets(), prevAction.getPortlets());
268 | }
269 |
270 | /**
271 | * Compares to json nodes, if they are equals.
272 | *
273 | * @param s1
274 | * the first json node as string.
275 | *
276 | * @param s2
277 | * the second json node as string.
278 | *
279 | * @return
280 | * true, if both are equals, else false.
281 | */
282 | public boolean areJsonNodesEquals(final String s1, final String s2) {
283 |
284 | try {
285 | ObjectMapper mapper = new ObjectMapper();
286 | JsonNode node1 = mapper.readTree(s1);
287 | JsonNode node2 = mapper.readTree(s2);
288 | return node1.equals(node2);
289 | }
290 | catch (JsonProcessingException exception) {
291 | LOGGER.log(Level.SEVERE, "Json could not be parsed: ", exception);
292 | }
293 |
294 | return false;
295 | }
296 |
297 | /*
298 | JavaScriptMethod block. All methods are called from a jelly file.
299 | */
300 |
301 | /**
302 | * Saves the actual dashboard configuration to {@link MonitorConfigurationProperty}.
303 | *
304 | * @param config
305 | * the config string to update.
306 | *
307 | */
308 | @JavaScriptMethod
309 | public void updateMonitorConfiguration(final String config) {
310 | MonitorConfigurationProperty
311 | .forCurrentUser()
312 | .ifPresent(monitorConfigurationProperty ->
313 | monitorConfigurationProperty.createOrUpdateConfiguration(getConfigurationId(), config));
314 | }
315 |
316 | /**
317 | * Get the current dashboard configuration of user.
318 | *
319 | * @return
320 | * the config of the corresponding {@link MonitorConfigurationProperty} of the actual project
321 | * or the default configuration of Jenkinsfile if no config exists in {@link MonitorConfigurationProperty}
322 | * for actual project.
323 | */
324 | @JavaScriptMethod
325 | public String getConfiguration() {
326 |
327 | MonitorConfigurationProperty monitorConfigurationProperty = MonitorConfigurationProperty
328 | .forCurrentUser().orElse(null);
329 |
330 | return monitorConfigurationProperty == null
331 | ? resolvePortlets() : monitorConfigurationProperty.getConfiguration(getConfigurationId()).getConfig();
332 | }
333 |
334 | /**
335 | * Checks if the actual configuration is synced with the default one (Jenkinsfile or empty one).
336 | *
337 | * @return
338 | * true, if synced, else false.
339 | */
340 | @JavaScriptMethod
341 | public boolean isMonitorConfigurationSynced() {
342 |
343 | MonitorConfigurationProperty monitorConfigurationProperty = MonitorConfigurationProperty.forCurrentUser()
344 | .orElse(null);
345 |
346 | if (monitorConfigurationProperty == null) {
347 | return true;
348 | }
349 |
350 | MonitorConfigurationProperty.MonitorConfiguration projectConfiguration =
351 | monitorConfigurationProperty.getConfiguration(getConfigurationId());
352 |
353 | if (projectConfiguration == null) {
354 | return true;
355 | }
356 |
357 | MonitorConfigurationProperty.MonitorConfiguration defaultConfiguration =
358 | monitorConfigurationProperty.getConfiguration(MonitorConfigurationProperty.DEFAULT_ID);
359 |
360 | return areJsonNodesEquals(projectConfiguration.getConfig(), defaultConfiguration.getConfig());
361 | }
362 |
363 | /**
364 | * Resolves the portlet string of the {@link Monitor}.
365 | *
366 | * @return
367 | * the portlet string of {@link MonitoringCustomAction} if exists,
368 | * else the one of {@link MonitoringDefaultAction}.
369 | */
370 | @JavaScriptMethod
371 | public String resolvePortlets() {
372 | MonitoringCustomAction action = getRun().getAction(MonitoringCustomAction.class);
373 | return action == null ? getPortlets() : action.getPortlets();
374 | }
375 |
376 | /**
377 | * Reset the current project configuration to default.
378 | */
379 | @JavaScriptMethod
380 | public void resetMonitorConfiguration() {
381 | MonitorConfigurationProperty
382 | .forCurrentUser()
383 | .ifPresent(monitorConfigurationProperty -> monitorConfigurationProperty.removeConfiguration(getConfigurationId()));
384 | }
385 |
386 | @Override
387 | public Object getTarget() {
388 | setDefaultMonitorConfiguration();
389 | return this;
390 | }
391 |
392 | /**
393 | * Gets all {@link MonitorPortlet} for corresponding {@link MonitorPortletFactory}.
394 | *
395 | * @param build
396 | * the reference build.
397 | *
398 | * @return
399 | * all available {@link MonitorPortlet}.
400 | */
401 | public static List extends MonitorPortlet> getAvailablePortlets(final Run, ?> build) {
402 | return PortletUtils.getAvailablePortlets(build);
403 | }
404 |
405 | /**
406 | * Get all portlet factories, type of {@link MonitorPortletFactory}.
407 | *
408 | * @return
409 | * all factories as list.
410 | */
411 | public static List extends MonitorPortletFactory> getFactories() {
412 | return PortletUtils.getFactories();
413 | }
414 |
415 | /**
416 | * Gets all {@link MonitorPortlet} for one {@link MonitorPortletFactory}.
417 | *
418 | * @param build
419 | * the build to get the portlets for.
420 | *
421 | * @param factory
422 | * the factory to get the portlets for.
423 | *
424 | * @return
425 | * the filtered portlets.
426 | */
427 | public static List extends MonitorPortlet> getAvailablePortletsForFactory(
428 | final Run, ?> build, final MonitorPortletFactory factory) {
429 | return PortletUtils.getAvailablePortletsForFactory(build, factory);
430 | }
431 | }
432 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/monitoring/MonitoringDefaultActionFactory.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.monitoring;
2 |
3 | import edu.umd.cs.findbugs.annotations.NonNull;
4 | import hudson.Extension;
5 | import hudson.model.Action;
6 | import hudson.model.Job;
7 | import hudson.model.Run;
8 | import io.jenkins.plugins.monitoring.util.PullRequestUtils;
9 | import jenkins.model.TransientActionFactory;
10 |
11 | import java.util.Collection;
12 | import java.util.Collections;
13 |
14 | /**
15 | * A {@link TransientActionFactory} to add an action to specific {@link Run}.
16 | *
17 | * @author Simon Symhoven
18 | */
19 | @Extension
20 | public class MonitoringDefaultActionFactory extends TransientActionFactory {
21 | @Override
22 | public Class type() {
23 | return Run.class;
24 | }
25 |
26 | @NonNull
27 | @Override
28 | public Collection extends Action> createFor(@NonNull final Run run) {
29 |
30 | if (run.isBuilding()) {
31 | return Collections.emptyList();
32 | }
33 |
34 | final Job, ?> job = run.getParent();
35 |
36 | if (PullRequestUtils.isPullRequest(job)) {
37 | return Collections.singletonList(new MonitoringDefaultAction(run));
38 | }
39 |
40 | return Collections.emptyList();
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/monitoring/MonitoringMultibranchProjectAction.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.monitoring;
2 |
3 | import hudson.model.Action;
4 | import hudson.model.Job;
5 | import hudson.model.ProminentProjectAction;
6 | import io.jenkins.plugins.monitoring.util.PullRequestUtils;
7 | import jenkins.branch.MultiBranchProject;
8 | import jenkins.scm.api.metadata.ContributorMetadataAction;
9 | import jenkins.scm.api.metadata.ObjectMetadataAction;
10 | import jenkins.scm.api.mixin.ChangeRequestSCMHead2;
11 | import org.jenkinsci.plugins.workflow.multibranch.BranchJobProperty;
12 |
13 | import java.util.List;
14 | import java.util.Optional;
15 | import java.util.stream.Collectors;
16 |
17 | /**
18 | * This action displays a link on the side panel of a {@link MultiBranchProject}.
19 | * The action is responsible to render the basic overview of all open pull requests
20 | * via its associated 'index.jelly' view.
21 | *
22 | * @author Simon Symhoven
23 | */
24 | public class MonitoringMultibranchProjectAction implements ProminentProjectAction, Action {
25 |
26 | private static final String URI = "pull-request-monitoring";
27 | private static final String ICONS_PREFIX = "/plugin/pull-request-monitoring/icons/";
28 | private static final String ICON_SMALL = ICONS_PREFIX + "line-graph-32x32.png";
29 | private static final String ICON_BIG = ICONS_PREFIX + "line-graph-64x64.png";
30 |
31 | private final transient MultiBranchProject, ?> multiBranchProject;
32 |
33 | /**
34 | * Creates a new instance of {@link MonitoringMultibranchProjectAction}.
35 | *
36 | * @param multiBranchProject
37 | * the project that owns this action.
38 | */
39 | public MonitoringMultibranchProjectAction(final MultiBranchProject, ?> multiBranchProject) {
40 | this.multiBranchProject = multiBranchProject;
41 | }
42 |
43 | @Override
44 | public String getIconFileName() {
45 | return ICON_BIG;
46 | }
47 |
48 | @Override
49 | public String getDisplayName() {
50 | return Messages.ProjectAction_Name();
51 | }
52 |
53 | @Override
54 | public String getUrlName() {
55 | return URI;
56 | }
57 |
58 | public MultiBranchProject, ?> getProject() {
59 | return multiBranchProject;
60 | }
61 |
62 | /**
63 | * Filters all jobs of selected {@link MultiBranchProject} by "Pull Request".
64 | *
65 | * @return
66 | * filtered list of all {@link #getJobs() jobs} by "Pull Request".
67 | */
68 | public List> getPullRequests() {
69 | return getJobs().stream().filter(PullRequestUtils::isPullRequest).collect(Collectors.toList());
70 | }
71 |
72 | /**
73 | * Get the {@link ChangeRequestSCMHead2} for a specific {@link Job}.
74 | *
75 | * @param job
76 | * the job to get {@link ChangeRequestSCMHead2} for.
77 | *
78 | * @return
79 | * the {@link ChangeRequestSCMHead2} of job.
80 | */
81 | public ChangeRequestSCMHead2 getScmHead(final Job, ?> job) {
82 | return (ChangeRequestSCMHead2) job.getProperty(BranchJobProperty.class).getBranch().getHead();
83 | }
84 |
85 | /**
86 | * Fetch all jobs (items) of current {@link MultiBranchProject}.
87 | *
88 | * @return
89 | * {@link List} of all jobs of current {@link MultiBranchProject}.
90 | */
91 | private List> getJobs() {
92 | return multiBranchProject.getItems().stream().map(item -> (Job, ?>) item).collect(Collectors.toList());
93 | }
94 |
95 | /**
96 | * Get the {@link ObjectMetadataAction} for a given job, e.g. the name of
97 | * the pull request and the link to the repository.
98 | *
99 | * @param job
100 | * the job to get {@link ObjectMetadataAction} for.
101 | * @return
102 | * the {@link ObjectMetadataAction} for the given job as {@link Optional}.
103 | */
104 | public Optional getObjectMetaData(final Job, ?> job) {
105 | return Optional.ofNullable(
106 | job.getProperty(BranchJobProperty.class).getBranch().getAction(ObjectMetadataAction.class));
107 | }
108 |
109 | /**
110 | * Get the {@link ContributorMetadataAction} for a given job, e.g. the name of the contributor.
111 | *
112 | * @param job
113 | * the job to get {@link ContributorMetadataAction} for.
114 | * @return
115 | * the {@link ContributorMetadataAction} for the given job as {@link Optional}.
116 | */
117 | public Optional getContributorMetaData(final Job, ?> job) {
118 | return Optional.ofNullable(
119 | job.getProperty(BranchJobProperty.class).getBranch().getAction(ContributorMetadataAction.class));
120 | }
121 |
122 | public static String getURI() {
123 | return URI;
124 | }
125 |
126 | public static String getIconSmall() {
127 | return ICON_SMALL;
128 | }
129 |
130 | public static String getIconBig() {
131 | return ICON_BIG;
132 | }
133 |
134 | }
135 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/monitoring/MonitoringMultibranchProjectActionFactory.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.monitoring;
2 |
3 | import edu.umd.cs.findbugs.annotations.NonNull;
4 | import hudson.Extension;
5 | import hudson.model.Action;
6 | import jenkins.branch.MultiBranchProject;
7 | import jenkins.model.TransientActionFactory;
8 |
9 | import java.util.Collection;
10 | import java.util.Collections;
11 |
12 | /**
13 | * A {@link TransientActionFactory} to add an action to specific {@link MultiBranchProject}.
14 | *
15 | * @author Simon Symhoven
16 | */
17 | @Extension
18 | public class MonitoringMultibranchProjectActionFactory extends TransientActionFactory {
19 |
20 | /**
21 | * Specifies the {@link Class} of the job ({@link MultiBranchProject}) to add the action to.
22 | *
23 | * @return
24 | * the {@link Class} of the job to add the action to.
25 | */
26 | @Override
27 | public Class type() {
28 | return MultiBranchProject.class;
29 | }
30 |
31 | /**
32 | * Add the action to the selected {@link MultiBranchProject}.
33 | *
34 | * @param multiBranchProject
35 | * the job to add the action to.
36 | * @return
37 | * {@link Collections} of {@link MonitoringMultibranchProjectAction}.
38 | */
39 | @Override
40 | @NonNull
41 | public Collection extends Action> createFor(@NonNull final MultiBranchProject multiBranchProject) {
42 | return Collections.singletonList(new MonitoringMultibranchProjectAction(multiBranchProject));
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/monitoring/MonitoringWorkflowJobAction.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.monitoring;
2 |
3 | import hudson.model.Action;
4 | import org.jenkinsci.plugins.workflow.job.WorkflowJob;
5 |
6 | /**
7 | * This action displays a link on the side panel of a {@link WorkflowJob}. The action is only displayed if the job
8 | * is a pull request, which is described in the associated {@link MonitoringWorkflowJobActionFactory}.
9 | * The action is responsible to reference the latest build of the job and navigates to the corresponding
10 | * {@link MonitoringDefaultAction}.
11 | *
12 | * @author Simon Symhoven
13 | */
14 | public class MonitoringWorkflowJobAction implements Action {
15 |
16 | private final transient WorkflowJob workflowJob;
17 |
18 | /**
19 | * Creates a new instance of {@link MonitoringWorkflowJobAction}.
20 | *
21 | * @param workflowJob
22 | * the job that owns owns this action.
23 | */
24 | public MonitoringWorkflowJobAction(final WorkflowJob workflowJob) {
25 | this.workflowJob = workflowJob;
26 | }
27 |
28 | @Override
29 | public String getIconFileName() {
30 | return MonitoringMultibranchProjectAction.getIconSmall();
31 | }
32 |
33 | @Override
34 | public String getDisplayName() {
35 | return String.format("%s '%s'", Messages.JobAction_Name(), workflowJob.getLastBuild().getDisplayName());
36 | }
37 |
38 | @Override
39 | public String getUrlName() {
40 | return workflowJob.getLastBuild().getNumber() + "/" + MonitoringMultibranchProjectAction.getURI();
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/monitoring/MonitoringWorkflowJobActionFactory.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.monitoring;
2 |
3 | import edu.umd.cs.findbugs.annotations.NonNull;
4 | import hudson.Extension;
5 | import hudson.model.Action;
6 | import io.jenkins.plugins.monitoring.util.PullRequestUtils;
7 | import jenkins.model.TransientActionFactory;
8 | import org.jenkinsci.plugins.workflow.job.WorkflowJob;
9 |
10 | import java.util.Collection;
11 | import java.util.Collections;
12 |
13 | /**
14 | * A {@link TransientActionFactory} to add an action to specific {@link WorkflowJob}.
15 | *
16 | * @author Simon Symhoven
17 | */
18 | @Extension
19 | public class MonitoringWorkflowJobActionFactory extends TransientActionFactory {
20 |
21 | /**
22 | * Specifies the {@link Class} of the job {@link WorkflowJob} to add the action to.
23 | *
24 | * @return
25 | * the {@link Class} of job to add the action to.
26 | */
27 | @Override
28 | public Class type() {
29 | return WorkflowJob.class;
30 | }
31 |
32 | /**
33 | * Add the action to the selected {@link WorkflowJob} if its a Pull Request.
34 | *
35 | * @param workflowJob
36 | * the job to add the action to.
37 | * @return
38 | * {@link Collections} of {@link MonitoringWorkflowJobAction} if
39 | * {@link WorkflowJob} is a Pull Request, else a empty collection.
40 | */
41 | @NonNull
42 | @Override
43 | public Collection extends Action> createFor(@NonNull final WorkflowJob workflowJob) {
44 |
45 | if (workflowJob.getLastBuild() == null) {
46 | return Collections.emptyList();
47 | }
48 |
49 | if (PullRequestUtils.isPullRequest(workflowJob)) {
50 | return Collections.singletonList(new MonitoringWorkflowJobAction(workflowJob));
51 | }
52 |
53 | return Collections.emptyList();
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/monitoring/util/PortletUtils.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.monitoring.util;
2 |
3 | import edu.umd.cs.findbugs.annotations.NonNull;
4 | import hudson.ExtensionList;
5 | import hudson.model.Run;
6 | import io.jenkins.plugins.monitoring.MonitorPortlet;
7 | import io.jenkins.plugins.monitoring.MonitorPortletFactory;
8 | import org.everit.json.schema.Schema;
9 | import org.everit.json.schema.loader.SchemaLoader;
10 | import org.json.JSONArray;
11 | import org.json.JSONObject;
12 | import org.json.JSONTokener;
13 |
14 | import java.io.IOException;
15 | import java.io.InputStream;
16 | import java.util.Collection;
17 | import java.util.List;
18 | import java.util.logging.Level;
19 | import java.util.logging.Logger;
20 | import java.util.stream.Collectors;
21 |
22 | /**
23 | * A utility class for the portlets.
24 | */
25 | public final class PortletUtils {
26 | private static final Logger LOGGER = Logger.getLogger(PortletUtils.class.getName());
27 |
28 | private PortletUtils() {
29 | // make checkstyle happy.
30 | }
31 |
32 | /**
33 | * Gets all {@link MonitorPortlet} for corresponding {@link MonitorPortletFactory}.
34 | *
35 | * @param build
36 | * the reference build.
37 | *
38 | * @return
39 | * all available {@link MonitorPortlet}.
40 | */
41 | public static List extends MonitorPortlet> getAvailablePortlets(final Run, ?> build) {
42 | return getFactories()
43 | .stream()
44 | .map(factory -> factory.getPortlets(build))
45 | .flatMap(Collection::stream)
46 | .collect(Collectors.toList());
47 | }
48 |
49 | /**
50 | * Get all portlet factories, type of {@link MonitorPortletFactory}.
51 | *
52 | * @return
53 | * all factories as list.
54 | */
55 | public static List extends MonitorPortletFactory> getFactories() {
56 | return ExtensionList.lookup(MonitorPortletFactory.class);
57 | }
58 |
59 | /**
60 | * Gets all {@link MonitorPortlet} for one {@link MonitorPortletFactory}.
61 | *
62 | * @param build
63 | * the build to get the portlets for.
64 | *
65 | * @param factory
66 | * the factory to get the portlets for.
67 | *
68 | * @return
69 | * the filtered portlets.
70 | */
71 | public static List extends MonitorPortlet> getAvailablePortletsForFactory(
72 | final Run, ?> build, final MonitorPortletFactory factory) {
73 | return getFactories()
74 | .stream()
75 | .filter(fac -> fac.equals(factory))
76 | .map(fac -> fac.getPortlets(build))
77 | .flatMap(Collection::stream)
78 | .collect(Collectors.toList());
79 | }
80 |
81 | /**
82 | * Get all the default portlets as configuration.
83 | *
84 | * @param build
85 | * the build to get the portlets for.
86 | *
87 | * @return
88 | * the json array configuration as string.
89 | */
90 | public static String getDefaultPortletsAsConfiguration(Run, ?> build) {
91 | return new JSONArray(getAvailablePortlets(build)
92 | .stream()
93 | .filter(MonitorPortlet::isDefault)
94 | .map(portlet -> new JSONObject(String.format("{\"id\": \"%s\"}", portlet.getId())))
95 | .toArray())
96 | .toString();
97 | }
98 |
99 | /**
100 | * Validate the json configuration.
101 | *
102 | * @param configuration
103 | * the configuration as json
104 | *
105 | * @return
106 | * true, if the configuration is valid, else false.
107 | */
108 | public static boolean isValidConfiguration(@NonNull final String configuration) {
109 | try (InputStream schemaStream = PortletUtils.class.getResourceAsStream("/schema.json")) {
110 | JSONObject jsonSchema = new JSONObject(new JSONTokener(schemaStream));
111 | JSONArray jsonSubject = new JSONArray(configuration);
112 | Schema schema = SchemaLoader.load(jsonSchema);
113 | schema.validate(jsonSubject);
114 | return true;
115 | }
116 | catch (IOException exception) {
117 | LOGGER.log(Level.SEVERE, "Invalid Configuration found: ", exception);
118 | return false;
119 | }
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/monitoring/util/PullRequestUtils.java:
--------------------------------------------------------------------------------
1 | package io.jenkins.plugins.monitoring.util;
2 |
3 | import hudson.model.Job;
4 | import jenkins.scm.api.SCMHead;
5 | import jenkins.scm.api.mixin.ChangeRequestSCMHead2;
6 | import org.jenkinsci.plugins.workflow.multibranch.BranchJobProperty;
7 |
8 | /**
9 | * A utility class for pull requests.
10 | */
11 | public final class PullRequestUtils {
12 |
13 | private PullRequestUtils() {
14 | // make checkstyle happy.
15 | }
16 |
17 | /**
18 | * Checks whether a given {@link Job} is a pull request or not.
19 | *
20 | * @param job
21 | * the job to analyse.
22 | *
23 | * @return
24 | * true if the job is a pull request, else false.
25 | */
26 | public static boolean isPullRequest(Job, ?> job) {
27 | BranchJobProperty branchJobProperty = job.getProperty(BranchJobProperty.class);
28 |
29 | if (branchJobProperty == null) {
30 | return false;
31 | }
32 |
33 | SCMHead head = branchJobProperty.getBranch().getHead();
34 | return head instanceof ChangeRequestSCMHead2;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/resources/alerts/taglib:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/pull-request-monitoring-plugin/d602c91131a8112eef8cc9d00aee013f4443a307/src/main/resources/alerts/taglib
--------------------------------------------------------------------------------
/src/main/resources/alerts/warning.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Creates a warning if portlets are not available anymore.
6 |
7 |
8 | Owner of the page.
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
${%title}
22 |
23 |
${%description}
24 | ${unavailablePortlets}
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/src/main/resources/alerts/warning.properties:
--------------------------------------------------------------------------------
1 | title=Some portlets seem to be no longer available!
2 | description=It looks like the portlets with the following IDs are no longer available. \
3 | They have been automatically removed from the current user configuration.
--------------------------------------------------------------------------------
/src/main/resources/index.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 | This plugin offers a possibility to display and aggregate the results (in the form of individual views) of a pull
4 | request in a configurable dashboard. Views can only be accessed or displayed if the corresponding plugin fulfils
5 | certain requirements and already provides a view.
6 |