Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/issuable/components')
-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
8 files changed, 406 insertions, 0 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>
+ `,
+};