Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
f76d1f4
Add user Chris
CHRLOUIE Feb 24, 2026
49243e8
Fix 'Enable Variable Substitution' toggle spelling
CHRLOUIE Feb 24, 2026
2ae0944
Update package-lock.json (remove unused dependencies)
CHRLOUIE Feb 24, 2026
68bb90b
Add postal code validation/masking to text input element
CHRLOUIE Feb 24, 2026
c89c492
Change postal code mask to use maska instead of regex
CHRLOUIE Feb 24, 2026
d671296
Merge pull request #444 from bcgov/postal-code
CHRLOUIE Feb 24, 2026
ac74c70
Disable form version duplication
CHRLOUIE Feb 25, 2026
d5d1f71
Add reason for disabling duplicate form version comment
CHRLOUIE Feb 25, 2026
5a18c0e
Merge pull request #445 from bcgov/disable-form-version-duplication
CHRLOUIE Feb 25, 2026
fe0aa91
Autolinter
JohYoshidaBCGov Mar 3, 2026
06cc09b
Add formatting and validation to the number and currency input's fields
CHRLOUIE Feb 27, 2026
eb4d23e
Fix visibility toggles being reset on import
CHRLOUIE Mar 5, 2026
3ff3b2f
Fix data binding source defaulting to first source on import
CHRLOUIE Mar 5, 2026
e0ca0d9
Merge pull request #449 from bcgov/number-currency-input-fields-forma…
CHRLOUIE Mar 9, 2026
7c4e83f
Fix: create fallback "Imported Data Source" with correct next ID on f…
CHRLOUIE Mar 6, 2026
7765bdf
Merge branch 'dev' into form-importer-fix
CHRLOUIE Mar 9, 2026
dfd9785
Add warning log message and notification when "Imported Data Source" …
CHRLOUIE Mar 9, 2026
bf12e8d
Update existing form elements during reimport so data bindings can be…
CHRLOUIE Mar 9, 2026
4d787d1
Add checkbox groups and currency inputs to the importer
CHRLOUIE Mar 9, 2026
381e340
Fix number and currency input's field constraint validations
CHRLOUIE Mar 11, 2026
b389ebd
Add readonly/required toggle buttons for Always and Portal options
JohYoshidaBCGov Mar 11, 2026
f3500a5
Merge branch 'dev' into ADO-3613-Allow-field-to-be-readonly/required-…
JohYoshidaBCGov Mar 11, 2026
355b71d
Swap positions of dev and test
JohYoshidaBCGov Mar 11, 2026
25a7c65
Factor out template selector
JohYoshidaBCGov Mar 12, 2026
5890906
Factor out Name field
JohYoshidaBCGov Mar 12, 2026
494e984
Factor out Reference ID field
JohYoshidaBCGov Mar 12, 2026
96779b1
Merge pull request #452 from bcgov/fix-NumberInput-CurrencyInput
CHRLOUIE Mar 12, 2026
9b94fdd
Merge pull request #453 from bcgov/ADO-3613-Allow-field-to-be-readonl…
JohYoshidaBCGov Mar 12, 2026
90e7609
Factor out Element Type field
JohYoshidaBCGov Mar 12, 2026
6ce827b
Factor out Help Text field
JohYoshidaBCGov Mar 12, 2026
f66128c
Require functions to return Components
JohYoshidaBCGov Mar 12, 2026
9017332
Factor out Visibility toggles
JohYoshidaBCGov Mar 12, 2026
8aadfe0
Factor out Required grid
JohYoshidaBCGov Mar 12, 2026
dc5c08f
Factor out ReadOnly grid
JohYoshidaBCGov Mar 12, 2026
c5423f9
Factor out Template and SaveOnSubmit toggles
JohYoshidaBCGov Mar 12, 2026
f07b5bd
Factor out Tags field
JohYoshidaBCGov Mar 12, 2026
5e7577f
Simplify by passing mode instead of isCreate
JohYoshidaBCGov Mar 12, 2026
4c3ee7b
Bugfix
JohYoshidaBCGov Mar 12, 2026
5d584c0
Bugfix
JohYoshidaBCGov Mar 12, 2026
cdf13fc
Merge pull request #454 from bcgov/refactor--Reorder-Form-Deployments
JohYoshidaBCGov Mar 12, 2026
b421c6b
Fix checkbox group nullable defaultSelected field missing from $nulla…
CHRLOUIE Feb 26, 2026
3d4f5af
Add support for min and max instance limits in repeaters
CHRLOUIE Mar 12, 2026
cb36cf0
Merge pull request #459 from bcgov/limit-repeater-instances
CHRLOUIE Mar 16, 2026
fcd77c9
Merge pull request #450 from bcgov/form-importer-fix
CHRLOUIE Mar 18, 2026
17ca995
Merge branch 'dev' into refactor--GeneralTabHelper
JohYoshidaBCGov Mar 20, 2026
0c027a1
Merge pull request #461 from bcgov/refactor--GeneralTabHelper
JohYoshidaBCGov Mar 24, 2026
20d913b
Update FormsTableSeeder to add the forms only on Klamm Prod
CHRLOUIE Mar 24, 2026
e526866
Update seeders without duplicate data checking that run on default
CHRLOUIE Mar 25, 2026
167d5b6
Update seeders to allow id to auto increment on insert
CHRLOUIE Mar 26, 2026
a75c3c9
Remove duplicate software sources selected for the same form from for…
CHRLOUIE Mar 26, 2026
86f0c2b
Ensure software sources can only be selected once for each form
CHRLOUIE Mar 26, 2026
1e237c8
Add the software source "Adobe Livecycle" to specified form numbers
CHRLOUIE Mar 26, 2026
99b78da
Merge branch 'dev' into update-form-software-sources
CHRLOUIE Mar 27, 2026
ffd5c8b
Run the form software source update seeder as part of the default see…
CHRLOUIE Mar 30, 2026
1ae2b8e
Merge pull request #462 from bcgov/update-form-software-sources
CHRLOUIE Mar 30, 2026
7389d13
Merge branch 'main' into dev
JohYoshidaBCGov Mar 30, 2026
d9e66bb
Merge pull request #464 from bcgov/fix-merge-conflict
JohYoshidaBCGov Mar 30, 2026
82fc942
Merge branch 'main' into dev
JohYoshidaBCGov Mar 30, 2026
b8ec00c
Merge pull request #465 from bcgov/fix-merge-conflict
JohYoshidaBCGov Mar 30, 2026
092d7d8
Anonymization/testing efficiency updates (#460)
brysonjbest Mar 31, 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
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,7 @@ VITE_REVERB_APP_KEY="${REVERB_APP_KEY}"
VITE_REVERB_HOST=localhost
VITE_REVERB_PORT="${REVERB_PORT}"
VITE_REVERB_SCHEME="${REVERB_SCHEME}"

ANONYMIZATION_UPLOAD_RETENTION_DAYS=30
ANONYMIZATION_STAGING_RETENTION_DAYS=7
ANONYMIZATION_PACKAGE_OWNER=ANON_DATA
2 changes: 2 additions & 0 deletions .github/workflows/build-and-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -150,3 +150,5 @@ jobs:
run: |
oc rollout restart deployment/klamm-app
oc rollout restart deployment/klamm-queue-worker
oc rollout restart deployment/klamm-queue-anonymization-worker
oc rollout restart deployment/klamm-reverb-worker
7 changes: 6 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ RUN apt-get update && apt-get install -y \
postgresql-client \
nodejs \
npm \
&& docker-php-ext-install pdo_mysql pdo_pgsql pgsql mbstring exif pcntl bcmath gd intl zip \
&& docker-php-ext-install pdo_mysql pdo_pgsql pgsql mbstring exif pcntl bcmath gd intl zip opcache \
&& pecl install redis \
&& docker-php-ext-enable redis opcache \
&& apt-get clean && rm -rf /var/lib/apt/lists/*

# Enable Apache mod_rewrite
Expand Down Expand Up @@ -70,6 +72,9 @@ COPY 000-default.conf /etc/apache2/sites-available/000-default.conf
# Copy PHP upload config
COPY docker/php/uploads.ini /usr/local/etc/php/conf.d/uploads.ini

# Copy PHP OPcache config for production performance
COPY docker/php/opcache.ini /usr/local/etc/php/conf.d/opcache.ini

# Generate APP_KEY
RUN echo "APP_KEY=" > .env
RUN php artisan key:generate
Expand Down
7 changes: 5 additions & 2 deletions Dockerfile.worker
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ RUN apt-get update && apt-get install -y \
postgresql-client \
nodejs \
npm \
&& docker-php-ext-install pdo_mysql pdo_pgsql pgsql mbstring exif pcntl bcmath gd intl zip \
&& docker-php-ext-install pdo_mysql pdo_pgsql pgsql mbstring exif pcntl bcmath gd intl zip opcache \
&& pecl install redis \
&& docker-php-ext-enable redis \
&& docker-php-ext-enable redis opcache \
&& apt-get clean && rm -rf /var/lib/apt/lists/*

# Set working directory
Expand All @@ -30,6 +30,9 @@ COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
# Copy existing application directory contents
COPY . /var/www

# Copy PHP OPcache config for production performance
COPY docker/php/opcache.ini /usr/local/etc/php/conf.d/opcache.ini

# Install Node.js dependencies if package.json exists
RUN if [ -f package.json ]; then npm install; fi

Expand Down
127 changes: 91 additions & 36 deletions app/Console/Commands/PurgeAnonymizationUploads.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,26 @@
use App\Models\Anonymizer\AnonymousUpload;
use Carbon\CarbonImmutable;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use Throwable;

class PurgeAnonymizationUploads extends Command
{
protected $signature = 'anonymization:purge-uploads {--dry-run : Report what would be deleted without deleting files} {--limit=500 : Max records to process per run}';
protected $signature = 'anonymization:purge-uploads {--dry-run : Report what would be deleted without deleting files} {--limit=500 : Max records to process per run} {--staging-limit=1000 : Max uploads to scan for staging pruning per run} {--staging-upload-chunk=200 : Upload IDs per staging delete pass} {--row-chunk=5000 : Staging row IDs deleted per statement}';

protected $description = 'Deletes stored anonymization CSV uploads after their retention period has elapsed.';
protected $description = 'Deletes retained anonymization upload files and prunes stale staging rows.';

public function handle(): int
{
$now = CarbonImmutable::now();
$dryRun = (bool) $this->option('dry-run');
$limit = (int) $this->option('limit');
$stagingLimit = max(1, (int) $this->option('staging-limit'));
$stagingUploadChunk = max(1, (int) $this->option('staging-upload-chunk'));
$rowChunk = max(100, (int) $this->option('row-chunk'));
$stagingRetentionDays = max(1, (int) config('anonymizer.staging_retention_days', 7));
$stagingCutoff = $now->subDays($stagingRetentionDays);

$query = AnonymousUpload::query()
->whereNotNull('path')
Expand All @@ -30,57 +36,106 @@ public function handle(): int

$uploads = $query->get();

if ($uploads->isEmpty()) {
$this->info('No expired uploads to purge.');
return self::SUCCESS;
}

$purged = 0;
$missing = 0;
$failed = 0;

foreach ($uploads as $upload) {
$disk = $upload->file_disk ?: config('filesystems.default', 'local');
$path = $upload->path;

if (! $path) {
continue;
}

$storage = Storage::disk($disk);

try {
$exists = $storage->exists($path);
if ($uploads->isNotEmpty()) {
foreach ($uploads as $upload) {
$disk = $upload->file_disk ?: config('filesystems.default', 'local');
$path = $upload->path;

if ($dryRun) {
$this->line(sprintf('[dry-run] %s:%s (%s)', $disk, $path, $exists ? 'exists' : 'missing'));
if (! $path) {
continue;
}

if ($exists) {
$storage->delete($path);
$purged++;
} else {
$missing++;
$storage = Storage::disk($disk);

try {
$exists = $storage->exists($path);

if ($dryRun) {
$this->line(sprintf('[dry-run] %s:%s (%s)', $disk, $path, $exists ? 'exists' : 'missing'));
continue;
}

if ($exists) {
$storage->delete($path);
$purged++;
} else {
$missing++;
}

$upload->forceFill([
'file_deleted_at' => $now,
'file_deleted_reason' => 'retention',
])->save();
} catch (Throwable $exception) {
$failed++;
report($exception);
$this->error(sprintf('Failed to purge upload #%d (%s:%s): %s', $upload->id, $disk, $path, $exception->getMessage()));
}
}
}

$upload->forceFill([
'file_deleted_at' => $now,
'file_deleted_reason' => 'retention',
])->save();
} catch (Throwable $exception) {
$failed++;
report($exception);
$this->error(sprintf('Failed to purge upload #%d (%s:%s): %s', $upload->id, $disk, $path, $exception->getMessage()));
$stagingUploadIds = AnonymousUpload::query()
->whereIn('status', ['completed', 'failed'])
->where('updated_at', '<=', $stagingCutoff)
->orderBy('id')
->limit($stagingLimit)
->pluck('id')
->all();

$stagingRows = 0;
$stagingUploads = count($stagingUploadIds);

if ($stagingUploadIds !== []) {
if ($dryRun) {
$stagingRows = (int) DB::table('anonymous_siebel_stagings')
->whereIn('upload_id', $stagingUploadIds)
->count();
} else {
foreach (array_chunk($stagingUploadIds, $stagingUploadChunk) as $idChunk) {
do {
$rowIds = DB::table('anonymous_siebel_stagings')
->whereIn('upload_id', $idChunk)
->orderBy('id')
->limit($rowChunk)
->pluck('id')
->all();

if ($rowIds === []) {
break;
}

$stagingRows += (int) DB::table('anonymous_siebel_stagings')
->whereIn('id', $rowIds)
->delete();
} while (count($rowIds) === $rowChunk);
}
}
}

if ($dryRun) {
$this->info(sprintf('Dry run complete. Candidates: %d', $uploads->count()));
$this->info(sprintf(
'Dry run complete. File candidates: %d. Staging candidates: %d uploads, %d rows (older than %d days).',
$uploads->count(),
$stagingUploads,
$stagingRows,
$stagingRetentionDays
));
return self::SUCCESS;
}

$this->info(sprintf('Purged: %d, missing: %d, failed: %d', $purged, $missing, $failed));
$this->info(sprintf(
'Purged files: %d, missing files: %d, failed file purges: %d, pruned staging uploads: %d, pruned staging rows: %d (older than %d days)',
$purged,
$missing,
$failed,
$stagingUploads,
$stagingRows,
$stagingRetentionDays
));

return $failed > 0 ? self::FAILURE : self::SUCCESS;
}
Expand Down
25 changes: 25 additions & 0 deletions app/Constants/Fodig/Anonymizer/SiebelMetadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,31 @@ final class SiebelMetadata
'RELATED_COLUMNS',
];

public const TEMP_HEADER_COLUMNS = [
'DB_INSTANCE',
'OWNER',
'QUALFIELD',
'COLUMN_ID',
'TABLE_NAME',
'COLUMN_NAME',
'ANON_RULE',
'ANON_NOTE',
'PR_KEY',
'REF_TAB_NAME',
'NUM_DISTINCT',
'NUM_NOT_NULL',
'NUM_NULLS',
'NUM_ROWS',
'DATA_TYPE',
'DATA_LENGTH',
'DATA_PRECISION',
'DATA_SCALE',
'COMMENTS',
'SBL_USER_NAME',
'SBL_DESC_TEXT',
'NULLABLE',
];

public const IMPORT_DIRECTORY = 'anonymous-siebel/imports';

// Defaults applied when transforming Siebel-column CSVs without explicit scope
Expand Down
50 changes: 28 additions & 22 deletions app/Filament/Components/FormDeploymentsManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,18 @@ public static function schema()
->schema([
Grid::make(3)
->schema([
// Test Environment
Section::make('Test Environment')
->icon('heroicon-o-beaker')
// Dev Environment
Section::make('Development Environment')
->icon('heroicon-o-cog')
->compact()
->schema([
Placeholder::make('test_deployment_info')
Placeholder::make('dev_deployment_info')
->label('')
->content(function ($record) {
if (!$record) return 'No deployment details yet';
if (!$record)
return 'No deployment details yet';

$deployment = FormDeployment::getDeploymentForFormAndEnvironment($record->id, 'test');
$deployment = FormDeployment::getDeploymentForFormAndEnvironment($record->id, 'dev');
if (!$deployment) {
return 'No deployment details yet';
}
Expand All @@ -46,7 +47,7 @@ public static function schema()
})
->extraAttributes(['class' => 'text-sm']),
Actions::make([
Action::make('deploy_to_test')
Action::make('deploy_to_dev')
->label('Deploy')
->icon('heroicon-o-rocket-launch')
->color('warning')
Expand All @@ -56,7 +57,8 @@ public static function schema()
Select::make('form_version_id')
->label('Form Version')
->options(function ($record) {
if (!$record) return [];
if (!$record)
return [];
return $record->formVersions()
->whereIn('status', ['approved', 'published'])
->get()
Expand All @@ -73,26 +75,27 @@ public static function schema()
->action(function (array $data, $record) {
FormDeployment::deployToEnvironment(
$data['form_version_id'],
'test',
'dev',
Carbon::parse($data['deployed_at'])
);
})
->successNotificationTitle('Successfully deployed to Test environment'),
->successNotificationTitle('Successfully deployed to Development environment'),
])
->alignment(Alignment::Left),
]),

// Dev Environment
Section::make('Development Environment')
->icon('heroicon-o-cog')
// Test Environment
Section::make('Test Environment')
->icon('heroicon-o-beaker')
->compact()
->schema([
Placeholder::make('dev_deployment_info')
Placeholder::make('test_deployment_info')
->label('')
->content(function ($record) {
if (!$record) return 'No deployment details yet';
if (!$record)
return 'No deployment details yet';

$deployment = FormDeployment::getDeploymentForFormAndEnvironment($record->id, 'dev');
$deployment = FormDeployment::getDeploymentForFormAndEnvironment($record->id, 'test');
if (!$deployment) {
return 'No deployment details yet';
}
Expand All @@ -101,7 +104,7 @@ public static function schema()
})
->extraAttributes(['class' => 'text-sm']),
Actions::make([
Action::make('deploy_to_dev')
Action::make('deploy_to_test')
->label('Deploy')
->icon('heroicon-o-rocket-launch')
->color('info')
Expand All @@ -111,7 +114,8 @@ public static function schema()
Select::make('form_version_id')
->label('Form Version')
->options(function ($record) {
if (!$record) return [];
if (!$record)
return [];
return $record->formVersions()
->whereIn('status', ['approved', 'published'])
->get()
Expand All @@ -128,11 +132,11 @@ public static function schema()
->action(function (array $data, $record) {
FormDeployment::deployToEnvironment(
$data['form_version_id'],
'dev',
'test',
Carbon::parse($data['deployed_at'])
);
})
->successNotificationTitle('Successfully deployed to Development environment'),
->successNotificationTitle('Successfully deployed to Test environment'),
])
->alignment(Alignment::Left),
]),
Expand All @@ -145,7 +149,8 @@ public static function schema()
Placeholder::make('prod_deployment_info')
->label('')
->content(function ($record) {
if (!$record) return 'No deployment details yet';
if (!$record)
return 'No deployment details yet';

$deployment = FormDeployment::getDeploymentForFormAndEnvironment($record->id, 'prod');
if (!$deployment) {
Expand All @@ -166,7 +171,8 @@ public static function schema()
Select::make('form_version_id')
->label('Form Version')
->options(function ($record) {
if (!$record) return [];
if (!$record)
return [];
return $record->formVersions()
->whereIn('status', ['approved', 'published'])
->get()
Expand Down
Loading
Loading