summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorClement Ho <ClemMakesApps@gmail.com>2017-04-14 16:01:25 -0500
committerClement Ho <ClemMakesApps@gmail.com>2017-04-14 16:01:25 -0500
commitf1e420f6865c57e69bb34fa6f55264d19640e11a (patch)
tree7b5b4f9e9b395f722a626ac5eac4270e5d51d100
parent29e0b635dcf7cae982857378e93917d29dc61506 (diff)
downloadgitlab-ce-refactor-time-tracking.tar.gz
Refactor time trackingrefactor-time-tracking
-rw-r--r--app/assets/javascripts/issuable/components/time_tracking/collapsed_state.js57
-rw-r--r--app/assets/javascripts/issuable/components/time_tracking/comparison_pane.js78
-rw-r--r--app/assets/javascripts/issuable/components/time_tracking/estimate_only_pane.js15
-rw-r--r--app/assets/javascripts/issuable/components/time_tracking/help_state.js26
-rw-r--r--app/assets/javascripts/issuable/components/time_tracking/issuable_time_tracking.js59
-rw-r--r--app/assets/javascripts/issuable/components/time_tracking/no_tracking_pane.js8
-rw-r--r--app/assets/javascripts/issuable/components/time_tracking/spent_only_pane.js15
-rw-r--r--app/assets/javascripts/issuable/components/time_tracking/time_tracker.js148
-rw-r--r--app/assets/javascripts/issuable/event_hub.js3
-rw-r--r--app/assets/javascripts/issuable/issuable_bundle.js16
-rw-r--r--app/assets/javascripts/issuable/services/issuable_service.js14
-rw-r--r--app/assets/javascripts/issuable/time_tracking/components/collapsed_state.js42
-rw-r--r--app/assets/javascripts/issuable/time_tracking/components/comparison_pane.js70
-rw-r--r--app/assets/javascripts/issuable/time_tracking/components/estimate_only_pane.js14
-rw-r--r--app/assets/javascripts/issuable/time_tracking/components/help_state.js25
-rw-r--r--app/assets/javascripts/issuable/time_tracking/components/no_tracking_pane.js12
-rw-r--r--app/assets/javascripts/issuable/time_tracking/components/spent_only_pane.js14
-rw-r--r--app/assets/javascripts/issuable/time_tracking/components/time_tracker.js117
-rw-r--r--app/assets/javascripts/issuable/time_tracking/time_tracking_bundle.js66
-rw-r--r--app/assets/javascripts/main.js1
-rw-r--r--app/assets/javascripts/subbable_resource.js51
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml14
-rw-r--r--spec/javascripts/issuable_time_tracker_spec.js2
-rw-r--r--spec/javascripts/subbable_resource_spec.js63
24 files changed, 445 insertions, 485 deletions
diff --git a/app/assets/javascripts/issuable/components/time_tracking/collapsed_state.js b/app/assets/javascripts/issuable/components/time_tracking/collapsed_state.js
new file mode 100644
index 00000000000..2101a4bb056
--- /dev/null
+++ b/app/assets/javascripts/issuable/components/time_tracking/collapsed_state.js
@@ -0,0 +1,57 @@
+import stopwatchSvg from 'icons/_icon_stopwatch.svg';
+
+import '../../../lib/utils/pretty_time';
+
+export default {
+ name: 'time-tracking-collapsed-state',
+ props: {
+ showComparisonState: {
+ type: Boolean,
+ required: true,
+ },
+ showSpentOnlyState: {
+ type: Boolean,
+ required: true,
+ },
+ showEstimateOnlyState: {
+ type: Boolean,
+ required: true,
+ },
+ showNoTimeTrackingState: {
+ type: Boolean,
+ required: true,
+ },
+ timeSpentHumanReadable: {
+ type: String,
+ required: false,
+ },
+ timeEstimateHumanReadable: {
+ type: String,
+ required: false,
+ },
+ },
+ methods: {
+ abbreviateTime(timeStr) {
+ return gl.utils.prettyTime.abbreviateTime(timeStr);
+ },
+ },
+ template: `
+ <div class='sidebar-collapsed-icon'>
+ ${stopwatchSvg}
+ <div class='time-tracking-collapsed-summary'>
+ <div class='compare' v-if='showComparisonState'>
+ <span>{{ abbreviateTime(timeSpentHumanReadable) }} / {{ abbreviateTime(timeEstimateHumanReadable) }}</span>
+ </div>
+ <div class='estimate-only' v-if='showEstimateOnlyState'>
+ <span class='bold'>-- / {{ abbreviateTime(timeEstimateHumanReadable) }}</span>
+ </div>
+ <div class='spend-only' v-if='showSpentOnlyState'>
+ <span class='bold'>{{ abbreviateTime(timeSpentHumanReadable) }} / --</span>
+ </div>
+ <div class='no-tracking' v-if='showNoTimeTrackingState'>
+ <span class='no-value'>None</span>
+ </div>
+ </div>
+ </div>
+ `,
+};
diff --git a/app/assets/javascripts/issuable/components/time_tracking/comparison_pane.js b/app/assets/javascripts/issuable/components/time_tracking/comparison_pane.js
new file mode 100644
index 00000000000..564f5cadcec
--- /dev/null
+++ b/app/assets/javascripts/issuable/components/time_tracking/comparison_pane.js
@@ -0,0 +1,78 @@
+import '../../../lib/utils/pretty_time';
+
+const prettyTime = gl.utils.prettyTime;
+
+export default {
+ name: 'time-tracking-comparison-pane',
+ props: {
+ timeSpent: {
+ type: Number,
+ required: true,
+ },
+ timeEstimate: {
+ type: Number,
+ required: true,
+ },
+ timeSpentHumanReadable: {
+ type: String,
+ required: true,
+ },
+ timeEstimateHumanReadable: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ parsedRemaining() {
+ const diffSeconds = this.timeEstimate - this.timeSpent;
+ return prettyTime.parseSeconds(diffSeconds);
+ },
+ timeRemainingHumanReadable() {
+ return prettyTime.stringifyTime(this.parsedRemaining);
+ },
+ timeRemainingTooltip() {
+ const prefix = this.timeRemainingMinutes < 0 ? 'Over by' : 'Time remaining:';
+ return `${prefix} ${this.timeRemainingHumanReadable}`;
+ },
+ /* Diff values for comparison meter */
+ timeRemainingMinutes() {
+ return this.timeEstimate - this.timeSpent;
+ },
+ timeRemainingPercent() {
+ return `${Math.floor((this.timeSpent / this.timeEstimate) * 100)}%`;
+ },
+ timeRemainingStatusClass() {
+ return this.timeEstimate >= this.timeSpent ? 'within_estimate' : 'over_estimate';
+ },
+ /* Parsed time values */
+ parsedEstimate() {
+ return prettyTime.parseSeconds(this.timeEstimate);
+ },
+ parsedSpent() {
+ return prettyTime.parseSeconds(this.timeSpent);
+ },
+ },
+ template: `
+ <div class='time-tracking-comparison-pane'>
+ <div class='compare-meter' data-toggle='tooltip' data-placement='top' role='timeRemainingDisplay'
+ :aria-valuenow='timeRemainingTooltip'
+ :title='timeRemainingTooltip'
+ :data-original-title='timeRemainingTooltip'
+ :class='timeRemainingStatusClass'>
+ <div class='meter-container' role='timeSpentPercent' :aria-valuenow='timeRemainingPercent'>
+ <div :style='{ width: timeRemainingPercent }' class='meter-fill'></div>
+ </div>
+ <div class='compare-display-container'>
+ <div class='compare-display pull-left'>
+ <span class='compare-label'>Spent</span>
+ <span class='compare-value spent'>{{ timeSpentHumanReadable }}</span>
+ </div>
+ <div class='compare-display estimated pull-right'>
+ <span class='compare-label'>Est</span>
+ <span class='compare-value'>{{ timeEstimateHumanReadable }}</span>
+ </div>
+ </div>
+ </div>
+ </div>
+ `,
+};
diff --git a/app/assets/javascripts/issuable/components/time_tracking/estimate_only_pane.js b/app/assets/javascripts/issuable/components/time_tracking/estimate_only_pane.js
new file mode 100644
index 00000000000..d61fb26ed0d
--- /dev/null
+++ b/app/assets/javascripts/issuable/components/time_tracking/estimate_only_pane.js
@@ -0,0 +1,15 @@
+export default {
+ name: 'time-tracking-estimate-only-pane',
+ props: {
+ timeEstimateHumanReadable: {
+ type: String,
+ required: true,
+ },
+ },
+ template: `
+ <div class='time-tracking-estimate-only-pane'>
+ <span class='bold'>Estimated:</span>
+ {{ timeEstimateHumanReadable }}
+ </div>
+ `,
+};
diff --git a/app/assets/javascripts/issuable/components/time_tracking/help_state.js b/app/assets/javascripts/issuable/components/time_tracking/help_state.js
new file mode 100644
index 00000000000..f955337b62b
--- /dev/null
+++ b/app/assets/javascripts/issuable/components/time_tracking/help_state.js
@@ -0,0 +1,26 @@
+export default {
+ name: 'time-tracking-help-state',
+ props: {
+ docsUrl: {
+ type: String,
+ required: true,
+ },
+ },
+ template: `
+ <div class='time-tracking-help-state'>
+ <div class='time-tracking-info'>
+ <h4>Track time with slash commands</h4>
+ <p>Slash commands can be used in the issues description and comment boxes.</p>
+ <p>
+ <code>/estimate</code>
+ will update the estimated time with the latest command.
+ </p>
+ <p>
+ <code>/spend</code>
+ will update the sum of the time spent.
+ </p>
+ <a class='btn btn-default learn-more-button' :href='docsUrl'>Learn more</a>
+ </div>
+ </div>
+ `,
+};
diff --git a/app/assets/javascripts/issuable/components/time_tracking/issuable_time_tracking.js b/app/assets/javascripts/issuable/components/time_tracking/issuable_time_tracking.js
new file mode 100644
index 00000000000..62c05169666
--- /dev/null
+++ b/app/assets/javascripts/issuable/components/time_tracking/issuable_time_tracking.js
@@ -0,0 +1,59 @@
+import '~/smart_interval';
+
+import timeTracker from './time_tracker';
+import eventHub from '../../event_hub';
+
+export default {
+ el: '#issuable-time-tracker',
+ data() {
+ const selector = this.$options.el;
+ const element = document.querySelector(selector);
+
+ const docsUrl = element.dataset.docsUrl;
+
+ return {
+ issuable: {},
+ docsUrl,
+ };
+ },
+ components: {
+ 'issuable-time-tracker': timeTracker,
+ },
+ methods: {
+ fetchIssuable() {
+ eventHub.$emit('fetchIssuable');
+ },
+ updateState(data) {
+ this.issuable = data;
+ },
+ listenForSlashCommands() {
+ $(document).on('ajax:success', '.gfm-form', (e, data) => {
+ const subscribedCommands = ['spend_time', 'time_estimate'];
+ const changedCommands = data.commands_changes
+ ? Object.keys(data.commands_changes)
+ : [];
+ if (changedCommands && _.intersection(subscribedCommands, changedCommands).length) {
+ this.fetchIssuable();
+ }
+ });
+ },
+ },
+ created() {
+ eventHub.$on('receivedIssuable', data => this.updateState(data));
+ },
+ mounted() {
+ this.fetchIssuable();
+ this.listenForSlashCommands();
+ },
+ template: `
+ <div class="block">
+ <issuable-time-tracker
+ :time_estimate="issuable.time_estimate"
+ :time_spent="issuable.total_time_spent"
+ :human_time_estimate="issuable.human_time_estimate"
+ :human_time_spent="issuable.human_total_time_spent"
+ :docs-url="docsUrl"
+ />
+ </div>
+ `,
+};
diff --git a/app/assets/javascripts/issuable/components/time_tracking/no_tracking_pane.js b/app/assets/javascripts/issuable/components/time_tracking/no_tracking_pane.js
new file mode 100644
index 00000000000..79f1c5a6f27
--- /dev/null
+++ b/app/assets/javascripts/issuable/components/time_tracking/no_tracking_pane.js
@@ -0,0 +1,8 @@
+export default {
+ name: 'time-tracking-no-tracking-pane',
+ template: `
+ <div class='time-tracking-no-tracking-pane'>
+ <span class='no-value'>No estimate or time spent</span>
+ </div>
+ `,
+};
diff --git a/app/assets/javascripts/issuable/components/time_tracking/spent_only_pane.js b/app/assets/javascripts/issuable/components/time_tracking/spent_only_pane.js
new file mode 100644
index 00000000000..c71d6cd270b
--- /dev/null
+++ b/app/assets/javascripts/issuable/components/time_tracking/spent_only_pane.js
@@ -0,0 +1,15 @@
+export default {
+ name: 'time-tracking-spent-only-pane',
+ props: {
+ timeSpentHumanReadable: {
+ type: String,
+ required: true,
+ },
+ },
+ template: `
+ <div class='time-tracking-spend-only-pane'>
+ <span class='bold'>Spent:</span>
+ {{ timeSpentHumanReadable }}
+ </div>
+ `,
+};
diff --git a/app/assets/javascripts/issuable/components/time_tracking/time_tracker.js b/app/assets/javascripts/issuable/components/time_tracking/time_tracker.js
new file mode 100644
index 00000000000..57c72f258c6
--- /dev/null
+++ b/app/assets/javascripts/issuable/components/time_tracking/time_tracker.js
@@ -0,0 +1,148 @@
+import timeTrackingHelpState from './help_state';
+import timeTrackingCollapsedState from './collapsed_state';
+import timeTrackingSpentOnlyPane from './spent_only_pane';
+import timeTrackingNoTrackingPane from './no_tracking_pane';
+import timeTrackingEstimateOnlyPane from './estimate_only_pane';
+import timeTrackingComparisonPane from './comparison_pane';
+
+export default {
+ name: 'issuable-time-tracker',
+ props: {
+ time_estimate: {
+ type: Number,
+ required: true,
+ default: 0,
+ },
+ time_spent: {
+ type: Number,
+ required: true,
+ default: 0,
+ },
+ human_time_estimate: {
+ type: String,
+ required: false,
+ },
+ human_time_spent: {
+ type: String,
+ required: false,
+ },
+ docsUrl: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ showHelp: false,
+ };
+ },
+ components: {
+ 'time-tracking-collapsed-state': timeTrackingCollapsedState,
+ 'time-tracking-estimate-only-pane': timeTrackingEstimateOnlyPane,
+ 'time-tracking-spent-only-pane': timeTrackingSpentOnlyPane,
+ 'time-tracking-no-tracking-pane': timeTrackingNoTrackingPane,
+ 'time-tracking-comparison-pane': timeTrackingComparisonPane,
+ 'time-tracking-help-state': timeTrackingHelpState,
+ },
+ computed: {
+ timeSpent() {
+ return this.time_spent;
+ },
+ timeEstimate() {
+ return this.time_estimate;
+ },
+ timeEstimateHumanReadable() {
+ return this.human_time_estimate;
+ },
+ timeSpentHumanReadable() {
+ return this.human_time_spent;
+ },
+ hasTimeSpent() {
+ return !!this.timeSpent;
+ },
+ hasTimeEstimate() {
+ return !!this.timeEstimate;
+ },
+ showComparisonState() {
+ return this.hasTimeEstimate && this.hasTimeSpent;
+ },
+ showEstimateOnlyState() {
+ return this.hasTimeEstimate && !this.hasTimeSpent;
+ },
+ showSpentOnlyState() {
+ return this.hasTimeSpent && !this.hasTimeEstimate;
+ },
+ showNoTimeTrackingState() {
+ return !this.hasTimeEstimate && !this.hasTimeSpent;
+ },
+ showHelpState() {
+ return !!this.showHelp;
+ },
+ },
+ methods: {
+ toggleHelpState(show) {
+ this.showHelp = show;
+ },
+ },
+ template: `
+ <div class='time_tracker time-tracking-component-wrap' v-cloak>
+ <time-tracking-collapsed-state
+ :show-comparison-state='showComparisonState'
+ :show-no-time-tracking-state='showNoTimeTrackingState'
+ :show-help-state='showHelpState'
+ :show-spent-only-state='showSpentOnlyState'
+ :show-estimate-only-state='showEstimateOnlyState'
+ :time-spent-human-readable='timeSpentHumanReadable'
+ :time-estimate-human-readable='timeEstimateHumanReadable'
+ />
+ <div class='title hide-collapsed'>
+ Time tracking
+ <div
+ class='help-button pull-right'
+ v-if='!showHelpState'
+ @click='toggleHelpState(true)'
+ >
+ <i
+ class='fa fa-question-circle'
+ aria-hidden='true'
+ />
+ </div>
+ <div
+ class='close-help-button pull-right'
+ v-if='showHelpState'
+ @click='toggleHelpState(false)'>
+ <i
+ class='fa fa-close'
+ aria-hidden='true'
+ />
+ </div>
+ </div>
+ <div class='time-tracking-content hide-collapsed'>
+ <time-tracking-estimate-only-pane
+ v-if='showEstimateOnlyState'
+ :time-estimate-human-readable='timeEstimateHumanReadable'
+ />
+ <time-tracking-spent-only-pane
+ v-if='showSpentOnlyState'
+ :time-spent-human-readable='timeSpentHumanReadable'
+ />
+ <time-tracking-no-tracking-pane
+ v-if='showNoTimeTrackingState'
+ />
+ <time-tracking-comparison-pane
+ v-if='showComparisonState'
+ :time-estimate='timeEstimate'
+ :time-spent='timeSpent'
+ :time-spent-human-readable='timeSpentHumanReadable'
+ :time-estimate-human-readable='timeEstimateHumanReadable'
+ />
+ <transition name='help-state-toggle'>
+ <time-tracking-help-state
+ v-if='showHelpState'
+ :docs-url='docsUrl'
+ />
+ </transition>
+ </div>
+ </div>
+ `,
+};
diff --git a/app/assets/javascripts/issuable/event_hub.js b/app/assets/javascripts/issuable/event_hub.js
new file mode 100644
index 00000000000..0948c2e5352
--- /dev/null
+++ b/app/assets/javascripts/issuable/event_hub.js
@@ -0,0 +1,3 @@
+import Vue from 'vue';
+
+export default new Vue();
diff --git a/app/assets/javascripts/issuable/issuable_bundle.js b/app/assets/javascripts/issuable/issuable_bundle.js
index e927cc0077c..42630e909a4 100644
--- a/app/assets/javascripts/issuable/issuable_bundle.js
+++ b/app/assets/javascripts/issuable/issuable_bundle.js
@@ -1 +1,15 @@
-require('./time_tracking/time_tracking_bundle');
+import Vue from 'vue';
+import issuableTimeTracking from './components/time_tracking/issuable_time_tracking';
+
+import eventHub from './event_hub';
+import IssuableService from './services/issuable_service';
+
+const service = new IssuableService(gl.IssuableEndpoint);
+
+eventHub.$on('fetchIssuable', () => {
+ service.get().then((response) => {
+ eventHub.$emit('receivedIssuable', response.data);
+ });
+});
+
+document.addEventListener('DOMContentLoaded', () => new Vue(issuableTimeTracking));
diff --git a/app/assets/javascripts/issuable/services/issuable_service.js b/app/assets/javascripts/issuable/services/issuable_service.js
new file mode 100644
index 00000000000..7748bcbeea7
--- /dev/null
+++ b/app/assets/javascripts/issuable/services/issuable_service.js
@@ -0,0 +1,14 @@
+import Vue from 'vue';
+import VueResource from 'vue-resource';
+
+Vue.use(VueResource);
+
+export default class IssuableService {
+ constructor(endpoint) {
+ this.endpoint = endpoint;
+ }
+
+ get() {
+ return Vue.http.get(this.endpoint);
+ }
+}
diff --git a/app/assets/javascripts/issuable/time_tracking/components/collapsed_state.js b/app/assets/javascripts/issuable/time_tracking/components/collapsed_state.js
deleted file mode 100644
index aec13e78f42..00000000000
--- a/app/assets/javascripts/issuable/time_tracking/components/collapsed_state.js
+++ /dev/null
@@ -1,42 +0,0 @@
-import Vue from 'vue';
-import stopwatchSvg from 'icons/_icon_stopwatch.svg';
-
-require('../../../lib/utils/pretty_time');
-
-(() => {
- Vue.component('time-tracking-collapsed-state', {
- name: 'time-tracking-collapsed-state',
- props: [
- 'showComparisonState',
- 'showSpentOnlyState',
- 'showEstimateOnlyState',
- 'showNoTimeTrackingState',
- 'timeSpentHumanReadable',
- 'timeEstimateHumanReadable',
- ],
- methods: {
- abbreviateTime(timeStr) {
- return gl.utils.prettyTime.abbreviateTime(timeStr);
- },
- },
- template: `
- <div class='sidebar-collapsed-icon'>
- ${stopwatchSvg}
- <div class='time-tracking-collapsed-summary'>
- <div class='compare' v-if='showComparisonState'>
- <span>{{ abbreviateTime(timeSpentHumanReadable) }} / {{ abbreviateTime(timeEstimateHumanReadable) }}</span>
- </div>
- <div class='estimate-only' v-if='showEstimateOnlyState'>
- <span class='bold'>-- / {{ abbreviateTime(timeEstimateHumanReadable) }}</span>
- </div>
- <div class='spend-only' v-if='showSpentOnlyState'>
- <span class='bold'>{{ abbreviateTime(timeSpentHumanReadable) }} / --</span>
- </div>
- <div class='no-tracking' v-if='showNoTimeTrackingState'>
- <span class='no-value'>None</span>
- </div>
- </div>
- </div>
- `,
- });
-})();
diff --git a/app/assets/javascripts/issuable/time_tracking/components/comparison_pane.js b/app/assets/javascripts/issuable/time_tracking/components/comparison_pane.js
deleted file mode 100644
index c55e263f6f4..00000000000
--- a/app/assets/javascripts/issuable/time_tracking/components/comparison_pane.js
+++ /dev/null
@@ -1,70 +0,0 @@
-import Vue from 'vue';
-
-require('../../../lib/utils/pretty_time');
-
-(() => {
- const prettyTime = gl.utils.prettyTime;
-
- Vue.component('time-tracking-comparison-pane', {
- name: 'time-tracking-comparison-pane',
- props: [
- 'timeSpent',
- 'timeEstimate',
- 'timeSpentHumanReadable',
- 'timeEstimateHumanReadable',
- ],
- computed: {
- parsedRemaining() {
- const diffSeconds = this.timeEstimate - this.timeSpent;
- return prettyTime.parseSeconds(diffSeconds);
- },
- timeRemainingHumanReadable() {
- return prettyTime.stringifyTime(this.parsedRemaining);
- },
- timeRemainingTooltip() {
- const prefix = this.timeRemainingMinutes < 0 ? 'Over by' : 'Time remaining:';
- return `${prefix} ${this.timeRemainingHumanReadable}`;
- },
- /* Diff values for comparison meter */
- timeRemainingMinutes() {
- return this.timeEstimate - this.timeSpent;
- },
- timeRemainingPercent() {
- return `${Math.floor((this.timeSpent / this.timeEstimate) * 100)}%`;
- },
- timeRemainingStatusClass() {
- return this.timeEstimate >= this.timeSpent ? 'within_estimate' : 'over_estimate';
- },
- /* Parsed time values */
- parsedEstimate() {
- return prettyTime.parseSeconds(this.timeEstimate);
- },
- parsedSpent() {
- return prettyTime.parseSeconds(this.timeSpent);
- },
- },
- template: `
- <div class='time-tracking-comparison-pane'>
- <div class='compare-meter' data-toggle='tooltip' data-placement='top' role='timeRemainingDisplay'
- :aria-valuenow='timeRemainingTooltip'
- :title='timeRemainingTooltip'
- :data-original-title='timeRemainingTooltip'
- :class='timeRemainingStatusClass'>
- <div class='meter-container' role='timeSpentPercent' :aria-valuenow='timeRemainingPercent'>
- <div :style='{ width: timeRemainingPercent }' class='meter-fill'></div>
- </div>
- <div class='compare-display-container'>
- <div class='compare-display pull-left'>
- <span class='compare-label'>Spent</span>
- <span class='compare-value spent'>{{ timeSpentHumanReadable }}</span>
- </div>
- <div class='compare-display estimated pull-right'>
- <span class='compare-label'>Est</span>
- <span class='compare-value'>{{ timeEstimateHumanReadable }}</span>
- </div>
- </div>
- </div>
- </div>
- `,
- });
-})();
diff --git a/app/assets/javascripts/issuable/time_tracking/components/estimate_only_pane.js b/app/assets/javascripts/issuable/time_tracking/components/estimate_only_pane.js
deleted file mode 100644
index a7fbd704c40..00000000000
--- a/app/assets/javascripts/issuable/time_tracking/components/estimate_only_pane.js
+++ /dev/null
@@ -1,14 +0,0 @@
-import Vue from 'vue';
-
-(() => {
- Vue.component('time-tracking-estimate-only-pane', {
- name: 'time-tracking-estimate-only-pane',
- props: ['timeEstimateHumanReadable'],
- template: `
- <div class='time-tracking-estimate-only-pane'>
- <span class='bold'>Estimated:</span>
- {{ timeEstimateHumanReadable }}
- </div>
- `,
- });
-})();
diff --git a/app/assets/javascripts/issuable/time_tracking/components/help_state.js b/app/assets/javascripts/issuable/time_tracking/components/help_state.js
deleted file mode 100644
index 344b29ebea4..00000000000
--- a/app/assets/javascripts/issuable/time_tracking/components/help_state.js
+++ /dev/null
@@ -1,25 +0,0 @@
-import Vue from 'vue';
-
-(() => {
- Vue.component('time-tracking-help-state', {
- name: 'time-tracking-help-state',
- props: ['docsUrl'],
- template: `
- <div class='time-tracking-help-state'>
- <div class='time-tracking-info'>
- <h4>Track time with slash commands</h4>
- <p>Slash commands can be used in the issues description and comment boxes.</p>
- <p>
- <code>/estimate</code>
- will update the estimated time with the latest command.
- </p>
- <p>
- <code>/spend</code>
- will update the sum of the time spent.
- </p>
- <a class='btn btn-default learn-more-button' :href='docsUrl'>Learn more</a>
- </div>
- </div>
- `,
- });
-})();
diff --git a/app/assets/javascripts/issuable/time_tracking/components/no_tracking_pane.js b/app/assets/javascripts/issuable/time_tracking/components/no_tracking_pane.js
deleted file mode 100644
index b081adf5e64..00000000000
--- a/app/assets/javascripts/issuable/time_tracking/components/no_tracking_pane.js
+++ /dev/null
@@ -1,12 +0,0 @@
-import Vue from 'vue';
-
-(() => {
- Vue.component('time-tracking-no-tracking-pane', {
- name: 'time-tracking-no-tracking-pane',
- template: `
- <div class='time-tracking-no-tracking-pane'>
- <span class='no-value'>No estimate or time spent</span>
- </div>
- `,
- });
-})();
diff --git a/app/assets/javascripts/issuable/time_tracking/components/spent_only_pane.js b/app/assets/javascripts/issuable/time_tracking/components/spent_only_pane.js
deleted file mode 100644
index edb9169112f..00000000000
--- a/app/assets/javascripts/issuable/time_tracking/components/spent_only_pane.js
+++ /dev/null
@@ -1,14 +0,0 @@
-import Vue from 'vue';
-
-(() => {
- Vue.component('time-tracking-spent-only-pane', {
- name: 'time-tracking-spent-only-pane',
- props: ['timeSpentHumanReadable'],
- template: `
- <div class='time-tracking-spend-only-pane'>
- <span class='bold'>Spent:</span>
- {{ timeSpentHumanReadable }}
- </div>
- `,
- });
-})();
diff --git a/app/assets/javascripts/issuable/time_tracking/components/time_tracker.js b/app/assets/javascripts/issuable/time_tracking/components/time_tracker.js
deleted file mode 100644
index 0213522f551..00000000000
--- a/app/assets/javascripts/issuable/time_tracking/components/time_tracker.js
+++ /dev/null
@@ -1,117 +0,0 @@
-import Vue from 'vue';
-
-require('./help_state');
-require('./collapsed_state');
-require('./spent_only_pane');
-require('./no_tracking_pane');
-require('./estimate_only_pane');
-require('./comparison_pane');
-
-(() => {
- Vue.component('issuable-time-tracker', {
- name: 'issuable-time-tracker',
- props: [
- 'time_estimate',
- 'time_spent',
- 'human_time_estimate',
- 'human_time_spent',
- 'docsUrl',
- ],
- data() {
- return {
- showHelp: false,
- };
- },
- computed: {
- timeSpent() {
- return this.time_spent;
- },
- timeEstimate() {
- return this.time_estimate;
- },
- timeEstimateHumanReadable() {
- return this.human_time_estimate;
- },
- timeSpentHumanReadable() {
- return this.human_time_spent;
- },
- hasTimeSpent() {
- return !!this.timeSpent;
- },
- hasTimeEstimate() {
- return !!this.timeEstimate;
- },
- showComparisonState() {
- return this.hasTimeEstimate && this.hasTimeSpent;
- },
- showEstimateOnlyState() {
- return this.hasTimeEstimate && !this.hasTimeSpent;
- },
- showSpentOnlyState() {
- return this.hasTimeSpent && !this.hasTimeEstimate;
- },
- showNoTimeTrackingState() {
- return !this.hasTimeEstimate && !this.hasTimeSpent;
- },
- showHelpState() {
- return !!this.showHelp;
- },
- },
- methods: {
- toggleHelpState(show) {
- this.showHelp = show;
- },
- },
- template: `
- <div class='time_tracker time-tracking-component-wrap' v-cloak>
- <time-tracking-collapsed-state
- :show-comparison-state='showComparisonState'
- :show-help-state='showHelpState'
- :show-spent-only-state='showSpentOnlyState'
- :show-estimate-only-state='showEstimateOnlyState'
- :time-spent-human-readable='timeSpentHumanReadable'
- :time-estimate-human-readable='timeEstimateHumanReadable'>
- </time-tracking-collapsed-state>
- <div class='title hide-collapsed'>
- Time tracking
- <div class='help-button pull-right'
- v-if='!showHelpState'
- @click='toggleHelpState(true)'>
- <i class='fa fa-question-circle' aria-hidden='true'></i>
- </div>
- <div class='close-help-button pull-right'
- v-if='showHelpState'
- @click='toggleHelpState(false)'>
- <i class='fa fa-close' aria-hidden='true'></i>
- </div>
- </div>
- <div class='time-tracking-content hide-collapsed'>
- <time-tracking-estimate-only-pane
- v-if='showEstimateOnlyState'
- :time-estimate-human-readable='timeEstimateHumanReadable'>
- </time-tracking-estimate-only-pane>
- <time-tracking-spent-only-pane
- v-if='showSpentOnlyState'
- :time-spent-human-readable='timeSpentHumanReadable'>
- </time-tracking-spent-only-pane>
- <time-tracking-no-tracking-pane
- v-if='showNoTimeTrackingState'>
- </time-tracking-no-tracking-pane>
- <time-tracking-comparison-pane
- v-if='showComparisonState'
- :time-estimate='timeEstimate'
- :time-spent='timeSpent'
- :time-spent-human-readable='timeSpentHumanReadable'
- :time-estimate-human-readable='timeEstimateHumanReadable'>
- </time-tracking-comparison-pane>
- <transition name='help-state-toggle'>
- <time-tracking-help-state
- v-if='showHelpState'
- :docs-url='docsUrl'>
- </time-tracking-help-state>
- </transition>
- </div>
- </div>
- `,
- });
-})();
diff --git a/app/assets/javascripts/issuable/time_tracking/time_tracking_bundle.js b/app/assets/javascripts/issuable/time_tracking/time_tracking_bundle.js
deleted file mode 100644
index 1689a69e1ed..00000000000
--- a/app/assets/javascripts/issuable/time_tracking/time_tracking_bundle.js
+++ /dev/null
@@ -1,66 +0,0 @@
-import Vue from 'vue';
-import VueResource from 'vue-resource';
-
-require('./components/time_tracker');
-require('../../smart_interval');
-require('../../subbable_resource');
-
-Vue.use(VueResource);
-
-(() => {
- /* This Vue instance represents what will become the parent instance for the
- * sidebar. It will be responsible for managing `issuable` state and propagating
- * changes to sidebar components. We will want to create a separate service to
- * interface with the server at that point.
- */
-
- class IssuableTimeTracking {
- constructor(issuableJSON) {
- const parsedIssuable = JSON.parse(issuableJSON);
- return this.initComponent(parsedIssuable);
- }
-
- initComponent(parsedIssuable) {
- this.parentInstance = new Vue({
- el: '#issuable-time-tracker',
- data: {
- issuable: parsedIssuable,
- },
- methods: {
- fetchIssuable() {
- return gl.IssuableResource.get.call(gl.IssuableResource, {
- type: 'GET',
- url: gl.IssuableResource.endpoint,
- });
- },
- updateState(data) {
- this.issuable = data;
- },
- subscribeToUpdates() {
- gl.IssuableResource.subscribe(data => this.updateState(data));
- },
- listenForSlashCommands() {
- $(document).on('ajax:success', '.gfm-form', (e, data) => {
- const subscribedCommands = ['spend_time', 'time_estimate'];
- const changedCommands = data.commands_changes
- ? Object.keys(data.commands_changes)
- : [];
- if (changedCommands && _.intersection(subscribedCommands, changedCommands).length) {
- this.fetchIssuable();
- }
- });
- },
- },
- created() {
- this.fetchIssuable();
- },
- mounted() {
- this.subscribeToUpdates();
- this.listenForSlashCommands();
- },
- });
- }
- }
-
- gl.IssuableTimeTracking = IssuableTimeTracking;
-})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js
index c50ec24c818..8f9a4794de3 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -158,7 +158,6 @@ import './single_file_diff';
import './smart_interval';
import './snippets_list';
import './star';
-import './subbable_resource';
import './subscription';
import './subscription_select';
import './syntax_highlight';
diff --git a/app/assets/javascripts/subbable_resource.js b/app/assets/javascripts/subbable_resource.js
deleted file mode 100644
index d8191605128..00000000000
--- a/app/assets/javascripts/subbable_resource.js
+++ /dev/null
@@ -1,51 +0,0 @@
-(() => {
-/*
-* SubbableResource can be extended to provide a pubsub-style service for one-off REST
-* calls. Subscribe by passing a callback or render method you will use to handle responses.
- *
-* */
-
- class SubbableResource {
- constructor(resourcePath) {
- this.endpoint = resourcePath;
-
- // TODO: Switch to axios.create
- this.resource = $.ajax;
- this.subscribers = [];
- }
-
- subscribe(callback) {
- this.subscribers.push(callback);
- }
-
- publish(newResponse) {
- const responseCopy = _.extend({}, newResponse);
- this.subscribers.forEach((fn) => {
- fn(responseCopy);
- });
- return newResponse;
- }
-
- get(payload) {
- return this.resource(payload)
- .then(data => this.publish(data));
- }
-
- post(payload) {
- return this.resource(payload)
- .then(data => this.publish(data));
- }
-
- put(payload) {
- return this.resource(payload)
- .then(data => this.publish(data));
- }
-
- delete(payload) {
- return this.resource(payload)
- .then(data => this.publish(data));
- }
- }
-
- gl.SubbableResource = SubbableResource;
-})(window.gl || (window.gl = {}));
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index 2e0d6a129fb..0cdad58102b 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -74,12 +74,11 @@
= f.hidden_field 'milestone_id', value: issuable.milestone_id, id: nil
= dropdown_tag('Milestone', options: { title: 'Assign milestone', toggle_class: 'js-milestone-select js-extra-options', filter: true, dropdown_class: 'dropdown-menu-selectable', placeholder: 'Search milestones', data: { show_no: true, field_name: "#{issuable.to_ability_name}[milestone_id]", project_id: @project.id, issuable_id: issuable.id, milestones: namespace_project_milestones_path(@project.namespace, @project, :json), ability_name: issuable.to_ability_name, issue_update: issuable_json_path(issuable), use_id: true }})
- if issuable.has_attribute?(:time_estimate)
- #issuable-time-tracker.block
- %issuable-time-tracker{ ':time_estimate' => 'issuable.time_estimate', ':time_spent' => 'issuable.total_time_spent', ':human_time_estimate' => 'issuable.human_time_estimate', ':human_time_spent' => 'issuable.human_total_time_spent', 'docs-url' => help_page_path('workflow/time_tracking.md') }
- // Fallback while content is loading
- .title.hide-collapsed
- Time tracking
- = icon('spinner spin', 'aria-hidden': 'true')
+ #issuable-time-tracker{'data-docs-url' => help_page_path('workflow/time_tracking.md')}
+ // Fallback while content is loading
+ .title.hide-collapsed
+ Time tracking
+ = icon('spinner spin', 'aria-hidden': 'true')
- if issuable.has_attribute?(:due_date)
.block.due_date
.sidebar-collapsed-icon
@@ -169,8 +168,7 @@
= clipboard_button(text: project_ref, title: "Copy reference to clipboard", placement: "left")
:javascript
- gl.IssuableResource = new gl.SubbableResource('#{issuable_json_path(issuable)}');
- new gl.IssuableTimeTracking("#{escape_javascript(serialize_issuable(issuable))}");
+ gl.IssuableEndpoint = "#{issuable_json_path(issuable)}";
new MilestoneSelect('{"full_path":"#{@project.full_path}"}');
new LabelsSelect();
new IssuableContext('#{escape_javascript(current_user.to_json(only: [:username, :id, :name]))}');
diff --git a/spec/javascripts/issuable_time_tracker_spec.js b/spec/javascripts/issuable_time_tracker_spec.js
index 0a830f25e29..b380add1c7e 100644
--- a/spec/javascripts/issuable_time_tracker_spec.js
+++ b/spec/javascripts/issuable_time_tracker_spec.js
@@ -2,7 +2,7 @@
import Vue from 'vue';
-require('~/issuable/time_tracking/components/time_tracker');
+import '~/issuable/components/time_tracking/time_tracker';
function initTimeTrackingComponent(opts) {
setFixtures(`
diff --git a/spec/javascripts/subbable_resource_spec.js b/spec/javascripts/subbable_resource_spec.js
deleted file mode 100644
index 454386697f5..00000000000
--- a/spec/javascripts/subbable_resource_spec.js
+++ /dev/null
@@ -1,63 +0,0 @@
-/* eslint-disable max-len, arrow-parens, comma-dangle */
-
-require('~/subbable_resource');
-
-/*
-* Test that each rest verb calls the publish and subscribe function and passes the correct value back
-*
-*
-* */
-((global) => {
- describe('Subbable Resource', function () {
- describe('PubSub', function () {
- beforeEach(function () {
- this.MockResource = new global.SubbableResource('https://example.com');
- });
- it('should successfully add a single subscriber', function () {
- const callback = () => {};
- this.MockResource.subscribe(callback);
-
- expect(this.MockResource.subscribers.length).toBe(1);
- expect(this.MockResource.subscribers[0]).toBe(callback);
- });
-
- it('should successfully add multiple subscribers', function () {
- const callbackOne = () => {};
- const callbackTwo = () => {};
- const callbackThree = () => {};
-
- this.MockResource.subscribe(callbackOne);
- this.MockResource.subscribe(callbackTwo);
- this.MockResource.subscribe(callbackThree);
-
- expect(this.MockResource.subscribers.length).toBe(3);
- });
-
- it('should successfully publish an update to a single subscriber', function () {
- const state = { myprop: 1 };
-
- const callbacks = {
- one: (data) => expect(data.myprop).toBe(2),
- two: (data) => expect(data.myprop).toBe(2),
- three: (data) => expect(data.myprop).toBe(2)
- };
-
- const spyOne = spyOn(callbacks, 'one');
- const spyTwo = spyOn(callbacks, 'two');
- const spyThree = spyOn(callbacks, 'three');
-
- this.MockResource.subscribe(callbacks.one);
- this.MockResource.subscribe(callbacks.two);
- this.MockResource.subscribe(callbacks.three);
-
- state.myprop += 1;
-
- this.MockResource.publish(state);
-
- expect(spyOne).toHaveBeenCalled();
- expect(spyTwo).toHaveBeenCalled();
- expect(spyThree).toHaveBeenCalled();
- });
- });
- });
-})(window.gl || (window.gl = {}));