Skip to content

Commit

Permalink
Added default importer; Fixed bug in clockify importer
Browse files Browse the repository at this point in the history
  • Loading branch information
korridor committed Mar 12, 2024
1 parent 4738b75 commit 0a6ec55
Show file tree
Hide file tree
Showing 11 changed files with 157 additions and 326 deletions.
2 changes: 1 addition & 1 deletion app/Http/Requests/V1/TimeEntry/TimeEntryStoreRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public function rules(): array
'description' => [
'nullable',
'string',
'max:255',
'max:500',
],
// List of tag IDs
'tags' => [
Expand Down
2 changes: 1 addition & 1 deletion app/Http/Requests/V1/TimeEntry/TimeEntryUpdateRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public function rules(): array
'description' => [
'nullable',
'string',
'max:255',
'max:500',
],
// List of tag IDs
'tags' => [
Expand Down
26 changes: 13 additions & 13 deletions app/Service/Import/ImportDatabaseHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
use Closure;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Validator;

/**
* @template TModel of Model
Expand Down Expand Up @@ -43,18 +43,21 @@ class ImportDatabaseHelper

private int $createdCount;

private array $validate;

/**
* @param class-string<TModel> $model
* @param array<string> $identifiers
*/
public function __construct(string $model, array $identifiers, bool $attachToExisting = false, ?Closure $queryModifier = null, ?Closure $afterCreate = null)
public function __construct(string $model, array $identifiers, bool $attachToExisting = false, ?Closure $queryModifier = null, ?Closure $afterCreate = null, array $validate = [])
{
$this->model = $model;
$this->identifiers = $identifiers;
$this->attachToExisting = $attachToExisting;
$this->queryModifier = $queryModifier;
$this->afterCreate = $afterCreate;
$this->createdCount = 0;
$this->validate = $validate;
}

/**
Expand All @@ -71,11 +74,15 @@ private function getModelInstance(): Builder
*/
private function createEntity(array $identifierData, array $createValues, ?string $externalIdentifier): string
{
$model = new $this->model();
foreach ($identifierData as $identifier => $identifierValue) {
$model->{$identifier} = $identifierValue;
$data = array_merge($identifierData, $createValues);

$validator = Validator::make($data, $this->validate);
if ($validator->fails()) {
throw new ImportException('Invalid data: '.implode(', ', $validator->errors()->all()));
}
foreach ($createValues as $key => $value) {

$model = new $this->model();
foreach ($data as $key => $value) {
$model->{$key} = $value;
}
$model->save();
Expand Down Expand Up @@ -127,17 +134,10 @@ public function getKey(array $identifierData, array $createValues = [], ?string
if ($externalIdentifier !== null) {
$this->mapExternalIdentifierToInternalIdentifier[$externalIdentifier] = $hash;
}
Log::debug('HIT', [
'class' => $this->model,
]);

return $key;
}

Log::debug('MISS', [
'class' => $this->model,
]);

return $this->createEntity($identifierData, $createValues, $externalIdentifier);
} else {
throw new \RuntimeException('Not implemented');
Expand Down
62 changes: 3 additions & 59 deletions app/Service/Import/Importers/ClockifyProjectsImporter.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,59 +4,19 @@

namespace App\Service\Import\Importers;

use App\Models\Client;
use App\Models\Organization;
use App\Models\Project;
use App\Models\Task;
use App\Service\ColorService;
use App\Service\Import\ImportDatabaseHelper;
use Exception;
use Illuminate\Database\Eloquent\Builder;
use League\Csv\Exception as CsvException;
use League\Csv\Reader;

class ClockifyProjectsImporter implements ImporterContract
class ClockifyProjectsImporter extends DefaultImporter
{
private Organization $organization;

/**
* @var ImportDatabaseHelper<Project>
*/
private ImportDatabaseHelper $projectImportHelper;

/**
* @var ImportDatabaseHelper<Client>
*/
private ImportDatabaseHelper $clientImportHelper;

/**
* @var ImportDatabaseHelper<Task>
*/
private ImportDatabaseHelper $taskImportHelper;

#[\Override]
public function init(Organization $organization): void
{
$this->organization = $organization;
$this->projectImportHelper = new ImportDatabaseHelper(Project::class, ['name', 'organization_id'], true, function (Builder $builder) {
return $builder->where('organization_id', $this->organization->id);
});
$this->clientImportHelper = new ImportDatabaseHelper(Client::class, ['name', 'organization_id'], true, function (Builder $builder) {
return $builder->where('organization_id', $this->organization->id);
});
$this->taskImportHelper = new ImportDatabaseHelper(Task::class, ['name', 'project_id', 'organization_id'], true, function (Builder $builder) {
return $builder->where('organization_id', $this->organization->id);
});
}

/**
* @throws ImportException
*/
#[\Override]
public function importData(string $data): void
{
try {
$colorService = app(ColorService::class);
$reader = Reader::createFromString($data);
$reader->setHeaderOffset(0);
$reader->setDelimiter(',');
Expand All @@ -78,17 +38,14 @@ public function importData(string $data): void
'organization_id' => $this->organization->id,
], [
'client_id' => $clientId,
'color' => $colorService->getRandomColor(),
'color' => $this->colorService->getRandomColor(),
]);
}

if ($record['Tasks'] !== '') {
$tasks = explode(', ', $record['Tasks']);
foreach ($tasks as $task) {
if (strlen($task) > 255) {
throw new ImportException('Task is too long');
}
$taskId = $this->taskImportHelper->getKey([
$this->taskImportHelper->getKey([
'name' => $task,
'project_id' => $projectId,
'organization_id' => $this->organization->id,
Expand Down Expand Up @@ -127,17 +84,4 @@ private function validateHeader(array $header): void
}
}
}

#[\Override]
public function getReport(): ReportDto
{
return new ReportDto(
clientsCreated: $this->clientImportHelper->getCreatedCount(),
projectsCreated: $this->projectImportHelper->getCreatedCount(),
tasksCreated: $this->taskImportHelper->getCreatedCount(),
timeEntriesCreated: 0,
tagsCreated: 0,
usersCreated: 0,
);
}
}
105 changes: 19 additions & 86 deletions app/Service/Import/Importers/ClockifyTimeEntriesImporter.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,79 +4,14 @@

namespace App\Service\Import\Importers;

use App\Models\Client;
use App\Models\Organization;
use App\Models\Project;
use App\Models\Tag;
use App\Models\Task;
use App\Models\TimeEntry;
use App\Models\User;
use App\Service\ColorService;
use App\Service\Import\ImportDatabaseHelper;
use Exception;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Carbon;
use League\Csv\Exception as CsvException;
use League\Csv\Reader;

class ClockifyTimeEntriesImporter implements ImporterContract
class ClockifyTimeEntriesImporter extends DefaultImporter
{
private Organization $organization;

/**
* @var ImportDatabaseHelper<User>
*/
private ImportDatabaseHelper $userImportHelper;

/**
* @var ImportDatabaseHelper<Project>
*/
private ImportDatabaseHelper $projectImportHelper;

/**
* @var ImportDatabaseHelper<Tag>
*/
private ImportDatabaseHelper $tagImportHelper;

/**
* @var ImportDatabaseHelper<Client>
*/
private ImportDatabaseHelper $clientImportHelper;

/**
* @var ImportDatabaseHelper<Task>
*/
private ImportDatabaseHelper $taskImportHelper;

private int $timeEntriesCreated;

#[\Override]
public function init(Organization $organization): void
{
$this->organization = $organization;
$this->userImportHelper = new ImportDatabaseHelper(User::class, ['email'], true, function (Builder $builder) {
/** @var Builder<User> $builder */
return $builder->belongsToOrganization($this->organization);
}, function (User $user) {
$user->organizations()->attach($this->organization, [
'role' => 'placeholder',
]);
});
$this->projectImportHelper = new ImportDatabaseHelper(Project::class, ['name', 'organization_id'], true, function (Builder $builder) {
return $builder->where('organization_id', $this->organization->id);
});
$this->tagImportHelper = new ImportDatabaseHelper(Tag::class, ['name', 'organization_id'], true, function (Builder $builder) {
return $builder->where('organization_id', $this->organization->id);
});
$this->clientImportHelper = new ImportDatabaseHelper(Client::class, ['name', 'organization_id'], true, function (Builder $builder) {
return $builder->where('organization_id', $this->organization->id);
});
$this->taskImportHelper = new ImportDatabaseHelper(Task::class, ['name', 'project_id', 'organization_id'], true, function (Builder $builder) {
return $builder->where('organization_id', $this->organization->id);
});
$this->timeEntriesCreated = 0;
}

/**
* @return array<string>
*
Expand All @@ -90,9 +25,6 @@ private function getTags(string $tags): array
$tagsParsed = explode(', ', $tags);
$tagIds = [];
foreach ($tagsParsed as $tagParsed) {
if (strlen($tagParsed) > 255) {
throw new ImportException('Tag is too long');
}
$tagId = $this->tagImportHelper->getKey([
'name' => $tagParsed,
'organization_id' => $this->organization->id,
Expand All @@ -110,7 +42,6 @@ private function getTags(string $tags): array
public function importData(string $data): void
{
try {
$colorService = app(ColorService::class);
$reader = Reader::createFromString($data);
$reader->setHeaderOffset(0);
$reader->setDelimiter(',');
Expand Down Expand Up @@ -138,7 +69,7 @@ public function importData(string $data): void
'organization_id' => $this->organization->id,
], [
'client_id' => $clientId,
'color' => $colorService->getRandomColor(),
'color' => $this->colorService->getRandomColor(),
]);
}
$taskId = null;
Expand All @@ -154,18 +85,33 @@ public function importData(string $data): void
$timeEntry->task_id = $taskId;
$timeEntry->project_id = $projectId;
$timeEntry->organization_id = $this->organization->id;
if (strlen($record['Description']) > 500) {
throw new ImportException('Time entry description is too long');
}
$timeEntry->description = $record['Description'];
if (! in_array($record['Billable'], ['Yes', 'No'], true)) {
throw new ImportException('Invalid billable value');
}
$timeEntry->billable = $record['Billable'] === 'Yes';
$timeEntry->tags = $this->getTags($record['Tags']);
$start = Carbon::createFromFormat('m/d/Y H:i:s A', $record['Start Date'].' '.$record['Start Time'], 'UTC');

// Start
if (preg_match('/^[0-9]{1,2}:[0-9]{1,2} (AM|PM)$/', $record['Start Time']) === 1) {
$start = Carbon::createFromFormat('m/d/Y h:i A', $record['Start Date'].' '.$record['Start Time'], 'UTC');
} else {
$start = Carbon::createFromFormat('m/d/Y H:i:s A', $record['Start Date'].' '.$record['Start Time'], 'UTC');
}
if ($start === false) {
throw new ImportException('Start date ("'.$record['Start Date'].'") or time ("'.$record['Start Time'].'") are invalid');
}
$timeEntry->start = $start;
$end = Carbon::createFromFormat('m/d/Y H:i:s A', $record['End Date'].' '.$record['End Time'], 'UTC');

// End
if (preg_match('/^[0-9]{1,2}:[0-9]{1,2} (AM|PM)$/', $record['End Time']) === 1) {
$end = Carbon::createFromFormat('m/d/Y h:i A', $record['End Date'].' '.$record['End Time'], 'UTC');
} else {
$end = Carbon::createFromFormat('m/d/Y H:i:s A', $record['End Date'].' '.$record['End Time'], 'UTC');
}
if ($end === false) {
throw new ImportException('End date ("'.$record['End Date'].'") or time ("'.$record['End Time'].'") are invalid');
}
Expand Down Expand Up @@ -211,17 +157,4 @@ private function validateHeader(array $header): void
}
}
}

#[\Override]
public function getReport(): ReportDto
{
return new ReportDto(
clientsCreated: $this->clientImportHelper->getCreatedCount(),
projectsCreated: $this->projectImportHelper->getCreatedCount(),
tasksCreated: $this->taskImportHelper->getCreatedCount(),
timeEntriesCreated: $this->timeEntriesCreated,
tagsCreated: $this->tagImportHelper->getCreatedCount(),
usersCreated: $this->userImportHelper->getCreatedCount(),
);
}
}
Loading

0 comments on commit 0a6ec55

Please sign in to comment.