Skip to content

Commit 3b70723

Browse files
committed
Use AJAX to automate GitHub Webhook creation
Fixes #302
2 parents 862c341 + 50d56ef commit 3b70723

File tree

4 files changed

+205
-1
lines changed

4 files changed

+205
-1
lines changed

Source/pages/repo_update_page.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
<?php echo plugin_lang_get( 'update_repository' ) ?>
3939
</h4>
4040
<?php echo form_security_field( 'plugin_Source_repo_update' ) ?>
41-
<input type="hidden" name="repo_id" value="<?php echo $t_repo->id ?>"/>
41+
<input type="hidden" name="repo_id" id="repo_id" value="<?php echo $t_repo->id ?>"/>
4242
</div>
4343
<div class="widget-body">
4444
<div class="widget-main no-padding">

SourceGithub/SourceGithub.php

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,131 @@ public function register() {
3333

3434
public $type = 'github';
3535

36+
public function resources( $p_event ) {
37+
# Only include the javascript when it's actually needed
38+
parse_str( parse_url( $_SERVER['REQUEST_URI'], PHP_URL_QUERY ), $t_query );
39+
if( array_key_exists( 'page', $t_query ) ) {
40+
$t_page = basename( $t_query['page'] );
41+
if( $t_page == 'repo_update_page' ) {
42+
return '<script src="' . plugin_file( 'sourcegithub.js' ) . '"></script>';
43+
}
44+
}
45+
return null;
46+
}
47+
48+
public function hooks() {
49+
return parent::hooks() + array(
50+
"EVENT_LAYOUT_RESOURCES" => "resources",
51+
'EVENT_REST_API_ROUTES' => 'routes',
52+
);
53+
}
54+
55+
/**
56+
* Add the RESTful routes handled by this plugin.
57+
*
58+
* @param string $p_event_name The event name
59+
* @param array $p_event_args The event arguments
60+
* @return void
61+
*/
62+
public function routes( $p_event_name, $p_event_args ) {
63+
$t_app = $p_event_args['app'];
64+
$t_plugin = $this;
65+
$t_app->group(
66+
plugin_route_group(),
67+
function() use ( $t_app, $t_plugin ) {
68+
$t_app->post( '/{id}/webhook', [$t_plugin, 'route_webhook'] );
69+
}
70+
);
71+
}
72+
73+
/**
74+
* RESTful route to create GitHub checkin webhook
75+
*
76+
* @param Slim\Http\Request $p_request
77+
* @param Slim\Http\Response $p_response
78+
* @param array $p_args
79+
*
80+
* @return Slim\Http\Response
81+
*/
82+
public function route_webhook( $p_request, $p_response, $p_args ) {
83+
plugin_push_current( 'Source' );
84+
85+
# Make sure the given repository exists
86+
$t_repo_id = isset( $p_args['id'] ) ? $p_args['id'] : $p_request->getParam( 'id' );
87+
if( !SourceRepo::exists( $t_repo_id ) ) {
88+
return $p_response->withStatus( HTTP_STATUS_BAD_REQUEST, 'Invalid Repository Id' );
89+
}
90+
91+
# Check that the repo is of GitHub type
92+
$t_repo = SourceRepo::load( $t_repo_id );
93+
if( $t_repo->type != $this->type ) {
94+
return $p_response->withStatus( HTTP_STATUS_BAD_REQUEST, "Id $t_repo_id is not a GitHub repository" );
95+
}
96+
97+
$t_username = $t_repo->info['hub_username'];
98+
$t_reponame = $t_repo->info['hub_reponame'];
99+
100+
# GitHub webhook payload URL
101+
$t_payload_url = config_get( 'path' ) . plugin_page( 'checkin', true )
102+
. '&api_key=' . plugin_config_get( 'api_key' );
103+
104+
# Retrieve existing webhooks
105+
try {
106+
$t_github_api = new \GuzzleHttp\Client();
107+
$t_api_uri = SourceGithubPlugin::api_uri( $t_repo, "repos/$t_username/$t_reponame/hooks" );
108+
109+
$t_response = $t_github_api->get( $t_api_uri );
110+
}
111+
catch( GuzzleHttp\Exception\ClientException $e ) {
112+
return $e->getResponse();
113+
}
114+
$t_hooks = json_decode( (string) $t_response->getBody() );
115+
116+
# Determine if there is already a webhook attached to the plugin's payload URL
117+
$t_id = false;
118+
foreach( $t_hooks as $t_hook ) {
119+
if( $t_hook->name == 'web' && $t_hook->config->url == $t_payload_url ) {
120+
$t_id = $t_hook->id;
121+
break;
122+
}
123+
}
124+
125+
if( $t_id ) {
126+
# Webhook already exists for this URL
127+
# Set the Github URL so user can easily link to it
128+
$t_hook->web_url = "https://github.com/$t_username/$t_reponame/settings/hooks/" . $t_hook->id;
129+
return $p_response
130+
->withStatus( HTTP_STATUS_CONFLICT,
131+
plugin_lang_get( 'webhook_exists', 'SourceGithub' ) )
132+
->withJson( $t_hook );
133+
}
134+
135+
# Create new webhook
136+
try {
137+
$t_payload = array(
138+
'name' => 'web',
139+
'config' => array(
140+
'url' => $t_payload_url,
141+
'content_type' => 'json',
142+
'secret' => $t_repo->info['hub_webhook_secret'],
143+
)
144+
);
145+
146+
$t_github_response = $t_github_api->post( $t_api_uri,
147+
array( GuzzleHttp\RequestOptions::JSON => $t_payload )
148+
);
149+
}
150+
catch( GuzzleHttp\Exception\ClientException $e ) {
151+
return $e->getResponse();
152+
}
153+
154+
return $p_response
155+
->withStatus( HTTP_STATUS_CREATED,
156+
plugin_lang_get( 'webhook_success', 'SourceGithub' ) )
157+
->withHeader('Content-type', 'application/json')
158+
->write( $t_github_response->getBody() );
159+
}
160+
36161
public function show_type() {
37162
return plugin_lang_get( 'github' );
38163
}
@@ -161,17 +286,25 @@ public function update_repo_form( $p_repo ) {
161286
<td class="category"><?php echo plugin_lang_get( 'hub_app_access_token' ) ?></td>
162287
<td>
163288
<?php
289+
$t_hide_webhook_create = true;
290+
164291
if( empty( $t_hub_app_client_id ) || empty( $t_hub_app_secret ) ) {
165292
echo plugin_lang_get( 'hub_app_client_id_secret_missing' );
166293
} elseif( empty( $t_hub_app_access_token ) ) {
167294
print_link( $this->oauth_authorize_uri( $p_repo ), plugin_lang_get( 'hub_app_authorize' ) );
168295
} else {
296+
$t_hide_webhook_create = false;
169297
echo plugin_lang_get( 'hub_app_authorized' );
170298
# @TODO This would be better with an AJAX, but this will do for now
171299
?>
172300
<input type="submit" class="btn btn-xs btn-primary btn-white btn-round" name="revoke" value="<?php echo plugin_lang_get( 'hub_app_revoke' ) ?>"/>
173301
<?php
174302
}
303+
304+
# Only show the webhook creation div when the app has been authorized
305+
if( $t_hide_webhook_create ) {
306+
$t_webhook_create_css = ' class="hidden"';
307+
}
175308
?>
176309
</td>
177310
</tr>
@@ -180,6 +313,17 @@ public function update_repo_form( $p_repo ) {
180313
<td class="category"><?php echo plugin_lang_get( 'hub_webhook_secret' ) ?></td>
181314
<td>
182315
<input type="text" name="hub_webhook_secret" maxlength="250" size="40" value="<?php echo string_attribute( $t_hub_webhook_secret ) ?>"/>
316+
<div id="webhook_create"<?php echo $t_webhook_create_css; ?>>
317+
<div class="space-2"></div>
318+
<button type="button" class="btn btn-primary btn-white btn-round btn-sm">
319+
<?php echo plugin_lang_get( 'webhook_create' ); ?>
320+
</button>
321+
322+
<span id="webhook_status">
323+
<i class="ace-icon fa fa-lg"></i>
324+
<span></span>
325+
</span>
326+
</div>
183327
</td>
184328
</tr>
185329

SourceGithub/files/sourcegithub.js

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copyright (c) 2019 Damien Regad
2+
// Licensed under the MIT license
3+
4+
/**
5+
* Namespace for global function used in list_action.js
6+
*/
7+
var SourceGithub = SourceGithub || {};
8+
9+
/**
10+
* Return MantisBT REST API URL for given endpoint
11+
* @param {string} endpoint
12+
* @returns {string} REST API URL
13+
*/
14+
SourceGithub.rest_api = function(endpoint) {
15+
// Using the full URL (through index.php) to avoid issues on sites
16+
// where URL rewriting is not working
17+
return "api/rest/index.php/plugins/SourceGithub/" + endpoint;
18+
};
19+
20+
jQuery(document).ready(function($) {
21+
$('#webhook_create > button').click(webhook_create);
22+
23+
function webhook_create() {
24+
var repo_id = $('#repo_id').val();
25+
var status_icon = $('#webhook_status > i');
26+
var status_message = $('#webhook_status > span');
27+
28+
$.ajax({
29+
type: 'POST',
30+
url: SourceGithub.rest_api(repo_id + '/webhook'),
31+
success: function(data, textStatus, xhr) {
32+
status_icon.removeClass("fa-exclamation-triangle red").addClass("fa-check green");
33+
status_message.text(xhr.statusText);
34+
$('#webhook_create > button').prop("disabled", true);
35+
},
36+
error: function(xhr, textStatus, errorThrown) {
37+
status_icon.removeClass("fa-check green").addClass("fa-exclamation-triangle red");
38+
39+
var details = JSON.parse(xhr.responseText);
40+
if (xhr.status === 409) {
41+
status_message.html(
42+
'<a href="' + details.web_url + '">' + errorThrown + '</a>'
43+
);
44+
} else {
45+
status_message.text(errorThrown);
46+
}
47+
48+
console.error(
49+
'Webhook creation failed',
50+
{ error: errorThrown, details: details, request: this.url, x: textStatus }
51+
);
52+
}
53+
});
54+
}
55+
56+
});

SourceGithub/lang/strings_english.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ $s_plugin_SourceGithub_hub_app_authorize = 'Click to Authorize';
2121
$s_plugin_SourceGithub_hub_app_authorized = 'Authorized';
2222
$s_plugin_SourceGithub_hub_app_revoke = 'Revoke';
2323

24+
$s_plugin_SourceGithub_webhook_create = 'Create Webhook';
25+
$s_plugin_SourceGithub_webhook_success= 'Webhook created successfully';
26+
$s_plugin_SourceGithub_webhook_exists = 'Webhook already exists';
27+
2428
$s_plugin_SourceGithub_repo_authorized = '<p>MantisBT is now authorized to access this GitHub repository.</p>';
2529
$s_plugin_SourceGithub_repo_authorization_failed = '<p style="color: red;">Sorry, MantisBT could not be authorized to access this GitHub repository.</p>';
2630

0 commit comments

Comments
 (0)