Skip to content

Commit

Permalink
More tests and custom progress bar
Browse files Browse the repository at this point in the history
  • Loading branch information
mutantsan committed Jul 26, 2024
1 parent e0aeb65 commit 9d05d3c
Show file tree
Hide file tree
Showing 9 changed files with 406 additions and 86 deletions.
112 changes: 78 additions & 34 deletions ckanext/bulk/assets/scripts/bulk-manager-form.js
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ ckan.module("bulk-manager-form", function () {
text: prevValue,
value: prevValue,
});
this.input.tomselect.setValue(prevValue);
this.input.tomselect.setValue(prevValue, true);
};
}
});
Expand All @@ -307,55 +307,49 @@ ckan.module("bulk-manager-form", function () {
return;
}

if (!update_on.length) {
if (this.updateToBlock.is(":visible") && !update_on.length) {
iziToast.error({ message: "Please, fill the update fields" });
return;
}

const bulkProgressBar = this._initProgressBar();
this.bulkProgressBar = this._initProgressBar();

for (let i = 0; i < this.bulkEntitiesToUpdate.length; i++) {
const entity = this.bulkEntitiesToUpdate[i];

try {
await this._callUpdateEntity(entity, entity_type, update_on, action);
bulkProgressBar.animate(
bulkProgressBar.value() + 1 / this.bulkEntitiesToUpdate.length
this.bulkProgressBar.animate(
this.bulkProgressBar.value() + this.bulkProgressBarPercent
);
} catch (error) {
iziToast.error({ message: error });
}
};

bulkProgressBar.destroy();
this.bulkProgressBar.destroy();

iziToast.success({ message: "Bulk operation is finished. Check the logs to see results" });

this.bulkModalLogs.iziModal(
"setContent",
{
content: "<pre class='language-javascript'>"
+ JSON.stringify(this.bulkLogs, null, 2)
+ "</pre>",
}
);

Prism.highlightElement(this.bulkModalLogs.find("pre")[0]);
},

_initProgressBar: function () {
const bulkProgressBar = new ProgressBar.Line($("#bulk-progress-container").get(0), {
strokeWidth: 4,
easing: 'easeInOut',
duration: 1400,
color: '#206b82',
trailColor: '#EEE',
trailWidth: 1,
svgStyle: { width: '100%', height: '100%' },
text: {
style: {
position: 'absolute',
left: '0',
top: '30px',
},
autoStyleContainer: false
},
step: (_, bar) => {
bar.setText(Math.round(bar.value() * 100) + ' %');
}
});
const bulkProgressBar = new BulkProgressBar("#bulk-progress-container", {});

bulkProgressBar.animate(0);

this.bulkProgressBarPercent = 100 / this.bulkEntitiesToUpdate.length;

return bulkProgressBar;
},

Expand All @@ -369,14 +363,6 @@ ckan.module("bulk-manager-form", function () {
}, (resp) => {
this.bulkLogs.push(resp.result);

this.bulkModalLogs.iziModal(
"setContent",
{
content: "<pre class='language-javascript'>"
+ JSON.stringify(this.bulkLogs, null, 2)
+ "</pre>",
}
);
done(resp);
}, (resp) => {
fail(resp)
Expand All @@ -385,3 +371,61 @@ ckan.module("bulk-manager-form", function () {
}
}
})

class BulkProgressBar {
/**
* Create a new progress bar instance inside a container
*
* @param {String} container - container selector
* @param {Object} options - progress bar options
*/
constructor(container, options) {
this.container = $(container);

if (!this.container) {
throw new Error("Container not found");
}

this.options = options;
this.progress = 0;

this.container.addClass("bulk-progress-bar");
this.container.append(`
<div id='pb-runner-wrapper'>
<div id='pb-runner'></div>
</div>
`);
this.container.append("<div id='pb-status'></div>");
}

/**
* Animate the progress bar
*
* @param {Number} value - progress value from 0 to 100
*/
animate(value) {
value = Math.round(value * 100) / 100;

this.progress = value;

this.container.find("#pb-runner").width(`${value}%`);
this.container.find("#pb-status").html(`${value}%`);
}

/**
* Get current progress value
*
* @returns {Number} - current progress value
*/
value() {
return this.progress;
}

/**
* Destroy the progress bar inside a container
*/
destroy() {
this.container.removeClass("bulk-progress-bar");
this.container.empty();
}
}
1 change: 1 addition & 0 deletions ckanext/bulk/assets/scss/bulk.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@

@use "elements/modals";
@use "elements/forms";
@use "elements/progressbar";

@use "global";
20 changes: 20 additions & 0 deletions ckanext/bulk/assets/scss/elements/_progressbar.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
.bulk-progress-bar {
#pb-runner-wrapper {
width: 100%;
background: #e2e2e2;
height: 2px;
display: flex;
align-items: center;

#pb-runner {
background: #206b82;
height: 10px;
width: 0%;
transition: width 300ms linear;
}
}

#pb-status {
margin-top: 1rem;
}
}
19 changes: 18 additions & 1 deletion ckanext/bulk/assets/styles/bulk.css

Large diffs are not rendered by default.

7 changes: 0 additions & 7 deletions ckanext/bulk/assets/vendor/progressbar.min.js

This file was deleted.

1 change: 0 additions & 1 deletion ckanext/bulk/assets/webassets.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ bulk-js:
- vendor/iziModal.js
- vendor/iziToast.js
- vendor/prism.min.js
- vendor/progressbar.min.js

- scripts/bulk.js
- scripts/bulk-manager-form.js
Expand Down
90 changes: 63 additions & 27 deletions ckanext/bulk/entity_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ def __init__(self, entity_type: str):
class EntityManager:
entity_type = ""
show_action = ""
patch_action = ""
delete_action = ""

@classmethod
@abstractmethod
Expand All @@ -52,13 +54,6 @@ def search_entities_by_filters(
) -> list[dict[str, Any]]:
pass

@classmethod
@abstractmethod
def update_entity(
cls, entity_id: str, update_items: list[UpdateItem]
) -> dict[str, Any]:
pass

@classmethod
def get_entity_by_id(cls, entity_id: str) -> dict[str, Any] | None:
try:
Expand Down Expand Up @@ -109,14 +104,39 @@ def combine_filters(cls, filters: list[FilterItem]) -> list[CombinedFilter]:

return combined_filters

@classmethod
def update_entity(
cls, entity_id: str, update_items: list[UpdateItem]
) -> dict[str, Any]:
entity = cls.get_entity_by_id(entity_id)

if not entity:
raise tk.ObjectNotFound(f"Entity {entity_id} not found")

return tk.get_action(cls.patch_action)(
{"ignore_auth": True},
{
"id": entity_id,
**cls.update_items_to_dict(update_items),
},
)

@classmethod
def update_items_to_dict(cls, update_items: list[UpdateItem]) -> dict[str, Any]:
return {item["field"]: item["value"] for item in update_items}

@classmethod
def delete_entity(cls, entity_id: str) -> bool:
tk.get_action(cls.delete_action)({"ignore_auth": True}, {"id": entity_id})

return True


class DatasetEntityManager(EntityManager):
entity_type = "dataset"
show_action = "package_show"
patch_action = "package_patch"
delete_action = "package_delete"

@classmethod
def get_fields(cls) -> list[FieldItem]:
Expand Down Expand Up @@ -169,31 +189,40 @@ def search_entities_by_filters(

query = f" {global_operator} ".join(q_list)

return tk.get_action("package_search")(
{"ignore_auth": True}, {"q": query, "include_private": True}
)["results"]
start = 0
rows = 100

@classmethod
def update_entity(
cls, entity_id: str, update_items: list[UpdateItem]
) -> dict[str, Any]:
package = cls.get_entity_by_id(entity_id)
results = []

if not package:
raise tk.ObjectNotFound(f"Dataset {entity_id} not found")
while True:
result = tk.get_action("package_search")(
{"ignore_auth": True},
{
"q": query,
"rows": rows,
"start": start,
"include_private": True,
"include_drafts": True,
"include_deleted": True,
},
)

return tk.get_action("package_patch")(
{"ignore_auth": True},
{
"id": entity_id,
**cls.update_items_to_dict(update_items),
},
)
results.extend(result["results"])

start += len(result["results"])

if start >= result["count"]:
break

return results


class DatasetResourceEntityManager(EntityManager):
entity_type = "dataset_resource"

show_action = "resource_show"
patch_action = "resource_patch"
delete_action = "resource_delete"

@classmethod
def get_fields(cls) -> list[FieldItem]:
Expand Down Expand Up @@ -239,8 +268,11 @@ def search_entities_by_filters(

class GroupEntityManager(EntityManager):
entity_type = "group"

list_action = "group_list"
show_action = "group_show"
patch_action = "group_patch"
delete_action = "group_delete"

@classmethod
def get_fields(cls) -> list[FieldItem]:
Expand Down Expand Up @@ -277,8 +309,9 @@ def search_entities_by_filters(
If we need an OR operator we should use `any` instead of `all` func.
"""
# TODO: for now we're going to fetch only 25 groups
item_list = tk.get_action(cls.show_action)(
# TODO: for now we're going to fetch only 25 groups due to some
# core restrictions.
item_list = tk.get_action(cls.list_action)(
{"ignore_auth": True}, {"all_fields": True}
)

Expand Down Expand Up @@ -335,8 +368,11 @@ def search_entities_by_filters(

class OrganizationEntityManager(GroupEntityManager):
entity_type = "organization"
action = "organization_list"

list_action = "organization_list"
show_action = "organization_show"
patch_action = "organization_patch"
delete_action = "organization_delete"


def get_entity_managers() -> dict[str, type[EntityManager]]:
Expand Down
Loading

0 comments on commit 9d05d3c

Please sign in to comment.