Skip to content
Open
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
40 changes: 36 additions & 4 deletions resources/js/admin/devlink/components/LocalBundles.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,15 @@ import BundleModal from './BundleModal.vue';
import DeleteModal from './DeleteModal.vue';
import { useRouter, useRoute } from 'vue-router/composables';
import UpdateBundle from './UpdateBundle.vue';
import PaginationTable from '../../../components/shared/PaginationTable.vue';

const vue = getCurrentInstance().proxy;
const router = useRouter();
const route = useRoute();
const bundles = ref([]);
const meta = ref({});
const page = ref(1);
const perPage = ref(15);
const editModal = ref(null);
const confirmDeleteModal = ref(null);
const confirmPublishNewVersion = ref(null);
Expand Down Expand Up @@ -50,9 +54,18 @@ onMounted(() => {

const load = () => {
ProcessMaker.apiClient
.get(`/devlink/local-bundles?filter=${filter.value}`)
.get('/devlink/local-bundles', {
params: {
filter: filter.value,
page: page.value,
per_page: perPage.value,
order_by: 'created_at',
order_direction: 'desc',
}
})
.then((result) => {
bundles.value = result.data.data;
meta.value = result.data.meta;
refreshKey.value++;
});
};
Expand Down Expand Up @@ -131,6 +144,7 @@ const create = () => {
ProcessMaker.apiClient
.post('/devlink/local-bundles', selected.value)
.then((result) => {
page.value = 1;
load();
});
};
Expand Down Expand Up @@ -197,9 +211,21 @@ const debouncedLoad = debounce(load, 300);

// Function called on change
const handleFilterChange = () => {
page.value = 1;
debouncedLoad();
};

const handlePageChange = (newPage) => {
page.value = newPage;
load();
};

const handlePerPageChange = (newPerPage) => {
page.value = 1;
perPage.value = newPerPage;
load();
};

const canEdit = (bundle) => {
return bundle.dev_link === null;
}
Expand Down Expand Up @@ -280,9 +306,9 @@ const handleInstallationComplete = () => {
<Origin :dev-link="data.item.dev_link"></Origin>
</template>
<template #cell(version)="data">
{{ data.item.version }} <VersionCheck
:key="`version-check-${data.item.id}-${refreshKey}`"
@updateAvailable="setUpdateAvailable(data.item, $event)"
{{ data.item.version }} <VersionCheck
:key="`version-check-${data.item.id}-${refreshKey}`"
@updateAvailable="setUpdateAvailable(data.item, $event)"
:dev-link="data.item">
</VersionCheck>
</template>
Expand All @@ -301,6 +327,12 @@ const handleInstallationComplete = () => {
<div>{{ $t("Create a bundle to easily share assets and settings between ProcessMaker instances.") }}</div>
</div>
</div>
<pagination-table
:meta="meta"
data-cy="local-bundles-pagination"
@page-change="handlePageChange"
@per-page-change="handlePerPageChange"
/>
</div>
</template>

Expand Down
13 changes: 10 additions & 3 deletions resources/js/components/shared/AddToBundle.vue
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,8 @@ const save = (event) => {

<template>
<b-modal ref="modal" @ok="save"
:ok-title="vue.$t('Save')"
cancel-variant="light"
:ok-title="vue.$t('Save')"
cancel-variant="light"
modal-class="add-to-bundle-modal">
<template #modal-title>
<b>{{ vue.$t('Add asset to bundle') }}</b>
Expand All @@ -119,7 +119,14 @@ const save = (event) => {
</p>
<b>{{ vue.$t('Bundles') }}</b>
<BackendSelect
url="devlink/local-bundles?editable=true"
url="devlink/local-bundles"
:query-params="{
editable: true,
per_page: 100,
order_by: 'created_at',
order_direction: 'desc'
}"
:remote-search="true"
value-field="id"
text-field="name"
v-model="selected"
Expand Down
87 changes: 77 additions & 10 deletions resources/js/components/shared/BackendSelect.vue
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<script setup>
import { defineProps, onMounted, ref, watch, defineEmits, getCurrentInstance } from 'vue';
import { defineProps, onMounted, onBeforeUnmount, ref, watch, defineEmits, getCurrentInstance } from 'vue';
import debounce from 'lodash/debounce';

const vue = getCurrentInstance().proxy;
const options = ref([]);
const value = ref(props.value);
const emit = defineEmits(['input']);

// get component props
Expand All @@ -21,27 +21,94 @@ const props = defineProps({
required: true
},
value: {
type: Array,
}
type: [Array, Object],
default: null
},
queryParams: {
type: Object,
default: () => ({})
},
remoteSearch: {
type: Boolean,
default: false
},
});

const value = ref(props.value);
const loading = ref(false);
const lastSearchTerm = ref('');
let requestSequence = 0;

watch(value, () => {
emit('input', value.value);
});

onMounted(() => {
window.ProcessMaker.apiClient.get(props.url).then((response) => {
options.value = response.data.data;
watch(() => props.value, () => {
value.value = props.value;
});

const loadOptions = (filter = '') => {
const requestId = ++requestSequence;
const remoteFilter = typeof filter === 'string' ? filter : '';

loading.value = true;
const params = { ...props.queryParams };
if (props.remoteSearch) {
lastSearchTerm.value = remoteFilter;
params.filter = remoteFilter;
}
globalThis.ProcessMaker.apiClient.get(props.url, { params }).then((response) => {
if (requestId === requestSequence) {
options.value = response.data.data;
}
}).finally(() => {
if (requestId === requestSequence) {
loading.value = false;
}
});
})
};

const debouncedLoadOptions = debounce(loadOptions, 300);
Comment thread
eiresendez marked this conversation as resolved.

const search = (filter) => {
if (props.remoteSearch) {
const remoteFilter = typeof filter === 'string' ? filter : '';
lastSearchTerm.value = remoteFilter;
debouncedLoadOptions(remoteFilter);
}
};

const handleOpen = () => {
if (!props.remoteSearch) {
loadOptions();
return;
}

if (options.value.length === 0) {
loadOptions(lastSearchTerm.value);
}
};

onMounted(() => {
loadOptions();
});

onBeforeUnmount(() => {
debouncedLoadOptions.cancel();
});
</script>

<template>
<div>
<multiselect v-model="value" :deselect-label="vue.$t('Can\'t remove this value')" :track-by="props.valueField" :label="props.textField"
:placeholder="vue.$t('Type here to search')" :options="options" :searchable="true" :allow-empty="false"
:multiple="true">
:placeholder="vue.$t('Type here to search')" :options="options" :searchable="true" :allow-empty="false"
:multiple="true" :loading="loading" :internal-search="!props.remoteSearch" @open="handleOpen" @search-change="search">
<template slot="noResult">
{{ vue.$t('No elements found. Consider changing the search query.') }}
</template>
<template slot="noOptions">
{{ vue.$t('No Data Available') }}
</template>
</multiselect>
</div>
</template>
72 changes: 72 additions & 0 deletions tests/Feature/Api/DevLinkTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,78 @@ public function testShowBundle()
$this->assertEquals($bundle->id, $response->json()['id']);
}

public function testLocalBundlesCanOrderNewestBundlesFirst()
{
$oldBundle = Bundle::factory()->create([
'created_at' => now()->subDays(2),
]);
$newBundle = Bundle::factory()->create([
'created_at' => now(),
]);

$response = $this->apiCall('GET', route('api.devlink.local-bundles', [
'order_by' => 'created_at',
'order_direction' => 'desc',
]));

$response->assertStatus(200);
$this->assertEquals($newBundle->id, $response->json('data.0.id'));
$this->assertNotEquals($oldBundle->id, $response->json('data.0.id'));
}

public function testLocalBundlesCanReturnOneHundredRecords()
{
Bundle::factory()->count(101)->create();

$response = $this->apiCall('GET', route('api.devlink.local-bundles', [
'per_page' => 100,
]));

$response->assertStatus(200);
$this->assertCount(100, $response->json('data'));
$this->assertEquals(100, $response->json('meta.per_page'));
}

public function testLocalBundlesFilterFindsBundleOutsideFirstPage()
{
$targetBundle = Bundle::factory()->create([
'name' => 'FOUR-31727 Search Target',
'created_at' => now()->subDays(2),
]);
Bundle::factory()->count(15)->create([
'created_at' => now(),
]);

$response = $this->apiCall('GET', route('api.devlink.local-bundles', [
'filter' => 'FOUR-31727 Search Target',
]));

$response->assertStatus(200);
$this->assertEquals($targetBundle->id, $response->json('data.0.id'));
$this->assertCount(1, $response->json('data'));
}

public function testLocalBundlesEditableFilterExcludesRemoteBundles()
{
$devLink = DevLink::factory()->create();
$localBundle = Bundle::factory()->create([
'dev_link_id' => null,
]);
$remoteBundle = Bundle::factory()->create([
'dev_link_id' => $devLink->id,
]);

$response = $this->apiCall('GET', route('api.devlink.local-bundles', [
'editable' => true,
'per_page' => 100,
]));

$response->assertStatus(200);
$bundleIds = collect($response->json('data'))->pluck('id');
$this->assertTrue($bundleIds->contains($localBundle->id));
$this->assertFalse($bundleIds->contains($remoteBundle->id));
}

public function testAddAssets()
{
$screen1 = Screen::factory()->create();
Expand Down
Loading