summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/commit/pipelines/pipelines_table.js29
-rw-r--r--app/assets/javascripts/issue_show/components/app.vue29
-rw-r--r--app/assets/javascripts/issue_show/services/index.js16
-rw-r--r--app/assets/javascripts/lib/utils/poll.js105
-rw-r--r--app/assets/javascripts/lib/utils/socket/socket_manager.js108
-rw-r--r--app/assets/javascripts/lib/utils/socket/subscription.js68
-rw-r--r--app/assets/javascripts/lib/utils/socket/subscription_store.js27
-rw-r--r--app/assets/javascripts/lib/utils/socket/visibility_socket_manager.js24
-rw-r--r--app/assets/javascripts/pipelines/components/graph/graph_component.vue22
-rw-r--r--app/assets/javascripts/pipelines/pipelines.js28
10 files changed, 243 insertions, 213 deletions
diff --git a/app/assets/javascripts/commit/pipelines/pipelines_table.js b/app/assets/javascripts/commit/pipelines/pipelines_table.js
index 98698143d22..1a9ceb5a102 100644
--- a/app/assets/javascripts/commit/pipelines/pipelines_table.js
+++ b/app/assets/javascripts/commit/pipelines/pipelines_table.js
@@ -1,5 +1,4 @@
import Vue from 'vue';
-import Visibility from 'visibilityjs';
import pipelinesTableComponent from '../../vue_shared/components/pipelines_table';
import PipelinesService from '../../pipelines/services/pipelines_service';
import PipelineStore from '../../pipelines/stores/pipelines_store';
@@ -9,7 +8,7 @@ import errorState from '../../pipelines/components/error_state.vue';
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import '../../lib/utils/common_utils';
import '../../vue_shared/vue_resource_interceptor';
-import Poll from '../../lib/utils/poll';
+import VisibilitySocketManager from '../../lib/utils/socket/visibility_socket_manager';
/**
*
@@ -90,29 +89,9 @@ export default Vue.component('pipelines-table', {
this.helpPagePath = element.dataset.helpPagePath;
this.service = new PipelinesService(this.endpoint);
- this.poll = new Poll({
- resource: this.service,
- method: 'getPipelines',
- successCallback: this.successCallback,
+ this.subscription = VisibilitySocketManager.subscribe(this.endpoint, null, {
+ updateCallback: this.successCallback,
errorCallback: this.errorCallback,
- notificationCallback: this.setIsMakingRequest,
- });
-
- if (!Visibility.hidden()) {
- this.isLoading = true;
- this.poll.makeRequest();
- } else {
- // If tab is not visible we need to make the first request so we don't show the empty
- // state without knowing if there are any pipelines
- this.fetchPipelines();
- }
-
- Visibility.change(() => {
- if (!Visibility.hidden()) {
- this.poll.restart();
- } else {
- this.poll.stop();
- }
});
eventHub.$on('refreshPipelines', this.fetchPipelines);
@@ -123,7 +102,7 @@ export default Vue.component('pipelines-table', {
},
destroyed() {
- this.poll.stop();
+ VisibilitySocketManager.remove(this.subscription);
},
methods: {
diff --git a/app/assets/javascripts/issue_show/components/app.vue b/app/assets/javascripts/issue_show/components/app.vue
index 770a0dcd27e..50853c79afd 100644
--- a/app/assets/javascripts/issue_show/components/app.vue
+++ b/app/assets/javascripts/issue_show/components/app.vue
@@ -1,7 +1,5 @@
<script>
-import Visibility from 'visibilityjs';
-import Poll from '../../lib/utils/poll';
-import Service from '../services/index';
+import VisibilitySocketManager from '../../lib/utils/socket/visibility_socket_manager';
import Store from '../stores';
import titleComponent from './title.vue';
import descriptionComponent from './description.vue';
@@ -52,29 +50,14 @@ export default {
titleComponent,
},
created() {
- const resource = new Service(this.endpoint);
- const poll = new Poll({
- resource,
- method: 'getData',
- successCallback: (res) => {
- this.store.updateState(res.json());
+ VisibilitySocketManager.subscribe(this.endpoint, null, {
+ updateCallback: (response) => {
+ this.store.updateState(response.json());
},
- errorCallback(err) {
- throw new Error(err);
+ errorCallback(error) {
+ throw new Error(error);
},
});
-
- if (!Visibility.hidden()) {
- poll.makeRequest();
- }
-
- Visibility.change(() => {
- if (!Visibility.hidden()) {
- poll.restart();
- } else {
- poll.stop();
- }
- });
},
};
</script>
diff --git a/app/assets/javascripts/issue_show/services/index.js b/app/assets/javascripts/issue_show/services/index.js
deleted file mode 100644
index 348ad8d6813..00000000000
--- a/app/assets/javascripts/issue_show/services/index.js
+++ /dev/null
@@ -1,16 +0,0 @@
-import Vue from 'vue';
-import VueResource from 'vue-resource';
-
-Vue.use(VueResource);
-
-export default class Service {
- constructor(endpoint) {
- this.endpoint = endpoint;
-
- this.resource = Vue.resource(this.endpoint);
- }
-
- getData() {
- return this.resource.get();
- }
-}
diff --git a/app/assets/javascripts/lib/utils/poll.js b/app/assets/javascripts/lib/utils/poll.js
deleted file mode 100644
index e31cc5fbabe..00000000000
--- a/app/assets/javascripts/lib/utils/poll.js
+++ /dev/null
@@ -1,105 +0,0 @@
-import httpStatusCodes from './http_status';
-
-/**
- * Polling utility for handling realtime updates.
- * Service for vue resouce and method need to be provided as props
- *
- * @example
- * new Poll({
- * resource: resource,
- * method: 'name',
- * data: {page: 1, scope: 'all'}, // optional
- * successCallback: () => {},
- * errorCallback: () => {},
- * notificationCallback: () => {}, // optional
- * }).makeRequest();
- *
- * Usage in pipelines table with visibility lib:
- *
- * const poll = new Poll({
- * resource: this.service,
- * method: 'getPipelines',
- * data: { page: pageNumber, scope },
- * successCallback: this.successCallback,
- * errorCallback: this.errorCallback,
- * notificationCallback: this.updateLoading,
- * });
- *
- * if (!Visibility.hidden()) {
- * poll.makeRequest();
- * }
- *
- * Visibility.change(() => {
- * if (!Visibility.hidden()) {
- * poll.restart();
- * } else {
- * poll.stop();
- * }
-* });
- *
- * 1. Checks for response and headers before start polling
- * 2. Interval is provided by `Poll-Interval` header.
- * 3. If `Poll-Interval` is -1, we stop polling
- * 4. If HTTP response is 200, we poll.
- * 5. If HTTP response is different from 200, we stop polling.
- *
- */
-export default class Poll {
- constructor(options = {}) {
- this.options = options;
- this.options.data = options.data || {};
- this.options.notificationCallback = options.notificationCallback ||
- function notificationCallback() {};
-
- this.intervalHeader = 'POLL-INTERVAL';
- this.timeoutID = null;
- this.canPoll = true;
- }
-
- checkConditions(response) {
- const headers = gl.utils.normalizeHeaders(response.headers);
- const pollInterval = parseInt(headers[this.intervalHeader], 10);
-
- if (pollInterval > 0 && response.status === httpStatusCodes.OK && this.canPoll) {
- this.timeoutID = setTimeout(() => {
- this.makeRequest();
- }, pollInterval);
- }
- this.options.successCallback(response);
- }
-
- makeRequest() {
- const { resource, method, data, errorCallback, notificationCallback } = this.options;
-
- // It's called everytime a new request is made. Useful to update the status.
- notificationCallback(true);
-
- return resource[method](data)
- .then((response) => {
- this.checkConditions(response);
- notificationCallback(false);
- })
- .catch((error) => {
- notificationCallback(false);
- errorCallback(error);
- });
- }
-
- /**
- * Stops the polling recursive chain
- * and guarantees if the timeout is already running it won't make another request by
- * cancelling the previously established timeout.
- */
- stop() {
- this.canPoll = false;
- clearTimeout(this.timeoutID);
- }
-
- /**
- * Restarts polling after it has been stoped
- */
- restart() {
- this.canPoll = true;
- this.makeRequest();
- }
-}
diff --git a/app/assets/javascripts/lib/utils/socket/socket_manager.js b/app/assets/javascripts/lib/utils/socket/socket_manager.js
new file mode 100644
index 00000000000..2f4f428ebee
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/socket/socket_manager.js
@@ -0,0 +1,108 @@
+import socketIO from 'socket.io-client';
+import Socket from 'socket.io-client/lib/socket';
+import Subscription from './subscription';
+import SubscriptionStore from './subscription_store';
+
+const SocketManager = {
+ socketPath: '',
+ socket: {},
+ store: SubscriptionStore,
+ subscriptionsCount: 0,
+
+ init(socketPath) {
+ this.socketPath = socketPath;
+
+ this.removeAll();
+ },
+
+ connect() {
+ if (this.socket instanceof Socket) return Promise.resolve();
+
+ return new Promise((resolve, reject) => {
+ this.socket = socketIO(this.socketPath);
+
+ this.socket.on('connect', resolve);
+ this.socket.on('connect_error', reject);
+ this.socket.on('connect_timeout', reject);
+ });
+ },
+
+ subscribe(endpointOrSubscription, data, callbacks) {
+ this.connect().then(() => {
+ const subscription = this.getSubscription(endpointOrSubscription, data, callbacks);
+
+ subscription.subscribe();
+
+ return subscription;
+ }).catch((error) => {
+ // temporary
+ // eslint-disable-next-line no-console
+ console.log('connect error', error);
+ });
+ },
+
+ remove(subscription) {
+ subscription.unsubscribe();
+
+ this.store.remove(subscription);
+ },
+
+ unsubscribeAll() {
+ const subscriptions = this.store.getAll();
+
+ if (!subscriptions) return;
+
+ subscriptions.forEach(subscription => subscription.unsubscribe());
+ },
+
+ subscribeAll() {
+ const subscriptions = this.store.getAll();
+
+ if (!subscriptions) return;
+
+ subscriptions.forEach(subscription => subscription.subscribe());
+ },
+
+ removeAll() {
+ this.unsubscribeAll();
+ this.store.removeAll();
+ },
+
+ getSubscription(endpointOrSubscription, data, callbacks) {
+ let subscription;
+
+ if (endpointOrSubscription instanceof Subscription) {
+ subscription = endpointOrSubscription;
+ } else {
+ subscription = this.createSubscription({
+ endpoint: endpointOrSubscription,
+ data,
+ callbacks,
+ });
+ }
+
+ return subscription;
+ },
+
+ createSubscription({
+ endpoint,
+ data,
+ callbacks,
+ }) {
+ this.subscriptionsCount += 1;
+
+ const subscription = new Subscription({
+ endpoint,
+ data,
+ callbacks,
+ socket: this.socket,
+ id: this.subscriptionsCount,
+ });
+
+ this.store.add(subscription);
+
+ return subscription;
+ },
+};
+
+export default SocketManager;
diff --git a/app/assets/javascripts/lib/utils/socket/subscription.js b/app/assets/javascripts/lib/utils/socket/subscription.js
new file mode 100644
index 00000000000..46373a78acb
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/socket/subscription.js
@@ -0,0 +1,68 @@
+class Subscription {
+ constructor({
+ id,
+ endpoint,
+ data,
+ socket,
+ callbacks,
+ }) {
+ this.id = id;
+ this.endpoint = endpoint;
+ this.data = data;
+ this.socket = socket;
+ this.updateCallback = callbacks.updateCallback;
+ this.errorCallback = callbacks.errorCallback;
+
+ this.setPayload();
+ this.setEventNames();
+ }
+
+ subscribe() {
+ this.socket.emit(this.eventNames.subscribe, this.payload, this.acknowledge);
+
+ this.bindListeners();
+ }
+
+ unsubscribe() {
+ this.socket.emit(this.eventNames.unsubscribe, this.payload, this.acknowledge);
+
+ this.unbindListeners();
+ }
+
+ bindListeners() {
+ this.socket.on(this.eventNames.update, this.updateCallback);
+ this.socket.on(this.eventNames.error, this.errorCallback);
+ }
+
+ unbindListeners() {
+ this.socket.removeListener(this.eventNames.update, this.updateCallback);
+ this.socket.removeListener(this.eventNames.error, this.errorCallback);
+ }
+
+ setPayload() {
+ this.payload = {
+ id: this.id,
+ endpoint: this.endpoint,
+ data: this.data,
+ };
+ }
+
+ setEventNames() {
+ this.eventNames = {
+ subscribe: `subscribe:${this.endpoint}`,
+ unsubscribe: `unsubscribe:${this.endpoint}`,
+ update: `update:${this.id}`,
+ error: `error:${this.id}`,
+ };
+ }
+
+ acknowledge(response, ...args) {
+ if (response.error) this.errorCallback(response.error, ...args);
+
+ // temporary
+ // eslint-disable-next-line no-console
+ console.log('ACK', ...args);
+ }
+}
+
+export default Subscription;
diff --git a/app/assets/javascripts/lib/utils/socket/subscription_store.js b/app/assets/javascripts/lib/utils/socket/subscription_store.js
new file mode 100644
index 00000000000..06b43da1360
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/socket/subscription_store.js
@@ -0,0 +1,27 @@
+const SocketStore = {
+ subscriptions: {},
+
+ add(subscription) {
+ this.subscriptions[subscription.id] = subscription;
+
+ return subscription;
+ },
+
+ remove(subscription) {
+ delete this.subscriptions[subscription.id];
+ },
+
+ get(subscriptionID) {
+ return this.subscriptions[subscriptionID];
+ },
+
+ getAll() {
+ Object.values(this.subscriptions);
+ },
+
+ removeAll() {
+ this.subscriptions = {};
+ },
+};
+
+export default SocketStore;
diff --git a/app/assets/javascripts/lib/utils/socket/visibility_socket_manager.js b/app/assets/javascripts/lib/utils/socket/visibility_socket_manager.js
new file mode 100644
index 00000000000..d363ae4bd44
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/socket/visibility_socket_manager.js
@@ -0,0 +1,24 @@
+import SocketManager from './socket_manager';
+
+const VisibilitySocketManager = {
+ init(socketPath) {
+ super.init(socketPath);
+
+ document.addEventListener('visibilitychange', () => this.toggleAllSockets());
+ },
+
+ toggleAllSockets() {
+ if (document.hidden) {
+ super.unsubscribeAll();
+ } else {
+ super.subscribeAll();
+ }
+ },
+};
+
+Object.setPrototypeOf(VisibilitySocketManager, SocketManager);
+
+// temporary
+VisibilitySocketManager.init('/broker');
+
+export default VisibilitySocketManager;
diff --git a/app/assets/javascripts/pipelines/components/graph/graph_component.vue b/app/assets/javascripts/pipelines/components/graph/graph_component.vue
index 14c98847d93..f04eeae71ea 100644
--- a/app/assets/javascripts/pipelines/components/graph/graph_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/graph_component.vue
@@ -1,11 +1,10 @@
<script>
/* global Flash */
- import Visibility from 'visibilityjs';
- import Poll from '../../../lib/utils/poll';
import PipelineService from '../../services/pipeline_service';
import PipelineStore from '../../stores/pipeline_store';
import stageColumnComponent from './stage_column_component.vue';
import loadingIcon from '../../../vue_shared/components/loading_icon.vue';
+ import VisibilitySocketManager from '../../../lib/utils/socket/visibility_socket_manager';
import '../../../flash';
export default {
@@ -29,25 +28,10 @@
created() {
this.service = new PipelineService(this.endpoint);
- const poll = new Poll({
- resource: this.service,
- method: 'getPipeline',
- successCallback: this.successCallback,
+ VisibilitySocketManager.subscribe(this.endpoint, null, {
+ updateCallback: this.successCallback,
errorCallback: this.errorCallback,
});
-
- if (!Visibility.hidden()) {
- this.isLoading = true;
- poll.makeRequest();
- }
-
- Visibility.change(() => {
- if (!Visibility.hidden()) {
- poll.restart();
- } else {
- poll.stop();
- }
- });
},
methods: {
diff --git a/app/assets/javascripts/pipelines/pipelines.js b/app/assets/javascripts/pipelines/pipelines.js
index d6952d1ee5f..18b91556e83 100644
--- a/app/assets/javascripts/pipelines/pipelines.js
+++ b/app/assets/javascripts/pipelines/pipelines.js
@@ -1,4 +1,3 @@
-import Visibility from 'visibilityjs';
import PipelinesService from './services/pipelines_service';
import eventHub from './event_hub';
import pipelinesTableComponent from '../vue_shared/components/pipelines_table';
@@ -8,7 +7,7 @@ import errorState from './components/error_state.vue';
import navigationTabs from './components/navigation_tabs';
import navigationControls from './components/nav_controls';
import loadingIcon from '../vue_shared/components/loading_icon.vue';
-import Poll from '../lib/utils/poll';
+import VisibilitySocketManager from '../lib/utils/socket/visibility_socket_manager';
export default {
props: {
@@ -140,30 +139,9 @@ export default {
created() {
this.service = new PipelinesService(this.endpoint);
- const poll = new Poll({
- resource: this.service,
- method: 'getPipelines',
- data: { page: this.pageParameter, scope: this.scopeParameter },
- successCallback: this.successCallback,
+ VisibilitySocketManager.subscribe(this.endpoint, null, {
+ updateCallback: this.successCallback,
errorCallback: this.errorCallback,
- notificationCallback: this.setIsMakingRequest,
- });
-
- if (!Visibility.hidden()) {
- this.isLoading = true;
- poll.makeRequest();
- } else {
- // If tab is not visible we need to make the first request so we don't show the empty
- // state without knowing if there are any pipelines
- this.fetchPipelines();
- }
-
- Visibility.change(() => {
- if (!Visibility.hidden()) {
- poll.restart();
- } else {
- poll.stop();
- }
});
eventHub.$on('refreshPipelines', this.fetchPipelines);