diff --git a/client/src/components/Page/PageList.test.js b/client/src/components/Page/PageList.test.js
index 5bbce2dc08e4..ef61d1f07397 100644
--- a/client/src/components/Page/PageList.test.js
+++ b/client/src/components/Page/PageList.test.js
@@ -5,6 +5,7 @@ import MockAdapter from "axios-mock-adapter";
import { formatDistanceToNow, parseISO } from "date-fns";
import flushPromises from "flush-promises";
import { PiniaVuePlugin } from "pinia";
+import { useUserStore } from "stores/userStore";
import { getLocalVue, wait } from "tests/jest/helpers";
import PageList from "./PageList.vue";
@@ -98,16 +99,29 @@ describe("PgeList.vue", () => {
const mockPublishedPageData = [publishedPage];
const mockTwoPageData = [privatePage, pageA];
- function mountPersonalGrid() {
+ function mountGrid(propsData) {
+ const pinia = createTestingPinia();
+ const userStore = useUserStore();
+ userStore.currentUser = { username: "jimmyPage", tags_used: [] };
+
wrapper = mount(PageList, {
- propsData: propsDataPersonalGrid,
+ propsData,
localVue,
+ pinia,
stubs: {
icon: { template: "
" },
},
});
}
+ function mountPersonalGrid() {
+ mountGrid(propsDataPersonalGrid);
+ }
+
+ function mountPublishedGrid() {
+ mountGrid(propsDataPublishedGrid);
+ }
+
describe(" with empty page list", () => {
beforeEach(async () => {
axiosMock.onAny().reply(200, [], { total_matches: "0" });
@@ -145,14 +159,7 @@ describe("PgeList.vue", () => {
jest.spyOn(PageList.methods, "decorateData").mockImplementation((page) => {
page.shared = false;
});
- wrapper = mount(PageList, {
- propsData: propsDataPersonalGrid,
- localVue,
- pinia: createTestingPinia(),
- stubs: {
- icon: { template: "" },
- },
- });
+ mountPersonalGrid();
await flushPromises();
});
@@ -215,14 +222,7 @@ describe("PgeList.vue", () => {
jest.spyOn(PageList.methods, "decorateData").mockImplementation((page) => {
page.shared = true;
});
- wrapper = mount(PageList, {
- propsData: propsDataPersonalGrid,
- localVue,
- pinia: createTestingPinia(),
- stubs: {
- icon: { template: "" },
- },
- });
+ mountPersonalGrid();
await flushPromises();
});
it("updates filter when published icon is clicked", async () => {
@@ -257,14 +257,7 @@ describe("PgeList.vue", () => {
jest.spyOn(PageList.methods, "decorateData").mockImplementation((page) => {
page.shared = false;
});
- wrapper = mount(PageList, {
- propsData: propsDataPublishedGrid,
- localVue,
- pinia: createTestingPinia(),
- stubs: {
- icon: { template: "" },
- },
- });
+ mountPublishedGrid();
await flushPromises();
});
@@ -301,14 +294,7 @@ describe("PgeList.vue", () => {
jest.spyOn(PageList.methods, "decorateData").mockImplementation((page) => {
page.shared = false;
});
- wrapper = mount(PageList, {
- propsData: propsDataPublishedGrid,
- localVue,
- pinia: createTestingPinia(),
- stubs: {
- icon: { template: "" },
- },
- });
+ mountPublishedGrid();
await flushPromises();
});
diff --git a/client/src/components/Page/PageList.vue b/client/src/components/Page/PageList.vue
index 619bb0067139..2f941d190b5a 100644
--- a/client/src/components/Page/PageList.vue
+++ b/client/src/components/Page/PageList.vue
@@ -50,7 +50,7 @@
clickable
:value="row.item.tags"
:index="row.index"
- :disabled="published"
+ :disabled="published || !currentUserOwnsItem(row.item)"
@input="(tags) => onTags(tags, row.index)"
@tag-click="(tag) => applyFilter('tag', tag, true)" />
@@ -100,11 +100,13 @@ import StatelessTags from "components/TagsMultiselect/StatelessTags";
import UtcDate from "components/UtcDate";
import paginationMixin from "components/Workflow/paginationMixin";
import { getAppRoot } from "onload/loadConfig";
+import { storeToRefs } from "pinia";
import Filtering, { contains, equals, expandNameTag, toBool } from "utils/filtering";
import _l from "utils/localization";
import { useRouter } from "vue-router/composables";
import { updateTags } from "@/api/tags";
+import { useUserStore } from "@/stores/userStore";
import { absPath } from "@/utils/redirect";
import PageDropdown from "./PageDropdown";
@@ -224,8 +226,10 @@ export default {
},
setup() {
const router = useRouter();
+ const { currentUser } = storeToRefs(useUserStore());
return {
router,
+ currentUser,
};
},
data() {
@@ -304,6 +308,9 @@ export default {
shareLink: function (item) {
this.router.push(`sharing?id=${item.id}`);
},
+ currentUserOwnsItem: function (item) {
+ return item.username === this.currentUser.username;
+ },
decorateData(page) {
const Galaxy = getGalaxyInstance();
page.shared = page.username !== Galaxy.user.attributes.username;
diff --git a/doc/source/releases/23.2.rst b/doc/source/releases/23.2.rst
index d02cdf27c5da..6d40457cd68d 100644
--- a/doc/source/releases/23.2.rst
+++ b/doc/source/releases/23.2.rst
@@ -14,6 +14,9 @@ Enhancements
.. feature
+* Initial release notes for 23.2
+ (thanks to `@dannon `__).
+ `Pull Request 17259`_
* Make form repeat blocks reorderable
(thanks to `@ElectronicBlueberry `__).
`Pull Request 14892`_
@@ -135,6 +138,9 @@ Enhancements
`Pull Request 16345`_
.. enhancement_tag_jobs
+* Enable job resubmissions in k8s runner
+ (thanks to `@pcm32 `__).
+ `Pull Request 15238`_
* Towards SQLAlchemy 2.0 (upgrades to SQLAlchemy Core usage)
(thanks to `@jdavcs `__).
`Pull Request 16264`_
@@ -154,6 +160,9 @@ Enhancements
`Pull Request 17178`_
.. enhancement
+* Improve embed performance
+ (thanks to `@ElectronicBlueberry `__).
+ `Pull Request 17326`_
* Add parameter name to validation errors
(thanks to `@bernt-matthias `__).
`Pull Request 15440`_
@@ -554,6 +563,9 @@ Fixes
`Pull Request 17233`_
.. bug_tag_tools
+* Add back 1.1.0 version of Filtering1 tool
+ (thanks to `@mvdbeek `__).
+ `Pull Request 16883`_
* Add missing requirements to perl tools
(thanks to `@mvdbeek `__).
`Pull Request 16763`_
@@ -571,6 +583,9 @@ Fixes
`Pull Request 17036`_
.. bug_tag_workflows
+* Fix ``to_cwl`` for nested collections
+ (thanks to `@mvdbeek `__).
+ `Pull Request 17276`_
* Fix workflow output display without label
(thanks to `@mvdbeek `__).
`Pull Request 16749`_
@@ -629,6 +644,12 @@ Fixes
`Pull Request 17104`_
.. bug_tag_jobs
+* Rollback invalidated transaction
+ (thanks to `@jdavcs `__).
+ `Pull Request 17280`_
+* Rollback invalidated transaction: catch them earlier
+ (thanks to `@jdavcs `__).
+ `Pull Request 17312`_
* Ensure Job belongs to current SQLAlchemy session
(thanks to `@jdavcs `__).
`Pull Request 16647`_
@@ -666,6 +687,30 @@ Fixes
`Pull Request 17055`_
.. bug
+* Remove duplicates when copying sections for tool panel view
+ (thanks to `@bernt-matthias `__).
+ `Pull Request 17117`_
+* Tagging component performance improvements
+ (thanks to `@ElectronicBlueberry `__).
+ `Pull Request 17253`_
+* Always copy datasets in collection builder modals
+ (thanks to `@mvdbeek `__).
+ `Pull Request 17268`_
+* Install newer celery on python 3.8+
+ (thanks to `@mvdbeek `__).
+ `Pull Request 17309`_
+* Backport Rollback invalidated transaction: catch them earlier
+ (thanks to `@mvdbeek `__).
+ `Pull Request 17315`_
+* Discard SQLAlchemy session after task completion
+ (thanks to `@mvdbeek `__).
+ `Pull Request 17317`_
+* Scope session for job runner monitor loop
+ (thanks to `@mvdbeek `__).
+ `Pull Request 17319`_
+* Fix subworkflow edit button
+ (thanks to `@ElectronicBlueberry `__).
+ `Pull Request 17330`_
* Disable verbose parso logging in db_shell.py
(thanks to `@mvdbeek `__).
`Pull Request 16410`_
diff --git a/doc/source/releases/23.2_announce_user.rst b/doc/source/releases/23.2_announce_user.rst
index 0e77b1a3d1ae..5d4955bfc9ef 100644
--- a/doc/source/releases/23.2_announce_user.rst
+++ b/doc/source/releases/23.2_announce_user.rst
@@ -172,6 +172,9 @@ Builtin Tool Updates
===========================================================
.. tools
+* Add back 1.1.0 version of Filtering1 tool
+ (thanks to `@mvdbeek `__).
+ `Pull Request 16883`_
* Update cellxgene interactive tool to 1.1.1
(thanks to `@pcm32 `__).
`Pull Request 15313`_
diff --git a/doc/source/releases/23.2_prs.rst b/doc/source/releases/23.2_prs.rst
index 5f3aebf59e19..ae544f45c277 100644
--- a/doc/source/releases/23.2_prs.rst
+++ b/doc/source/releases/23.2_prs.rst
@@ -1,5 +1,21 @@
.. github_links
+.. _Pull Request 15238: https://github.com/galaxyproject/galaxy/pull/15238
+.. _Pull Request 16883: https://github.com/galaxyproject/galaxy/pull/16883
+.. _Pull Request 17117: https://github.com/galaxyproject/galaxy/pull/17117
+.. _Pull Request 17253: https://github.com/galaxyproject/galaxy/pull/17253
+.. _Pull Request 17259: https://github.com/galaxyproject/galaxy/pull/17259
+.. _Pull Request 17268: https://github.com/galaxyproject/galaxy/pull/17268
+.. _Pull Request 17276: https://github.com/galaxyproject/galaxy/pull/17276
+.. _Pull Request 17280: https://github.com/galaxyproject/galaxy/pull/17280
+.. _Pull Request 17309: https://github.com/galaxyproject/galaxy/pull/17309
+.. _Pull Request 17312: https://github.com/galaxyproject/galaxy/pull/17312
+.. _Pull Request 17315: https://github.com/galaxyproject/galaxy/pull/17315
+.. _Pull Request 17317: https://github.com/galaxyproject/galaxy/pull/17317
+.. _Pull Request 17319: https://github.com/galaxyproject/galaxy/pull/17319
+.. _Pull Request 17323: https://github.com/galaxyproject/galaxy/pull/17323
+.. _Pull Request 17326: https://github.com/galaxyproject/galaxy/pull/17326
+.. _Pull Request 17330: https://github.com/galaxyproject/galaxy/pull/17330
.. _Pull Request 14892: https://github.com/galaxyproject/galaxy/pull/14892
.. _Pull Request 14955: https://github.com/galaxyproject/galaxy/pull/14955
.. _Pull Request 15000: https://github.com/galaxyproject/galaxy/pull/15000
diff --git a/doc/source/releases/24.0_announce.rst b/doc/source/releases/24.0_announce.rst
index b85453e89568..d6ef5449d037 100644
--- a/doc/source/releases/24.0_announce.rst
+++ b/doc/source/releases/24.0_announce.rst
@@ -2,7 +2,7 @@
:orphan:
===========================================================
-24.0 Galaxy Release
+24.0 Galaxy Release (Early 2024)
===========================================================
diff --git a/lib/galaxy/dependencies/pinned-requirements.txt b/lib/galaxy/dependencies/pinned-requirements.txt
index cc8d86eec7dc..396004201838 100644
--- a/lib/galaxy/dependencies/pinned-requirements.txt
+++ b/lib/galaxy/dependencies/pinned-requirements.txt
@@ -92,6 +92,7 @@ idna==3.5 ; python_version >= "3.7" and python_version < "3.12"
importlib-metadata==4.13.0 ; python_version >= "3.7" and python_version < "3.12"
importlib-resources==5.12.0 ; python_version >= "3.7" and python_version < "3.12"
isa-rwval==0.10.10 ; python_version >= "3.7" and python_version < "3.12"
+isal==1.3.0 ; python_version >= "3.7" and python_version < "3.12"
isodate==0.6.1 ; python_version >= "3.7" and python_version < "3.12"
jinja2==3.1.2 ; python_version >= "3.7" and python_version < "3.12"
jmespath==1.0.1 ; python_version >= "3.7" and python_version < "3.12"
diff --git a/lib/galaxy/model/store/__init__.py b/lib/galaxy/model/store/__init__.py
index e8a6c95fb377..973cec825999 100644
--- a/lib/galaxy/model/store/__init__.py
+++ b/lib/galaxy/model/store/__init__.py
@@ -2753,7 +2753,7 @@ def _finalize(self) -> None:
out_file = out_file_name[: -len(".zip")]
else:
out_file = out_file_name
- rval = shutil.make_archive(out_file, "zip", self.export_directory)
+ rval = shutil.make_archive(out_file, "fastzip", self.export_directory)
if not self.file_source_uri:
shutil.move(rval, self.out_file)
else:
diff --git a/lib/galaxy/util/compression_utils.py b/lib/galaxy/util/compression_utils.py
index c10a6b4ed822..205cfd5558ca 100644
--- a/lib/galaxy/util/compression_utils.py
+++ b/lib/galaxy/util/compression_utils.py
@@ -3,6 +3,7 @@
import io
import logging
import os
+import shutil
import tarfile
import tempfile
import zipfile
@@ -30,6 +31,12 @@
is_gzip,
)
+try:
+ from isal import isal_zlib
+except ImportError:
+ isal_zlib = None # type: ignore[assignment,unused-ignore]
+
+
log = logging.getLogger(__name__)
FileObjTypeStr = Union[IO[str], io.TextIOWrapper]
@@ -345,3 +352,84 @@ def zipfile_ok(self, path_to_archive: StrPath) -> bool:
if not member_path.startswith(basename):
return False
return True
+
+
+class FastZipFile(zipfile.ZipFile):
+ """
+ Simple wrapper around ZipFile that uses the default compression strategy of ISA-L
+ to write zip files. Ignores compresslevel and compresstype arguments, and is
+ 3 to 4 times faster than the zlib implementation at the default compression level.
+ """
+
+ def _open_to_write(self, *args, **kwargs): # type: ignore[no-untyped-def]
+ zwf = super()._open_to_write(*args, **kwargs) # type: ignore[misc]
+ if isal_zlib and self.compression == zipfile.ZIP_DEFLATED:
+ zwf._compressor = isal_zlib.compressobj(isal_zlib.ISAL_DEFAULT_COMPRESSION, isal_zlib.DEFLATED, -15, 9)
+ return zwf
+
+
+# modified from shutil._make_zipfile
+def make_fast_zipfile(
+ base_name: str,
+ base_dir: str,
+ verbose: int = 0,
+ dry_run: int = 0,
+ logger: Optional[logging.Logger] = None,
+ owner: Optional[str] = None,
+ group: Optional[str] = None,
+ root_dir: Optional[str] = None,
+) -> str:
+ """Create a zip file from all the files under 'base_dir'.
+
+ The output zip file will be named 'base_name' + ".zip". Returns the
+ name of the output zip file.
+ """
+
+ zip_filename = base_name + ".zip"
+ archive_dir = os.path.dirname(base_name)
+
+ if archive_dir and not os.path.exists(archive_dir):
+ if logger is not None:
+ logger.info("creating %s", archive_dir)
+ if not dry_run:
+ os.makedirs(archive_dir)
+
+ if logger is not None:
+ logger.info("creating '%s' and adding '%s' to it", zip_filename, base_dir)
+
+ if not dry_run:
+ with FastZipFile(zip_filename, mode="w", compression=zipfile.ZIP_DEFLATED) as zf:
+ arcname = os.path.normpath(base_dir)
+ if root_dir is not None:
+ base_dir = os.path.join(root_dir, base_dir)
+ base_dir = os.path.normpath(base_dir)
+ if arcname != os.curdir:
+ zf.write(base_dir, arcname)
+ if logger is not None:
+ logger.info("adding '%s'", base_dir)
+ for dirpath, dirnames, filenames in os.walk(base_dir):
+ arcdirpath = dirpath
+ if root_dir is not None:
+ arcdirpath = os.path.relpath(arcdirpath, root_dir)
+ arcdirpath = os.path.normpath(arcdirpath)
+ for name in sorted(dirnames):
+ path = os.path.join(dirpath, name)
+ arcname = os.path.join(arcdirpath, name)
+ zf.write(path, arcname)
+ if logger is not None:
+ logger.info("adding '%s'", path)
+ for name in filenames:
+ path = os.path.join(dirpath, name)
+ path = os.path.normpath(path)
+ if os.path.isfile(path):
+ arcname = os.path.join(arcdirpath, name)
+ zf.write(path, arcname)
+ if logger is not None:
+ logger.info("adding '%s'", path)
+
+ if root_dir is not None:
+ zip_filename = os.path.abspath(zip_filename)
+ return zip_filename
+
+
+shutil.register_archive_format("fastzip", make_fast_zipfile)
diff --git a/packages/data/setup.cfg b/packages/data/setup.cfg
index 02f74386b9d5..866021657d87 100644
--- a/packages/data/setup.cfg
+++ b/packages/data/setup.cfg
@@ -44,6 +44,7 @@ install_requires =
h5grove
h5py
isa-rwval
+ isal
MarkupSafe
msal
mrcfile
diff --git a/packages/files/setup.cfg b/packages/files/setup.cfg
index 9f89c257b476..8ef367a9d14f 100644
--- a/packages/files/setup.cfg
+++ b/packages/files/setup.cfg
@@ -34,6 +34,7 @@ include_package_data = True
install_requires =
galaxy-util
fs
+ isal
typing-extensions
packages = find:
python_requires = >=3.7
diff --git a/pyproject.toml b/pyproject.toml
index ec98c6f49ef2..f6d9c0ca03a5 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -71,6 +71,7 @@ h5py = "*"
importlib-metadata = "<5" # Work around https://github.com/celery/kombu/issues/1600
importlib-resources = "*"
isa-rwval = ">=0.10.10"
+isal = "*"
kombu = "*"
lagom = "*"
Mako = "*"