Skip to content

Commit 4fb18f3

Browse files
committed
allow missing client columns in clockify importer, fixes #1149
1 parent ba374c0 commit 4fb18f3

7 files changed

Lines changed: 74 additions & 5 deletions

File tree

app/Service/Import/Importers/ClockifyProjectsImporter.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ public function importData(string $data, string $timezone): void
2929
$records = $reader->getRecords();
3030
foreach ($records as $record) {
3131
$clientId = null;
32-
if ($record['Client'] !== '') {
32+
// Newer Clockify exports no longer contain a "Client" column.
33+
if (($record['Client'] ?? '') !== '') {
3334
$clientId = $this->clientImportHelper->getKey([
3435
'name' => $record['Client'],
3536
'organization_id' => $this->organization->id,
@@ -45,7 +46,7 @@ public function importData(string $data, string $timezone): void
4546
'color' => $this->colorService->getRandomColor(),
4647
'is_billable' => $record['Billability'] === 'Yes',
4748
'billable_rate' => $billableRateKey !== null && $record[$billableRateKey] !== '' ? (int) (((float) $record[$billableRateKey]) * 100) : null,
48-
'estimated_time' => $record['Estimated (h)'] !== '' && is_numeric($record['Estimated (h)']) ? (int) ($record['Estimated (h)'] * 3600) : null,
49+
'estimated_time' => isset($record['Estimated (h)']) && is_numeric($record['Estimated (h)']) ? (int) ($record['Estimated (h)'] * 3600) : null,
4950
'archived_at' => $record['Status'] === 'Archived' ? Carbon::now() : null,
5051
]);
5152
}
@@ -80,7 +81,6 @@ private function validateHeader(array $header): void
8081
{
8182
$requiredFields = [
8283
'Project',
83-
'Client',
8484
'Status',
8585
'Visibility',
8686
'Billability',

app/Service/Import/Importers/ClockifyTimeEntriesImporter.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ public function importData(string $data, string $timezone): void
7272
]);
7373
$member = $this->memberImportHelper->getModelById($memberId);
7474
$clientId = null;
75-
if ($record['Client'] !== '') {
75+
if (($record['Client'] ?? '') !== '') {
7676
$clientId = $this->clientImportHelper->getKey([
7777
'name' => $record['Client'],
7878
'organization_id' => $this->organization->id,
@@ -215,7 +215,6 @@ private function validateHeader(array $header): void
215215
{
216216
$requiredFields = [
217217
'Project',
218-
'Client',
219218
'Description',
220219
'User',
221220
'Group',
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
"Project","Status","Visibility","Billability","Tasks","Tracked (h)","Estimated (h)","Remaining (h)","Overage (h)","Progress(%)","Billable (h)","Non-billable (h)","Billable Rate (USD)","Amount (USD)","Project members","Project manager","Note"
2+
"Project Without Client Column","Active","Public","Yes","Task 1, Task 2","0.00","100.00","","","","0.00","0.00","100.01","0.00","Constantin Graf","",""
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
"Project","Description","Task","User","Group","Email","Tags","Billable","Start Date","Start Time","End Date","End Time","Duration (h)","Duration (decimal)","Billable Rate (USD)","Billable Amount (USD)"
2+
"Project A","","","Peter Tester","","peter.test@email.test","Development, Backend","No","03/04/2024","10:23:52 AM","03/04/2024","10:23:52 AM","00:00:00","0.00","0.00","0.00"
3+
"Project B","Working hard","Task 1","Peter Tester","","peter.test@email.test","","Yes","03/04/2024","10:23 AM","03/04/2024","11:23:01 AM","01:00:01","0.00","0.00","0.00"
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
"Project","Description","Task","User","Group","Email","Tags","Billable","Start Date","Start Time","End Date","End Time","Client"
2+
"Project A","Working hard","Task 1","Peter Tester","","peter.test@email.test","","Yes","03/04/2024","10:23 AM","03/04/2024","11:23:01 AM"

tests/Unit/Service/Import/Importers/ClockifyProjectsImporterTest.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,29 @@ public function test_import_supports_renamed_tasks_column(): void
9696
);
9797
}
9898

99+
public function test_import_of_test_file_without_client_column_succeeds(): void
100+
{
101+
// Arrange
102+
$organization = Organization::factory()->create();
103+
$timezone = 'Europe/Vienna';
104+
$importer = new ClockifyProjectsImporter;
105+
$importer->init($organization);
106+
// Newer Clockify exports no longer contain a "Client" column.
107+
$data = Storage::disk('testfiles')->get('clockify_projects_import_test_4.csv');
108+
109+
// Act
110+
$importer->importData($data, $timezone);
111+
112+
// Assert
113+
$project = Project::query()->where('organization_id', $organization->id)->where('name', 'Project Without Client Column')->firstOrFail();
114+
$this->assertNull($project->client_id);
115+
$this->assertSame(100 * 3600, $project->estimated_time);
116+
$this->assertEqualsCanonicalizing(
117+
['Task 1', 'Task 2'],
118+
Task::query()->where('project_id', $project->id)->pluck('name')->all(),
119+
);
120+
}
121+
99122
public function test_import_supports_activities_column_alias_for_tasks(): void
100123
{
101124
// Arrange

tests/Unit/Service/Import/Importers/ClockifyTimeEntriesImporterTest.php

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,46 @@ public function test_import_supports_activity_column_alias_for_task(): void
136136
$this->assertSame(1, $report->tasksCreated);
137137
}
138138

139+
public function test_import_of_test_file_without_client_column_succeeds(): void
140+
{
141+
// Arrange
142+
$organization = Organization::factory()->create();
143+
$timezone = 'Europe/Vienna';
144+
$importer = new ClockifyTimeEntriesImporter;
145+
$importer->init($organization);
146+
// Newer Clockify exports no longer contain a "Client" column.
147+
$data = Storage::disk('testfiles')->get('clockify_time_entries_import_test_6.csv');
148+
149+
// Act
150+
$importer->importData($data, $timezone);
151+
$report = $importer->getReport();
152+
153+
// Assert
154+
$this->assertSame(2, $report->timeEntriesCreated);
155+
$this->assertSame(2, $report->projectsCreated);
156+
$this->assertSame(0, $report->clientsCreated);
157+
}
158+
159+
public function test_import_of_test_file_with_client_column_but_missing_values_succeeds(): void
160+
{
161+
// Arrange
162+
$organization = Organization::factory()->create();
163+
$timezone = 'Europe/Vienna';
164+
$importer = new ClockifyTimeEntriesImporter;
165+
$importer->init($organization);
166+
// Rows shorter than the header are padded with null by the CSV reader.
167+
$data = Storage::disk('testfiles')->get('clockify_time_entries_import_test_7.csv');
168+
169+
// Act
170+
$importer->importData($data, $timezone);
171+
$report = $importer->getReport();
172+
173+
// Assert
174+
$this->assertSame(1, $report->timeEntriesCreated);
175+
$this->assertSame(1, $report->projectsCreated);
176+
$this->assertSame(0, $report->clientsCreated);
177+
}
178+
139179
public function test_import_fails_if_month_in_date_is_bigger_than_12(): void
140180
{
141181
// Arrange

0 commit comments

Comments
 (0)