Skip to content

Commit e73b6dc

Browse files
committed
#178: add initial sync functionality
1 parent 9152072 commit e73b6dc

File tree

5 files changed

+100
-31
lines changed

5 files changed

+100
-31
lines changed

src/main/java/de/doubleslash/keeptime/rest/integration/heimat/HeimatAPI.java

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import de.doubleslash.keeptime.rest.integration.heimat.model.HeimatTime;
55
import org.springframework.core.ParameterizedTypeReference;
66
import org.springframework.http.HttpStatus;
7+
import org.springframework.http.ResponseEntity;
78
import org.springframework.web.client.RestClient;
89
import org.springframework.web.util.UriBuilder;
910

@@ -65,19 +66,34 @@ public List<HeimatTime> getMyTimes(final LocalDate forDate) {
6566

6667
// POST /my/times
6768
public void addMyTime(final HeimatTime heimatTime) {
68-
restClient.post()
69-
.uri(uriBuilder -> {
70-
UriBuilder builder = uriBuilder.path("/my/times");
71-
return builder.build();
72-
})
73-
.header("Content-Type", "application/json")
74-
.body(heimatTime)
75-
.retrieve()
76-
.onStatus(HttpStatus.UNAUTHORIZED::equals, (request, response) -> {
77-
throw new UnauthorizedException();
78-
});
69+
final ResponseEntity<String> responseEntity = restClient.post()
70+
.uri(uriBuilder -> {
71+
UriBuilder builder = uriBuilder.path("/my/times");
72+
return builder.build();
73+
})
74+
.header("Content-Type", "application/json")
75+
.body(heimatTime)
76+
.retrieve()
77+
.onStatus(HttpStatus.UNAUTHORIZED::equals,
78+
(request, response) -> {
79+
throw new UnauthorizedException();
80+
})
81+
.toEntity(String.class);
82+
83+
System.out.println("Status Code: " + responseEntity.getStatusCode());
84+
System.out.println("Response Body: " + responseEntity.getBody());
7985
}
8086

8187
// DELETE /my/times/{id}
88+
public void deleteMyTime(final long timeId) {
89+
final ResponseEntity<String> responseEntity = restClient.delete().uri(uriBuilder -> {
90+
UriBuilder builder = uriBuilder.path("/my/times/" + timeId);
91+
return builder.build();
92+
}).retrieve().onStatus(HttpStatus.UNAUTHORIZED::equals, (request, response) -> {
93+
throw new UnauthorizedException();
94+
}).toEntity(String.class);
8295

96+
System.out.println("Status Code: " + responseEntity.getStatusCode());
97+
System.out.println("Response Body: " + responseEntity.getBody());
98+
}
8399
}

src/main/java/de/doubleslash/keeptime/view/ExternalProjectsSyncController.java

Lines changed: 68 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import javafx.scene.layout.HBox;
1919
import javafx.scene.layout.VBox;
2020
import javafx.scene.shape.Circle;
21+
import javafx.stage.Stage;
2122
import javafx.util.converter.LocalTimeStringConverter;
2223
import org.slf4j.Logger;
2324
import org.slf4j.LoggerFactory;
@@ -34,7 +35,7 @@
3435
@Component
3536
public class ExternalProjectsSyncController {
3637

37-
private static final Logger LOG = LoggerFactory.getLogger(MapExternalProjectsController.class);
38+
private static final Logger LOG = LoggerFactory.getLogger(ExternalProjectsSyncController.class);
3839

3940
@FXML
4041
private TableView<TableRow> mappingTableView;
@@ -60,21 +61,26 @@ public class ExternalProjectsSyncController {
6061
private final ExternalProjectsMappingsRepository externalProjectsMappingsRepository;
6162

6263
private final LocalTimeStringConverter localTimeStringConverter = new LocalTimeStringConverter(FormatStyle.MEDIUM);
64+
private ObservableList<TableRow> items;
65+
private final HeimatAPI heimatAPI;
66+
private LocalDate currentReportDate;
67+
private Stage thisStage;
6368

6469
public ExternalProjectsSyncController(final Controller controller, final Model model, HeimatSettings heimatSettings,
6570
ExternalProjectsMappingsRepository externalProjectsMappingsRepository) {
6671
this.controller = controller;
6772
this.model = model;
6873
this.heimatSettings = heimatSettings;
6974
this.externalProjectsMappingsRepository = externalProjectsMappingsRepository;
75+
heimatAPI = new HeimatAPI(heimatSettings.getHeimatUrl(), heimatSettings.getHeimatPat());
7076
}
7177

7278
public void initForDate(LocalDate currentReportDate, List<Work> currentWorkItems) {
7379
dayOfSyncLabel.setText(currentReportDate.format(DateTimeFormatter.BASIC_ISO_DATE));
74-
75-
final HeimatAPI heimatAPI = new HeimatAPI(heimatSettings.getHeimatUrl(), heimatSettings.getHeimatPat());
80+
this.currentReportDate = currentReportDate;
7681
// TODO check if external projects are available for the currentDay
7782
// final List<HeimatTask> heimatTasks = heimatAPI.getMyTasks(currentReportDate);
83+
// TODO add a spinner while loading?
7884
final List<HeimatTime> heimatTimes = heimatAPI.getMyTimes(currentReportDate);
7985
final List<ExternalProjectMapping> mappedProjects = externalProjectsMappingsRepository.findByExternalSystemId(
8086
ExternalSystem.Heimat);
@@ -95,13 +101,13 @@ public void initForDate(LocalDate currentReportDate, List<Work> currentWorkItems
95101
.getId()
96102
== project.getId())
97103
.findAny();
104+
Optional<HeimatTime> optionalAlreadyBookedTime = Optional.empty();
98105
if (optionalHeimatMapping.isPresent()) {
99106
isMappedInHeimat = true;
100-
final Optional<HeimatTime> optionalAlreadyBookedTime = heimatTimes.stream()
101-
.filter(heimatTime -> heimatTime.taskId()
102-
== optionalHeimatMapping.get()
103-
.getExternalTaskId())
104-
.findAny();
107+
optionalAlreadyBookedTime = heimatTimes.stream()
108+
.filter(heimatTime -> heimatTime.taskId()
109+
== optionalHeimatMapping.get().getExternalTaskId())
110+
.findAny();
105111
if (optionalAlreadyBookedTime.isPresent()) {
106112
heimatNotes = optionalAlreadyBookedTime.get().note();
107113
heimatTimeSeconds = optionalAlreadyBookedTime.get().durationInMinutes() * 60L;
@@ -123,10 +129,11 @@ public void initForDate(LocalDate currentReportDate, List<Work> currentWorkItems
123129
if (!isMappedInHeimat) {
124130
canBeSynced = "Not mapped in Heimat";
125131
}
126-
list.add(new TableRow(project, isMappedInHeimat, canBeSynced, heimatNotes, keeptimeNotes, keeptimeNotes,
127-
heimatTimeSeconds, projectWorkSeconds, projectWorkSeconds));
132+
list.add(new TableRow(project, isMappedInHeimat ? optionalHeimatMapping.get().getExternalTaskId() : -1,
133+
optionalAlreadyBookedTime.orElse(null), isMappedInHeimat, canBeSynced, heimatNotes, keeptimeNotes,
134+
keeptimeNotes, heimatTimeSeconds, projectWorkSeconds, projectWorkSeconds));
128135
}
129-
final ObservableList<TableRow> items = FXCollections.observableArrayList(list);
136+
items = FXCollections.observableArrayList(list);
130137
mappingTableView.setItems(items);
131138

132139
ObservableList<TableRow> items2 = FXCollections.observableArrayList(
@@ -138,6 +145,8 @@ public void initForDate(LocalDate currentReportDate, List<Work> currentWorkItems
138145
.mapToLong(item -> item.userTimeSeconds.getValue())
139146
.sum())), items2);
140147
sumTimeLabel.textProperty().bind(Bindings.concat("Total Sum: ", totalSum));
148+
// TODO add a label with current Heimat Time
149+
// TODO add a label with current Keeptime time
141150
}
142151

143152
@FXML
@@ -161,6 +170,7 @@ protected void updateItem(Project item, boolean empty) {
161170
setGraphic(null);
162171
setText(null);
163172
} else {
173+
// TODO add a reference to the Heimat project which will be used
164174
//label.setText(item.getName());
165175
setText(item.getName());
166176
final Circle circle = new Circle(6, item.getColor());
@@ -222,6 +232,8 @@ protected void updateItem(TableRow item, boolean empty) {
222232
textArea.setPrefHeight(50);
223233
textArea.setPrefWidth(100);
224234
textArea.setWrapText(true);
235+
// TODO mark textArea red when not content
236+
// TODO make it possible to copy content of heimatNotesLabel
225237
hbox.getChildren().addAll(new Label("Heimat:"), heimatNotesLabel);
226238
container.getChildren().addAll(textArea, hbox);
227239
}
@@ -235,7 +247,7 @@ protected void updateItem(TableRow item, boolean empty) {
235247
setGraphic(null);
236248
} else {
237249
textArea.setText(item.keeptimeNotes.get());
238-
stringChangeListener = (obs, oldText, newText) -> item.keeptimeNotes.set(newText);
250+
stringChangeListener = (obs, oldText, newText) -> item.userNotes.set(newText);
239251
textArea.textProperty().addListener(stringChangeListener);
240252
heimatNotesLabel.setText(item.heimatNotes.get());
241253
setGraphic(container);
@@ -251,12 +263,43 @@ protected void updateItem(TableRow item, boolean empty) {
251263

252264
saveButton.setOnAction((ae) -> {
253265
LOG.debug("New mappings to be synced '{}'.", "TODO");
254-
// TODO close
266+
267+
// TODO show progress
268+
items.stream().filter(tr -> tr.shouldSyncCheckBox.get()).forEach(tr -> {
269+
if (tr.userNotes.get().isEmpty()) {
270+
LOG.warn("No notes were given. Skipping '{}'", tr.project.getName());
271+
return;
272+
}
273+
final int durationInMinutes = Math.toIntExact(tr.userTimeSeconds.get() / 60);
274+
if (durationInMinutes <= 0 || durationInMinutes % 15 != 0) {
275+
LOG.warn("Duration '{}' is not valid for project '{}'.", durationInMinutes, tr.project.getName());
276+
return;
277+
}
278+
279+
final HeimatTime heimatTime = new HeimatTime(tr.heimatTaskId, currentReportDate, null, null,
280+
durationInMinutes, tr.userNotes.get(), 0L);
281+
282+
try {
283+
if (tr.optionalExistingTime != null) {
284+
LOG.info("Removing existing booked time '{}'", tr.optionalExistingTime);
285+
heimatAPI.deleteMyTime(tr.optionalExistingTime.id());
286+
}
287+
LOG.info("Adding new time time '{}'", heimatTime);
288+
heimatAPI.addMyTime(heimatTime);
289+
} catch (Exception e) {
290+
// TODO show errors to the user
291+
LOG.error("Error while persisting time '{}'", heimatTime, e);
292+
}
293+
});
294+
295+
thisStage.close();
255296
});
256297

257298
cancelButton.setOnAction(ae -> {
258-
// TODO Close
299+
thisStage.close();
259300
});
301+
302+
// TODO offer some way to book time to an additional project?
260303
}
261304

262305
private void setUpTimeSpinner(final Spinner<LocalTime> spinner) {
@@ -319,7 +362,13 @@ public static LocalTime incrementToNextFullQuarter(LocalTime time) {
319362
return time.plusMinutes(increment).withSecond(0).withNano(0);
320363
}
321364

365+
public void setStage(final Stage thisStage) {
366+
this.thisStage = thisStage;
367+
}
368+
322369
static class TableRow {
370+
private final long heimatTaskId;
371+
private final HeimatTime optionalExistingTime;
323372
public BooleanProperty shouldSyncCheckBox;
324373
public Project project;
325374

@@ -333,10 +382,12 @@ static class TableRow {
333382

334383
public StringProperty syncStatus;
335384

336-
public TableRow(final Project project, final boolean shouldSync, final String syncStatus,
337-
final String heimatNotes, final String keeptimeNotes, String userNotes, final long heimatTimeSeconds,
338-
final long keeptimeSeconds, final long userSeconds) {
385+
public TableRow(final Project project, long heimatTaskId, HeimatTime optionalExistingTime,
386+
final boolean shouldSync, final String syncStatus, final String heimatNotes, final String keeptimeNotes,
387+
String userNotes, final long heimatTimeSeconds, final long keeptimeSeconds, final long userSeconds) {
339388
this.project = project;
389+
this.heimatTaskId = heimatTaskId;
390+
this.optionalExistingTime = optionalExistingTime;
340391
this.shouldSyncCheckBox = new SimpleBooleanProperty(shouldSync);
341392
this.syncStatus = new SimpleStringProperty(syncStatus);
342393
this.heimatNotes = new SimpleStringProperty(heimatNotes);

src/main/java/de/doubleslash/keeptime/view/MapExternalProjectsController.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ private void initialize() {
100100
value.setPredicate(null);
101101
}));
102102
filterOnlyWorkCheckBox.setSelected(true);
103+
//value.add(new ProjectMapping(null, null)); // TODO somehow allow to create a new project for a task
103104
mappingTableView.setItems(value);
104105

105106
// KeepTime Project column

src/main/java/de/doubleslash/keeptime/view/ReportController.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,8 +157,9 @@ private void showSyncStage() throws IOException {
157157
ExternalProjectsSyncController settingsController = fxmlLoader2.getController();
158158
settingsController.initForDate(currentReportDate, currentWorkItems);
159159
Stage settingsStage = new Stage();
160-
//settingsController.setStage(settingsStage);
161-
settingsStage.initModality(Modality.APPLICATION_MODAL);
160+
settingsController.setStage(settingsStage);
161+
settingsStage.initOwner(this.stage);
162+
//settingsStage.initModality(Modality.WINDOW_MODAL);
162163
settingsStage.setTitle("External Project Sync");
163164
settingsStage.setResizable(true);
164165
settingsStage.getIcons().add(new Image(Resources.getResource(RESOURCE.ICON_MAIN).toString()));

src/main/resources/db/migration/V1_1_8__initial_heimat_integration.sql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
CREATE TABLE external_project_mapping (
1+
CREATE TABLE IF NOT EXISTS external_project_mapping (
22
id BIGINT GENERATED BY DEFAULT AS IDENTITY,
33
project_id BIGINT NOT NULL,
44

0 commit comments

Comments
 (0)