Skip to content

Commit

Permalink
Added support to graph webhook creation for both new and old projects
Browse files Browse the repository at this point in the history
  • Loading branch information
lorenzo-cavazzi committed Feb 27, 2019
1 parent b9854d1 commit c6b254b
Show file tree
Hide file tree
Showing 9 changed files with 196 additions and 13 deletions.
56 changes: 56 additions & 0 deletions src/api-client/graph.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*!
* Copyright 2019 - Swiss Data Science Center (SDSC)
* A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and
* Eidgenössische Technische Hochschule Zürich (ETHZ).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

function addGraphMethods(client) {
// using simpleFetch instead of clientFetch because we can get a 4xx response and we never have a body
// https://github.com/SwissDataScienceCenter/renku-graph/tree/master/webhook-service
client.checkGraphWebhook = (projectId) => {
const url = `${client.baseUrl}/graph/projects/${projectId}/webhooks/validation`;
return client.simpleFetch(url, 'POST').then((resp) => {
if (resp.status === 200) {
return true;
}
else if (resp.status === 404) {
return false;
}
else {
// erros expected: 401, 500
throw new Error(`Error ${resp.status}`);
}
});
}

client.createGraphWebhook = (projectId) => {
const url = `${client.baseUrl}/graph/projects/${projectId}/webhooks`;
return client.simpleFetch(url, 'POST').then((resp) => {
if (resp.status === 200 || resp.status === 201) {
return true;
}
else if (resp.status === 401) {
return false;
}
else {
// erros expected: 500
throw new Error(`Error ${resp.status}`);
}
});
}

}

export default addGraphMethods;
18 changes: 18 additions & 0 deletions src/api-client/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import addRepositoryMethods from './repository';
import addUserMethods from './user';
import addKuMethods from './ku';
import addInstanceMethods from './instance';
import addGraphMethods from './graph';

import testClient from './test-client'

Expand Down Expand Up @@ -57,6 +58,7 @@ class APIClient {
addUserMethods(this);
addKuMethods(this);
addInstanceMethods(this);
addGraphMethods(this);
}

// A fetch method which is attached to a API client instance so that it can
Expand Down Expand Up @@ -112,6 +114,22 @@ class APIClient {
})
}

// clientFetch does't handle non-2xx responses (ex: graph APIs)
// can't suppress the error on chrome console on 404 anyway...
// REF: https://stackoverflow.com/questions/4500741/suppress-chrome-failed-to-load-resource-messages-in-console
simpleFetch(
url,
method='GET'
) {
const urlObject = new URL(url);
let headers = new Headers({
'credentials': 'same-origin',
'X-Requested-With': 'XMLHttpRequest'
});

return fetch(urlObject, { headers, method });
}

doLogin() {
window.location = `${this.baseUrl}/auth/login?redirect_url=${encodeURIComponent(window.location.href)}`;
}
Expand Down
8 changes: 6 additions & 2 deletions src/api-client/project.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,16 @@ function addProjectMethods(client) {
const headers = client.getBasicHeaders();
headers.append('Content-Type', 'application/json');

let createGraphWebhookPromise;
const newProjectPromise = client.clientFetch(`${client.baseUrl}/projects`, {
method: 'POST',
headers: headers,
body: JSON.stringify(gitlabProject)
})
.then(resp => resp.data);
.then(resp => {
createGraphWebhookPromise = client.createGraphWebhook(resp.data.id);
return resp.data
});

// When the provided version does not exist, we log an error and uses latest.
// Maybe this should raise a more prominent alarm?
Expand All @@ -98,7 +102,7 @@ function addProjectMethods(client) {
return getPayload(gitlabProject.name, 'latest')
});

return Promise.all([newProjectPromise, payloadPromise])
return Promise.all([newProjectPromise, payloadPromise, createGraphWebhookPromise])
.then(([data, payload]) => {
return client.postCommit(data.id, payload).then(() => data);
});
Expand Down
2 changes: 1 addition & 1 deletion src/api-client/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ function renkuFetch(url, options) {
return Promise.reject(networkError);
})

// Raise an errror for all 200 responses.
// Raise an error for all non 200 responses.
.then((response) => {
if (response.status >= 200 && response.status < 300) {
return response;
Expand Down
5 changes: 5 additions & 0 deletions src/project/Project.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Button.alert-link {
padding: 0;
border: 0;
vertical-align: bottom;
}
21 changes: 20 additions & 1 deletion src/project/Project.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,25 @@ class View extends Component {
async fetchBranches() { return this.projectState.fetchBranches(this.props.client, this.props.id); }
async fetchCIJobs() { return this.projectState.fetchCIJobs(this.props.client, this.props.id); }
async fetchProjectFiles() { return this.projectState.fetchProjectFiles(this.props.client, this.props.id); }
async createGraphWebhook() { this.projectState.createGraphWebhook(this.props.client, this.props.id); }

async fetchAll() {
await this.fetchProject();
this.fetchCIJobs();
this.checkGraphWebhook();
}

checkGraphWebhook() {
// check if user is also owner
if (this.props.user == null || this.projectState.get('core') == null) {
this.projectState.set('core.graphWebhookPossible', false);
return
}
const userIsOwner = this.projectState.get('core.owner.id') === this.props.user.id;
this.projectState.set('core.graphWebhookPossible', userIsOwner);
if (userIsOwner) {
this.projectState.fetchGraphWebhookStatus(this.props.client, this.props.id);
}
}

getStarred(user, projectId) {
Expand Down Expand Up @@ -246,7 +261,11 @@ class View extends Component {
this.fetchProjectFiles();
this.fetchModifiedFiles();
},
fetchCIJobs: () => { this.fetchCIJobs() }
fetchCIJobs: () => { this.fetchCIJobs() },
onGraphWebhookCreate: (e) => {
e.preventDefault();
this.createGraphWebhook();
},
};

mapStateToProps(state, ownProps) {
Expand Down
65 changes: 59 additions & 6 deletions src/project/Project.present.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import ReactMarkdown from 'react-markdown'
import filesize from 'filesize';

import { Container, Row, Col } from 'reactstrap';
import { Alert, Table } from 'reactstrap';
import { Alert, UncontrolledAlert, Table } from 'reactstrap';
import { Button, Form, FormGroup, FormText, Label } from 'reactstrap';
import { Input } from 'reactstrap';
import { Nav, NavItem } from 'reactstrap';
Expand All @@ -48,6 +48,8 @@ import { ExternalLink, Loader, RenkuNavLink, TimeCaption } from '../utils/UIComp
import { SpecialPropVal } from '../model/Model'
import { ProjectTags, ProjectTagList } from './shared'

import './Project.css';

const imageBuildStatusText = {
failed: 'No notebook image has been built. You can still open a notebook server with the default image.',
canceled: 'The notebook image build has been cancelled. You can still open a notebook server with the default image',
Expand Down Expand Up @@ -134,17 +136,68 @@ class MergeRequestSuggestions extends Component {
*/
class KnowledgeGraphIntegrationWarningBanner extends Component {
render() {
const doesUserHaveRights = false;
const isProjectedIntegrated = false;
if (!doesUserHaveRights || isProjectedIntegrated) return null;
const errorKnowledgeGraph = this.props.core.graphWebhookError;
if (errorKnowledgeGraph) {
return(
<Row>
<Col xs={12} md={12}>
<UncontrolledAlert color="danger">
Some error occured with the Knowledge-graph integration.
</UncontrolledAlert>
</Col>
</Row>
)
}

const doesUserHaveRights = this.props.core.graphWebhookPossible;
if (!doesUserHaveRights) return null;
const isProjectedIntegrated = this.props.core.graphWebhookAvailable;
const created = this.props.core.graphWebhookCreated;
if (isProjectedIntegrated && !created) return null;
const loading = this.props.core.graphWebhookLoading;
return (
<Row>
<Col xs={12} md={9}>
<div>Knowledge-graph integration is not turned on.</div>
<Col xs={12} md={12}>
<KnowledgeGraphWarning
createWebhook={ this.props.onGraphWebhookCreate }
loading={ loading }
done={ isProjectedIntegrated }
/>
</Col>
</Row>
)
}
}

class KnowledgeGraphWarning extends Component {
render() {
if (this.props.done) {
return (
<UncontrolledAlert color="success">
Webhook successfully created.
</UncontrolledAlert>
)
}
return (
<UncontrolledAlert color="warning">
Knowledge-graph integration is not turned on. <KnowledgeGraphLink {...this.props}/>
</UncontrolledAlert>
)
}
}

class KnowledgeGraphLink extends Component {
render() {
if (this.props.loading) {
return (
<span>
Turning it on... <Loader size="12" inline="true" />
</span>
)
}
return (
<Button color="link alert-link" onClick={this.props.createWebhook}>Turn it on now.</Button>
)
}
}

Expand Down
28 changes: 28 additions & 0 deletions src/project/Project.state.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,34 @@ class ProjectModel extends StateModel {
super(projectSchema, stateBinding, stateHolder, initialState)
}

fetchGraphWebhookStatus(client, id) {
// temporary value to avoid fake warning.
this.set('core.graphWebhookAvailable', true);
return client.checkGraphWebhook(id)
.then((resp) => {
this.set('core.graphWebhookAvailable', resp);
})
.catch((err) => {
this.set('core.graphWebhookError', err);
});
}

createGraphWebhook(client, id) {
this.set('core.graphWebhookLoading', true);
return client.createGraphWebhook(id)
.then((resp) => {
this.set('core.graphWebhookAvailable', resp);
this.set('core.graphWebhookLoading', false);
if (resp) {
// we need this to display a positive feedback when the webhook is created
this.set('core.graphWebhookCreated', true);
}
})
.catch((err) => {
this.set('core.graphWebhookError', err);
});
}

// TODO: Do we really want to re-fetch the entire project on every change?
fetchProject(client, id) {
return client.getProject(id, {statistics:true})
Expand Down
6 changes: 3 additions & 3 deletions src/utils/UIComponents.js
Original file line number Diff line number Diff line change
Expand Up @@ -212,12 +212,12 @@ class Loader extends Component {
const borderRight = borderTop; // Added a borderRight to make a half-circle
const borderRadius = "50%";
const animation = "spin 2s linear infinite";
const left = this.props.inline ? "" : "40%", right = left;
const display = this.props.inline ? "inline-block" : "";
return <div style={{
width: d, height: d,
border, borderTop, borderRight, borderRadius, animation,
border, borderTop, borderRight, borderRadius, animation, left, right, display,
position: 'relative',
left: "40%",
top: "40%"
}}></div>
}
}
Expand Down

0 comments on commit c6b254b

Please sign in to comment.