adapt clockify importer to support task -> tasks column rename

This commit is contained in:
Gregor Vostrak
2026-06-25 00:20:29 +02:00
parent 2a40bb7edb
commit ab9f6e6afb
3 changed files with 42 additions and 6 deletions

View File

@@ -25,6 +25,7 @@ class ClockifyProjectsImporter extends DefaultImporter
$header = $reader->getHeader();
$this->validateHeader($header);
$billableRateKey = $this->getBillableRateKey($header);
$tasksKey = $this->getTasksKey($header);
$records = $reader->getRecords();
foreach ($records as $record) {
$clientId = null;
@@ -49,8 +50,8 @@ class ClockifyProjectsImporter extends DefaultImporter
]);
}
if ($record['Task'] !== '') {
$tasks = explode(', ', $record['Task']);
if ($record[$tasksKey] !== '') {
$tasks = explode(', ', $record[$tasksKey]);
foreach ($tasks as $task) {
$this->taskImportHelper->getKey([
'name' => $task,
@@ -83,13 +84,26 @@ class ClockifyProjectsImporter extends DefaultImporter
'Status',
'Visibility',
'Billability',
'Task',
];
foreach ($requiredFields as $requiredField) {
if (! in_array($requiredField, $header, true)) {
throw new ImportException('Invalid CSV header, missing field: '.$requiredField);
}
}
// Clockify renamed the "Task" column to "Tasks" in newer exports; accept either.
if (! in_array('Task', $header, true) && ! in_array('Tasks', $header, true)) {
throw new ImportException('Invalid CSV header, missing field: Tasks');
}
}
/**
* Clockify renamed the "Task" column to "Tasks" in newer exports.
*
* @param array<string> $header
*/
private function getTasksKey(array $header): string
{
return in_array('Tasks', $header, true) ? 'Tasks' : 'Task';
}
/**

View File

@@ -1,3 +1,3 @@
"Project","Client","Status","Visibility","Billability","Task","Tracked (h)","Estimated (h)","Remaining (h)","Overage (h)","Progress(%)","Billable (h)","Non-billable (h)","Billable Rate (USD)","Amount (USD)","Project members","Project manager","Note"
"Active Project","Big Company","Active","Public","Yes","Task 1, Task 2","0.00","100.00","","","","0.00","0.00","100.01","0.00","Constantin Graf","",""
"Archived Project","","Archived","Public","Yes","","0.00","","","","","0.00","0.00","","0.00","Constantin Graf","",""
"Project","Client","Status","Visibility","Billability","Tasks","Tracked (h)","Estimated (h)","Remaining (h)","Overage (h)","Tracked (USD)","Estimated (USD)","Remaining (USD)","Overage (USD)","Progress(%)","Recurring estimate","Billable (h)","Non-billable (h)","Billable Rate (USD)","Amount (USD)","Cost Rate (USD)","Expenses (USD)","Billable expenses (USD)","Non-billable expenses (USD)","Additional fields","Project members","Project manager","Note"
"Active Project","Big Company","Active","Public","Yes","Task 1, Task 2","0.00","100.00","","","","","","","","","0.00","0.00","100.01","0.00","","0.00","0.00","0.00","","Constantin Graf","",""
"Archived Project","","Archived","Public","Yes","","0.00","","","","","","","","","","0.00","0.00","","0.00","","0.00","0.00","0.00","","Constantin Graf","",""
1 Project Client Status Visibility Billability Task Tasks Tracked (h) Estimated (h) Remaining (h) Overage (h) Tracked (USD) Estimated (USD) Remaining (USD) Overage (USD) Progress(%) Recurring estimate Billable (h) Non-billable (h) Billable Rate (USD) Amount (USD) Cost Rate (USD) Expenses (USD) Billable expenses (USD) Non-billable expenses (USD) Additional fields Project members Project manager Note
2 Active Project Big Company Active Public Yes Task 1, Task 2 Task 1, Task 2 0.00 100.00 0.00 0.00 100.01 0.00 0.00 0.00 0.00 Constantin Graf
3 Archived Project Archived Public Yes 0.00 0.00 0.00 0.00 0.00 0.00 0.00 Constantin Graf

View File

@@ -6,6 +6,7 @@ namespace Tests\Unit\Service\Import\Importers;
use App\Models\Organization;
use App\Models\Project;
use App\Models\Task;
use App\Service\Import\Importers\ClockifyProjectsImporter;
use App\Service\Import\Importers\DefaultImporter;
use App\Service\Import\Importers\ImportException;
@@ -73,4 +74,25 @@ class ClockifyProjectsImporterTest extends ImporterTestAbstract
$this->assertNotNull($archivedProject->archived_at);
$this->assertTrue($archivedProject->is_archived);
}
public function test_import_supports_renamed_tasks_column(): void
{
// Arrange
$organization = Organization::factory()->create();
$timezone = 'Europe/Vienna';
$importer = new ClockifyProjectsImporter;
$importer->init($organization);
// Newer Clockify exports rename the "Task" column to "Tasks".
$data = Storage::disk('testfiles')->get('clockify_projects_import_test_2.csv');
// Act
$importer->importData($data, $timezone);
// Assert
$activeProject = Project::query()->where('organization_id', $organization->id)->where('name', 'Active Project')->firstOrFail();
$this->assertEqualsCanonicalizing(
['Task 1', 'Task 2'],
Task::query()->where('project_id', $activeProject->id)->pluck('name')->all(),
);
}
}