epic/kan-3 #95
@@ -27,44 +27,57 @@ class CreateProject
|
|||||||
{
|
{
|
||||||
return DB::transaction(function () use ($attributes, $actor): Project {
|
return DB::transaction(function () use ($attributes, $actor): Project {
|
||||||
$project = Project::query()->create(Arr::only($attributes, ['key', 'name']));
|
$project = Project::query()->create(Arr::only($attributes, ['key', 'name']));
|
||||||
$baseTemplate = $this->createProjectBaseTemplate->handle($project);
|
$creationMode = Arr::get($attributes, 'creation_mode', 'scratch');
|
||||||
|
$sourceTemplateId = Arr::get($attributes, 'source_template_id');
|
||||||
|
$baseTemplate = null;
|
||||||
|
$sourceTemplate = null;
|
||||||
|
|
||||||
$project->forceFill([
|
if ($creationMode === 'template' && $sourceTemplateId !== null) {
|
||||||
'project_template_id' => $baseTemplate->id,
|
$sourceTemplate = ProjectTemplate::query()
|
||||||
])->save();
|
->published()
|
||||||
|
->findOrFail((int) $sourceTemplateId);
|
||||||
|
|
||||||
|
$project->forceFill([
|
||||||
|
'project_template_id' => $sourceTemplate->id,
|
||||||
|
])->save();
|
||||||
|
} else {
|
||||||
|
$baseTemplate = $this->createProjectBaseTemplate->handle($project);
|
||||||
|
|
||||||
|
$project->forceFill([
|
||||||
|
'project_template_id' => $baseTemplate->id,
|
||||||
|
])->save();
|
||||||
|
}
|
||||||
|
|
||||||
$project = $project->fresh() ?? $project;
|
$project = $project->fresh() ?? $project;
|
||||||
|
|
||||||
$this->initializeProjectTicketMetadata->handle($project);
|
$this->initializeProjectTicketMetadata->handle($project);
|
||||||
$this->permissionService->grant($actor, Permission::ProjectAdmin, $project);
|
$this->permissionService->grant($actor, Permission::ProjectAdmin, $project);
|
||||||
|
|
||||||
$creationMode = $attributes['creation_mode'] ?? 'scratch';
|
if ($creationMode === 'duplicate' && Arr::get($attributes, 'source_project_id') !== null) {
|
||||||
|
$sourceProject = Project::query()->findOrFail((int) Arr::get($attributes, 'source_project_id'));
|
||||||
if ($creationMode === 'duplicate' && isset($attributes['source_project_id'])) {
|
|
||||||
$sourceProject = Project::query()->findOrFail((int) $attributes['source_project_id']);
|
|
||||||
|
|
||||||
$this->duplicateProjectConfiguration->handle(
|
$this->duplicateProjectConfiguration->handle(
|
||||||
$sourceProject,
|
$sourceProject,
|
||||||
$project,
|
$project,
|
||||||
array_values($attributes['copy_areas'] ?? []),
|
array_values(Arr::get($attributes, 'copy_areas', [])),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($creationMode === 'template' && isset($attributes['source_template_id'])) {
|
if ($creationMode === 'template' && $sourceTemplate !== null) {
|
||||||
$template = ProjectTemplate::query()->findOrFail((int) $attributes['source_template_id']);
|
|
||||||
|
|
||||||
$this->duplicateProjectConfiguration->apply(
|
$this->duplicateProjectConfiguration->apply(
|
||||||
$project,
|
$project->fresh() ?? $project,
|
||||||
$template->configuration_snapshot ?? [],
|
$sourceTemplate->configuration_snapshot ?? [],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
$baseTemplate->forceFill([
|
if ($baseTemplate !== null) {
|
||||||
'configuration_snapshot' => $this->duplicateProjectConfiguration->export(
|
$baseTemplate->forceFill([
|
||||||
$project->fresh() ?? $project,
|
'configuration_snapshot' => $this->duplicateProjectConfiguration->export(
|
||||||
DuplicateProjectConfiguration::copyableAreas(),
|
$project->fresh() ?? $project,
|
||||||
),
|
DuplicateProjectConfiguration::copyableAreas(),
|
||||||
])->save();
|
),
|
||||||
|
])->save();
|
||||||
|
}
|
||||||
|
|
||||||
return $project->fresh() ?? $project;
|
return $project->fresh() ?? $project;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ class ResolveProjectTemplateState
|
|||||||
'effectiveConfiguration' => array_replace($templateConfiguration, array_intersect_key($currentConfiguration, $overriddenAreaValues)),
|
'effectiveConfiguration' => array_replace($templateConfiguration, array_intersect_key($currentConfiguration, $overriddenAreaValues)),
|
||||||
'inheritedAreas' => $inheritedAreas,
|
'inheritedAreas' => $inheritedAreas,
|
||||||
'inheritedConfiguration' => array_intersect_key($templateConfiguration, $inheritedAreaValues),
|
'inheritedConfiguration' => array_intersect_key($templateConfiguration, $inheritedAreaValues),
|
||||||
'inheritsFromTemplate' => true,
|
'inheritsFromTemplate' => $projectTemplate->is_published,
|
||||||
'overriddenAreas' => $overriddenAreas,
|
'overriddenAreas' => $overriddenAreas,
|
||||||
'overriddenConfiguration' => array_intersect_key($currentConfiguration, $overriddenAreaValues),
|
'overriddenConfiguration' => array_intersect_key($currentConfiguration, $overriddenAreaValues),
|
||||||
'templateConfiguration' => $templateConfiguration,
|
'templateConfiguration' => $templateConfiguration,
|
||||||
|
|||||||
+8
-40
@@ -59,83 +59,51 @@ class Project extends Model
|
|||||||
|
|
||||||
public function boardColumns(): HasMany
|
public function boardColumns(): HasMany
|
||||||
{
|
{
|
||||||
return $this->hasMany(
|
return $this->hasMany(BoardColumn::class)
|
||||||
BoardColumn::class,
|
|
||||||
$this->project_template_id === null ? 'project_id' : 'project_template_id',
|
|
||||||
$this->project_template_id === null ? 'id' : 'project_template_id',
|
|
||||||
)
|
|
||||||
->orderBy('position');
|
->orderBy('position');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function ticketFieldDefinitions(): HasMany
|
public function ticketFieldDefinitions(): HasMany
|
||||||
{
|
{
|
||||||
return $this->hasMany(
|
return $this->hasMany(TicketFieldDefinition::class)
|
||||||
TicketFieldDefinition::class,
|
|
||||||
$this->project_template_id === null ? 'project_id' : 'project_template_id',
|
|
||||||
$this->project_template_id === null ? 'id' : 'project_template_id',
|
|
||||||
)
|
|
||||||
->orderBy('position');
|
->orderBy('position');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function ticketPriorities(): HasMany
|
public function ticketPriorities(): HasMany
|
||||||
{
|
{
|
||||||
return $this->hasMany(
|
return $this->hasMany(TicketPriority::class)
|
||||||
TicketPriority::class,
|
|
||||||
$this->project_template_id === null ? 'project_id' : 'project_template_id',
|
|
||||||
$this->project_template_id === null ? 'id' : 'project_template_id',
|
|
||||||
)
|
|
||||||
->orderBy('position');
|
->orderBy('position');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function ticketLinkTypes(): HasMany
|
public function ticketLinkTypes(): HasMany
|
||||||
{
|
{
|
||||||
return $this->hasMany(
|
return $this->hasMany(TicketLinkType::class)
|
||||||
TicketLinkType::class,
|
|
||||||
$this->project_template_id === null ? 'project_id' : 'project_template_id',
|
|
||||||
$this->project_template_id === null ? 'id' : 'project_template_id',
|
|
||||||
)
|
|
||||||
->orderBy('position');
|
->orderBy('position');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function ticketStatuses(): HasMany
|
public function ticketStatuses(): HasMany
|
||||||
{
|
{
|
||||||
return $this->hasMany(
|
return $this->hasMany(TicketStatus::class)
|
||||||
TicketStatus::class,
|
|
||||||
$this->project_template_id === null ? 'project_id' : 'project_template_id',
|
|
||||||
$this->project_template_id === null ? 'id' : 'project_template_id',
|
|
||||||
)
|
|
||||||
->orderBy('board_column_id')
|
->orderBy('board_column_id')
|
||||||
->orderBy('position');
|
->orderBy('position');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function ticketTypes(): HasMany
|
public function ticketTypes(): HasMany
|
||||||
{
|
{
|
||||||
return $this->hasMany(
|
return $this->hasMany(TicketType::class)
|
||||||
TicketType::class,
|
|
||||||
$this->project_template_id === null ? 'project_id' : 'project_template_id',
|
|
||||||
$this->project_template_id === null ? 'id' : 'project_template_id',
|
|
||||||
)
|
|
||||||
->orderBy('position');
|
->orderBy('position');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function repositoryConnections(): HasMany
|
public function repositoryConnections(): HasMany
|
||||||
{
|
{
|
||||||
return $this->hasMany(
|
return $this->hasMany(RepositoryConnection::class)
|
||||||
RepositoryConnection::class,
|
|
||||||
$this->project_template_id === null ? 'project_id' : 'project_template_id',
|
|
||||||
$this->project_template_id === null ? 'id' : 'project_template_id',
|
|
||||||
)
|
|
||||||
->orderBy('provider')
|
->orderBy('provider')
|
||||||
->orderBy('repository_full_name');
|
->orderBy('repository_full_name');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function notificationActions(): HasMany
|
public function notificationActions(): HasMany
|
||||||
{
|
{
|
||||||
return $this->hasMany(
|
return $this->hasMany(NotificationAction::class)
|
||||||
NotificationAction::class,
|
|
||||||
$this->project_template_id === null ? 'project_id' : 'project_template_id',
|
|
||||||
$this->project_template_id === null ? 'id' : 'project_template_id',
|
|
||||||
)
|
|
||||||
->orderBy('name')
|
->orderBy('name')
|
||||||
->orderBy('id');
|
->orderBy('id');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import {
|
|||||||
} from '@/components/ui/sidebar';
|
} from '@/components/ui/sidebar';
|
||||||
import { dashboard } from '@/routes';
|
import { dashboard } from '@/routes';
|
||||||
import { index as adminUsers } from '@/routes/admin/users';
|
import { index as adminUsers } from '@/routes/admin/users';
|
||||||
|
import { index as projectTemplatesIndex } from '@/routes/project-templates';
|
||||||
import { index as projectsIndex } from '@/routes/projects';
|
import { index as projectsIndex } from '@/routes/projects';
|
||||||
import { index as teamsIndex } from '@/routes/teams';
|
import { index as teamsIndex } from '@/routes/teams';
|
||||||
import type { NavItem } from '@/types';
|
import type { NavItem } from '@/types';
|
||||||
@@ -48,7 +49,7 @@ const mainNavItems = computed<NavItem[]>(() => {
|
|||||||
if (page.props.auth.permissions.projectCreate) {
|
if (page.props.auth.permissions.projectCreate) {
|
||||||
items.push({
|
items.push({
|
||||||
title: 'Project templates',
|
title: 'Project templates',
|
||||||
href: '/project-templates',
|
href: projectTemplatesIndex(),
|
||||||
icon: Layers3,
|
icon: Layers3,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,11 @@ import {
|
|||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import AppLayout from '@/layouts/AppLayout.vue';
|
import AppLayout from '@/layouts/AppLayout.vue';
|
||||||
import { show as projectShow } from '@/routes/projects/index';
|
import { index as projectsIndex, show as projectShow } from '@/routes/projects/index';
|
||||||
|
import {
|
||||||
|
create as projectTemplateCreate,
|
||||||
|
store as projectTemplateStore,
|
||||||
|
} from '@/routes/projects/templates';
|
||||||
import type { BreadcrumbItem, ProjectTemplatesCreatePageProps } from '@/types';
|
import type { BreadcrumbItem, ProjectTemplatesCreatePageProps } from '@/types';
|
||||||
|
|
||||||
const props = defineProps<ProjectTemplatesCreatePageProps>();
|
const props = defineProps<ProjectTemplatesCreatePageProps>();
|
||||||
@@ -23,7 +27,7 @@ const props = defineProps<ProjectTemplatesCreatePageProps>();
|
|||||||
const breadcrumbs: BreadcrumbItem[] = [
|
const breadcrumbs: BreadcrumbItem[] = [
|
||||||
{
|
{
|
||||||
title: 'Projects',
|
title: 'Projects',
|
||||||
href: '/projects',
|
href: projectsIndex(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: props.project.name,
|
title: props.project.name,
|
||||||
@@ -31,7 +35,7 @@ const breadcrumbs: BreadcrumbItem[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Save as new template',
|
title: 'Save as new template',
|
||||||
href: `/projects/${props.project.id}/templates/create`,
|
href: projectTemplateCreate(props.project.id),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -50,7 +54,7 @@ const copyAreasError = computed(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const submit = (): void => {
|
const submit = (): void => {
|
||||||
form.post(`/projects/${props.project.id}/templates`, {
|
form.post(projectTemplateStore.url(props.project.id), {
|
||||||
preserveScroll: true,
|
preserveScroll: true,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -15,7 +15,11 @@ import {
|
|||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import AppLayout from '@/layouts/AppLayout.vue';
|
import AppLayout from '@/layouts/AppLayout.vue';
|
||||||
import { index as projectTemplatesIndex } from '@/routes/project-templates';
|
import {
|
||||||
|
edit as projectTemplatesEdit,
|
||||||
|
index as projectTemplatesIndex,
|
||||||
|
update as projectTemplatesUpdate,
|
||||||
|
} from '@/routes/project-templates';
|
||||||
import type { BreadcrumbItem, ProjectTemplatesEditPageProps } from '@/types';
|
import type { BreadcrumbItem, ProjectTemplatesEditPageProps } from '@/types';
|
||||||
|
|
||||||
const props = defineProps<ProjectTemplatesEditPageProps>();
|
const props = defineProps<ProjectTemplatesEditPageProps>();
|
||||||
@@ -27,7 +31,7 @@ const breadcrumbs: BreadcrumbItem[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: props.template.name,
|
title: props.template.name,
|
||||||
href: `/project-templates/${props.template.id}/edit`,
|
href: projectTemplatesEdit(props.template.id),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -48,7 +52,7 @@ const copyAreasError = computed(() => {
|
|||||||
const linkedProjectCount = computed(() => props.template.linked_project_count ?? 0);
|
const linkedProjectCount = computed(() => props.template.linked_project_count ?? 0);
|
||||||
|
|
||||||
const submit = (): void => {
|
const submit = (): void => {
|
||||||
form.patch(`/project-templates/${props.template.id}`, {
|
form.patch(projectTemplatesUpdate.url(props.template.id), {
|
||||||
preserveScroll: true,
|
preserveScroll: true,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,7 +11,11 @@ import {
|
|||||||
CardTitle,
|
CardTitle,
|
||||||
} from '@/components/ui/card';
|
} from '@/components/ui/card';
|
||||||
import AppLayout from '@/layouts/AppLayout.vue';
|
import AppLayout from '@/layouts/AppLayout.vue';
|
||||||
import { edit as projectTemplatesEdit } from '@/routes/project-templates';
|
import {
|
||||||
|
destroy as projectTemplatesDestroy,
|
||||||
|
edit as projectTemplatesEdit,
|
||||||
|
index as projectTemplatesIndex,
|
||||||
|
} from '@/routes/project-templates';
|
||||||
import { create as projectsCreate, index as projectsIndex } from '@/routes/projects/index';
|
import { create as projectsCreate, index as projectsIndex } from '@/routes/projects/index';
|
||||||
import type { BreadcrumbItem, ProjectTemplatesIndexPageProps } from '@/types';
|
import type { BreadcrumbItem, ProjectTemplatesIndexPageProps } from '@/types';
|
||||||
|
|
||||||
@@ -20,12 +24,12 @@ const props = defineProps<ProjectTemplatesIndexPageProps>();
|
|||||||
const breadcrumbs: BreadcrumbItem[] = [
|
const breadcrumbs: BreadcrumbItem[] = [
|
||||||
{
|
{
|
||||||
title: 'Project templates',
|
title: 'Project templates',
|
||||||
href: '/project-templates',
|
href: projectTemplatesIndex(),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const removeTemplate = (templateId: number): void => {
|
const removeTemplate = (templateId: number): void => {
|
||||||
router.delete(`/project-templates/${templateId}`, {
|
router.delete(projectTemplatesDestroy.url(templateId), {
|
||||||
preserveScroll: true,
|
preserveScroll: true,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ const submit = (): void => {
|
|||||||
form.source_template_id = '';
|
form.source_template_id = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
form.post(ProjectController.store(), {
|
form.post(ProjectController.store.url(), {
|
||||||
preserveScroll: true,
|
preserveScroll: true,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import {
|
|||||||
notifications as notificationSettings,
|
notifications as notificationSettings,
|
||||||
repositories as repositorySettings,
|
repositories as repositorySettings,
|
||||||
} from '@/routes/projects/settings';
|
} from '@/routes/projects/settings';
|
||||||
|
import { create as projectTemplateCreate } from '@/routes/projects/templates';
|
||||||
import type { BreadcrumbItem, ProjectShowPageProps } from '@/types';
|
import type { BreadcrumbItem, ProjectShowPageProps } from '@/types';
|
||||||
|
|
||||||
const props = defineProps<ProjectShowPageProps>();
|
const props = defineProps<ProjectShowPageProps>();
|
||||||
@@ -99,7 +100,7 @@ const projectAreas = [
|
|||||||
as-child
|
as-child
|
||||||
variant="outline"
|
variant="outline"
|
||||||
>
|
>
|
||||||
<Link :href="`/projects/${project.id}/templates/create`">
|
<Link :href="projectTemplateCreate(project.id)">
|
||||||
Save as new template
|
Save as new template
|
||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -89,12 +89,9 @@ const submitSwitchTemplate = (): void => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
switchTemplateForm.submit(
|
switchTemplateForm.post(ProjectController.switchTemplate.url(props.project.id), {
|
||||||
ProjectController.switchTemplate(props.project.id),
|
preserveScroll: true,
|
||||||
{
|
});
|
||||||
preserveScroll: true,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const submitResetTemplate = (): void => {
|
const submitResetTemplate = (): void => {
|
||||||
@@ -102,7 +99,7 @@ const submitResetTemplate = (): void => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
resetTemplateForm.submit(ProjectController.resetTemplate(props.project.id), {
|
resetTemplateForm.post(ProjectController.resetTemplate.url(props.project.id), {
|
||||||
preserveScroll: true,
|
preserveScroll: true,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -112,7 +109,7 @@ const submitDelete = (): void => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteForm.submit(ProjectController.destroy(props.project.id), {
|
deleteForm.delete(ProjectController.destroy.url(props.project.id), {
|
||||||
preserveScroll: true,
|
preserveScroll: true,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
namespace Tests\Feature\Api;
|
namespace Tests\Feature\Api;
|
||||||
|
|
||||||
use App\Actions\ProjectTicketMetadata\InitializeProjectTicketMetadata;
|
|
||||||
use App\Actions\Projects\DuplicateProjectConfiguration;
|
use App\Actions\Projects\DuplicateProjectConfiguration;
|
||||||
|
use App\Actions\ProjectTicketMetadata\InitializeProjectTicketMetadata;
|
||||||
use App\Models\BoardColumn;
|
use App\Models\BoardColumn;
|
||||||
use App\Models\Project;
|
use App\Models\Project;
|
||||||
use App\Models\ProjectTemplate;
|
use App\Models\ProjectTemplate;
|
||||||
@@ -102,15 +102,19 @@ class ProjectApiTest extends TestCase
|
|||||||
|
|
||||||
$project->refresh();
|
$project->refresh();
|
||||||
|
|
||||||
$this->assertNotSame($template->id, $project->project_template_id);
|
$this->assertSame($template->id, $project->project_template_id);
|
||||||
$this->assertDatabaseHas('project_templates', [
|
$this->assertDatabaseMissing('project_templates', [
|
||||||
'id' => $project->project_template_id,
|
|
||||||
'project_id' => $project->id,
|
'project_id' => $project->id,
|
||||||
'is_published' => false,
|
'is_published' => false,
|
||||||
]);
|
]);
|
||||||
$this->assertFalse($project->show_author);
|
$this->assertFalse($project->show_author);
|
||||||
$this->assertFalse($project->show_story_points);
|
$this->assertFalse($project->show_story_points);
|
||||||
$this->assertSame(['Spike'], $project->ticketTypes()->pluck('name')->all());
|
$this->assertSame(['Spike'], $project->ticketTypes()->pluck('name')->all());
|
||||||
|
$this->assertDatabaseHas('ticket_types', [
|
||||||
|
'project_id' => $project->id,
|
||||||
|
'project_template_id' => $template->id,
|
||||||
|
'name' => 'Spike',
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_project_api_exposes_template_context_and_overrides(): void
|
public function test_project_api_exposes_template_context_and_overrides(): void
|
||||||
|
|||||||
@@ -285,9 +285,8 @@ class ProjectControllerTest extends TestCase
|
|||||||
|
|
||||||
$project->refresh();
|
$project->refresh();
|
||||||
|
|
||||||
$this->assertNotSame($template->id, $project->project_template_id);
|
$this->assertSame($template->id, $project->project_template_id);
|
||||||
$this->assertDatabaseHas('project_templates', [
|
$this->assertDatabaseMissing('project_templates', [
|
||||||
'id' => $project->project_template_id,
|
|
||||||
'project_id' => $project->id,
|
'project_id' => $project->id,
|
||||||
'is_published' => false,
|
'is_published' => false,
|
||||||
]);
|
]);
|
||||||
@@ -295,6 +294,7 @@ class ProjectControllerTest extends TestCase
|
|||||||
$this->assertSame(false, $project->show_story_points);
|
$this->assertSame(false, $project->show_story_points);
|
||||||
$this->assertDatabaseHas('ticket_types', [
|
$this->assertDatabaseHas('ticket_types', [
|
||||||
'project_id' => $project->id,
|
'project_id' => $project->id,
|
||||||
|
'project_template_id' => $template->id,
|
||||||
'name' => 'Bug',
|
'name' => 'Bug',
|
||||||
'position' => 1,
|
'position' => 1,
|
||||||
'is_parent' => false,
|
'is_parent' => false,
|
||||||
@@ -661,6 +661,11 @@ class ProjectControllerTest extends TestCase
|
|||||||
$this->assertTrue($project->show_author);
|
$this->assertTrue($project->show_author);
|
||||||
$this->assertTrue($project->show_story_points);
|
$this->assertTrue($project->show_story_points);
|
||||||
$this->assertSame(['Incident', 'Maintenance'], $project->ticketTypes()->pluck('name')->all());
|
$this->assertSame(['Incident', 'Maintenance'], $project->ticketTypes()->pluck('name')->all());
|
||||||
|
$this->assertDatabaseHas('ticket_types', [
|
||||||
|
'project_id' => $project->id,
|
||||||
|
'project_template_id' => $newTemplate->id,
|
||||||
|
'name' => 'Incident',
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_project_admins_can_reset_projects_to_their_current_template_baseline(): void
|
public function test_project_admins_can_reset_projects_to_their_current_template_baseline(): void
|
||||||
@@ -711,6 +716,11 @@ class ProjectControllerTest extends TestCase
|
|||||||
$this->assertTrue($project->show_author);
|
$this->assertTrue($project->show_author);
|
||||||
$this->assertTrue($project->show_story_points);
|
$this->assertTrue($project->show_story_points);
|
||||||
$this->assertSame(['Bug', 'Feature'], $project->ticketTypes()->pluck('name')->all());
|
$this->assertSame(['Bug', 'Feature'], $project->ticketTypes()->pluck('name')->all());
|
||||||
|
$this->assertDatabaseHas('ticket_types', [
|
||||||
|
'project_id' => $project->id,
|
||||||
|
'project_template_id' => $template->id,
|
||||||
|
'name' => 'Bug',
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_project_template_switch_requires_exact_project_key_confirmation(): void
|
public function test_project_template_switch_requires_exact_project_key_confirmation(): void
|
||||||
|
|||||||
@@ -192,15 +192,32 @@ class ProjectTemplateControllerTest extends TestCase
|
|||||||
'show_author' => false,
|
'show_author' => false,
|
||||||
'show_story_points' => false,
|
'show_story_points' => false,
|
||||||
]);
|
]);
|
||||||
|
TicketType::factory()->create([
|
||||||
|
'project_id' => $sourceProject->id,
|
||||||
|
'name' => 'Feature',
|
||||||
|
'position' => 1,
|
||||||
|
'is_parent' => false,
|
||||||
|
]);
|
||||||
|
|
||||||
$template = ProjectTemplate::factory()->create([
|
$template = ProjectTemplate::factory()->create([
|
||||||
'name' => 'Platform Template',
|
'name' => 'Platform Template',
|
||||||
'slug' => 'platform-template',
|
'slug' => 'platform-template',
|
||||||
'copy_areas' => [DuplicateProjectConfiguration::COPY_PROJECT_SETTINGS],
|
'copy_areas' => [
|
||||||
|
DuplicateProjectConfiguration::COPY_PROJECT_SETTINGS,
|
||||||
|
DuplicateProjectConfiguration::COPY_TICKET_TYPES,
|
||||||
|
],
|
||||||
'configuration_snapshot' => [
|
'configuration_snapshot' => [
|
||||||
DuplicateProjectConfiguration::COPY_PROJECT_SETTINGS => [
|
DuplicateProjectConfiguration::COPY_PROJECT_SETTINGS => [
|
||||||
'show_author' => false,
|
'show_author' => false,
|
||||||
'show_story_points' => false,
|
'show_story_points' => false,
|
||||||
],
|
],
|
||||||
|
DuplicateProjectConfiguration::COPY_TICKET_TYPES => [
|
||||||
|
[
|
||||||
|
'name' => 'Bug',
|
||||||
|
'position' => 1,
|
||||||
|
'is_parent' => false,
|
||||||
|
],
|
||||||
|
],
|
||||||
],
|
],
|
||||||
'source_project_id' => $sourceProject->id,
|
'source_project_id' => $sourceProject->id,
|
||||||
]);
|
]);
|
||||||
@@ -214,6 +231,18 @@ class ProjectTemplateControllerTest extends TestCase
|
|||||||
'show_author' => true,
|
'show_author' => true,
|
||||||
'show_story_points' => false,
|
'show_story_points' => false,
|
||||||
]);
|
]);
|
||||||
|
TicketType::factory()->create([
|
||||||
|
'project_id' => $inheritingProject->id,
|
||||||
|
'name' => 'Bug',
|
||||||
|
'position' => 1,
|
||||||
|
'is_parent' => false,
|
||||||
|
]);
|
||||||
|
TicketType::factory()->create([
|
||||||
|
'project_id' => $overriddenProject->id,
|
||||||
|
'name' => 'Custom',
|
||||||
|
'position' => 1,
|
||||||
|
'is_parent' => false,
|
||||||
|
]);
|
||||||
|
|
||||||
$sourceProject->forceFill([
|
$sourceProject->forceFill([
|
||||||
'show_author' => true,
|
'show_author' => true,
|
||||||
@@ -226,7 +255,10 @@ class ProjectTemplateControllerTest extends TestCase
|
|||||||
->patch(route('project-templates.update', $template), [
|
->patch(route('project-templates.update', $template), [
|
||||||
'name' => 'Platform Template v2',
|
'name' => 'Platform Template v2',
|
||||||
'slug' => 'should-not-change',
|
'slug' => 'should-not-change',
|
||||||
'copy_areas' => [DuplicateProjectConfiguration::COPY_PROJECT_SETTINGS],
|
'copy_areas' => [
|
||||||
|
DuplicateProjectConfiguration::COPY_PROJECT_SETTINGS,
|
||||||
|
DuplicateProjectConfiguration::COPY_TICKET_TYPES,
|
||||||
|
],
|
||||||
'confirm_linked_projects_update' => true,
|
'confirm_linked_projects_update' => true,
|
||||||
])
|
])
|
||||||
->assertRedirect('/project-templates');
|
->assertRedirect('/project-templates');
|
||||||
@@ -242,12 +274,26 @@ class ProjectTemplateControllerTest extends TestCase
|
|||||||
'show_author' => true,
|
'show_author' => true,
|
||||||
'show_story_points' => true,
|
'show_story_points' => true,
|
||||||
],
|
],
|
||||||
|
DuplicateProjectConfiguration::COPY_TICKET_TYPES => [
|
||||||
|
[
|
||||||
|
'name' => 'Feature',
|
||||||
|
'position' => 1,
|
||||||
|
'is_parent' => false,
|
||||||
|
],
|
||||||
|
],
|
||||||
], $template->configuration_snapshot);
|
], $template->configuration_snapshot);
|
||||||
|
|
||||||
$this->assertTrue($inheritingProject->show_author);
|
$this->assertTrue($inheritingProject->show_author);
|
||||||
$this->assertTrue($inheritingProject->show_story_points);
|
$this->assertTrue($inheritingProject->show_story_points);
|
||||||
|
$this->assertSame(['Feature'], $inheritingProject->ticketTypes()->pluck('name')->all());
|
||||||
|
$this->assertDatabaseHas('ticket_types', [
|
||||||
|
'project_id' => $inheritingProject->id,
|
||||||
|
'project_template_id' => $template->id,
|
||||||
|
'name' => 'Feature',
|
||||||
|
]);
|
||||||
|
|
||||||
$this->assertTrue($overriddenProject->show_author);
|
$this->assertTrue($overriddenProject->show_author);
|
||||||
$this->assertFalse($overriddenProject->show_story_points);
|
$this->assertFalse($overriddenProject->show_story_points);
|
||||||
|
$this->assertSame(['Custom'], $overriddenProject->ticketTypes()->pluck('name')->all());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user