Skip to content

Commit 8507b4a

Browse files
authored
Import user Roadmap (#59)
* v0.7.0 * Boilerplate for CSV roadmap import * Add style and hints * Boilerplate interaction * Wait what? * Update babel config to support split Long story short: the issue was that the system (for lack of a better word) was looking for files that were prefixed with es6... whereas my core-js files were prefixed with es. Obviously, files would not be found. I updated my packages to make sure we were running the latest versions for everything and then, since this did not work, updated my babel config as proposed here: vuejs/vue-cli#3678. And now, after 3h, I can finally split a fucking string :) * Fix display issues in chrome (unrelated) * Verify import * Ensure epic validation works * And properly notify the user * Reset error message when input is emptied * Improve styling of error message * Refactor epic status validation and clean empty li * Add some documentation and informations * Bring epic creation in app (rather than in modal) * Batch create epics! * Ensuring linter gods are pleased.
1 parent 93fdb63 commit 8507b4a

File tree

13 files changed

+172
-40
lines changed

13 files changed

+172
-40
lines changed

babel.config.js

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
module.exports = {
2-
presets: [
3-
'@vue/app'
4-
]
2+
presets: [
3+
["@vue/app", {
4+
useBuiltIns: "entry"
5+
}]
6+
]
57
}

package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "fuzz-roadmap",
3-
"version": "1.0.0",
3+
"version": "1.1.0",
44
"description": "A roadmapping tool that lets you forget about timelines",
55
"author": "Adrien D. Ahlqvist",
66
"bugs": "https://github.com/Armitage35/fuzzy-roadmap/issues",
@@ -17,7 +17,7 @@
1717
"deploy": "bash src/deploy.sh"
1818
},
1919
"dependencies": {
20-
"core-js": "^3.1.4",
20+
"core-js": "^3.2.1",
2121
"html2canvas": "^1.0.0-rc.3",
2222
"izitoast": "^1.4.0",
2323
"tippy.js": "^4.3.5",

src/App.scss

+10
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,16 @@ button {
4949
}
5050
}
5151

52+
textarea {
53+
border: $defaultBorder;
54+
resize: none;
55+
overflow: auto;
56+
width: 90%;
57+
font-family: $mainFonts;
58+
border-radius: 5px;
59+
padding: 12px;
60+
}
61+
5262
.app {
5363
display: flex;
5464
}

src/App.vue

+23-2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
@createEpic="createEpic($event)"
1010
@deleteEpic="deleteEpic($event)"
1111
@deleteRoadmap="resetRoadmap"
12+
@importRoadmap="batchAddEpics($event)"
1213
@toggleModal="toggleModal($event)"
1314
@updateEpic="updateEpic($event)"
1415
@updateSettings="updateSettings($event)"
@@ -26,6 +27,7 @@
2627
<Toolbar
2728
@toggleModal="toggleModal($event)"
2829
@exportRoadmap="toggleModal($event)"
30+
@importRoadmap="toggleModal($event)"
2931
@openResetRoadmapModal="toggleModal($event)"></Toolbar>
3032
</div>
3133
</template>
@@ -174,7 +176,22 @@
174176
saveRoadmapInClient() {
175177
localStorage.setItem('roadmap', JSON.stringify(this.userEpics));
176178
},
177-
createEpic(newEpic) {
179+
createEpic(epicData) {
180+
let newEpic = {
181+
epicName: {
182+
displayName: epicData[0],
183+
fullName: epicData[0],
184+
},
185+
status: epicData[1],
186+
creationDate: new Date(),
187+
order: 1,
188+
resolution: {
189+
resolved: epicData[1] === 'done' ? true : false,
190+
resolutionDate: epicData[1] === 'done' ? new Date() : null,
191+
},
192+
author: this.userDetails.userName
193+
}
194+
178195
this.userEpics.unshift(newEpic);
179196
this.toggleModal();
180197
@@ -185,7 +202,11 @@
185202
message: 'You are getting the hang of this',
186203
position: "topRight"
187204
});
188-
205+
},
206+
batchAddEpics (batch) {
207+
for (let i = 0; i < batch.length; i++) {
208+
this.createEpic(batch[i]);
209+
}
189210
}
190211
},
191212
computed: {

src/components/Modal/CreateEpicModal/CreateEpicModal.vue

+2-26
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@
2525
</div>
2626
</div>
2727
<div class="modal-actions">
28-
<button type="button" class="bttn-secondary" @click="closeModal">Cancel changes</button>
29-
<button type="button" class="bttn-primary" @click="saveEpic">Save</button>
28+
<button type="button" class="bttn-secondary" @click="$emit('toggleModal', '');">Cancel changes</button>
29+
<button type="button" class="bttn-primary" @click="$emit('createEpic', [epicName, epicStatus])">Save</button>
3030
</div>
3131
</div>
3232
</template>
@@ -39,30 +39,6 @@
3939
epicName: "",
4040
epicStatus: "inProgress"
4141
}
42-
},
43-
methods: {
44-
closeModal() {
45-
this.$emit('toggleModal', "");
46-
},
47-
saveEpic() {
48-
let newEpic = {
49-
epicName: {
50-
displayName: this.epicName,
51-
fullName: this.epicName,
52-
},
53-
status: this.epicStatus,
54-
creationDate: new Date(),
55-
order: 1,
56-
resolution: {
57-
resolved: this.epicStatus === 'done' ? true : false,
58-
resolutionDate: this.epicStatus === 'done' ? new Date() : null,
59-
},
60-
author: this.author
61-
}
62-
63-
this.$emit('createEpic', newEpic);
64-
65-
}
6642
}
6743
}
6844
</script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
.modal-import-hint {
2+
font-style: italic;
3+
text-transform: none;
4+
color: $lightGrey;
5+
font-size: 14px;
6+
margin: 12px 0;
7+
width: 90%;
8+
}
9+
10+
.modal-import-error {
11+
color: $red;
12+
font-weight: bold;
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
<template>
2+
<div class="modal-content">
3+
<div class="modal-data">
4+
<label for="epicContent">Paste your roadmap here</label>
5+
<br/>
6+
<textarea name="roadmap" placeholder="Epic name... Epic status..." wrap="off" cols="30" rows="5" spellcheck="false" v-model="epicsImport"></textarea>
7+
<div class="modal-import-error" v-if="importError.status">{{ importError.message }}</div>
8+
<div class="modal-import-hint">Hint: paste your content from a .CSV (comma separated) file to add them to your current roadmap. We will only import your epics names and statuses. The accepted statuses can be either 'inProgress', 'soon', 'later' or 'done', anything else will be rejected.</div>
9+
</div>
10+
<div class="modal-actions">
11+
<button type="button" class="bttn-secondary" @click="$emit('toggleModal')">Close</button>
12+
<button type="button" class="bttn-primary" @click="runVerification">Launch import</button>
13+
</div>
14+
</div>
15+
</template>
16+
17+
<script>
18+
export default {
19+
data: function() {
20+
return {
21+
epicsImport: '', // controlled by user input. Consumed by importedRoadmap
22+
cleanedUpImport: [], // finished import
23+
importError: {
24+
status: false,
25+
message: ''
26+
}
27+
}
28+
},
29+
methods: {
30+
runVerification() {
31+
this.cleanImport();
32+
33+
if (this.isRoadmapEven() && this.verifyEpicStatuses()) {
34+
this.importRoadmap();
35+
} else {
36+
return this.importError.message
37+
}
38+
},
39+
importRoadmap() {
40+
this.$emit('importRoadmap', this.cleanedUpImport);
41+
},
42+
isRoadmapEven() {
43+
if (Number.isInteger(this.importedRoadmap.length / 2)) {
44+
return true
45+
} else {
46+
this.importError.status = true;
47+
this.importError.message = 'Some of your epics seem to be missing a status';
48+
return false
49+
}
50+
},
51+
verifyEpicStatuses() {
52+
let statuses = true;
53+
let validStatuses = ['inProgress', 'soon', 'later', 'done'];
54+
55+
// We iterate two by two because we want to check the statuses, not each element
56+
for (let i = 0; i < this.importedRoadmap.length; i += 2) {
57+
if (validStatuses.includes(this.importedRoadmap[i + 1])){
58+
statuses = true;
59+
} else {
60+
this.importError.status = true;
61+
this.importError.message = 'Some of your statuses are incorrect';
62+
statuses = false;
63+
}
64+
}
65+
return statuses;
66+
},
67+
cleanImport() {
68+
// removing empty lines / sections in array
69+
for (let i = 0; i < this.importedRoadmap.length; i++) {
70+
if (this.importedRoadmap[i] == '') {
71+
this.importedRoadmap.splice(i, 1);
72+
}
73+
}
74+
75+
// making the long array into many smaller arrays of pairs
76+
for (let i = 0; i < this.importedRoadmap.length; i += 2) {
77+
this.cleanedUpImport.push([this.importedRoadmap[i], this.importedRoadmap[i +1]])
78+
}
79+
}
80+
},
81+
computed: {
82+
importedRoadmap() {
83+
// transforms the epic import string into an array
84+
return this.epicsImport.replace(/\n/g, ', ').split(', ');
85+
}
86+
},
87+
watch: {
88+
epicsImport: function () {
89+
if (this.epicsImport === ''){
90+
this.importError.status = false;
91+
this.importError.message = '';
92+
}
93+
}
94+
}
95+
}
96+
</script>

src/components/Modal/Modal.vue

+8
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@
2626
@deleteEpic="deleteEpic($event)"
2727
@updateEpic="updateEpic($event)"
2828
></EpicDetailsModal>
29+
<ImportRoadmapModal
30+
v-if="modalType === 'importRoadmap'"
31+
@importRoadmap="$emit('importRoadmap', $event)"
32+
></ImportRoadmapModal>
2933
<ExportRoadmapModal
3034
v-if="modalType === 'exportRoadmap'"
3135
:epics="epics"
@@ -44,6 +48,7 @@
4448
import CreateEpicModal from './CreateEpicModal/CreateEpicModal.vue';
4549
import EpicDetailsModal from './EpicDetailsModal/EpicDetailsModal.vue';
4650
import ExportRoadmapModal from './ExportRoadmapModal/ExportRoadmapModal'
51+
import ImportRoadmapModal from './ImportRoadmapModal/ImportRoadmapModal.vue'
4752
import ResetRoadmapModal from './ResetRoadmapModal/ResetRoadmapModal.vue';
4853
import SettingsModal from './SettingsModal/SettingsModal.vue';
4954
@@ -53,6 +58,7 @@
5358
CreateEpicModal,
5459
EpicDetailsModal,
5560
ExportRoadmapModal,
61+
ImportRoadmapModal,
5662
ResetRoadmapModal,
5763
SettingsModal
5864
},
@@ -69,6 +75,8 @@
6975
return 'Epic details';
7076
case 'exportRoadmap':
7177
return 'Export roadmap';
78+
case 'importRoadmap':
79+
return 'Import roadmap';
7280
default:
7381
return 'Hum, this is rather embarassing...'
7482
}

src/components/Toolbar/Toolbar.scss

+2-2
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ $toolbarPadding: 25px;
3939
.toolbar-roadmapName {
4040
@extend %toolBarContent;
4141
padding-left: $toolbarPadding;
42-
justify-content: left;
42+
justify-content: flex-start;
4343
cursor: pointer;
4444
margin: auto;
4545
text-transform: none;
@@ -61,6 +61,6 @@ $toolbarPadding: 25px;
6161
.toolbar-advancedFeatures {
6262
@extend %toolBarContent;
6363
margin: auto;
64-
justify-content: right;
64+
justify-content: flex-end;
6565
padding-right: $toolbarPadding;
6666
}

src/components/Toolbar/Toolbar.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
@click="$emit('exportRoadmap', 'exportRoadmap')"></i>
2323
<i class="fas fa-upload"
2424
data-tippy="Import roadmap"
25-
@click="featureNotReady"></i>
25+
@click="$emit('importRoadmap', 'importRoadmap')"></i>
2626
<i class="fas fa-trash"
2727
data-tippy="Reset roadmap"
2828
@click="$emit('openResetRoadmapModal', 'resetRoadmapModal')"></i>

src/master.scss

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
@import './components/Modal/Modal.scss';
88
@import './components/Modal/SettingsModal/SettingsModal.scss';
99
@import './components/Modal/EpicDetailsModal/EpicDetailsModal.scss';
10+
@import './components/Modal/ImportRoadmapModal/ImportRoadmapModal.scss';
1011
@import './components/Modal/ResetRoadmapModal/ResetRoadmapModal.scss';
1112
@import './components/Modal/ExportRoadmapModal/ExportRoadmapModal.scss';
1213

src/utilities/testImport.csv

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
test 1, done
2+
test 2, inProgress
3+
test 3, later
4+
test 4, soon
5+
test 5, done

yarn.lock

+4-4
Original file line numberDiff line numberDiff line change
@@ -2422,10 +2422,10 @@ core-js@^2.6.5:
24222422
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.9.tgz#6b4b214620c834152e179323727fc19741b084f2"
24232423
integrity sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A==
24242424

2425-
core-js@^3.1.4:
2426-
version "3.1.4"
2427-
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.1.4.tgz#3a2837fc48e582e1ae25907afcd6cf03b0cc7a07"
2428-
integrity sha512-YNZN8lt82XIMLnLirj9MhKDFZHalwzzrL9YLt6eb0T5D0EDl4IQ90IGkua8mHbnxNrkj1d8hbdizMc0Qmg1WnQ==
2425+
core-js@^3.2.1:
2426+
version "3.2.1"
2427+
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.2.1.tgz#cd41f38534da6cc59f7db050fe67307de9868b09"
2428+
integrity sha512-Qa5XSVefSVPRxy2XfUC13WbvqkxhkwB3ve+pgCQveNgYzbM/UxZeu1dcOX/xr4UmfUd+muuvsaxilQzCyUurMw==
24292429

24302430
[email protected], core-util-is@~1.0.0:
24312431
version "1.0.2"

0 commit comments

Comments
 (0)