Skip to content

Commit

Permalink
Merge pull request #37 from Eerovil/feature/generate
Browse files Browse the repository at this point in the history
Feature/generate
  • Loading branch information
Eerovil committed Mar 10, 2021
2 parents e6a5328 + e4b9efe commit 1e77d7e
Show file tree
Hide file tree
Showing 17 changed files with 494 additions and 206 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
1.5.0

- Creating new events is improved:
- They "snap" to each other on create
- Project is automatically set
- Issue is automatically set if commit begins with "issue slug"
- The new entry automatically spans all time entries

- Switched to a week view with buttons to switch weeks


1.4.0

- Make the page responsive for mobile
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
Flask==1.0.3
GitPython==2.1.11
gitdb2==3.0.1
python-dateutil==2.8.0
slackclient==2.0.1
pytz==2019.1
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

setup(
name='tracklater',
version='1.4.0',
version='1.5.0',
description=('TrackLater helps you track time after-the-fact by combining clues and showing'
'your day on a simple timeline view.'),
long_description=long_description,
Expand Down
10 changes: 8 additions & 2 deletions tracklater/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ def store_parser_to_database(parser, module_name, start_date, end_date):
for issue in parser.issues:
issue.module = module_name
db.session.merge(issue)
Project.query.delete()
for project in parser.projects:
project.module = module_name
db.session.merge(project)
Expand Down Expand Up @@ -68,10 +67,17 @@ def __init__(self, start_date, end_date, modules=None) -> None:
self.modules[module_name] = parser

def parse(self) -> None:
parsers = []
group_to_project = {project.group: project.pid for project in Project.query.all()}
for module_name, parser in self.modules.items():
set_parser_caching_data(parser, module_name)
parser.parse()
logger.warning("Parsing %s", module_name)
parser.parse()
parsers.append((module_name, parser))
for entry in parser.entries:
new_project = group_to_project.get(entry.group, None)
if new_project:
entry.project = new_project
store_parser_to_database(self.modules[module_name], module_name,
start_date=self.start_date, end_date=self.end_date)
logger.warning("Task done %s", module_name)
3 changes: 2 additions & 1 deletion tracklater/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,5 +94,6 @@ def to_dict(self):
"title": self.title,
"text": self.text,
"extra_data": self.extra_data,
"duration": self.duration
"duration": self.duration,
"group": self.group
}
5 changes: 4 additions & 1 deletion tracklater/settings_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ class Dummy(object):
settings_wrapper: Any = Dummy()

if not os.path.exists(user_config_path):
os.mkdir(appdirs.user_config_dir())
try:
os.mkdir(appdirs.user_config_dir())
except FileExistsError:
pass
shutil.copy(os.path.join(DIRECTORY, 'example_settings.json'), user_config_path)
logger.error("No user settings file! Modify the example settings created (path: %s)",
user_config_path)
Expand Down
173 changes: 168 additions & 5 deletions tracklater/static/daytimeline.vue.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
var daytimeline = Vue.component("daytimeline", {
template: `
<div>
<vuetimeline ref="timeline"
:items="items"
:groups="groups"
Expand All @@ -8,6 +9,7 @@ var daytimeline = Vue.component("daytimeline", {
:selection=selection
@select="select">
</vuetimeline>
</div>
`,
props: ["entries"],
data() {
Expand Down Expand Up @@ -52,13 +54,22 @@ var daytimeline = Vue.component("daytimeline", {
},
onAdd: function(item, callback) {
if (this.modules[item.group].capabilities.includes('addentry')) {
let timeSnippet = this.generateTimeSnippet(item.start, item.group);
if (!timeSnippet) {
return;
}
let entry = {
start_time: item.start.addHours(-0.5),
end_time: item.start.addHours(0.5),
start_time: timeSnippet.start_time,
end_time: timeSnippet.end_time,
title: "Unnamed Entry",
module: item.group,
project: ''
}
let detectedIssue = this.detectIssue(timeSnippet);
if (detectedIssue) {
entry.title = detectedIssue.message;
entry.project = detectedIssue.project;
}
this.$emit('addEntry', entry)
}
},
Expand All @@ -76,13 +87,18 @@ var daytimeline = Vue.component("daytimeline", {
remove: this.modules[entry.module].capabilities.includes('deleteentry')
},
}
let colorObj = this.modules[entry.module].color;
color = colorObj[entry.group] || colorObj.global;
if (entry.end_time != undefined) {
row.end = new Date(entry.end_time);
if (this.modules[entry.module].color != null) {
row.style = `background-color: ${this.modules[entry.module].color}`
if (color != null) {
row.style = `background-color: ${color}`
}
} else {
row.type = 'point'
if (color != null) {
row.className += ` point-color-${color}`
}
}
return row
});
Expand Down Expand Up @@ -123,7 +139,150 @@ var daytimeline = Vue.component("daytimeline", {
}
}
return true;
}
},
generateTimeSnippet(middle_time, activeModule) {
// Go backwards and forwards unit "not much" is happening, and return the
// start and end time. If nothing is happening, return an hour.
const cutoffSeconds = 400;
let ret = {
start_time: middle_time.addHours(-0.5),
end_time: middle_time.addHours(0.5),
}
const sorted = this.entries.slice().filter(i => this.timeEntryModules.includes(i.module)).sort((a, b) => {
if (new Date(a.start_time) > new Date(b.start_time)) {
return 1;
}
if (new Date(a.start_time) < new Date(b.start_time)) {
return -1;
}
return 0;
}).map(i => {
i.start_time = new Date(i.start_time)
i.end_time = new Date(i.end_time)
return i
})
function parseRet(_ret) {
// Update ret to fix overlapping issues
for (el of sorted) {
if (el.module !== activeModule) {
continue;
}
// If any toggl entry starts or ends between ret times, change ret.
if (el.start_time < _ret.end_time && el.start_time > _ret.start_time) {
_ret.end_time = el.start_time;
}
if (el.end_time < _ret.end_time && el.end_time > _ret.start_time) {
_ret.start_time = el.end_time;
}
}
if (_ret.start_time >= _ret.end_time) {
return
}
return _ret
}
console.log('sorted: ', sorted);
console.log('middle_time: ', middle_time);
if (sorted.length == 0) {
return parseRet(ret);
}
// Special case: first thyme entry is after middle_time. Not good
if (sorted[0].start_time > middle_time) {
return parseRet(ret);
}
// Special case: last thyme entry is before middle_time. Not good
if (sorted[sorted.length - 1].end_time < middle_time) {
return parseRet(ret);
}
// Find the middle thyme entry
let middleIndex;
for (let i in sorted) {
if (sorted[i].end_time > middle_time) {
middleIndex = i;
break;
}
}
// Middle item is too far
if (sorted[middleIndex].start_time.getTime() - middle_time.getTime() > cutoffSeconds * 1000) {
return parseRet(ret);
}
console.log('middleIndex: ', middleIndex);
if (!middleIndex) {
return parseRet(ret);
}
// Go back
let prevTime = sorted[middleIndex].start_time
for (let i=middleIndex; i>=0; i--) {
if (prevTime.getTime() - sorted[i].end_time.getTime() > cutoffSeconds * 1000) {
ret.start_time = prevTime.addHours(-0.2);
break;
}
if (sorted[i].module == activeModule) {
// We reached another toggl entry! Return its end_time here for no overlap
ret.start_time = sorted[i].end_time
break;
}
prevTime = sorted[i].start_time
}
// Go forward
prevTime = sorted[middleIndex].end_time
for (let i=middleIndex; i<sorted.length; i++) {
if (sorted[i].start_time.getTime() - prevTime.getTime() > cutoffSeconds * 1000) {
ret.end_time = prevTime.addHours(0.2);
break;
}
if (sorted[i].module == activeModule) {
// We reached another toggl entry! Return its start_time here for no overlap
ret.end_time = sorted[i].start_time
break;
}
prevTime = sorted[i].end_time
}
return parseRet(ret);
},
detectIssue(timeSnippet) {
const entries = this.entries.slice()
.filter(i => ["gitmodule"]
.includes(i.module))
.filter(i => (new Date(i.start_time) < timeSnippet.end_time && new Date(i.start_time) > timeSnippet.start_time))
.sort((a, b) => {
if (new Date(a.start_time) > new Date(b.start_time)) {
return 1;
}
if (new Date(a.start_time) < new Date(b.start_time)) {
return -1;
}
return 0;
})
.reverse()
if (entries.length == 0) {
return null
}
let ret = {
group: entries[0].group
}
let issueFound = false;
entries.forEach(entry => {
if (issueFound) {
return;
}
// Try to parse issue
let issueMatch = entry.text.match(/^\w* - ([^ ]+)(.*)/)
if (issueMatch) {
let issueSlug = issueMatch[1]
let issue = this.$store.getters.findIssueByKey(issueSlug);
if (issue) {
ret.group = issue.group;
ret.message = issue.key + " " + issue.title;
issueFound = true;
} else {
ret.message = issueMatch[1] + issueMatch[2];
ret.group = entry.group;
}
}
});
ret.project = this.$store.getters.getProjectId(ret.group);
return ret
},
},
watch: {
entries(entries, oldEntries) {
Expand All @@ -149,6 +308,9 @@ var daytimeline = Vue.component("daytimeline", {
modules() {
return this.$store.state.modules;
},
timeEntryModules() {
return Object.keys(this.modules).filter(key => this.modules[key].capabilities.includes("entries"));
},
groups() {
ret = []
for (let module_name in this.modules) {
Expand All @@ -172,6 +334,7 @@ var daytimeline = Vue.component("daytimeline", {
end: day_end,
editable: true,
zoomable: (screen.width < 960),
showCurrentTime: false,
horizontalScroll: false,
moveable: true,
margin: {
Expand Down

0 comments on commit 1e77d7e

Please sign in to comment.