Skip to content

Commit

Permalink
available farms randomization (#1370)
Browse files Browse the repository at this point in the history
* init get farm count

* add js docs and make the return type number

* add ret_count to filter options and applie it to the request

* WIP:create randomize farm page logic

* WIP: fix reset selected pages issue

* WIP: make page size flexable

* make the page size dynamic

* clean up

* ignore page value on getting farms count

* reset farms on filter changes

* handle empty farms array

* use sendFullResponse inside send
  • Loading branch information
0oM4R authored Nov 16, 2023
1 parent d9ed2e2 commit 28e90ba
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 39 deletions.
10 changes: 8 additions & 2 deletions packages/grid_client/src/helpers/requests.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { default as axios, Method } from "axios";

async function send(method: Method, url: string, body: string, headers: Record<string, string>) {
async function sendWithFullResponse(method: Method, url: string, body: string, headers: Record<string, string>) {
const options = {
method: method,
url: url,
Expand All @@ -11,6 +11,12 @@ async function send(method: Method, url: string, body: string, headers: Record<s
if (response.status >= 400) {
throw Error(`HTTP request failed with status code: ${response.status} due to: ${response.data}`);
}
return response;
}

async function send(method: Method, url: string, body: string, headers: Record<string, string>) {
const response = await sendWithFullResponse(method, url, body, headers);
return response.data;
}
export { send };

export { send, sendWithFullResponse };
6 changes: 6 additions & 0 deletions packages/grid_client/src/modules/capacity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,12 @@ class Capacity {
return await this.nodes.filterFarms(options);
}

@expose
@validateInput
async getFarmsCount(options?: FarmFilterOptions): Promise<number> {
return await this.nodes.getFarmsCount(options);
}

@expose
@validateInput
async checkFarmHasFreePublicIps(options?: FarmHasFreePublicIPsModel): Promise<boolean> {
Expand Down
2 changes: 2 additions & 0 deletions packages/grid_client/src/modules/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -592,6 +592,7 @@ class FilterOptions {
@Expose() @IsOptional() @IsBoolean() rentable?: boolean;
@Expose() @IsOptional() @IsInt() @Min(1) rentedBy?: number;
@Expose() @IsOptional() @IsBoolean() randomize?: boolean;
@Expose() @IsOptional() @IsBoolean() ret_count?: boolean;
@Expose() @IsOptional() @Transform(({ value }) => NodeStatus[value]) @IsEnum(NodeStatus) status?: NodeStatus;
}

Expand Down Expand Up @@ -619,6 +620,7 @@ class FarmFilterOptions {
@Expose() @IsOptional() @IsInt() ownedBy?: number;
@Expose() @IsOptional() @IsInt() farmId?: number;
@Expose() @IsOptional() @IsBoolean() randomize?: boolean;
@Expose() @IsOptional() @IsBoolean() ret_count?: boolean;
}

class CalculatorModel {
Expand Down
23 changes: 22 additions & 1 deletion packages/grid_client/src/primitives/nodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import urlJoin from "url-join";

import { RMB } from "../clients";
import { Graphql } from "../clients/graphql/client";
import { send } from "../helpers/requests";
import { send, sendWithFullResponse } from "../helpers/requests";
import { FarmFilterOptions, FilterOptions, NodeStatus } from "../modules/models";

interface FarmInfo {
Expand Down Expand Up @@ -317,6 +317,26 @@ class Nodes {
return farms;
}

/**
* Retrieves the count of available farms with optional filter options.
*
* @param options - An object containing filter options to refine the farm count.
* @param url - (Optional) The URL to send the request to. If not provided, it defaults to the proxy URL defined in the class.
* @returns A Promise that resolves to the count of available farms as a number.
* @throws Error if there is an issue with the HTTP request or response.
*/
async getFarmsCount(options: FilterOptions = {}, url = ""): Promise<number> {
url = url || this.proxyURL;
options.ret_count = true;
options.page = 1;
const query = this.getFarmUrlQuery(options);
try {
return +(await sendWithFullResponse("get", urlJoin(url, `/farms?${query}`), "", {})).headers["count"];
} catch (err) {
throw Error(`Error while requesting the grid proxy due ${err}`);
}
}

/**
* Get farm id from farm name.
* It returns 0 in case the farm name is not found.
Expand Down Expand Up @@ -388,6 +408,7 @@ class Nodes {
node_certified: options.nodeCertified,
farm_id: options.farmId,
randomize: options.randomize,
ret_count: options.ret_count,
};
return Object.entries(params)
.map(param => param.join("="))
Expand Down
95 changes: 59 additions & 36 deletions packages/playground/src/components/select_farm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
<section>
<h6 class="text-h5 mb-4 mt-2">Choose a Location</h6>
<SelectCountry v-model="country" />

<input-validator :rules="[validators.required('Farm is required.')]" :value="farm?.farmID" ref="farmInput">
<input-tooltip tooltip="The name of the farm that you want to deploy inside it.">
<v-autocomplete
Expand All @@ -28,8 +27,6 @@
@click="loadFarms"
:loading="loading"
>
<!-- @click="loadNextPage" -->
<!-- :loading="loading" -->
Load More Farms
</v-btn>
</div>
Expand All @@ -41,13 +38,13 @@
</template>

<script lang="ts" setup>
import { onMounted, onUnmounted, type PropType, ref, watch } from "vue";
import { onMounted, onUnmounted, type PropType, type Ref, ref, watch } from "vue";
import { useInputRef } from "@/hooks/input_validator";
import { useProfileManager } from "../stores/profile_manager";
import type { Farm } from "../types";
import { getFarms } from "../utils/get_farms";
import { getFarms, getFarmsPages } from "../utils/get_farms";
import { getGrid } from "../utils/grid";
import { useFarmGatewayManager } from "./farm_gateway_manager.vue";
import { useFarm } from "./select_farm_manager.vue";
Expand Down Expand Up @@ -76,7 +73,7 @@ const emits = defineEmits<{
}>();
const SIZE = 20;
const page = ref(1);
const page = ref();
const farmInput = useInputRef();
const profileManager = useProfileManager();
Expand Down Expand Up @@ -107,40 +104,46 @@ watch(
},
{ immediate: true },
);
const selectedPages = ref([]) as Ref<number[]>;
const farms = ref<Farm[]>([]);
let initialized = false;
const shouldBeUpdated = ref(false);
const pagesCount = ref();
function prepareFilters(filters: Filters, twinId: number): FarmFilterOptions {
return {
size: SIZE,
page: page.value,
country: country.value,
nodeMRU: filters.memory ? Math.round(filters.memory / 1024) : undefined,
nodeHRU: filters.disk,
nodeSRU: filters.ssd,
publicIp: filters.publicIp,
availableFor: twinId,
nodeCertified: filters.certified,
nodeRentedBy: filters.rentedBy ? filters.rentedBy : undefined,
nodeHasGPU: filters.hasGPU ? filters.hasGPU : undefined,
};
}
async function loadFarms() {
farmInput.value?.setStatus(ValidatorStatus.Pending);
loading.value = true;
const oldFarm = farm.value;
const grid = await getGrid(profileManager.profile!);
const filters = props.filters;
const _farms = await getFarms(
grid!,
{
size: SIZE,
page: page.value,
country: country.value,
nodeMRU: filters.memory ? Math.round(filters.memory / 1024) : undefined,
nodeHRU: filters.disk,
nodeSRU: filters.ssd,
publicIp: filters.publicIp,
availableFor: grid!.twinId,
nodeCertified: filters.certified,
nodeRentedBy: filters.rentedBy ? filters.rentedBy : undefined,
nodeHasGPU: filters.hasGPU ? filters.hasGPU : undefined,
},
{ exclusiveFor: props.exclusiveFor },
);
if (page.value === 1) {
farms.value = _farms;
} else {
farms.value = farms.value.concat(_farms);
const _farms = await getFarms(grid!, prepareFilters(props.filters, grid!.twinId), {
exclusiveFor: props.exclusiveFor,
});
selectedPages.value.push(page.value);
if (!_farms.length && selectedPages.value.length !== pagesCount.value) {
page.value = setRandomPage();
await loadFarms();
return;
}
farms.value = farms.value.concat(_farms);
if (oldFarm) {
farm.value = undefined;
Expand All @@ -159,16 +162,37 @@ async function loadFarms() {
}
}
page.value = _farms.length < SIZE ? -1 : page.value + 1;
page.value = selectedPages.value.length === pagesCount.value ? -1 : setRandomPage();
loading.value = false;
}
onMounted(loadFarms);
function setRandomPage() {
let randPage = Math.floor(Math.random() * pagesCount.value) + 1;
while (selectedPages.value.includes(randPage)) randPage = Math.floor(Math.random() * pagesCount.value) + 1;
return randPage;
}
async function resetPages() {
farms.value = [];
loading.value = true;
farmInput.value?.setStatus(ValidatorStatus.Pending);
selectedPages.value = [];
const grid = await getGrid(profileManager.profile!);
if (!grid) {
console.log("can't get the grid");
pagesCount.value = 1;
} else {
pagesCount.value = await getFarmsPages(grid, prepareFilters(props.filters, grid.twinId), SIZE);
}
page.value = setRandomPage();
await loadFarms();
}
onMounted(resetPages);
onUnmounted(() => FarmGatewayManager?.unregister());
const shouldBeUpdated = ref(false);
watch(
() => ({ ...props.filters, country: country.value }),
(value, oldValue) => {
async (value, oldValue) => {
if (
value.cpu === oldValue.cpu &&
value.memory === oldValue.memory &&
Expand All @@ -181,7 +205,6 @@ watch(
value.hasGPU === oldValue.hasGPU
)
return;
shouldBeUpdated.value = true;
},
);
Expand All @@ -191,13 +214,13 @@ watch([loading, shouldBeUpdated], async ([l, s]) => {
shouldBeUpdated.value = false;
clearTimeout(delay.value);
delay.value = setTimeout(async () => {
page.value = 1;
await loadFarms();
await resetPages();
}, 2000);
});
</script>

<script lang="ts">
import type { FarmFilterOptions } from "@threefold/grid_client";
import { nextTick } from "vue";
import { ValidatorStatus } from "@/hooks/form_validator";
Expand Down
10 changes: 10 additions & 0 deletions packages/playground/src/utils/get_farms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ import { gqlClient, gridProxyClient } from "../clients";
export interface GetFarmsOptions {
exclusiveFor?: string;
}

export async function getFarmsPages(grid: GridClient, filters: FarmFilterOptions, pageSize: number): Promise<number> {
try {
const count = (await grid.capacity.getFarmsCount(filters)) || 1;
return Math.ceil(count / pageSize);
} catch {
return 1;
}
}

export async function getFarms(
grid: GridClient,
filters: FarmFilterOptions,
Expand Down

0 comments on commit 28e90ba

Please sign in to comment.