From 39687c2fdf1b8f9feee3e389047c4a8a3464cb67 Mon Sep 17 00:00:00 2001 From: Evgeny Zakrevsky Date: Thu, 27 Dec 2012 19:23:11 +0400 Subject: [PATCH] IDEA-74652 Time tracking features. Support non one step issues - few work items may be sending. --- .../src/com/intellij/tasks/LocalTask.java | 17 ++- plugins/tasks/tasks-api/tasks-api.iml | 1 + .../intellij/tasks/impl/LocalTaskImpl.java | 71 ++++++++-- plugins/tasks/tasks-core/tasks-core.iml | 1 + .../SendTimeTrackingInformationDialog.form | 130 ++++++++++++++++++ .../SendTimeTrackingInformationDialog.java | 44 ++++-- .../timeTracking/TasksToolWindowPanel.java | 71 +++++++--- .../timeTracking/TimeTrackingManager.java | 16 ++- .../tasks/timeTracking/model/WorkItem.java | 42 ++++++ 9 files changed, 344 insertions(+), 49 deletions(-) create mode 100644 plugins/tasks/tasks-time-tracking/src/com/intellij/tasks/timeTracking/SendTimeTrackingInformationDialog.form create mode 100644 plugins/tasks/tasks-time-tracking/src/com/intellij/tasks/timeTracking/model/WorkItem.java diff --git a/plugins/tasks/tasks-api/src/com/intellij/tasks/LocalTask.java b/plugins/tasks/tasks-api/src/com/intellij/tasks/LocalTask.java index f40aafa247b9..392a29628875 100644 --- a/plugins/tasks/tasks-api/src/com/intellij/tasks/LocalTask.java +++ b/plugins/tasks/tasks-api/src/com/intellij/tasks/LocalTask.java @@ -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 workItems); + + public abstract List getWorkItems(); + + public abstract void addWorkItem(WorkItem workItem); + + public abstract Date getLastPost(); + + public abstract void setLastPost(Date date); + + public abstract long getTimeSpentFromLastPost(); } diff --git a/plugins/tasks/tasks-api/tasks-api.iml b/plugins/tasks/tasks-api/tasks-api.iml index d2b16e29fcaa..7aa9d4c5a91e 100644 --- a/plugins/tasks/tasks-api/tasks-api.iml +++ b/plugins/tasks/tasks-api/tasks-api.iml @@ -11,6 +11,7 @@ + diff --git a/plugins/tasks/tasks-core/src/com/intellij/tasks/impl/LocalTaskImpl.java b/plugins/tasks/tasks-core/src/com/intellij/tasks/impl/LocalTaskImpl.java index 04fbb1c3ac9f..bf67baa320db 100644 --- a/plugins/tasks/tasks-core/src/com/intellij/tasks/impl/LocalTaskImpl.java +++ b/plugins/tasks/tasks-core/src/com/intellij/tasks/impl/LocalTaskImpl.java @@ -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 myChangeLists = new ArrayList(); - private long myTimeSpent = 0; private boolean myRunning = false; + private List myWorkItems = new ArrayList(); + 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 workItems) { + myWorkItems = workItems; + } + + @NotNull + @Property(surroundWithTag = false) + @AbstractCollection(surroundWithTag = false, elementTag="workItem") + @Override + public List 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; + } } diff --git a/plugins/tasks/tasks-core/tasks-core.iml b/plugins/tasks/tasks-core/tasks-core.iml index e9a954fc69cf..ef979b8d1e11 100644 --- a/plugins/tasks/tasks-core/tasks-core.iml +++ b/plugins/tasks/tasks-core/tasks-core.iml @@ -38,6 +38,7 @@ + diff --git a/plugins/tasks/tasks-time-tracking/src/com/intellij/tasks/timeTracking/SendTimeTrackingInformationDialog.form b/plugins/tasks/tasks-time-tracking/src/com/intellij/tasks/timeTracking/SendTimeTrackingInformationDialog.form new file mode 100644 index 000000000000..2470a2f7ef4f --- /dev/null +++ b/plugins/tasks/tasks-time-tracking/src/com/intellij/tasks/timeTracking/SendTimeTrackingInformationDialog.form @@ -0,0 +1,130 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/plugins/tasks/tasks-time-tracking/src/com/intellij/tasks/timeTracking/SendTimeTrackingInformationDialog.java b/plugins/tasks/tasks-time-tracking/src/com/intellij/tasks/timeTracking/SendTimeTrackingInformationDialog.java index 209916bf3c0e..e3296bb6a8ba 100644 --- a/plugins/tasks/tasks-time-tracking/src/com/intellij/tasks/timeTracking/SendTimeTrackingInformationDialog.java +++ b/plugins/tasks/tasks-time-tracking/src/com/intellij/tasks/timeTracking/SendTimeTrackingInformationDialog.java @@ -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; } diff --git a/plugins/tasks/tasks-time-tracking/src/com/intellij/tasks/timeTracking/TasksToolWindowPanel.java b/plugins/tasks/tasks-time-tracking/src/com/intellij/tasks/timeTracking/TasksToolWindowPanel.java index dc6e2d9edada..9e28eec7fe34 100644 --- a/plugins/tasks/tasks-time-tracking/src/com/intellij/tasks/timeTracking/TasksToolWindowPanel.java +++ b/plugins/tasks/tasks-time-tracking/src/com/intellij/tasks/timeTracking/TasksToolWindowPanel.java @@ -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() { @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((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(); diff --git a/plugins/tasks/tasks-time-tracking/src/com/intellij/tasks/timeTracking/TimeTrackingManager.java b/plugins/tasks/tasks-time-tracking/src/com/intellij/tasks/timeTracking/TimeTrackingManager.java index 3efce4e6c71c..ba31e7d2d365 100644 --- a/plugins/tasks/tasks-time-tracking/src/com/intellij/tasks/timeTracking/TimeTrackingManager.java +++ b/plugins/tasks/tasks-time-tracking/src/com/intellij/tasks/timeTracking/TimeTrackingManager.java @@ -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; } } diff --git a/plugins/tasks/tasks-time-tracking/src/com/intellij/tasks/timeTracking/model/WorkItem.java b/plugins/tasks/tasks-time-tracking/src/com/intellij/tasks/timeTracking/model/WorkItem.java new file mode 100644 index 000000000000..9e348e1485f3 --- /dev/null +++ b/plugins/tasks/tasks-time-tracking/src/com/intellij/tasks/timeTracking/model/WorkItem.java @@ -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; + } +}