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 = "*"