Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
542681d
Update canyongbs/common to 2.46.0
danharrin Oct 23, 2026
c5f9d5a
chore: Rector type declaration set
danharrin Oct 23, 2026
4a23256
feat(advisory): implement advisory severity sorting feature and relat…
nency-canyon Oct 9, 2026
8621946
feat: Add Advisory Data Transfer Objects and Controllers
nency-canyon Oct 12, 2026
4322728
Refactor advisory data handling and UI components
nency-canyon Oct 13, 2026
cfca68c
Update code formatting and copyright headers
canyon-gbs-automation[bot] Oct 13, 2026
6ed24fe
Enhance advisory page by integrating AssetFilterTabs component and up…
nency-canyon Oct 14, 2026
287219e
Filter out internal advisory updates in search and display functional…
nency-canyon Oct 14, 2026
fe426cf
cleanup
danharrin Oct 15, 2026
50d31d0
php cleanup
danharrin Oct 15, 2026
0871d3d
refactor: remove unused components and improve UI consistency
nency-canyon Oct 16, 2026
01e0108
Resolve PHPStan findings in portal advisory tests
nency-canyon Oct 21, 2026
9f4ab91
refactor: replace Expression with whereRaw for query optimization in …
nency-canyon Oct 21, 2026
72b0018
Restore changes from merge commits
danharrin Oct 23, 2026
cc82007
Update code formatting
danharrin Oct 23, 2026
15acff1
Drop redundant status filter, nested 404 cards and align pipeline gro…
danharrin Oct 23, 2026
944d2d6
Share paginated page data loading and standardise portal tables
danharrin Oct 23, 2026
728339b
Gate portal service request endpoints and restrict service requests t…
danharrin Oct 23, 2026
ea8ddef
Fix portal router error, share load failure state and category articl…
danharrin Oct 23, 2026
9e90045
Only link to service requests from the not found page when service ma…
danharrin Oct 23, 2026
63f9077
Merge remote-tracking branch 'origin/main' into AIDAPP-1595
danharrin Oct 23, 2026
e9198e4
Remove unused BaseTextarea component
danharrin Oct 23, 2026
0069473
Update BootScreen.vue
danharrin Oct 23, 2026
384bf0b
address review findings
danharrin Oct 23, 2026
decb66e
additional findings
danharrin Oct 23, 2026
d360245
Merge remote-tracking branch 'origin/main' into AIDAPP-1595
danharrin Oct 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .cleanup-tasks/2026_10_09_advisory_severity_sort_feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
title: Advisory Severity Sort Feature
created: 2026-10-09
---

## Feature Flags

- App\Features\AdvisorySeveritySortFeature

## Temporary Migrations

## Additional Cleanup

<!--
Only list cleanup that has no home in code. Do NOT restate obvious feature-flag
removals (keep the active path, drop the inactive path), and do NOT include file
paths or line numbers. For non-obvious changes, put a `TODO: Cleanup Task (<tag>)`
comment at the change site and reference the tag here — e.g.
"Search for `TODO: Cleanup Task (some-feature)`".
-->
Original file line number Diff line number Diff line change
Expand Up @@ -70,16 +70,7 @@ class EditKnowledgeBaseItem extends EditRecord
protected static string $resource = KnowledgeBaseItemResource::class;

#[Url]
public ?string $tab = null;

public function mount(int | string $record): void
{
parent::mount($record);

if (! in_array($this->tab, ['resource', 'properties'])) {
$this->tab = 'resource';
}
}
public ?string $tab = 'resource';

public function form(Schema $schema): Schema
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,16 +59,7 @@ class ViewKnowledgeBaseItem extends ViewRecord
protected static string $resource = KnowledgeBaseItemResource::class;

#[Url]
public ?string $tab = null;

public function mount(int | string $record): void
{
parent::mount($record);

if (! in_array($this->tab, ['resource', 'properties', 'concerns', 'health'])) {
$this->tab = 'resource';
}
}
public ?string $tab = 'resource';

public function getTitle(): string|Htmlable
{
Expand Down
69 changes: 40 additions & 29 deletions app-modules/portal/routes/api.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
use AidingApp\Portal\Http\Controllers\KnowledgeManagementPortal\LicenseManagementPortalController;
use AidingApp\Portal\Http\Controllers\KnowledgeManagementPortal\ServiceMonitorStatusController;
use AidingApp\Portal\Http\Controllers\KnowledgeManagementPortal\ServiceRequestTypesController;
use AidingApp\Portal\Http\Controllers\KnowledgeManagementPortal\ShowAdvisoryController;
use AidingApp\Portal\Http\Controllers\KnowledgeManagementPortal\ShowPortalProjectController;
use AidingApp\Portal\Http\Controllers\KnowledgeManagementPortal\ShowServiceMonitorStatusController;
use AidingApp\Portal\Http\Controllers\KnowledgeManagementPortal\StoreKnowledgeBaseArticleVoteController;
Expand All @@ -64,6 +65,7 @@
use AidingApp\Portal\Http\Controllers\KnowledgeManagementPortal\StoreServiceRequestUpdateController;
use AidingApp\Portal\Http\Middleware\EnsureKnowledgeManagementPortalIsEmbeddableAndAuthorized;
use AidingApp\Portal\Http\Middleware\EnsureKnowledgeManagementPortalIsEnabled;
use AidingApp\Portal\Http\Middleware\EnsureKnowledgeManagementPortalServiceManagementIsEnabled;
use AidingApp\ServiceManagement\Http\Controllers\ServiceRequestMediaDownloadController;
use App\Http\Middleware\TrackContactPresence;
use Illuminate\Support\Facades\Route;
Expand Down Expand Up @@ -124,43 +126,48 @@
Route::get('/articles/{article}', [KnowledgeManagementPortalArticleController::class, 'show'])
->name('article.show-canonical');

Route::get('/service-request-type/select', [ServiceRequestTypesController::class, 'index'])
->name('service-request-type.index');
Route::get('/tags', GetKnowledgeManagementPortalTagsController::class)
->name('tags.index');

Route::get('/service-requests', [GetServiceRequestsController::class, 'index'])
->name('service-request.index');
Route::middleware(EnsureKnowledgeManagementPortalServiceManagementIsEnabled::class)->group(function (): void {
Route::get('/service-request-type/select', [ServiceRequestTypesController::class, 'index'])
->name('service-request-type.index');

Route::get('/service-request/create/{type}', GetServiceRequestFormController::class)
->name('service-request.create');
Route::get('/service-requests', [GetServiceRequestsController::class, 'index'])
->name('service-request.index');

Route::post('/service-request/create/{type}', StoreServiceRequestController::class)
->middleware(['auth:sanctum'])
->name('service-request.store');
Route::get('/service-request/create/{type}', GetServiceRequestFormController::class)
->name('service-request.create');

Route::post('/service-request/create/{type}/generate-questions', GenerateServiceRequestQuestionsController::class)
->middleware(['auth:sanctum'])
->name('service-request.generate-questions');
Route::post('/service-request/create/{type}', StoreServiceRequestController::class)
->middleware(['auth:sanctum'])
->name('service-request.store');

Route::post('/service-request/create/{type}/evaluate-ai-resolution', EvaluateServiceRequestAiResolutionController::class)
->middleware(['auth:sanctum'])
->name('service-request.evaluate-ai-resolution');
Route::post('/service-request/create/{type}/generate-questions', GenerateServiceRequestQuestionsController::class)
->middleware(['auth:sanctum'])
->name('service-request.generate-questions');

Route::get('/service-request/request-upload-url', GetServiceRequestUploadUrl::class)
->middleware(['auth:sanctum'])
->name('service-request.request-upload-url');
Route::post('/service-request/create/{type}/evaluate-ai-resolution', EvaluateServiceRequestAiResolutionController::class)
->middleware(['auth:sanctum'])
->name('service-request.evaluate-ai-resolution');

Route::post('/service-request/store-secret', StoreServiceRequestSecretController::class)
->middleware(['auth:sanctum', 'throttle:service-request-secrets'])
->name('service-request.store-secret');
Route::get('/service-request/request-upload-url', GetServiceRequestUploadUrl::class)
->middleware(['auth:sanctum'])
->name('service-request.request-upload-url');

Route::post('/service-request/store-secret', StoreServiceRequestSecretController::class)
->middleware(['auth:sanctum', 'throttle:service-request-secrets'])
->name('service-request.store-secret');

Route::get('/service-request/{serviceRequest}', [GetServiceRequestsController::class, 'show'])
->middleware(['auth:sanctum'])
->name('service-request.show');
Route::post('/service-request-update/store', StoreServiceRequestUpdateController::class)
->name('service-request-update.storeServiceRequestUpdate');
Route::get('/service-request/media/{media}/download', ServiceRequestMediaDownloadController::class)
->name('service-request.media.download');
});

Route::get('/tags', GetKnowledgeManagementPortalTagsController::class)
->name('tags.index');
Route::get('/service-request/{serviceRequest}', [GetServiceRequestsController::class, 'show'])
->name('service-request.show');
Route::post('/service-request-update/store', StoreServiceRequestUpdateController::class)
->name('service-request-update.storeServiceRequestUpdate');
Route::get('/service-request/media/{media}/download', ServiceRequestMediaDownloadController::class)
->name('service-request.media.download');
Route::get('/knowledge-base-article/media/{media}/download', KnowledgeBaseArticleMediaDownloadController::class)
->name('knowledge-base-article.media.download');
Route::post('/knowledge_base_article_vote/store', StoreKnowledgeBaseArticleVoteController::class)
Expand All @@ -173,6 +180,10 @@
Route::get('/advisories', AdvisoryController::class)
->middleware(['auth:sanctum'])
->name('advisories');
Route::get('/advisories/{advisory}', ShowAdvisoryController::class)
->middleware(['auth:sanctum'])
->whereUuid('advisory')
->name('advisories.show');
Route::get('/status', ServiceMonitorStatusController::class)
->middleware(['auth:sanctum'])
->name('status');
Expand Down
2 changes: 2 additions & 0 deletions app-modules/portal/routes/web.php
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@
->name('service');
Route::get('/advisories', RenderKnowledgeManagementPortal::class)
->name('advisories');
Route::get('/advisories/{advisory}', RenderKnowledgeManagementPortal::class)
->name('advisories.show');
Route::get('/assets', RenderKnowledgeManagementPortal::class)
->name('assets');
Route::get('/licenses', RenderKnowledgeManagementPortal::class)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
<!--
<?php

/*
<COPYRIGHT>

Copyright © 2016-2026, Canyon GBS Inc. All rights reserved.
Expand Down Expand Up @@ -30,32 +32,39 @@
<https://www.canyongbs.com> or contact us via email at legal@canyongbs.com.

</COPYRIGHT>
-->
<template>
<div
role="status"
class="flex flex-col items-center justify-center gap-4 py-20 rounded-[var(--rounding-lg)] border border-dashed border-gray-200 bg-white text-center"
>
<slot name="icon">
<span class="flex h-16 w-16 items-center justify-center rounded-2xl bg-gray-100">
<svg
class="h-8 w-8 text-gray-300"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
stroke-width="1.5"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M20.25 7.5l-.625 10.632a2.25 2.25 0 01-2.247 2.118H6.622a2.25 2.25 0 01-2.247-2.118L3.75 7.5M10 11.25h4M3.375 7.5h17.25c.621 0 1.125-.504 1.125-1.125v-1.5c0-.621-.504-1.125-1.125-1.125H3.375c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125z"
/>
</svg>
</span>
</slot>
<div>
<slot />
</div>
</div>
</template>
*/

namespace AidingApp\Portal\DataTransferObjects;

use AidingApp\ServiceManagement\Models\Advisory;
use Spatie\LaravelData\Attributes\MapOutputName;
use Spatie\LaravelData\Data;
use Spatie\LaravelData\Mappers\SnakeCaseMapper;

#[MapOutputName(SnakeCaseMapper::class)]
class AdvisoryData extends Data
{
public function __construct(
public string $id,
public string $title,
public string $description,
public ?AdvisorySeverityData $severity,
public ?AdvisoryStatusData $status,
public string $lastUpdatedAt,
) {}

/**
* Requires the `WithLastUpdatedAt` scope to have been applied to the query.
*/
public static function fromModel(Advisory $advisory): self
{
return new self(
id: $advisory->getKey(),
title: $advisory->title,
description: $advisory->description,
severity: $advisory->severity ? AdvisorySeverityData::fromModel($advisory->severity) : null,
status: $advisory->status ? AdvisoryStatusData::fromModel($advisory->status) : null,
lastUpdatedAt: $advisory->getLastUpdatedAt()->toIso8601String(),
);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
<!--
<?php

/*
<COPYRIGHT>

Copyright © 2016-2026, Canyon GBS Inc. All rights reserved.
Expand Down Expand Up @@ -30,56 +32,47 @@
<https://www.canyongbs.com> or contact us via email at legal@canyongbs.com.

</COPYRIGHT>
-->
<script setup>
import { computed } from 'vue';

const props = defineProps({
tone: {
type: String,
default: 'neutral',
validator: (v) => ['success', 'warning', 'danger', 'neutral'].includes(v),
},
pulse: {
type: Boolean,
default: false,
},
});

const dotClasses = computed(() => {
const classes = {
success: 'bg-green-500',
warning: 'bg-orange-500',
danger: 'bg-red-500',
neutral: 'bg-gray-500',
};
*/

return classes[props.tone] ?? classes.neutral;
});
namespace AidingApp\Portal\DataTransferObjects;

const toneClasses = computed(() => {
const classes = {
success: 'bg-green-100 text-green-800 ring-green-600/20',
warning: 'bg-orange-100 text-orange-800 ring-orange-600/20',
danger: 'bg-red-100 text-red-800 ring-red-600/20',
neutral: 'bg-gray-100 text-gray-700 ring-gray-500/20',
};
use AidingApp\ServiceManagement\Models\Advisory;
use AidingApp\ServiceManagement\Models\AdvisoryUpdate;
use Spatie\LaravelData\Attributes\MapOutputName;
use Spatie\LaravelData\Data;
use Spatie\LaravelData\Mappers\SnakeCaseMapper;

return classes[props.tone] ?? classes.neutral;
});
</script>
#[MapOutputName(SnakeCaseMapper::class)]
class AdvisoryDetailData extends Data
{
/**
* @param list<AdvisoryUpdateData> $updates The advisory's updates, newest first.
*/
public function __construct(
public string $id,
public string $title,
public string $description,
public ?AdvisorySeverityData $severity,
public ?AdvisoryStatusData $status,
public ?string $departmentName,
public array $updates,
) {}

<template>
<span
:class="[
'inline-flex items-center gap-1.5 px-2.5 py-1 text-xs font-semibold ring-1 ring-inset rounded-[var(--rounding-md)]',
toneClasses,
]"
>
<span
:class="['h-1.5 w-1.5 shrink-0 rounded-full', dotClasses, pulse ? 'animate-pulse' : '']"
aria-hidden="true"
/>
<slot />
</span>
</template>
/**
* Expects the advisory's updates to have been loaded newest first.
*/
public static function fromModel(Advisory $advisory): self
{
return new self(
id: $advisory->getKey(),
title: $advisory->title,
description: $advisory->description,
severity: $advisory->severity ? AdvisorySeverityData::fromModel($advisory->severity) : null,
status: $advisory->status ? AdvisoryStatusData::fromModel($advisory->status) : null,
departmentName: $advisory->assignedDepartment?->name,
updates: $advisory->advisoryUpdates
->map(fn (AdvisoryUpdate $advisoryUpdate): AdvisoryUpdateData => AdvisoryUpdateData::fromModel($advisoryUpdate))
->all(),
);
}
}
Loading