IDEA-74652 Time tracking features. Support non one step issues - few work items may be sending.

This commit is contained in:
Evgeny Zakrevsky
2012-12-27 20:53:43 +04:00
parent 77fc4c5626
commit 39687c2fdf
9 changed files with 344 additions and 49 deletions
@@ -16,6 +16,7 @@
package com.intellij.tasks;
import com.intellij.tasks.timeTracking.model.WorkItem;
import com.intellij.util.xmlb.annotations.Attribute;
import org.jetbrains.annotations.NotNull;
@@ -50,11 +51,21 @@ public abstract class LocalTask extends Task {
public abstract void removeChangelist(final ChangeListInfo info);
public abstract long getTimeSpent();
public abstract void setTimeSpent(long time);
public abstract long getTotalTimeSpent();
public abstract boolean isRunning();
public abstract void setRunning(final boolean running);
public abstract void setWorkItems(List<WorkItem> workItems);
public abstract List<WorkItem> getWorkItems();
public abstract void addWorkItem(WorkItem workItem);
public abstract Date getLastPost();
public abstract void setLastPost(Date date);
public abstract long getTimeSpentFromLastPost();
}
+1
View File
@@ -11,6 +11,7 @@
<orderEntry type="module" module-name="vcs-api" />
<orderEntry type="library" name="http-client-3.1" level="project" />
<orderEntry type="module" module-name="platform-impl" />
<orderEntry type="module" module-name="tasks-time-tracking" />
</component>
</module>
@@ -19,6 +19,7 @@ package com.intellij.tasks.impl;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.IconLoader;
import com.intellij.tasks.*;
import com.intellij.tasks.timeTracking.model.WorkItem;
import com.intellij.util.xmlb.annotations.AbstractCollection;
import com.intellij.util.xmlb.annotations.Attribute;
import com.intellij.util.xmlb.annotations.Property;
@@ -58,8 +59,9 @@ public class LocalTaskImpl extends LocalTask {
private boolean myActive;
private List<ChangeListInfo> myChangeLists = new ArrayList<ChangeListInfo>();
private long myTimeSpent = 0;
private boolean myRunning = false;
private List<WorkItem> myWorkItems = new ArrayList<WorkItem>();
private Date myLastPost = null;
/** for serialization */
@@ -82,7 +84,7 @@ public class LocalTaskImpl extends LocalTask {
if (origin instanceof LocalTaskImpl) {
myChangeLists = ((LocalTaskImpl)origin).getChangeLists();
myActive = ((LocalTaskImpl)origin).isActive();
myTimeSpent = ((LocalTaskImpl)origin).getTimeSpent();
myWorkItems = ((LocalTaskImpl)origin).getWorkItems();
myRunning = ((LocalTaskImpl)origin).isRunning();
}
}
@@ -264,13 +266,12 @@ public class LocalTaskImpl extends LocalTask {
return myCustomIcon;
}
@Tag("timeSpent")
public long getTimeSpent() {
return myTimeSpent;
}
public void setTimeSpent(final long timeSpent) {
myTimeSpent = timeSpent;
public long getTotalTimeSpent() {
long timeSpent = 0;
for (WorkItem item : myWorkItems) {
timeSpent += item.duration;
}
return timeSpent;
}
@Tag("running")
@@ -282,4 +283,56 @@ public class LocalTaskImpl extends LocalTask {
public void setRunning(final boolean running) {
myRunning = running;
}
@Override
public void setWorkItems(final List<WorkItem> workItems) {
myWorkItems = workItems;
}
@NotNull
@Property(surroundWithTag = false)
@AbstractCollection(surroundWithTag = false, elementTag="workItem")
@Override
public List<WorkItem> getWorkItems() {
return myWorkItems;
}
@Override
public void addWorkItem(final WorkItem workItem) {
myWorkItems.add(workItem);
}
@Tag("lastWorkItemPostingDate")
@Override
public Date getLastPost() {
return myLastPost;
}
@Override
public void setLastPost(final Date date) {
myLastPost = date;
}
@Override
public long getTimeSpentFromLastPost() {
long timeSpent = 0;
if (myLastPost != null) {
for (WorkItem item : myWorkItems) {
if (item.from.getTime() < myLastPost.getTime()) {
if (item.from.getTime() + item.duration > myLastPost.getTime()) {
timeSpent += item.from.getTime() + item.duration - myLastPost.getTime();
}
}
else {
timeSpent += item.duration;
}
}
}
else {
for (WorkItem item : myWorkItems) {
timeSpent += item.duration;
}
}
return timeSpent;
}
}
+1
View File
@@ -38,6 +38,7 @@
</orderEntry>
<orderEntry type="library" name="gson" level="project" />
<orderEntry type="module" module-name="xml" />
<orderEntry type="module" module-name="tasks-time-tracking" />
</component>
</module>
@@ -0,0 +1,130 @@
<?xml version="1.0" encoding="UTF-8"?>
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="com.intellij.tasks.timeTracking.SendTimeTrackingInformationDialog">
<grid id="27dc6" binding="myPanel" layout-manager="GridLayoutManager" row-count="3" column-count="3" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<xy x="20" y="20" width="721" height="328"/>
</constraints>
<properties/>
<border type="none"/>
<children>
<component id="c5275" class="com.intellij.ui.components.JBLabel">
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="Issue:"/>
</properties>
</component>
<component id="59138" class="com.intellij.ui.components.JBLabel">
<constraints>
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="9" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="&amp;Comment:"/>
<toolTipText value=""/>
</properties>
</component>
<component id="225af" class="javax.swing.JLabel" binding="myTaskNameLabel">
<constraints>
<grid row="0" column="1" row-span="1" col-span="2" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="Label"/>
</properties>
</component>
<scrollpane id="f2f7d" class="com.intellij.ui.components.JBScrollPane">
<constraints>
<grid row="2" column="1" row-span="1" col-span="2" vsize-policy="7" hsize-policy="7" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
<border type="none"/>
<children>
<component id="e531f" class="javax.swing.JTextArea" binding="myCommentTextArea">
<constraints/>
<properties/>
</component>
</children>
</scrollpane>
<grid id="50cf9" layout-manager="GridLayoutManager" row-count="3" column-count="3" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="5" left="0" bottom="5" right="0"/>
<constraints>
<grid row="1" column="0" row-span="1" col-span="3" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="true"/>
</constraints>
<properties/>
<border type="none"/>
<children>
<component id="45822" class="javax.swing.JRadioButton" binding="myFromPreviousPostRadioButton" default-binding="true">
<constraints>
<grid row="1" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="From &amp;previous post:"/>
</properties>
</component>
<component id="a4c4" class="javax.swing.JRadioButton" binding="myCustomRadioButton" default-binding="true">
<constraints>
<grid row="2" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="Custo&amp;m:"/>
</properties>
</component>
<component id="89994" class="javax.swing.JTextField" binding="myFromPreviousPostTextField">
<constraints>
<grid row="1" column="2" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
<preferred-size width="150" height="-1"/>
</grid>
</constraints>
<properties>
<editable value="false"/>
</properties>
</component>
<component id="c9593" class="javax.swing.JTextField" binding="myCustomTextField">
<constraints>
<grid row="2" column="2" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
<preferred-size width="150" height="-1"/>
</grid>
</constraints>
<properties>
<text value="0d 0h 0m"/>
</properties>
</component>
<component id="65cc2" class="javax.swing.JRadioButton" binding="myTotallyRadioButton" default-binding="true">
<constraints>
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="Totall&amp;y:"/>
</properties>
</component>
<component id="8f5d7" class="javax.swing.JTextField" binding="myTotallyTextField">
<constraints>
<grid row="0" column="2" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
<preferred-size width="150" height="-1"/>
</grid>
</constraints>
<properties>
<editable value="false"/>
</properties>
</component>
<component id="bbd54" class="com.intellij.ui.components.JBLabel">
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="Time Spent:"/>
</properties>
</component>
</children>
</grid>
</children>
</grid>
<buttonGroups>
<group name="myGroup1">
<member id="45822"/>
<member id="65cc2"/>
<member id="a4c4"/>
</group>
</buttonGroups>
</form>
@@ -22,12 +22,10 @@ import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.ui.ValidationInfo;
import com.intellij.tasks.LocalTask;
import com.intellij.tasks.TaskRepository;
import com.intellij.ui.ScrollPaneFactory;
import com.intellij.util.ui.FormBuilder;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -41,26 +39,39 @@ public class SendTimeTrackingInformationDialog extends DialogWrapper {
@Nullable private final Project myProject;
private final LocalTask myTask;
private JTextField myTimeSpentField;
private JTextArea myCommentField;
private JRadioButton myFromPreviousPostRadioButton;
private JRadioButton myTotallyRadioButton;
private JRadioButton myCustomRadioButton;
private JTextField myFromPreviousPostTextField;
private JTextField myTotallyTextField;
private JTextField myCustomTextField;
private JTextArea myCommentTextArea;
private JPanel myPanel;
private JLabel myTaskNameLabel;
protected SendTimeTrackingInformationDialog(@Nullable final Project project, final LocalTask localTask) {
super(project);
myProject = project;
myTask = localTask;
setTitle("Time Tracking");
myTaskNameLabel.setText(myTask.getPresentableName());
myFromPreviousPostRadioButton.setSelected(true);
if (myTask.getLastPost() == null) {
myFromPreviousPostRadioButton.setVisible(false);
myFromPreviousPostTextField.setVisible(false);
myTotallyRadioButton.setSelected(true);
}
myFromPreviousPostTextField.setText(formatDuration(myTask.getTimeSpentFromLastPost()));
myTotallyTextField.setText(formatDuration(myTask.getTotalTimeSpent()));
init();
}
@Nullable
@Override
protected JComponent createCenterPanel() {
myTimeSpentField = new JTextField(String.valueOf(formatDuration(myTask.getTimeSpent())));
myCommentField = new JTextArea();
return FormBuilder.createFormBuilder()
.addComponent(new JLabel("Send information about activity on " + myTask.getPresentableName()))
.addLabeledComponent("Time spent:", myTimeSpentField, UIUtil.LARGE_VGAP)
.addLabeledComponent("Comment", ScrollPaneFactory.createScrollPane(myCommentField)).getPanel();
return myPanel;
}
private static String formatDuration(final long milliseconds) {
@@ -82,7 +93,9 @@ public class SendTimeTrackingInformationDialog extends DialogWrapper {
@Override
protected void doOKAction() {
final Matcher matcher = PATTERN.matcher(myTimeSpentField.getText());
String timeSpentText = myFromPreviousPostRadioButton.isSelected() ? myFromPreviousPostTextField.getText()
: myTotallyRadioButton.isSelected() ? myTotallyTextField.getText() : myCustomTextField.getText();
final Matcher matcher = PATTERN.matcher(timeSpentText);
if (matcher.matches()) {
final int timeSpent = Integer.valueOf(matcher.group(1)) * 24 * 60 + Integer.valueOf(matcher.group(2)) * 60 + Integer.valueOf(
matcher.group(3));
@@ -91,7 +104,8 @@ public class SendTimeTrackingInformationDialog extends DialogWrapper {
if (repository != null &&
repository.isSupported(TaskRepository.TIME_MANAGEMENT)) {
try {
repository.updateTimeSpent(myTask, timeSpent, myCommentField.getText());
repository.updateTimeSpent(myTask, timeSpent, myCommentTextArea.getText());
myTask.setLastPost(new Date());
}
catch (Exception e1) {
Messages
@@ -109,7 +123,9 @@ public class SendTimeTrackingInformationDialog extends DialogWrapper {
@Nullable
@Override
protected ValidationInfo doValidate() {
if (!PATTERN.matcher(myTimeSpentField.getText()).matches()) return new ValidationInfo("Time Spent has broken format");
String timeSpentText = myFromPreviousPostRadioButton.isSelected() ? myFromPreviousPostTextField.getText()
: myTotallyRadioButton.isSelected() ? myTotallyTextField.getText() : myCustomTextField.getText();
if (!PATTERN.matcher(timeSpentText).matches()) return new ValidationInfo("Time Spent has broken format");
return null;
}
@@ -136,26 +136,43 @@ public class TasksToolWindowPanel extends SimpleToolWindowPanel implements Dispo
});
group.add(new ModeToggleAction());
group.add(new StartStopAction());
group.add(new AnAction("Post work item to bugtracker", "Post work item to bugtracker", AllIcons.Actions.Export) {
@Override
public void actionPerformed(final AnActionEvent e) {
final LocalTask localTask = myTable.getSelectedObject();
if (localTask == null) return;
new SendTimeTrackingInformationDialog(myProject, localTask).show();
}
@Override
public void update(final AnActionEvent e) {
final LocalTask localTask = myTable.getSelectedObject();
if (localTask == null) {
e.getPresentation().setEnabled(false);
if (timeManagementExist()) {
group.add(new AnAction("Post work item to bugtracker", "Post work item to bugtracker", AllIcons.Actions.Export) {
@Override
public void actionPerformed(final AnActionEvent e) {
final LocalTask localTask = myTable.getSelectedObject();
if (localTask == null) return;
new SendTimeTrackingInformationDialog(myProject, localTask).show();
}
else {
final TaskRepository repository = localTask.getRepository();
e.getPresentation().setEnabled(repository != null && repository.isSupported(TaskRepository.TIME_MANAGEMENT));
@Override
public void update(final AnActionEvent e) {
final LocalTask localTask = myTable.getSelectedObject();
if (localTask == null) {
e.getPresentation().setEnabled(false);
}
else {
final TaskRepository repository = localTask.getRepository();
e.getPresentation().setEnabled(repository != null && repository.isSupported(TaskRepository.TIME_MANAGEMENT));
}
}
}
});
});
group.add(new ToggleAction("Show time spent from last post of work item", "Show time spent from last post of work item",
TasksIcons.Clock) {
@Override
public boolean isSelected(final AnActionEvent e) {
return myTimeTrackingManager.getState().showSpentTimeFromLastPost;
}
@Override
public void setSelected(final AnActionEvent e, final boolean state) {
myTimeTrackingManager.getState().showSpentTimeFromLastPost = state;
myTable.repaint();
}
});
}
final ActionToolbar actionToolBar = ActionManager.getInstance().createActionToolbar(ActionPlaces.UNKNOWN, group, myVertical);
return actionToolBar.getComponent();
}
@@ -166,7 +183,7 @@ public class TasksToolWindowPanel extends SimpleToolWindowPanel implements Dispo
@Override
public boolean value(final LocalTask task) {
return task.isActive() ||
(task.getTimeSpent() != 0 &&
(task.getTotalTimeSpent() != 0 &&
(myTimeTrackingManager.getState().showClosedTasks ||
!myTaskManager.isLocallyClosed(task)));
}
@@ -227,7 +244,8 @@ public class TasksToolWindowPanel extends SimpleToolWindowPanel implements Dispo
@Nullable
@Override
public String valueOf(final LocalTask task) {
long timeSpent = task.getTimeSpent();
long timeSpent =
myTimeTrackingManager.getState().showSpentTimeFromLastPost ? task.getTimeSpentFromLastPost() : task.getTotalTimeSpent();
if (task.isActive()) {
return formatDuration(timeSpent);
}
@@ -265,7 +283,11 @@ public class TasksToolWindowPanel extends SimpleToolWindowPanel implements Dispo
return new Comparator<LocalTask>() {
@Override
public int compare(final LocalTask o1, final LocalTask o2) {
return Comparing.compare(o1.getTimeSpent(), o2.getTimeSpent());
final long timeSpent1 =
myTimeTrackingManager.getState().showSpentTimeFromLastPost ? o1.getTimeSpentFromLastPost() : o1.getTotalTimeSpent();
final long timeSpent2 =
myTimeTrackingManager.getState().showSpentTimeFromLastPost ? o2.getTimeSpentFromLastPost() : o2.getTotalTimeSpent();
return Comparing.compare(timeSpent1, timeSpent2);
}
};
}
@@ -274,6 +296,15 @@ public class TasksToolWindowPanel extends SimpleToolWindowPanel implements Dispo
return new ListTableModel<LocalTask>((new ColumnInfo[]{task, spentTime}));
}
private boolean timeManagementExist() {
for (TaskRepository repository : myTaskManager.getAllRepositories()) {
if (repository.isSupported(TaskRepository.TIME_MANAGEMENT)) {
return true;
}
}
return false;
}
@Override
public void dispose() {
myTimer.stop();
@@ -8,6 +8,7 @@ import com.intellij.openapi.startup.StartupManager;
import com.intellij.openapi.wm.*;
import com.intellij.tasks.LocalTask;
import com.intellij.tasks.TaskManager;
import com.intellij.tasks.timeTracking.model.WorkItem;
import com.intellij.util.Alarm;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
@@ -15,6 +16,7 @@ import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Date;
/**
* User: Evgeny.Zakrevsky
@@ -37,6 +39,7 @@ public class TimeTrackingManager implements ProjectComponent, PersistentStateCom
private Timer myTimeTrackingTimer;
private Alarm myIdleAlarm;
private Runnable myActivityListener;
private LocalTask myLastActiveTask;
public TimeTrackingManager(Project project,
TaskManager taskManager) {
@@ -106,16 +109,22 @@ public class TimeTrackingManager implements ProjectComponent, PersistentStateCom
@Override
public void actionPerformed(final ActionEvent e) {
final LocalTask activeTask = myTaskManager.getActiveTask();
if (myLastActiveTask != activeTask) {
activeTask.addWorkItem(new WorkItem(new Date()));
}
if (getState().autoMode) {
activeTask.setTimeSpent(activeTask.getTimeSpent() + TIME_TRACKING_TIME_UNIT);
final WorkItem lastWorkItem = activeTask.getWorkItems().get(activeTask.getWorkItems().size() - 1);
lastWorkItem.duration += TIME_TRACKING_TIME_UNIT;
getState().totallyTimeSpent += TIME_TRACKING_TIME_UNIT;
}
else {
if (activeTask.isRunning()) {
activeTask.setTimeSpent(activeTask.getTimeSpent() + TIME_TRACKING_TIME_UNIT);
final WorkItem lastWorkItem = activeTask.getWorkItems().get(activeTask.getWorkItems().size() - 1);
lastWorkItem.duration += TIME_TRACKING_TIME_UNIT;
getState().totallyTimeSpent += TIME_TRACKING_TIME_UNIT;
}
}
myLastActiveTask = activeTask;
}
});
StartupManager.getInstance(myProject).registerStartupActivity(new Runnable() {
@@ -198,10 +207,11 @@ public class TimeTrackingManager implements ProjectComponent, PersistentStateCom
}
public static class Config {
public boolean enabled = true;
public boolean enabled = false;
public long totallyTimeSpent = 0;
public int suspendDelayInSeconds = 600;
public boolean autoMode = true;
public boolean showClosedTasks = true;
public boolean showSpentTimeFromLastPost = false;
}
}
@@ -0,0 +1,42 @@
/*
* Copyright 2000-2012 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.tasks.timeTracking.model;
import com.intellij.util.xmlb.annotations.Attribute;
import com.intellij.util.xmlb.annotations.Tag;
import java.util.Date;
/**
* User: evgeny.zakrevsky
* Date: 12/27/12
*/
@Tag("workItem")
public class WorkItem {
@Attribute("from")
public Date from;
@Attribute("duration")
public long duration;
/** For serialization */
@SuppressWarnings({"UnusedDeclaration"})
public WorkItem() {
}
public WorkItem(final Date from) {
this.from = from;
}
}