mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-06-15 13:32:43 +01:00
Added importer details endpoint
This commit is contained in:
committed by
Gregor Vostrak
parent
fe61a54c35
commit
1f79d5e50a
@@ -6,6 +6,8 @@ namespace App\Http\Controllers\Api\V1;
|
||||
|
||||
use App\Http\Requests\V1\Import\ImportRequest;
|
||||
use App\Models\Organization;
|
||||
use App\Service\Import\Importers\ImporterContract;
|
||||
use App\Service\Import\Importers\ImporterProvider;
|
||||
use App\Service\Import\Importers\ImportException;
|
||||
use App\Service\Import\ImportService;
|
||||
use Illuminate\Auth\Access\AuthorizationException;
|
||||
@@ -13,6 +15,39 @@ use Illuminate\Http\JsonResponse;
|
||||
|
||||
class ImportController extends Controller
|
||||
{
|
||||
/**
|
||||
* Get information about available importers
|
||||
*
|
||||
* @operationId getImporters
|
||||
*
|
||||
* @throws AuthorizationException
|
||||
*
|
||||
* @response array{data: array<array{ key: string, name: string, description: string }>}
|
||||
*/
|
||||
public function index(Organization $organization, ImporterProvider $importerProvider): JsonResponse
|
||||
{
|
||||
$this->checkPermission($organization, 'import');
|
||||
|
||||
$importers = $importerProvider->getImporters();
|
||||
|
||||
/** @var array<array{ key: string, name: string, description: string }> $importersResponse */
|
||||
$importersResponse = [];
|
||||
|
||||
foreach ($importers as $key => $importerClass) {
|
||||
/** @var ImporterContract $importer */
|
||||
$importer = new $importerClass();
|
||||
$importersResponse[] = [
|
||||
'key' => $key,
|
||||
'name' => $importer->getName(),
|
||||
'description' => $importer->getDescription(),
|
||||
];
|
||||
}
|
||||
|
||||
return new JsonResponse([
|
||||
'data' => $importersResponse,
|
||||
], 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Import data into the organization
|
||||
*
|
||||
|
||||
@@ -84,4 +84,16 @@ class ClockifyProjectsImporter extends DefaultImporter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function getName(): string
|
||||
{
|
||||
return __('importer.clockify_projects.name');
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function getDescription(): string
|
||||
{
|
||||
return __('importer.clockify_projects.description');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,4 +158,16 @@ class ClockifyTimeEntriesImporter extends DefaultImporter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function getName(): string
|
||||
{
|
||||
return __('importer.toggl_data_importer.name');
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function getDescription(): string
|
||||
{
|
||||
return __('importer.toggl_data_importer.description');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,4 +13,8 @@ interface ImporterContract
|
||||
public function importData(string $data): void;
|
||||
|
||||
public function getReport(): ReportDto;
|
||||
|
||||
public function getName(): string;
|
||||
|
||||
public function getDescription(): string;
|
||||
}
|
||||
|
||||
@@ -32,6 +32,14 @@ class ImporterProvider
|
||||
return array_keys($this->importers);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, class-string<ImporterContract>>
|
||||
*/
|
||||
public function getImporters(): array
|
||||
{
|
||||
return $this->importers;
|
||||
}
|
||||
|
||||
public function getImporter(string $type): ImporterContract
|
||||
{
|
||||
if (! array_key_exists($type, $this->importers)) {
|
||||
|
||||
@@ -114,4 +114,16 @@ class TogglDataImporter extends DefaultImporter
|
||||
throw new ImportException('Unknown error');
|
||||
}
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function getName(): string
|
||||
{
|
||||
return __('importer.toggl_data_importer.name');
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function getDescription(): string
|
||||
{
|
||||
return __('importer.toggl_data_importer.description');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,4 +142,16 @@ class TogglTimeEntriesImporter extends DefaultImporter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function getName(): string
|
||||
{
|
||||
return __('importer.toggl_time_entries.name');
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function getDescription(): string
|
||||
{
|
||||
return __('importer.toggl_time_entries.description');
|
||||
}
|
||||
}
|
||||
|
||||
32
lang/en/importer.php
Normal file
32
lang/en/importer.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
'clockify_time_entries' => [
|
||||
'name' => 'Clockify Time Entries',
|
||||
'description' => 'Go to REPORTS -> TIME -> Detailed in the navigation on the left. '.
|
||||
'Now select the date range that you want to export in the right top. '.
|
||||
'It is currently not possible to select more than one year. You can export each year seperatly and import them one after another.'.
|
||||
'Now click Export -> Save as CSV. The Export dropdown is in the header of the export table left of the printer symbol.',
|
||||
],
|
||||
'clockify_projects' => [
|
||||
'name' => 'Clockify Projects',
|
||||
'description' => 'Go to PROJECTS in the navigation on the left. '.
|
||||
'Now click on the three dots on the right of the project that you want to export and select Export. '.
|
||||
'Now click Export -> Save as CSV. The Export dropdown is in the header of the export table in the top right corner.',
|
||||
],
|
||||
'toggl_data_importer' => [
|
||||
'name' => 'Toggl Data Importer',
|
||||
'description' => 'Go to Admin -> Settings -> Data export. '.
|
||||
'Under "Data Export" select all items for export and click on "Export to email". '.
|
||||
'You will receive an email with a download link. Download the ZIP and upload it here. '.
|
||||
'The "Data Export" exports everything except time entries. '.
|
||||
'If you want to also import time entries use the "Toggl Time Entries" importer afterwards.',
|
||||
],
|
||||
'toggl_time_entries' => [
|
||||
'name' => 'Toggl Time Entries',
|
||||
'description' => 'Important: If you want to import a Toggl organization use the "Toggl Data Importer" before using this importer, since this export contains more details. '.
|
||||
'Go to Admin -> Settings -> Data export. Under "Time entries" select the year you want to export and click on "Export time entries". You can export all years one after another and import them one after another.',
|
||||
],
|
||||
];
|
||||
@@ -106,6 +106,7 @@ Route::middleware([
|
||||
|
||||
// Import routes
|
||||
Route::name('import.')->group(static function () {
|
||||
Route::get('/organizations/{organization}/importers', [ImportController::class, 'index'])->name('index');
|
||||
Route::post('/organizations/{organization}/import', [ImportController::class, 'import'])->name('import');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -25,7 +25,7 @@ class DeleteTeamTest extends TestCase
|
||||
$otherUser = User::factory()->create(), ['role' => 'test-role']
|
||||
);
|
||||
|
||||
$response = $this->delete('/teams/'.$team->id);
|
||||
$response = $this->delete('/teams/'.$team->getKey());
|
||||
|
||||
$this->assertNull($team->fresh());
|
||||
$this->assertCount(0, $otherUser->fresh()->teams);
|
||||
@@ -35,7 +35,7 @@ class DeleteTeamTest extends TestCase
|
||||
{
|
||||
$this->actingAs($user = User::factory()->withPersonalOrganization()->create());
|
||||
|
||||
$response = $this->delete('/teams/'.$user->currentTeam->id);
|
||||
$response = $this->delete('/teams/'.$user->currentTeam->getKey());
|
||||
|
||||
$this->assertNotNull($user->currentTeam->fresh());
|
||||
}
|
||||
|
||||
@@ -13,6 +13,49 @@ use Mockery\MockInterface;
|
||||
|
||||
class ImportEndpointTest extends ApiEndpointTestAbstract
|
||||
{
|
||||
public function test_index_fails_if_user_does_not_have_permission()
|
||||
{
|
||||
// Arrange
|
||||
$data = $this->createUserWithPermission([
|
||||
]);
|
||||
|
||||
Passport::actingAs($data->user);
|
||||
|
||||
// Act
|
||||
$response = $this->getJson(route('api.v1.import.index', ['organization' => $data->organization->getKey()]));
|
||||
|
||||
// Assert
|
||||
$response->assertForbidden();
|
||||
}
|
||||
|
||||
public function test_index_returns_importers_if_user_has_permission()
|
||||
{
|
||||
// Arrange
|
||||
$data = $this->createUserWithPermission([
|
||||
'import',
|
||||
]);
|
||||
Passport::actingAs($data->user);
|
||||
|
||||
// Act
|
||||
$response = $this->getJson(route('api.v1.import.index', ['organization' => $data->organization->getKey()]));
|
||||
|
||||
// Assert
|
||||
$response->assertOk();
|
||||
$response->assertJsonStructure([
|
||||
'data' => [
|
||||
[
|
||||
'key',
|
||||
'name',
|
||||
'description',
|
||||
],
|
||||
],
|
||||
]);
|
||||
$toggleTimeEntries = collect($response->json('data'))->where('key', 'toggl_time_entries')->first();
|
||||
$this->assertSame('toggl_time_entries', $toggleTimeEntries['key']);
|
||||
$this->assertSame('Toggl Time Entries', $toggleTimeEntries['name']);
|
||||
$this->assertSame(__('importer.toggl_time_entries.description'), $toggleTimeEntries['description']);
|
||||
}
|
||||
|
||||
public function test_import_fails_if_user_does_not_have_permission()
|
||||
{
|
||||
// Arrange
|
||||
@@ -22,7 +65,7 @@ class ImportEndpointTest extends ApiEndpointTestAbstract
|
||||
Passport::actingAs($data->user);
|
||||
|
||||
// Act
|
||||
$response = $this->postJson(route('api.v1.import.import', ['organization' => $data->organization->id]), [
|
||||
$response = $this->postJson(route('api.v1.import.import', ['organization' => $data->organization->getKey()]), [
|
||||
'type' => 'toggl_time_entries',
|
||||
'data' => base64_encode('some data'),
|
||||
'options' => [],
|
||||
@@ -48,7 +91,7 @@ class ImportEndpointTest extends ApiEndpointTestAbstract
|
||||
Passport::actingAs($user->user);
|
||||
|
||||
// Act
|
||||
$response = $this->postJson(route('api.v1.import.import', ['organization' => $user->organization->id]), [
|
||||
$response = $this->postJson(route('api.v1.import.import', ['organization' => $user->organization->getKey()]), [
|
||||
'type' => 'toggl_time_entries',
|
||||
'data' => base64_encode('some data'),
|
||||
]);
|
||||
@@ -84,7 +127,7 @@ class ImportEndpointTest extends ApiEndpointTestAbstract
|
||||
Passport::actingAs($user->user);
|
||||
|
||||
// Act
|
||||
$response = $this->postJson(route('api.v1.import.import', ['organization' => $user->organization->id]), [
|
||||
$response = $this->postJson(route('api.v1.import.import', ['organization' => $user->organization->getKey()]), [
|
||||
'type' => 'toggl_time_entries',
|
||||
'data' => base64_encode('some data'),
|
||||
]);
|
||||
|
||||
@@ -233,7 +233,7 @@ class TaskEndpointTest extends ApiEndpointTestAbstract
|
||||
$this->assertDatabaseHas(Task::class, [
|
||||
'name' => 'Task 1',
|
||||
'project_id' => $project->getKey(),
|
||||
'organization_id' => $data->organization->id,
|
||||
'organization_id' => $data->organization->getKey(),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ class OrganizationResourceTest extends FilamentTestCase
|
||||
// Arrange
|
||||
$user = User::factory()->create();
|
||||
$organizations = Organization::factory()->state([
|
||||
'user_id' => $user->id,
|
||||
'user_id' => $user->getKey(),
|
||||
])->createMany(5);
|
||||
|
||||
// Act
|
||||
|
||||
@@ -113,7 +113,7 @@ class TimeEntryModelTest extends ModelTestAbstract
|
||||
$this->assertSame('UTC', $timeEntry->start->getTimezone()->toRegionName());
|
||||
$this->assertSame('2021-01-01 13:00:00', $timeEntry->start->toDateTimeString());
|
||||
$this->assertDatabaseHas(TimeEntry::class, [
|
||||
'id' => $timeEntry->id,
|
||||
'id' => $timeEntry->getKey(),
|
||||
'start' => '2021-01-01 13:00:00',
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ class UserServiceTest extends TestCase
|
||||
$userService->changeOwnership($organization, $newOwner);
|
||||
|
||||
// Assert
|
||||
$this->assertSame($newOwner->id, $organization->refresh()->user_id);
|
||||
$this->assertSame($newOwner->getKey(), $organization->refresh()->user_id);
|
||||
$this->assertSame(Role::Owner->value, Membership::whereBelongsTo($newOwner)->whereBelongsTo($organization)->firstOrFail()->role);
|
||||
$this->assertSame(Role::Admin->value, Membership::whereBelongsTo($oldOwner)->whereBelongsTo($organization)->firstOrFail()->role);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user