mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-08-08 00:02:15 +01:00
Compare commits
11 Commits
6a41563057
...
feature/pa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3c61303d88 | ||
|
|
a70c4fe3fb | ||
|
|
c07c62bfab | ||
|
|
f599757761 | ||
|
|
e54fd2460c | ||
|
|
e997a2b39f | ||
|
|
f0de89b0e8 | ||
|
|
024283273a | ||
|
|
f01ab5fd9d | ||
|
|
4fb18f343f | ||
|
|
ba374c0371 |
32
.dockerignore
Normal file
32
.dockerignore
Normal file
@@ -0,0 +1,32 @@
|
||||
.git
|
||||
.gitmodules
|
||||
.github
|
||||
.DS_Store
|
||||
.fleet
|
||||
.idea
|
||||
.vscode
|
||||
*.log
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
|
||||
.phpunit.cache
|
||||
.phpunit.result.cache
|
||||
coverage
|
||||
test-results
|
||||
playwright-report
|
||||
blob-report
|
||||
playwright/.cache
|
||||
|
||||
node_modules
|
||||
extensions/*/node_modules
|
||||
|
||||
Homestead.json
|
||||
Homestead.yaml
|
||||
auth.json
|
||||
.env.backup
|
||||
.rnd
|
||||
|
||||
_ide_helper.php
|
||||
.phpstorm.meta.php
|
||||
|
||||
storage/logs/*
|
||||
18
.github/workflows/build-onpremise.yml
vendored
18
.github/workflows/build-onpremise.yml
vendored
@@ -8,6 +8,7 @@ on:
|
||||
pull_request:
|
||||
paths:
|
||||
- '.github/workflows/build-onpremise.yml'
|
||||
- 'extensions/manifest.json'
|
||||
- 'docker/prod/**'
|
||||
workflow_dispatch:
|
||||
|
||||
@@ -35,7 +36,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: "Check out code"
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0 # Required for WyriHaximus/github-action-get-previous-tag
|
||||
|
||||
@@ -91,14 +92,23 @@ jobs:
|
||||
if: steps.cache-vendor.outputs.cache-hit != 'true' # Skip if cache hit
|
||||
|
||||
- name: "Use Node.js"
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '20.x'
|
||||
|
||||
- name: "Read extension manifest"
|
||||
id: extension-manifest
|
||||
run: |
|
||||
{
|
||||
echo "invoicing_repository=$(jq -r '.Invoicing.repository' extensions/manifest.json)"
|
||||
echo "invoicing_ref=$(jq -r '.Invoicing.ref' extensions/manifest.json)"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: "Checkout invoicing extension"
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
repository: solidtime-io/extension-invoicing
|
||||
repository: ${{ steps.extension-manifest.outputs.invoicing_repository }}
|
||||
ref: ${{ steps.extension-manifest.outputs.invoicing_ref }}
|
||||
path: extensions/Invoicing
|
||||
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_INVOICING_EXTENSION }}
|
||||
|
||||
|
||||
32
.github/workflows/build-private.yml
vendored
32
.github/workflows/build-private.yml
vendored
@@ -8,6 +8,7 @@ on:
|
||||
pull_request:
|
||||
paths:
|
||||
- '.github/workflows/build-private.yml'
|
||||
- 'extensions/manifest.json'
|
||||
- 'docker/prod/**'
|
||||
workflow_dispatch:
|
||||
permissions:
|
||||
@@ -22,7 +23,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: "Check out code"
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0 # Required for WyriHaximus/github-action-get-previous-tag
|
||||
|
||||
@@ -68,14 +69,27 @@ jobs:
|
||||
run: cat .env
|
||||
|
||||
- name: "Use Node.js"
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '20.x'
|
||||
|
||||
- name: "Read extension manifest"
|
||||
id: extension-manifest
|
||||
run: |
|
||||
{
|
||||
echo "billing_repository=$(jq -r '.Billing.repository' extensions/manifest.json)"
|
||||
echo "billing_ref=$(jq -r '.Billing.ref' extensions/manifest.json)"
|
||||
echo "services_repository=$(jq -r '.Services.repository' extensions/manifest.json)"
|
||||
echo "services_ref=$(jq -r '.Services.ref' extensions/manifest.json)"
|
||||
echo "invoicing_repository=$(jq -r '.Invoicing.repository' extensions/manifest.json)"
|
||||
echo "invoicing_ref=$(jq -r '.Invoicing.ref' extensions/manifest.json)"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: "Checkout billing extension"
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
repository: solidtime-io/extension-billing
|
||||
repository: ${{ steps.extension-manifest.outputs.billing_repository }}
|
||||
ref: ${{ steps.extension-manifest.outputs.billing_ref }}
|
||||
path: extensions/Billing
|
||||
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_BILLING_EXTENSION }}
|
||||
|
||||
@@ -93,9 +107,10 @@ jobs:
|
||||
run: cd extensions/Billing && npm ci
|
||||
|
||||
- name: "Checkout services extension"
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
repository: solidtime-io/extension-services
|
||||
repository: ${{ steps.extension-manifest.outputs.services_repository }}
|
||||
ref: ${{ steps.extension-manifest.outputs.services_ref }}
|
||||
path: extensions/Services
|
||||
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_SERVICES_EXTENSION }}
|
||||
|
||||
@@ -111,9 +126,10 @@ jobs:
|
||||
run: cd extensions/Services && npm ci
|
||||
|
||||
- name: "Checkout invoicing extension"
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
repository: solidtime-io/extension-invoicing
|
||||
repository: ${{ steps.extension-manifest.outputs.invoicing_repository }}
|
||||
ref: ${{ steps.extension-manifest.outputs.invoicing_ref }}
|
||||
path: extensions/Invoicing
|
||||
ssh-key: ${{ secrets.SSH_PRIVATE_KEY_INVOICING_EXTENSION }}
|
||||
|
||||
|
||||
4
.github/workflows/build-public.yml
vendored
4
.github/workflows/build-public.yml
vendored
@@ -36,7 +36,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: "Check out code"
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0 # Required for WyriHaximus/github-action-get-previous-tag
|
||||
|
||||
@@ -92,7 +92,7 @@ jobs:
|
||||
if: steps.cache-vendor.outputs.cache-hit != 'true' # Skip if cache hit
|
||||
|
||||
- name: "Use Node.js"
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '20.x'
|
||||
|
||||
|
||||
2
.github/workflows/generate-api-docs.yml
vendored
2
.github/workflows/generate-api-docs.yml
vendored
@@ -29,7 +29,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: "Setup PHP"
|
||||
uses: shivammathur/setup-php@v2
|
||||
|
||||
4
.github/workflows/npm-build.yml
vendored
4
.github/workflows/npm-build.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: "Setup PHP (for Ziggy)"
|
||||
uses: shivammathur/setup-php@v2
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
run: composer install -n --prefer-dist
|
||||
|
||||
- name: "Use Node.js"
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '20.x'
|
||||
|
||||
|
||||
4
.github/workflows/npm-format-check.yml
vendored
4
.github/workflows/npm-format-check.yml
vendored
@@ -9,10 +9,10 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: "Use Node.js"
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '20.x'
|
||||
|
||||
|
||||
4
.github/workflows/npm-lint.yml
vendored
4
.github/workflows/npm-lint.yml
vendored
@@ -11,10 +11,10 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: "Use Node.js"
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '20.x'
|
||||
|
||||
|
||||
4
.github/workflows/npm-publish-api.yml
vendored
4
.github/workflows/npm-publish-api.yml
vendored
@@ -11,11 +11,11 @@ jobs:
|
||||
id-token: write
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
# Setup .npmrc file to publish to npm
|
||||
- name: Install root project dependencies
|
||||
run: npm ci
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '20.x'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
||||
4
.github/workflows/npm-publish-ui.yml
vendored
4
.github/workflows/npm-publish-ui.yml
vendored
@@ -11,9 +11,9 @@ jobs:
|
||||
id-token: write
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
# Setup .npmrc file to publish to npm
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '20.x'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
||||
4
.github/workflows/npm-test-unit.yml
vendored
4
.github/workflows/npm-test-unit.yml
vendored
@@ -13,10 +13,10 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: "Use Node.js"
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '20.x'
|
||||
|
||||
|
||||
4
.github/workflows/npm-typecheck.yml
vendored
4
.github/workflows/npm-typecheck.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: "Setup PHP (for Ziggy)"
|
||||
uses: shivammathur/setup-php@v2
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
run: composer install -n --prefer-dist
|
||||
|
||||
- name: "Use Node.js"
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '20.x'
|
||||
|
||||
|
||||
2
.github/workflows/phpstan.yml
vendored
2
.github/workflows/phpstan.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: "Setup PHP"
|
||||
uses: shivammathur/setup-php@v2
|
||||
|
||||
6
.github/workflows/phpunit.yml
vendored
6
.github/workflows/phpunit.yml
vendored
@@ -36,7 +36,7 @@ jobs:
|
||||
--health-retries 5
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: "Setup PHP"
|
||||
uses: shivammathur/setup-php@v2
|
||||
@@ -48,7 +48,7 @@ jobs:
|
||||
- name: "Run composer install"
|
||||
run: composer install -n --prefer-dist
|
||||
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '20.x'
|
||||
|
||||
@@ -65,7 +65,7 @@ jobs:
|
||||
php artisan passport:keys
|
||||
|
||||
- name: "Run PHPUnit"
|
||||
run: php artisan test --stop-on-failure --coverage-text --coverage-clover=coverage.xml
|
||||
run: php artisan test --parallel --stop-on-failure --coverage-text --coverage-clover=coverage.xml
|
||||
|
||||
- name: "Upload coverage reports to Codecov"
|
||||
uses: codecov/codecov-action@v7.0.0
|
||||
|
||||
2
.github/workflows/pint.yml
vendored
2
.github/workflows/pint.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: "Check code style"
|
||||
uses: aglipanci/laravel-pint-action@2.6
|
||||
|
||||
8
.github/workflows/playwright.yml
vendored
8
.github/workflows/playwright.yml
vendored
@@ -35,10 +35,10 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: "Setup node"
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '20.x'
|
||||
|
||||
@@ -99,10 +99,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: "Setup node"
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: '20.x'
|
||||
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -26,9 +26,10 @@ yarn-error.log
|
||||
/blob-report/
|
||||
/playwright/.cache/
|
||||
/coverage
|
||||
/extensions
|
||||
/extensions/*
|
||||
!/extensions/.gitkeep
|
||||
!/extensions/extensions_autoload.php
|
||||
!/extensions/manifest.json
|
||||
/auth.json
|
||||
/modules_statuses.json
|
||||
/k8s
|
||||
|
||||
@@ -66,7 +66,7 @@ class UserResource extends Resource
|
||||
->ignore($record?->getKey()),
|
||||
])
|
||||
->rule([
|
||||
'email',
|
||||
'email:rfc,strict',
|
||||
])
|
||||
->maxLength(255),
|
||||
Forms\Components\Toggle::make('is_placeholder')
|
||||
|
||||
@@ -25,7 +25,7 @@ class InvitationStoreRequest extends BaseFormRequest
|
||||
return [
|
||||
'email' => [
|
||||
'required',
|
||||
'email',
|
||||
'email:rfc,strict',
|
||||
],
|
||||
'role' => [
|
||||
'required',
|
||||
|
||||
@@ -41,7 +41,7 @@ class UserUpdateRequest extends BaseFormRequest
|
||||
'max:255',
|
||||
],
|
||||
'email' => [
|
||||
'email',
|
||||
'email:rfc,strict',
|
||||
'max:255',
|
||||
UniqueEloquent::make(User::class, 'email')->ignore($this->user->id)->query(function (Builder $query) {
|
||||
/** @var Builder<User> $query */
|
||||
|
||||
@@ -20,6 +20,11 @@ class RecalculateSpentTimeForProject implements ShouldDispatchAfterCommit, Shoul
|
||||
use Queueable;
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Delete the job if its models no longer exist.
|
||||
*/
|
||||
public bool $deleteWhenMissingModels = true;
|
||||
|
||||
public Project $project;
|
||||
|
||||
/**
|
||||
|
||||
@@ -20,6 +20,11 @@ class RecalculateSpentTimeForTask implements ShouldDispatchAfterCommit, ShouldQu
|
||||
use Queueable;
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Delete the job if its models no longer exist.
|
||||
*/
|
||||
public bool $deleteWhenMissingModels = true;
|
||||
|
||||
public Task $task;
|
||||
|
||||
/**
|
||||
|
||||
@@ -29,7 +29,8 @@ class ClockifyProjectsImporter extends DefaultImporter
|
||||
$records = $reader->getRecords();
|
||||
foreach ($records as $record) {
|
||||
$clientId = null;
|
||||
if ($record['Client'] !== '') {
|
||||
// Newer Clockify exports no longer contain a "Client" column.
|
||||
if (($record['Client'] ?? '') !== '') {
|
||||
$clientId = $this->clientImportHelper->getKey([
|
||||
'name' => $record['Client'],
|
||||
'organization_id' => $this->organization->id,
|
||||
@@ -45,7 +46,7 @@ class ClockifyProjectsImporter extends DefaultImporter
|
||||
'color' => $this->colorService->getRandomColor(),
|
||||
'is_billable' => $record['Billability'] === 'Yes',
|
||||
'billable_rate' => $billableRateKey !== null && $record[$billableRateKey] !== '' ? (int) (((float) $record[$billableRateKey]) * 100) : null,
|
||||
'estimated_time' => $record['Estimated (h)'] !== '' && is_numeric($record['Estimated (h)']) ? (int) ($record['Estimated (h)'] * 3600) : null,
|
||||
'estimated_time' => isset($record['Estimated (h)']) && is_numeric($record['Estimated (h)']) ? (int) ($record['Estimated (h)'] * 3600) : null,
|
||||
'archived_at' => $record['Status'] === 'Archived' ? Carbon::now() : null,
|
||||
]);
|
||||
}
|
||||
@@ -80,7 +81,6 @@ class ClockifyProjectsImporter extends DefaultImporter
|
||||
{
|
||||
$requiredFields = [
|
||||
'Project',
|
||||
'Client',
|
||||
'Status',
|
||||
'Visibility',
|
||||
'Billability',
|
||||
|
||||
@@ -72,7 +72,7 @@ class ClockifyTimeEntriesImporter extends DefaultImporter
|
||||
]);
|
||||
$member = $this->memberImportHelper->getModelById($memberId);
|
||||
$clientId = null;
|
||||
if ($record['Client'] !== '') {
|
||||
if (($record['Client'] ?? '') !== '') {
|
||||
$clientId = $this->clientImportHelper->getKey([
|
||||
'name' => $record['Client'],
|
||||
'organization_id' => $this->organization->id,
|
||||
@@ -215,7 +215,6 @@ class ClockifyTimeEntriesImporter extends DefaultImporter
|
||||
{
|
||||
$requiredFields = [
|
||||
'Project',
|
||||
'Client',
|
||||
'Description',
|
||||
'User',
|
||||
'Group',
|
||||
|
||||
@@ -171,7 +171,7 @@ abstract class DefaultImporter implements ImporterContract
|
||||
}, validate: [
|
||||
'email' => [
|
||||
'required',
|
||||
'email',
|
||||
'email:rfc,strict',
|
||||
'max:255',
|
||||
],
|
||||
]);
|
||||
|
||||
14
extensions/manifest.json
Normal file
14
extensions/manifest.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"Billing": {
|
||||
"repository": "solidtime-io/extension-billing",
|
||||
"ref": "v0.0.1"
|
||||
},
|
||||
"Services": {
|
||||
"repository": "solidtime-io/extension-services",
|
||||
"ref": "v0.0.1"
|
||||
},
|
||||
"Invoicing": {
|
||||
"repository": "solidtime-io/extension-invoicing",
|
||||
"ref": "v0.0.1"
|
||||
}
|
||||
}
|
||||
@@ -527,6 +527,11 @@ const selectedProjectColor = computed(() => {
|
||||
return currentProject.value?.color || 'var(--theme-color-icon-default)';
|
||||
});
|
||||
|
||||
const selectedClientName = computed(() => {
|
||||
const clientId = currentProject.value?.client_id;
|
||||
return clientId ? clientsById.value.get(clientId)?.name : undefined;
|
||||
});
|
||||
|
||||
// This state prevents the selection to jump to random items when the mouse cursor is
|
||||
// over an item and some Item in the Dropdown is selected by keyboard navigation to scroll into view
|
||||
const mouseEnterHighlightActivated = ref(true);
|
||||
@@ -572,15 +577,24 @@ const showCreateProject = ref(false);
|
||||
:size="props.size"
|
||||
:class="twMerge('w-full justify-start overflow-hidden', props.class)">
|
||||
<div
|
||||
class="w-3 h-3 rounded-full shrink-0"
|
||||
class="w-2.5 h-2.5 rounded-full shrink-0"
|
||||
:style="{ backgroundColor: selectedProjectColor }"></div>
|
||||
<span class="truncate shrink-[1] text-text-primary pr-1">{{
|
||||
<span class="truncate shrink-[1] text-text-primary">{{
|
||||
selectedProjectName
|
||||
}}</span>
|
||||
<template v-if="currentTask">
|
||||
<ChevronRightIcon class="w-4 h-4 text-text-tertiary shrink-0" />
|
||||
<ChevronRightIcon class="!size-3 text-text-primary shrink-0 -mx-1" />
|
||||
<span class="truncate shrink-[100]">{{ currentTask.name }}</span>
|
||||
</template>
|
||||
<template v-if="selectedClientName">
|
||||
<span class="dark:text-text-tertiary text-text-quaternary shrink-0"
|
||||
>•</span
|
||||
>
|
||||
<span
|
||||
class="truncate shrink-[200] dark:text-text-tertiary text-text-quaternary"
|
||||
>{{ selectedClientName }}</span
|
||||
>
|
||||
</template>
|
||||
</Button>
|
||||
<button
|
||||
v-if="allowReset && project !== null"
|
||||
|
||||
2
resources/testfiles/clockify_projects_import_test_4.csv
Normal file
2
resources/testfiles/clockify_projects_import_test_4.csv
Normal file
@@ -0,0 +1,2 @@
|
||||
"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"
|
||||
"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","",""
|
||||
|
@@ -0,0 +1,3 @@
|
||||
"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)"
|
||||
"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"
|
||||
"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"
|
||||
|
@@ -0,0 +1,2 @@
|
||||
"Project","Description","Task","User","Group","Email","Tags","Billable","Start Date","Start Time","End Date","End Time","Client"
|
||||
"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"
|
||||
|
@@ -5,7 +5,7 @@
|
||||
{{ __('Your currently running time entry ":description" is now running for more than 8 hours!', ['description' => $timeEntry->description]) }}
|
||||
@endif
|
||||
|
||||
{{ __('If you forgot to stop the Time Tracker you do that in solidtime:') }}
|
||||
{{ __('If you forgot to stop the Time Tracker, you can do so in solidtime:') }}
|
||||
|
||||
@component('mail::button', ['url' => $dashboardUrl])
|
||||
{{ __('Go to solidtime') }}
|
||||
|
||||
@@ -11,11 +11,11 @@ use Illuminate\Http\Client\ConnectionException;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use Tests\TestCase;
|
||||
use Tests\TestCaseWithDatabase;
|
||||
|
||||
#[CoversClass(SelfHostTelemetryCommand::class)]
|
||||
#[CoversClass(ApiService::class)]
|
||||
class SelfHostTelemetryCommandTest extends TestCase
|
||||
class SelfHostTelemetryCommandTest extends TestCaseWithDatabase
|
||||
{
|
||||
public function test_telemetry_sends_data_to_telemetry_endpoint_of_solidtime_cloud(): void
|
||||
{
|
||||
@@ -28,9 +28,12 @@ class SelfHostTelemetryCommandTest extends TestCase
|
||||
$exitCode = $this->withoutMockingConsoleOutput()->artisan('self-host:telemetry');
|
||||
|
||||
// Assert
|
||||
$this->assertSame(Command::SUCCESS, $exitCode);
|
||||
$output = Artisan::output();
|
||||
$this->assertSame('', $output);
|
||||
if ($exitCode !== Command::SUCCESS) {
|
||||
dump($output);
|
||||
}
|
||||
$this->assertSame(Command::SUCCESS, $exitCode);
|
||||
}
|
||||
|
||||
public function test_telemetry_sends_fails_gracefully_if_response_has_error_status_code(): void
|
||||
|
||||
@@ -96,6 +96,29 @@ class ClockifyProjectsImporterTest extends ImporterTestAbstract
|
||||
);
|
||||
}
|
||||
|
||||
public function test_import_of_test_file_without_client_column_succeeds(): void
|
||||
{
|
||||
// Arrange
|
||||
$organization = Organization::factory()->create();
|
||||
$timezone = 'Europe/Vienna';
|
||||
$importer = new ClockifyProjectsImporter;
|
||||
$importer->init($organization);
|
||||
// Newer Clockify exports no longer contain a "Client" column.
|
||||
$data = Storage::disk('testfiles')->get('clockify_projects_import_test_4.csv');
|
||||
|
||||
// Act
|
||||
$importer->importData($data, $timezone);
|
||||
|
||||
// Assert
|
||||
$project = Project::query()->where('organization_id', $organization->id)->where('name', 'Project Without Client Column')->firstOrFail();
|
||||
$this->assertNull($project->client_id);
|
||||
$this->assertSame(100 * 3600, $project->estimated_time);
|
||||
$this->assertEqualsCanonicalizing(
|
||||
['Task 1', 'Task 2'],
|
||||
Task::query()->where('project_id', $project->id)->pluck('name')->all(),
|
||||
);
|
||||
}
|
||||
|
||||
public function test_import_supports_activities_column_alias_for_tasks(): void
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -136,6 +136,46 @@ class ClockifyTimeEntriesImporterTest extends ImporterTestAbstract
|
||||
$this->assertSame(1, $report->tasksCreated);
|
||||
}
|
||||
|
||||
public function test_import_of_test_file_without_client_column_succeeds(): void
|
||||
{
|
||||
// Arrange
|
||||
$organization = Organization::factory()->create();
|
||||
$timezone = 'Europe/Vienna';
|
||||
$importer = new ClockifyTimeEntriesImporter;
|
||||
$importer->init($organization);
|
||||
// Newer Clockify exports no longer contain a "Client" column.
|
||||
$data = Storage::disk('testfiles')->get('clockify_time_entries_import_test_6.csv');
|
||||
|
||||
// Act
|
||||
$importer->importData($data, $timezone);
|
||||
$report = $importer->getReport();
|
||||
|
||||
// Assert
|
||||
$this->assertSame(2, $report->timeEntriesCreated);
|
||||
$this->assertSame(2, $report->projectsCreated);
|
||||
$this->assertSame(0, $report->clientsCreated);
|
||||
}
|
||||
|
||||
public function test_import_of_test_file_with_client_column_but_missing_values_succeeds(): void
|
||||
{
|
||||
// Arrange
|
||||
$organization = Organization::factory()->create();
|
||||
$timezone = 'Europe/Vienna';
|
||||
$importer = new ClockifyTimeEntriesImporter;
|
||||
$importer->init($organization);
|
||||
// Rows shorter than the header are padded with null by the CSV reader.
|
||||
$data = Storage::disk('testfiles')->get('clockify_time_entries_import_test_7.csv');
|
||||
|
||||
// Act
|
||||
$importer->importData($data, $timezone);
|
||||
$report = $importer->getReport();
|
||||
|
||||
// Assert
|
||||
$this->assertSame(1, $report->timeEntriesCreated);
|
||||
$this->assertSame(1, $report->projectsCreated);
|
||||
$this->assertSame(0, $report->clientsCreated);
|
||||
}
|
||||
|
||||
public function test_import_fails_if_month_in_date_is_bigger_than_12(): void
|
||||
{
|
||||
// Arrange
|
||||
|
||||
Reference in New Issue
Block a user