Skip to content

FOUR-20527 Merge New Services into the available column #7806

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Dec 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
155 changes: 102 additions & 53 deletions ProcessMaker/Http/Controllers/Api/V1_1/ProcessVariableController.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,20 @@
namespace ProcessMaker\Http\Controllers\Api\V1_1;

use ProcessMaker\Http\Controllers\Controller;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use ProcessMaker\Package\SavedSearch\Models\SavedSearch;
use ProcessMaker\Package\VariableFinder\Models\ProcessVariable;

class ProcessVariableController extends Controller
{

const CACHE_TTL = 60;
private static bool $mockData = false;

/**
* @OA\Schema(
* schema="Variable",
Expand Down Expand Up @@ -113,24 +117,12 @@ public function index(Request $request)
$excludeSavedSearch = $validated['savedSearchId'] ?? 0;

// Generate mock data
$mockData = $this->generateMockData($processIds);
if ($excludeSavedSearch) {
$savedSearch = SavedSearch::find($excludeSavedSearch);
$columns = $savedSearch->current_columns;
$mockData = $mockData->filter(function ($variable) use ($columns) {
return !$columns->pluck('field')->contains($variable['field']);
});
if (static::$mockData) {
$paginator = $this->getProcessesVariablesFromMock($processIds, $excludeSavedSearch, $page, $perPage, $request);
} else {
$paginator = $this->getProcessesVariables($processIds, $excludeSavedSearch, $page, $perPage, $request);
}

// Create paginator
$paginator = new LengthAwarePaginator(
$mockData->forPage($page, $perPage),
$mockData->count(),
$perPage,
$page,
['path' => $request->url()]
);

return response()->json([
'data' => array_values($paginator->items()),
'meta' => [
Expand All @@ -151,51 +143,108 @@ public function index(Request $request)
]);
}

private function generateMockData(array $processIds): Collection
/**
* Retrieve process variables from a mock source.
*
* @param array $processIds An array of process IDs to retrieve variables for.
* @param bool $excludeSavedSearch Flag to determine whether to exclude saved searches.
* @param int $page The page number for pagination.
* @param int $perPage The number of items per page for pagination.
* @param \Illuminate\Http\Request $request The HTTP request instance.
*
* @return array The list of process variables.
*/
private function getProcessesVariablesFromMock(array $processIds, $excludeSavedSearch, $page, $perPage, $request)
{
// Create a cache key based on process IDs
$cacheKey = 'process_variables_' . implode('_', $processIds);
if ($excludeSavedSearch) {
$cacheKey .= '_exclude_saved_search_' . $excludeSavedSearch;
}

// Try to get variables from cache first
$variables = Cache::remember($cacheKey, now()->addSeconds(5), function () use ($processIds) {
$variables = collect();

foreach ($processIds as $processId) {
// Generate 10 variables per process
for ($i = 1; $i <= 10; $i++) {
$variables->push([
'id' => $variables->count() + 1,
'process_id' => $processId,
'uuid' => (string) Str::uuid(),
'format' => $this->getRandomDataType(),
'label' => "Variable {$i} for Process {$processId}",
'field' => "data.var_{$processId}_{$i}",
'asset' => [
'id' => "asset_{$processId}_{$i}",
'type' => $this->getRandomAssetType(),
'name' => "Asset {$i} for Process {$processId}",
'uuid' => (string) Str::uuid(),
],
'default' => null,
'created_at' => now()->toIso8601String(),
'updated_at' => now()->toIso8601String(),
]);
}
$mockData = Cache::remember($cacheKey, now()->addSeconds(self::CACHE_TTL), function () use ($excludeSavedSearch) {
if (!$excludeSavedSearch) {
return collect();
}

return $variables;
$savedSearch = SavedSearch::find($excludeSavedSearch);
return collect(array_values($savedSearch->data_columns->toArray()));
});

return $variables;
if ($excludeSavedSearch) {
$savedSearch = SavedSearch::find($excludeSavedSearch);
$columns = $savedSearch->current_columns;
$mockData = $mockData->filter(function ($variable) use ($columns) {
return !$columns->pluck('field')->contains($variable['field']);
});
}

// Create paginator
return new LengthAwarePaginator(
$mockData->forPage($page, $perPage),
$mockData->count(),
$perPage,
$page,
['path' => $request->url()]
);
}

private function getRandomDataType(): string
/**
* Retrieve process variables for the given process IDs.
*
* @param array $processIds Array of process IDs to retrieve variables for.
* @param bool $excludeSavedSearch Flag to exclude saved searches.
* @param int $page The page number for pagination.
* @param int $perPage The number of items per page for pagination.
* @param \Illuminate\Http\Request $request The HTTP request instance.
* @return \Illuminate\Http\JsonResponse JSON response containing the process variables.
*/
public function getProcessesVariables(array $processIds, $excludeSavedSearch, $page, $perPage, $request)
{
return collect(['string', 'number', 'boolean', 'array'])->random();
// If the classes or tables do not exist, fallback to a saved search approach.
if (!class_exists(ProcessVariable::class) || !Schema::hasTable('process_variables')) {
return $this->getProcessesVariablesFromSavedSearch($processIds);
}

// Determine which columns to exclude based on the saved search
$activeColumns = [];
if ($excludeSavedSearch) {
$savedSearch = SavedSearch::find($excludeSavedSearch);
if ($savedSearch && $savedSearch->current_columns) {
$activeColumns = $savedSearch->current_columns->pluck('field')->toArray();
}
}

// Build a single query that joins process_variables, asset_variables, and var_finder_variables
// and applies filtering for excluded fields.
$query = DB::table('var_finder_variables AS vfv')
->join('asset_variables AS av', 'vfv.asset_variable_id', '=', 'av.id')
->join('process_variables AS pv', 'av.id', '=', 'pv.asset_variable_id')
->whereIn('pv.process_id', $processIds);

if (!empty($activeColumns)) {
$query->whereNotIn('vfv.field', $activeColumns);
}

// Paginate the query result directly
return $query->select(
'vfv.id',
'pv.process_id',
'vfv.data_type AS format',
'vfv.label',
'vfv.field',
DB::raw('NULL AS `default`'),
'vfv.created_at',
'vfv.updated_at',
)->paginate($perPage, ['*'], 'page', $page);
}

private function getRandomAssetType(): string
/**
* Change ProcessVariableController to use mock data
*
* @return void
*/
public static function mock()
{
return collect(['sensor', 'actuator', 'controller', 'device'])->random();
static::$mockData = true;
}
}
}
123 changes: 116 additions & 7 deletions tests/Feature/Api/V1_1/ProcessVariableControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,26 @@

namespace Tests\Feature\Api\V1_1;

use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Schema;
use ProcessMaker\Models\User;
use ProcessMaker\Package\SavedSearch\Models\SavedSearch;
use Tests\TestCase;
use Tests\Feature\Shared\RequestHelper;
use ProcessMaker\Package\VariableFinder\Models\ProcessVariable;
use Illuminate\Support\Str;
use ProcessMaker\Http\Controllers\Api\V1_1\ProcessVariableController;
use ProcessMaker\Models\Process;
use ProcessMaker\Models\Screen;
use ProcessMaker\Package\VariableFinder\Models\AssetVariable;
use ProcessMaker\Package\VariableFinder\Models\VarFinderVariable;

class ProcessVariableControllerTest extends TestCase
{
use RequestHelper;

private bool $isVariablesFinderEnabled;

/**
* Set up test environment by creating a test user and authenticating as them
*
Expand All @@ -20,6 +31,19 @@ public function setupCreateUser()
{
$this->user = User::factory()->create();
$this->actingAs($this->user);

// Check if the VariableFinder package is enabled
$this->isVariablesFinderEnabled = class_exists(ProcessVariable::class) && Schema::hasTable('process_variables');

// Create the processes variables
if (!$this->isVariablesFinderEnabled) {
// Mock the ProcessVariableController to use mock data instead of VariableFinder package
ProcessVariableController::mock();
$this->mockVariableFinder([1, 2, 3], null);
$this->mockVariableFinder([1, 2], null);
} else {
$this->loadVariableFinderData([1, 2, 3]);
}
}

/**
Expand All @@ -37,16 +61,10 @@ public function test_can_get_process_variables_with_pagination(): void
'*' => [
'id',
'process_id',
'uuid',
'format',
'label',
'field',
'asset' => [
'id',
'type',
'name',
'uuid',
],
'default',
'created_at',
'updated_at',
]
Expand All @@ -71,6 +89,94 @@ public function test_can_get_process_variables_with_pagination(): void
$this->assertEquals(30, $responseData['meta']['total']);
}

private function mockVariableFinder(array $processIds, $excludeSavedSearch)
{
// Create a cache key based on process IDs
$cacheKey = 'process_variables_' . implode('_', $processIds);
if ($excludeSavedSearch) {
$cacheKey .= '_exclude_saved_search_' . $excludeSavedSearch;
}

// Try to get variables from cache first
$variables = Cache::remember($cacheKey, now()->addSeconds(60), function () use ($processIds) {
$variables = collect();

foreach ($processIds as $processId) {
// Generate 10 variables per process
for ($i = 1; $i <= 10; $i++) {
$variables->push([
'id' => $variables->count() + 1,
'process_id' => $processId,
'format' => $this->getRandomDataType(),
'label' => "Variable {$i} for Process {$processId}",
'field' => "data.var_{$processId}_{$i}",
'default' => null,
'created_at' => now()->toIso8601String(),
'updated_at' => now()->toIso8601String(),
]);
}
}

return $variables;
});

return $variables;
}

private function getRandomDataType(): string
{
return collect(['string', 'int', 'boolean', 'array'])->random();
}

private function getRandomAssetType(): string
{
return collect(['sensor', 'actuator', 'controller', 'device'])->random();
}

private function loadVariableFinderData(array $processIds)
{
foreach ($processIds as $processId) {
$process = Process::factory()->create([
'id' => $processId,
]);
// 1. Create the AssetVariable record
$asset = [
'type' => $this->getRandomAssetType(),
'uuid' => (string) Str::uuid(),
];
$assetVariable = AssetVariable::create([
'uuid' => $asset['uuid'],
'asset_id' => 1, // Scren id=1
'asset_type' => Screen::class,
]);

// 2. Create the ProcessVariable record linking to the AssetVariable
ProcessVariable::create([
'uuid' => (string) Str::uuid(),
'process_id' => $processId,
'asset_variable_id' => $assetVariable->id,
]);

// Generate 10 variables per process
for ($i = 1; $i <= 10; $i++) {

// Generate data similarly to mockVariableFinder
$format = $this->getRandomDataType();
$label = "Variable {$i} for Process {$processId}";
$field = "data.var_{$processId}_{$i}";

// 3. Create the VarFinderVariable record linked to the same AssetVariable
VarFinderVariable::create([
'uuid' => (string) Str::uuid(),
'asset_variable_id' => $assetVariable->id,
'data_type' => $format,
'label' => $label,
'field' => $field,
]);
}
}
}

/**
* Test validation for required processIds parameter
*/
Expand Down Expand Up @@ -159,6 +265,9 @@ public function test_saved_search_id_filtering(): void
]);

// Make request with savedSearchId
if (!$this->isVariablesFinderEnabled) {
$this->mockVariableFinder([1], $savedSearch->id);
}
$response = $this->apiCall('GET', '/api/1.1/processes/variables?processIds=1&savedSearchId=' . $savedSearch->id);

$responseData = $response->json();
Expand Down
Loading