diff options
author | Clement Ho <ClemMakesApps@gmail.com> | 2017-04-14 16:01:25 -0500 |
---|---|---|
committer | Clement Ho <ClemMakesApps@gmail.com> | 2017-04-14 16:01:25 -0500 |
commit | f1e420f6865c57e69bb34fa6f55264d19640e11a (patch) | |
tree | 7b5b4f9e9b395f722a626ac5eac4270e5d51d100 | |
parent | 29e0b635dcf7cae982857378e93917d29dc61506 (diff) | |
download | gitlab-ce-refactor-time-tracking.tar.gz |
Refactor time trackingrefactor-time-tracking
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 = {})); |