From d05e8819009dc200d11453cdfff0e96aa5551b72 Mon Sep 17 00:00:00 2001 From: Penghai Date: Wed, 2 Jun 2021 14:32:40 +1000 Subject: [PATCH 01/13] Bump the version to 2021.1.1 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index c35e4eef9a..e7814f534f 100644 --- a/build.sbt +++ b/build.sbt @@ -116,7 +116,7 @@ name := "Equella" equellaMajor in ThisBuild := 2021 equellaMinor in ThisBuild := 1 -equellaPatch in ThisBuild := 0 +equellaPatch in ThisBuild := 1 equellaStream in ThisBuild := "Stable" equellaBuild in ThisBuild := buildConfig.value.getString("build.buildname") From 767e5eb72590f8a738ddd47e8c3b47fd264480b8 Mon Sep 17 00:00:00 2001 From: PenghaiZhang <47203811+PenghaiZhang@users.noreply.github.com> Date: Mon, 31 May 2021 09:09:52 +1000 Subject: [PATCH 02/13] Merge pull request #3048 from PenghaiZhang/bugfix/image-gallery-not-accessible Remove the ACL check for listing MIME types. --- .../scalasrc/com/tle/web/api/settings/MimeTypeResource.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/Source/Plugins/Core/com.equella.core/scalasrc/com/tle/web/api/settings/MimeTypeResource.scala b/Source/Plugins/Core/com.equella.core/scalasrc/com/tle/web/api/settings/MimeTypeResource.scala index 96682b1c4a..4fc31ab492 100644 --- a/Source/Plugins/Core/com.equella.core/scalasrc/com/tle/web/api/settings/MimeTypeResource.scala +++ b/Source/Plugins/Core/com.equella.core/scalasrc/com/tle/web/api/settings/MimeTypeResource.scala @@ -70,7 +70,6 @@ class MimeTypeResource { responseContainer = "List" ) def listMimeTypes: Response = { - LegacyGuice.mimePrivProvider.checkAuthorised() val mimeEntries = LegacyGuice.mimeTypeService.searchByMimeType(Constants.BLANK, 0, -1).getResults.asScala val mimeTypes = mimeEntries.map( From cb13fe8cd1f652024e661fd1dea454a0b58bc775 Mon Sep 17 00:00:00 2001 From: PenghaiZhang <47203811+PenghaiZhang@users.noreply.github.com> Date: Mon, 31 May 2021 09:10:36 +1000 Subject: [PATCH 03/13] Merge pull request #3050 from PenghaiZhang/bugfix/mime-type-filter-carry-over-to-video-gallery Exclude `mimeTypes` from a gallery search --- .../js/tsrc/modules/GallerySearchModule.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Source/Plugins/Core/com.equella.core/js/tsrc/modules/GallerySearchModule.ts b/Source/Plugins/Core/com.equella.core/js/tsrc/modules/GallerySearchModule.ts index a53e335133..c997aef010 100644 --- a/Source/Plugins/Core/com.equella.core/js/tsrc/modules/GallerySearchModule.ts +++ b/Source/Plugins/Core/com.equella.core/js/tsrc/modules/GallerySearchModule.ts @@ -508,7 +508,7 @@ export const videoGallerySearch = async ( options: SearchOptions ): Promise> => gallerySearch( - { ...options, musts: videoGalleryMusts }, + { ...options, musts: videoGalleryMusts, mimeTypes: undefined }, filterAttachmentsByVideo ); @@ -534,4 +534,8 @@ export const listImageGalleryClassifications = async ( export const listVideoGalleryClassifications = async ( options: SearchOptions ): Promise => - listClassifications({ ...options, musts: videoGalleryMusts }); + listClassifications({ + ...options, + musts: videoGalleryMusts, + mimeTypes: undefined, + }); From 4ae42856a7ab2bd6b33fc382462509b8e6747ad7 Mon Sep 17 00:00:00 2001 From: PenghaiZhang <47203811+PenghaiZhang@users.noreply.github.com> Date: Fri, 4 Jun 2021 17:10:49 +1000 Subject: [PATCH 04/13] Merge pull request #3064 from PenghaiZhang/bugfix/default-viewer-disabled Bugfix/default viewer disabled --- .../Core/com.equella.core/plugin-jpf.xml | 5 + .../lang/i18n-resource-centre.properties | 2 + .../v20211/EnableDefaultViewerMigration.java | 111 ++++++++++++++++++ .../tle/core/mimetypes/MimeTypeService.java | 12 ++ .../core/mimetypes/MimeTypeServiceImpl.java | 69 ++++------- .../institution/MimeEntryConverter.java | 15 +-- .../section/MimeDefaultViewerSection.java | 10 ++ .../section/MimeTypesEditSection.java | 13 +- oeq-ts-rest-api/src/MimeType.ts | 1 + 9 files changed, 184 insertions(+), 54 deletions(-) create mode 100644 Source/Plugins/Core/com.equella.core/src/com/tle/core/institution/migration/v20211/EnableDefaultViewerMigration.java diff --git a/Source/Plugins/Core/com.equella.core/plugin-jpf.xml b/Source/Plugins/Core/com.equella.core/plugin-jpf.xml index fb89dfe1c2..f624f92ea4 100644 --- a/Source/Plugins/Core/com.equella.core/plugin-jpf.xml +++ b/Source/Plugins/Core/com.equella.core/plugin-jpf.xml @@ -6061,6 +6061,11 @@ + + + + + diff --git a/Source/Plugins/Core/com.equella.core/resources/lang/i18n-resource-centre.properties b/Source/Plugins/Core/com.equella.core/resources/lang/i18n-resource-centre.properties index 425839efa3..e2a2c47090 100644 --- a/Source/Plugins/Core/com.equella.core/resources/lang/i18n-resource-centre.properties +++ b/Source/Plugins/Core/com.equella.core/resources/lang/i18n-resource-centre.properties @@ -81,6 +81,7 @@ /com.tle.core.entity.services.migration.v20202.facetedsearch.classification=Create a new table for faceted search classification /com.tle.core.entity.services.migration.v20202.removelastknownuserconstraint=Remove last known user composite constraint (username and institution ID) /com.tle.core.entity.services.migration.v20202.indexing.errored=Add column to attachment table to allow indexer to skip individual attachments that have failed. +/com.tle.core.entity.services.migration.v20211.enable.default.viewer=Ensure configured default viewers are also enabled /com.tle.core.entity.services.query.contains={0} is {1} /com.tle.core.entity.services.query.date.after={0} after {1} /com.tle.core.entity.services.query.date.before={0} before {1} @@ -2746,6 +2747,7 @@ mimetypes.migration.title=Update default MIME Type icons mimetypes.settings.description=Display a list of MIME types, where the properties can be edited, including viewer defaults mimetypes.settings.title=MIME types mimetypes.title=MIME type editor +mimetypes.default.viewer.not.enabled=Default viewer is not enabled missingprivileges=You do not have the required privilege to access this page\: {0} moddialog.accepted=Moderators already accepted moddialog.and=and diff --git a/Source/Plugins/Core/com.equella.core/src/com/tle/core/institution/migration/v20211/EnableDefaultViewerMigration.java b/Source/Plugins/Core/com.equella.core/src/com/tle/core/institution/migration/v20211/EnableDefaultViewerMigration.java new file mode 100644 index 0000000000..2e221cebbc --- /dev/null +++ b/Source/Plugins/Core/com.equella.core/src/com/tle/core/institution/migration/v20211/EnableDefaultViewerMigration.java @@ -0,0 +1,111 @@ +/* + * Licensed to The Apereo Foundation under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * The Apereo Foundation licenses this file to you under the Apache License, + * Version 2.0, (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.tle.core.institution.migration.v20211; + +import com.google.inject.Singleton; +import com.tle.common.Check; +import com.tle.core.guice.Bind; +import com.tle.core.hibernate.impl.HibernateMigrationHelper; +import com.tle.core.migration.AbstractHibernateDataMigration; +import com.tle.core.migration.MigrationInfo; +import com.tle.core.migration.MigrationResult; +import com.tle.core.mimetypes.MimeTypeConstants; +import com.tle.core.mimetypes.MimeTypeService; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.inject.Inject; +import javax.persistence.CollectionTable; +import javax.persistence.Column; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.Lob; +import javax.persistence.MapKeyColumn; +import org.hibernate.Session; +import org.hibernate.annotations.AccessType; +import org.hibernate.annotations.MapKeyType; +import org.hibernate.annotations.Type; + +/** + * Some existing MIME types do not have their default viewers enabled. This results in error + * messages showing in the New Search UI. So we use this migration to ensure the default viewer is + * in list of enabled viewers. + */ +@Bind +@Singleton +public class EnableDefaultViewerMigration extends AbstractHibernateDataMigration { + + @Inject private MimeTypeService mimeTypeService; + + @Override + protected void executeDataMigration( + HibernateMigrationHelper helper, MigrationResult result, Session session) throws Exception { + final List entries = session.createQuery("FROM MimeEntry").list(); + for (FakeMimeEntry entry : entries) { + Map attributes = entry.attributes; + // Calling 'getEnabledViewerList' to fix this issue as the list returned from this function + // include the default viewer whereas the one saved in attributes may have default viewer + // missing. + String enabledViewers = mimeTypeService.getEnabledViewerList(attributes); + if (!Check.isEmpty(enabledViewers)) { + attributes.put(MimeTypeConstants.KEY_ENABLED_VIEWERS, enabledViewers); + session.update(entry); + session.flush(); + } + } + result.incrementStatus(); + } + + @Override + protected int countDataMigrations(HibernateMigrationHelper helper, Session session) { + return 0; + } + + @Override + protected Class[] getDomainClasses() { + return new Class[] {FakeMimeEntry.class}; + } + + @Override + public MigrationInfo createMigrationInfo() { + return new MigrationInfo("com.tle.core.entity.services.migration.v20211.enable.default.viewer"); + } + + @Entity(name = "MimeEntry") + @AccessType("field") + public static class FakeMimeEntry { + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + long id; + + @ElementCollection + @Column(name = "element", nullable = false) + @CollectionTable( + name = "mime_entry_attributes", + joinColumns = @JoinColumn(name = "mime_entry_id")) + @Lob + @MapKeyColumn(name = "mapkey", length = 100, nullable = false) + @MapKeyType(@Type(type = "string")) + Map attributes = new HashMap(); + } +} diff --git a/Source/Plugins/Core/com.equella.core/src/com/tle/core/mimetypes/MimeTypeService.java b/Source/Plugins/Core/com.equella.core/src/com/tle/core/mimetypes/MimeTypeService.java index 80f43578c0..236e9f8def 100644 --- a/Source/Plugins/Core/com.equella.core/src/com/tle/core/mimetypes/MimeTypeService.java +++ b/Source/Plugins/Core/com.equella.core/src/com/tle/core/mimetypes/MimeTypeService.java @@ -26,6 +26,7 @@ import com.tle.web.controls.resource.ResourceAttachmentBean; import java.util.Collection; import java.util.List; +import java.util.Map; @NonNullByDefault public interface MimeTypeService { @@ -87,6 +88,17 @@ public interface MimeTypeService { @Nullable String getMimeEntryForAttachment(Attachment attachment); + /** + * Return a list of enabled viewers. If the default viewer does not exist in the list, add it to + * the list. However, if the default viewer is "file", it will not be included because "file" is + * added to the list dynamically by {@link com.tle.web.mimetypes.section.MimeDefaultViewerSection} + * + * @param attributes Configured attributes of MIME type + * @return A string representing a list of enabled viewers and formatted as a JSON array of + * strings (e.g. ["fancy", "toimg"]) + */ + String getEnabledViewerList(Map attributes); + interface MimeEntryChanges { void editMimeEntry(MimeEntry entry); } diff --git a/Source/Plugins/Core/com.equella.core/src/com/tle/core/mimetypes/MimeTypeServiceImpl.java b/Source/Plugins/Core/com.equella.core/src/com/tle/core/mimetypes/MimeTypeServiceImpl.java index 8b446944f1..fcc117227d 100644 --- a/Source/Plugins/Core/com.equella.core/src/com/tle/core/mimetypes/MimeTypeServiceImpl.java +++ b/Source/Plugins/Core/com.equella.core/src/com/tle/core/mimetypes/MimeTypeServiceImpl.java @@ -136,13 +136,6 @@ public void resetEntries() { @Override public MimeTypesSearchResults searchByMimeType(String mimeType, int offset, int length) { String query = (Check.isEmpty(mimeType) ? "" : mimeType.toLowerCase()); // $NON-NLS-1$ - /* - * Map typeMap = - * mimeCache.getCache().getMimeEntries(); List mimes = new - * ArrayList(); for( Entry entry : - * typeMap.entrySet() ) { if( entry.getKey().startsWith(query) ) { - * mimes.add(entry.getValue()); } } return mimes; - */ return mimeEntryDao.searchAll(query, offset, length); } @@ -393,45 +386,6 @@ public List getAllTextExtracters() { @SuppressWarnings("unchecked") @Override public List getTextExtractersForMimeEntry(final MimeEntry mimeEntry) { - // Collection extensions = textExtracterTracker - // .getExtensions(new Filter() - // { - // @Override - // public boolean include(Extension t) - // { - // Collection mimes = t.getParameters("mimeType"); //$NON-NLS-1$ - // for( Parameter mime : mimes ) - // { - // String m = mime.valueAsString(); - // int wildIndex = m.indexOf('*'); - // if( wildIndex >= 0 ) - // { - // String nonWild = m.substring(0, wildIndex); - // if( mimeType.startsWith(nonWild) ) - // { - // return true; - // } - // } - // else - // { - // if( mimeType.equals(m) ) - // { - // return true; - // } - // } - // } - // return false; - // } - // }); - // - // List extracters = new - // ArrayList(); - // for( Extension e : extensions ) - // { - // extracters.add(textExtracterTracker.getBeanByExtension(e)); - // } - // return extracters; - List extracters = getAllTextExtracters(); List filtered = new ArrayList(); for (TextExtracterExtension extracter : extracters) { @@ -493,6 +447,29 @@ public String getMimeEntryForAttachment(Attachment attachment) { return null; } + @Override + public String getEnabledViewerList(Map attributes) { + String defaultViewer = attributes.get(MimeTypeConstants.KEY_DEFAULT_VIEWERID); + String enabledViewers = attributes.get(MimeTypeConstants.KEY_ENABLED_VIEWERS); + // File viewer is handled differently in 'MimeDefaultViewerSection' so there is no need to + // add it to the list. + if (Check.isEmpty(defaultViewer) + || defaultViewer.equals(MimeTypeConstants.VAL_DEFAULT_VIEWERID)) { + return enabledViewers; + } + + List enabledViewerList = new ArrayList<>(); + if (!Check.isEmpty(enabledViewers)) { + enabledViewerList.addAll( + JSONArray.toCollection(JSONArray.fromObject(enabledViewers), String.class)); + } + if (!enabledViewerList.contains(defaultViewer)) { + enabledViewerList.add(defaultViewer); + } + + return JSONArray.fromObject(enabledViewerList).toString(); + } + private synchronized Map> getExtensionMap() { if (extensionMap == null) { extensionMap = new HashMap>(); diff --git a/Source/Plugins/Core/com.equella.core/src/com/tle/core/mimetypes/institution/MimeEntryConverter.java b/Source/Plugins/Core/com.equella.core/src/com/tle/core/mimetypes/institution/MimeEntryConverter.java index 9a69af94f0..36e2e0d7d9 100644 --- a/Source/Plugins/Core/com.equella.core/src/com/tle/core/mimetypes/institution/MimeEntryConverter.java +++ b/Source/Plugins/Core/com.equella.core/src/com/tle/core/mimetypes/institution/MimeEntryConverter.java @@ -27,9 +27,10 @@ import com.tle.core.hibernate.equella.service.InitialiserService; import com.tle.core.institution.convert.AbstractMigratableConverter; import com.tle.core.institution.convert.ConverterParams; +import com.tle.core.mimetypes.MimeTypeConstants; +import com.tle.core.mimetypes.MimeTypeService; import com.tle.core.mimetypes.dao.MimeEntryDao; import java.io.IOException; -import java.util.Iterator; import java.util.List; import java.util.Map; import javax.inject.Inject; @@ -43,6 +44,7 @@ public class MimeEntryConverter extends AbstractMigratableConverter { @Inject private MimeEntryDao mimeDao; @Inject private InitialiserService initialiserService; + @Inject private MimeTypeService mimeTypeService; @Override public void doDelete(Institution institution, ConverterParams callback) { @@ -102,15 +104,14 @@ public void importIt( @Override public void run() { MimeEntry mimeEntry = xmlHelper.readXmlFile(mimeFolder, entry); - Map attrs = mimeEntry.getAttributes(); - Iterator iter = attrs.values().iterator(); - while (iter.hasNext()) { - if (Check.isEmpty(iter.next())) { - iter.remove(); - } + String enabledViewers = mimeTypeService.getEnabledViewerList(attrs); + if (!Check.isEmpty(enabledViewers)) { + attrs.put(MimeTypeConstants.KEY_ENABLED_VIEWERS, enabledViewers); } + + attrs.values().removeIf(Check::isEmpty); mimeEntry.setInstitution(institution); mimeEntry.setId(0); diff --git a/Source/Plugins/Core/com.equella.core/src/com/tle/web/mimetypes/section/MimeDefaultViewerSection.java b/Source/Plugins/Core/com.equella.core/src/com/tle/web/mimetypes/section/MimeDefaultViewerSection.java index 8c200a8daa..3cba80ac15 100644 --- a/Source/Plugins/Core/com.equella.core/src/com/tle/web/mimetypes/section/MimeDefaultViewerSection.java +++ b/Source/Plugins/Core/com.equella.core/src/com/tle/web/mimetypes/section/MimeDefaultViewerSection.java @@ -225,6 +225,12 @@ public void loadEntry(SectionInfo info, MimeEntry entry) { } } + public boolean isDefaultViewerEnabled(SectionInfo info) { + String defaultViewerId = defaultViewer.getSelectedValueAsString(info); + Set enabledViewerList = enabledViewers.getSelectedValuesAsStrings(info); + return enabledViewerList.contains(defaultViewerId); + } + @Override public void saveEntry(SectionInfo info, MimeEntry entry) { String viewerId = defaultViewer.getSelectedValueAsString(info); @@ -285,6 +291,10 @@ public SectionResult renderHtml(RenderEventContext context) { HtmlBooleanState enabledState = enabledOption.getBooleanState(); CheckboxRenderer enabledCheck = new CheckboxRenderer(enabledState); + if (defaultOption.getBooleanState().isChecked()) { + enabledState.setChecked(true); + } + TextFieldRenderer configField = new TextFieldRenderer(viewerConfigs.getValueState(context, viewerId)); configField.setHidden(true); diff --git a/Source/Plugins/Core/com.equella.core/src/com/tle/web/mimetypes/section/MimeTypesEditSection.java b/Source/Plugins/Core/com.equella.core/src/com/tle/web/mimetypes/section/MimeTypesEditSection.java index 9d359d0af3..31ed861be9 100644 --- a/Source/Plugins/Core/com.equella.core/src/com/tle/web/mimetypes/section/MimeTypesEditSection.java +++ b/Source/Plugins/Core/com.equella.core/src/com/tle/web/mimetypes/section/MimeTypesEditSection.java @@ -67,7 +67,7 @@ @SuppressWarnings("nls") public class MimeTypesEditSection extends AbstractPrototypeSection implements HtmlRenderer { - private static PluginResourceHelper resources = + private static final PluginResourceHelper resources = ResourcesService.getResourceHelper(MimeTypesEditSection.class); public static final NameValue TAB_DETAILS = new BundleNameValue(resources.key("tab.details"), "details"); @@ -80,6 +80,9 @@ public class MimeTypesEditSection extends AbstractPrototypeSection Date: Wed, 9 Jun 2021 12:10:09 +1000 Subject: [PATCH 05/13] Merge pull request #3078 from PenghaiZhang/feature/kaltura-dialog-ebp Improve styles for Kaltura wizard control dialog. --- .../resources/web/sass/legacy.scss | 56 ++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/Source/Plugins/Core/com.equella.core/resources/web/sass/legacy.scss b/Source/Plugins/Core/com.equella.core/resources/web/sass/legacy.scss index 39c7460c4a..1391c1ec36 100644 --- a/Source/Plugins/Core/com.equella.core/resources/web/sass/legacy.scss +++ b/Source/Plugins/Core/com.equella.core/resources/web/sass/legacy.scss @@ -4262,7 +4262,8 @@ a.modal-control.modal-save { } } -.urlHandler { +.urlHandler, +.kalturaHandler { .modal-content .modal-content-background .modal-content-inner .float-left { @include editAttachmentDialogStyles; } @@ -4272,6 +4273,59 @@ a.modal-control.modal-save { } } +.kalturaHandler { + @include buttonShadowBorder; + + .choice.odd { + background-color: white; + } + + .choice.even { + background-color: #f3f1eb; + } + + img#kaltura-logo { + position: absolute; + bottom: 25px; + right: 35px; + opacity: 0.35; + } + + .choice-list div { + position: relative; + padding: $doubleMargin; + } + + div.choice-list { + border: 1px solid #8f8b7e; + } + + div.choice-list .choice { + cursor: pointer; + } + + div.choice-list .choice:hover { + background-color: #ffffcc; + } + + div.choice-list .choice.selected { + background-color: #80d2ee; + } + + .choice h4 { + font-size: 12px; + padding-bottom: $minimalMargin; + } + + #kaltura-query button { + background-color: $primaryColor; + color: set-text-color($primaryColor); + padding-left: $singleMargin; + padding-right: $singleMargin; + margin-left: $singleMargin; + } +} + .fileHandler, .urlHandler { @include buttonShadowBorder; From bb2583fe4fa38b1f38aa764028ef9c699643eeec Mon Sep 17 00:00:00 2001 From: SammyIsConfused Date: Mon, 7 Jun 2021 18:33:38 +1000 Subject: [PATCH 06/13] Merge pull request #3069 from SammyIsConfused/bugfix/allow_dead_attachment_links_in_new_search Bugfix/allow dead attachment links in new search --- .../js/__mocks__/GallerySearchModule.mock.ts | 15 +++ .../js/__mocks__/searchresult_mock_data.ts | 110 ++++++++++++++++++ .../components/ItemAttachmentLink.stories.tsx | 2 + .../tsrc/modules/GallerySearchModule.test.ts | 5 + .../tsrc/modules/ViewerModule.test.ts | 9 +- .../search/components/SearchResult.test.tsx | 82 +++++++++++++ .../js/tsrc/components/ItemAttachmentLink.tsx | 39 ++++--- .../js/tsrc/components/OEQThumb.tsx | 17 +-- .../js/tsrc/modules/ViewerModule.ts | 15 ++- .../SearchResultAttachmentsList.tsx | 57 +++++++-- .../js/tsrc/util/langstrings.ts | 2 + .../com/tle/web/api/search/SearchHelper.scala | 68 ++++++++++- .../api/search/model/SearchResultItem.scala | 4 + .../com/tle/core/item/dao/AttachmentDao.java | 4 - .../core/item/dao/impl/AttachmentDaoImpl.java | 11 -- .../tle/core/item/service/ItemService.java | 15 ++- .../item/service/impl/ItemServiceImpl.java | 5 + .../tle/core/mimetypes/MimeTypeService.java | 3 +- .../core/mimetypes/MimeTypeServiceImpl.java | 29 +++-- .../tests/rest/institution/items/41/94505.xml | 94 +++++++++++++++ .../institution/items/41/94505/_ITEM/item.xml | 1 + .../tests/rest/institution/items/53/95797.xml | 102 ++++++++++++++++ .../institution/items/53/95797/_ITEM/item.xml | 1 + .../tests/rest/institution/items/63/95807.xml | 102 ++++++++++++++++ .../institution/items/63/95807/_ITEM/item.xml | 1 + .../tests/rest/institution/items/73/95817.xml | 102 ++++++++++++++++ .../institution/items/73/95817/_ITEM/item.xml | 1 + oeq-ts-rest-api/src/Search.ts | 5 + oeq-ts-rest-api/test/Search.test.ts | 65 +++++++++++ 29 files changed, 892 insertions(+), 74 deletions(-) create mode 100644 autotest/Tests/tests/rest/institution/items/41/94505.xml create mode 100644 autotest/Tests/tests/rest/institution/items/41/94505/_ITEM/item.xml create mode 100644 autotest/Tests/tests/rest/institution/items/53/95797.xml create mode 100644 autotest/Tests/tests/rest/institution/items/53/95797/_ITEM/item.xml create mode 100644 autotest/Tests/tests/rest/institution/items/63/95807.xml create mode 100644 autotest/Tests/tests/rest/institution/items/63/95807/_ITEM/item.xml create mode 100644 autotest/Tests/tests/rest/institution/items/73/95817.xml create mode 100644 autotest/Tests/tests/rest/institution/items/73/95817/_ITEM/item.xml diff --git a/Source/Plugins/Core/com.equella.core/js/__mocks__/GallerySearchModule.mock.ts b/Source/Plugins/Core/com.equella.core/js/__mocks__/GallerySearchModule.mock.ts index 0db9aa8a7c..31ce13850f 100644 --- a/Source/Plugins/Core/com.equella.core/js/__mocks__/GallerySearchModule.mock.ts +++ b/Source/Plugins/Core/com.equella.core/js/__mocks__/GallerySearchModule.mock.ts @@ -42,6 +42,7 @@ export const basicImageSearchResponse: OEQ.Search.SearchResult { preview: false, mimeType: "image/png", hasGeneratedThumb: true, + brokenAttachment: false, links: { view: "https://example.com/inst/items/1eeb3df5-3809-4655-925b-24d994e42ff6/1/?attachment.uuid=7186d40d-6159-4d07-8eee-4f7ee0cfdc4e", @@ -75,6 +76,7 @@ describe("buildGalleryEntry", () => { id: "b18ed9ab-1ddb-4961-8935-22bbf1095b24", description: "The Odyssey by Homer | Summary & Analysis", preview: false, + brokenAttachment: false, links: { view: "https://example.com/inst/items/234b9bd6-b603-4e26-8214-b79b8aab0ed9/1/?attachment.uuid=b18ed9ab-1ddb-4961-8935-22bbf1095b24", @@ -151,6 +153,7 @@ describe("buildGallerySearchResultItem", () => { preview: false, mimeType: "image/jpeg", hasGeneratedThumb: true, + brokenAttachment: false, links: { view: "https://example.com/inst/items/535e4e9b-4836-4011-8857-eb29260bf155/1/?attachment.uuid=e7e84411-7cc6-4516-9bc8-d60dab47fccb", @@ -166,6 +169,7 @@ describe("buildGallerySearchResultItem", () => { preview: false, mimeType: "image/jpeg", hasGeneratedThumb: true, + brokenAttachment: false, links: { view: "https://example.com/inst/items/535e4e9b-4836-4011-8857-eb29260bf155/1/?attachment.uuid=17f3036e-a3c6-4e6d-85cb-aaa78ca2835b", @@ -181,6 +185,7 @@ describe("buildGallerySearchResultItem", () => { preview: false, mimeType: "video/mp4", hasGeneratedThumb: true, + brokenAttachment: false, links: { view: "https://example.com/inst/items/535e4e9b-4836-4011-8857-eb29260bf155/1/?attachment.uuid=16a3d193-430c-45f2-bd0c-6a3fc0a3bcaf", diff --git a/Source/Plugins/Core/com.equella.core/js/__tests__/tsrc/modules/ViewerModule.test.ts b/Source/Plugins/Core/com.equella.core/js/__tests__/tsrc/modules/ViewerModule.test.ts index dffa6c5200..0e626fa594 100644 --- a/Source/Plugins/Core/com.equella.core/js/__tests__/tsrc/modules/ViewerModule.test.ts +++ b/Source/Plugins/Core/com.equella.core/js/__tests__/tsrc/modules/ViewerModule.test.ts @@ -29,11 +29,14 @@ describe("determineViewer()", () => { it("returns link viewer details for non-file attachment", () => { const testLink = "http://some.link/blah"; - expect(determineViewer("blah", testLink)).toEqual([linkViewerId, testLink]); + expect(determineViewer("blah", testLink, false)).toEqual([ + linkViewerId, + testLink, + ]); }); it("returns a 'link' viewer if full parameters aren't provided for 'file' attachments", () => { - const [viewer] = determineViewer(fileAttachmentType, fileViewUrl); + const [viewer] = determineViewer(fileAttachmentType, fileViewUrl, false); expect(viewer).toEqual(linkViewerId); }); @@ -41,6 +44,7 @@ describe("determineViewer()", () => { const [viewer, url] = determineViewer( fileAttachmentType, fileViewUrl, + false, "not/used", "save" ); @@ -55,6 +59,7 @@ describe("determineViewer()", () => { determineViewer( fileAttachmentType, fileViewUrl, + false, "audio/x-mp3", // rather than test every supported MIME type, just using one to keep things short mimeTypeViewerId ) diff --git a/Source/Plugins/Core/com.equella.core/js/__tests__/tsrc/search/components/SearchResult.test.tsx b/Source/Plugins/Core/com.equella.core/js/__tests__/tsrc/search/components/SearchResult.test.tsx index 3fe0b77bbc..db3a4dc08d 100644 --- a/Source/Plugins/Core/com.equella.core/js/__tests__/tsrc/search/components/SearchResult.test.tsx +++ b/Source/Plugins/Core/com.equella.core/js/__tests__/tsrc/search/components/SearchResult.test.tsx @@ -257,6 +257,44 @@ describe("", () => { ); }); + describe("Dead attachments handling", () => { + it("should display dead attachments with a warning label", async () => { + const { oneDeadAttachObj } = mockData; + const { queryByTitle } = await renderSearchResult(oneDeadAttachObj); + expect( + queryByTitle(languageStrings.searchpage.deadAttachmentWarning) + ).toBeInTheDocument(); + }); + + it("should not render dead attachments as clickable links", async () => { + //item with one dead attachment and one intact attachment + const { oneDeadOneAliveAttachObj } = mockData; + const { getByText, queryByLabelText } = await renderSearchResult( + oneDeadOneAliveAttachObj + ); + + // Given a user clicks on a broken attachment + userEvent.click( + getByText(oneDeadOneAliveAttachObj.attachments![0].description!) + ); + + // There is no lightbox, as it is not rendered as a link + expect( + queryByLabelText(languageStrings.common.action.openInNewWindow) + ).not.toBeInTheDocument(); + + // Now if they click on the intact attachment instead... + userEvent.click( + getByText(oneDeadOneAliveAttachObj.attachments![1].description!) + ); + + // ...There is a lightbox + expect( + queryByLabelText(languageStrings.common.action.openInNewWindow) + ).toBeInTheDocument(); + }); + }); + describe("In Selection Session", () => { beforeAll(() => { updateMockGlobalCourseList(); @@ -406,5 +444,49 @@ describe("", () => { queryByLabelText(selectAllAttachmentsString) ).not.toBeInTheDocument(); }); + + describe("Dead attachments handling", () => { + it("Should not be possible to select a dead attachment", async () => { + updateMockGetRenderData({ + ...basicRenderData, + selectionSessionInfo: selectSummaryButtonDisabled, + }); + const { queryByLabelText } = await renderSearchResult( + mockData.oneDeadAttachObj + ); + expect( + queryByLabelText(selectAttachmentString) + ).not.toBeInTheDocument(); + }); + + it("Should not show the Select All Attachments button if all attachments are dead", async () => { + const { queryByLabelText } = await renderSearchResult( + mockData.oneDeadAttachObj + ); + expect( + queryByLabelText(selectAllAttachmentsString) + ).not.toBeInTheDocument(); + }); + + it("Should show the Select All Attachments button if at least one attachment is not dead", async () => { + const { queryByLabelText, getByTitle } = await renderSearchResult( + mockData.oneDeadOneAliveAttachObj + ); + expect( + queryByLabelText(selectAllAttachmentsString) + ).toBeInTheDocument(); + // Given the user clicks Select All Attachments for an item with a dead attachment + // and an alive attachment... + userEvent.click(getByTitle(selectAllAttachmentsString)); + + // The function should only have been called with the attachment + // 78883eff-7cf6-4b14-ab76-2b7f84dbe833 which is the intact one + expect( + mockSelectResourceForCourseList + ).toHaveBeenCalledWith("72558c1d-8788-4515-86c8-b24a28cc451e/1", [ + "78883eff-7cf6-4b14-ab76-2b7f84dbe833", + ]); + }); + }); }); }); diff --git a/Source/Plugins/Core/com.equella.core/js/tsrc/components/ItemAttachmentLink.tsx b/Source/Plugins/Core/com.equella.core/js/tsrc/components/ItemAttachmentLink.tsx index 4dd8d28394..144fe2b927 100644 --- a/Source/Plugins/Core/com.equella.core/js/tsrc/components/ItemAttachmentLink.tsx +++ b/Source/Plugins/Core/com.equella.core/js/tsrc/components/ItemAttachmentLink.tsx @@ -15,13 +15,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Link } from "@material-ui/core"; +import { Link, Typography } from "@material-ui/core"; import * as React from "react"; import { SyntheticEvent, useState } from "react"; import { AttachmentAndViewerConfig, isViewerLightboxConfig, ViewerLightboxConfig, + ViewerLinkConfig, } from "../modules/ViewerModule"; import { languageStrings } from "../util/langstrings"; import Lightbox, { LightboxProps } from "./Lightbox"; @@ -47,13 +48,29 @@ export interface ItemAttachmentLinkProps { const ItemAttachmentLink = ({ children, selectedAttachment: { - attachment: { description, mimeType }, + attachment: { description, mimeType, brokenAttachment }, viewerConfig, }, }: ItemAttachmentLinkProps) => { const { attachmentLink } = languageStrings.searchpage.searchResult; const [lightBoxProps, setLightBoxProps] = useState(); + const buildSimpleLink = (viewerConfig: ViewerLinkConfig): JSX.Element => { + return brokenAttachment ? ( + + {description} + + ) : ( + + {children} + + ); + }; const buildLightboxLink = ({ config }: ViewerLightboxConfig): JSX.Element => { if (!mimeType) { throw new Error( @@ -85,19 +102,11 @@ const ItemAttachmentLink = ({ ); }; - return isViewerLightboxConfig(viewerConfig) ? ( - buildLightboxLink(viewerConfig) - ) : ( - // Lightbox viewer not specified, so go with the default of a simple link. - - {children} - - ); + + return isViewerLightboxConfig(viewerConfig) + ? buildLightboxLink(viewerConfig) + : // Lightbox viewer not specified, so go with the default of a simple link. + buildSimpleLink(viewerConfig); }; export default ItemAttachmentLink; diff --git a/Source/Plugins/Core/com.equella.core/js/tsrc/components/OEQThumb.tsx b/Source/Plugins/Core/com.equella.core/js/tsrc/components/OEQThumb.tsx index 47ffce5450..1baae380e8 100644 --- a/Source/Plugins/Core/com.equella.core/js/tsrc/components/OEQThumb.tsx +++ b/Source/Plugins/Core/com.equella.core/js/tsrc/components/OEQThumb.tsx @@ -106,6 +106,12 @@ export default function OEQThumb({ if (hasGeneratedThumb) { return oeqProvidedThumb; } + if (mimeType === "equella/item") { + return ; + } + if (mimeType === "equella/link") { + return ; + } let result = defaultThumb; if (mimeType?.startsWith("image")) { result = ; @@ -116,7 +122,9 @@ export default function OEQThumb({ }; let oeqThumb = defaultThumb; - + if (attachment.brokenAttachment) { + return defaultThumb; + } switch (attachmentType) { case "file": oeqThumb = handleMimeType(mimeType); @@ -128,12 +136,7 @@ export default function OEQThumb({ oeqThumb = ; break; case "custom/resource": - oeqThumb = - mimeType === "equella/item" ? ( - - ) : ( - oeqProvidedThumb - ); + oeqThumb = handleMimeType(mimeType); break; case "custom/flickr": case "custom/youtube": diff --git a/Source/Plugins/Core/com.equella.core/js/tsrc/modules/ViewerModule.ts b/Source/Plugins/Core/com.equella.core/js/tsrc/modules/ViewerModule.ts index 646173f949..12b924c2c3 100644 --- a/Source/Plugins/Core/com.equella.core/js/tsrc/modules/ViewerModule.ts +++ b/Source/Plugins/Core/com.equella.core/js/tsrc/modules/ViewerModule.ts @@ -87,15 +87,19 @@ export interface AttachmentAndViewerConfig { * @param viewUrl the basic view URL returned in search results * @param mimeType the MIME type of the attachment to determine the viewer for * @param mimeTypeViewerId the server specified `ViewerId` for the attachment + * @param broken whether or not this has been marked as a broken attachment by the server */ export const determineViewer = ( attachmentType: string, viewUrl: string, + broken: boolean, mimeType?: string, mimeTypeViewerId?: OEQ.MimeType.ViewerId ): ViewerDefinition => { const simpleLinkView: ViewerDefinition = ["link", viewUrl]; - + if (broken) { + return simpleLinkView; + } if (attachmentType !== "file" && attachmentType !== "custom/resource") { // For non-file attachments, we currently just defer to the link provided by the server return simpleLinkView; @@ -163,6 +167,7 @@ export const getViewerDefinitionForAttachment = ( mimeType, links: { view: defaultViewUrl }, filePath, + brokenAttachment, } = attachment; const viewUrl = determineAttachmentViewUrl( itemUuid, @@ -172,7 +177,13 @@ export const getViewerDefinitionForAttachment = ( filePath ); - return determineViewer(attachmentType, viewUrl, mimeType, mimeTypeViewerId); + return determineViewer( + attachmentType, + viewUrl, + brokenAttachment, + mimeType, + mimeTypeViewerId + ); }; /** diff --git a/Source/Plugins/Core/com.equella.core/js/tsrc/search/components/SearchResultAttachmentsList.tsx b/Source/Plugins/Core/com.equella.core/js/tsrc/search/components/SearchResultAttachmentsList.tsx index d78f90c55a..e7a107a00a 100644 --- a/Source/Plugins/Core/com.equella.core/js/tsrc/search/components/SearchResultAttachmentsList.tsx +++ b/Source/Plugins/Core/com.equella.core/js/tsrc/search/components/SearchResultAttachmentsList.tsx @@ -37,6 +37,7 @@ import DragIndicatorIcon from "@material-ui/icons/DragIndicator"; import ExpandMore from "@material-ui/icons/ExpandMore"; import InsertDriveFile from "@material-ui/icons/InsertDriveFile"; import Search from "@material-ui/icons/Search"; +import Warning from "@material-ui/icons/Warning"; import * as OEQ from "@openequella/rest-api-client"; import * as React from "react"; import { SyntheticEvent, useEffect, useState } from "react"; @@ -138,8 +139,11 @@ export const SearchResultAttachmentsList = ({ return; } - const getViewerID = async (mimeType: string) => { + const getViewerID = async (broken: boolean, mimeType: string) => { let viewerDetails: OEQ.MimeType.MimeTypeViewerDetail | undefined; + if (broken) { + return undefined; + } try { viewerDetails = await getViewerDetails(mimeType); } catch (error) { @@ -155,8 +159,10 @@ export const SearchResultAttachmentsList = ({ const attachmentsAndViewerDefinitions = await Promise.all( attachments.map>( async (attachment) => { - const { mimeType } = attachment; - const viewerId = mimeType ? await getViewerID(mimeType) : undefined; + const { mimeType, brokenAttachment } = attachment; + const viewerId = mimeType + ? await getViewerID(brokenAttachment, mimeType) + : undefined; return { attachment, viewerDefinition: getViewerDefinitionForAttachment( @@ -234,14 +240,33 @@ export const SearchResultAttachmentsList = ({ setAttachExpanded(!attachExpanded); }; + const buildIcon = (broken: boolean) => { + if (broken) { + return ( + + + + ); + } + return inStructured ? : ; + }; + + const isAttachmentSelectable = (broken: boolean) => + inSelectionSession && !broken; + const attachmentsList = attachmentsAndViewerConfigs.map( (attachmentAndViewerConfig: AttachmentAndViewerConfig) => { const { - attachment: { id, description }, + attachment: { id, description, brokenAttachment }, } = attachmentAndViewerConfig; return ( { + if (brokenAttachment) { + event.stopPropagation(); + } + }} key={id} id={id} button @@ -250,13 +275,11 @@ export const SearchResultAttachmentsList = ({ data-itemversion={version} data-attachmentuuid={id} > - - {inStructured ? : } - + {buildIcon(brokenAttachment)} - {inSelectionSession && ( + {isAttachmentSelectable(brokenAttachment) && ( ); + // Only show the Select All Attachments button if at least one attachment is not dead + const atLeastOneIntactAttachment = attachmentsAndViewerConfigs.some( + ({ attachment }) => !attachment.brokenAttachment + ); + + const showSelectAllAttachments = atLeastOneIntactAttachment && !inSkinny; + const accordionSummaryContent = inSelectionSession ? ( {accordionText} - {!inSkinny && ( + {showSelectAllAttachments && ( { - const attachments = attachmentsAndViewerConfigs.map( - ({ attachment }) => attachment.id - ); + const attachments = attachmentsAndViewerConfigs + .filter( + // filter out dead attachments from select all function + ({ attachment }) => !attachment.brokenAttachment + ) + .map(({ attachment }) => attachment.id); handleSelectResource(itemKey, attachments); }} /> diff --git a/Source/Plugins/Core/com.equella.core/js/tsrc/util/langstrings.ts b/Source/Plugins/Core/com.equella.core/js/tsrc/util/langstrings.ts index 7cdb592ba5..95caa09d00 100644 --- a/Source/Plugins/Core/com.equella.core/js/tsrc/util/langstrings.ts +++ b/Source/Plugins/Core/com.equella.core/js/tsrc/util/langstrings.ts @@ -330,6 +330,8 @@ export const languageStrings = { newSearchHelperText: "Clears search text and filters", shareSearchHelperText: "Copy search link to clipboard", shareSearchConfirmationText: "Search link saved to clipboard", + deadAttachmentWarning: + "This attachment appears to be broken or inaccessible.", displayModeSelector: { title: "Display mode", modeItemList: "Item List", diff --git a/Source/Plugins/Core/com.equella.core/scalasrc/com/tle/web/api/search/SearchHelper.scala b/Source/Plugins/Core/com.equella.core/scalasrc/com/tle/web/api/search/SearchHelper.scala index bb1602a7a0..8b397cc4be 100644 --- a/Source/Plugins/Core/com.equella.core/scalasrc/com/tle/web/api/search/SearchHelper.scala +++ b/Source/Plugins/Core/com.equella.core/scalasrc/com/tle/web/api/search/SearchHelper.scala @@ -20,13 +20,15 @@ package com.tle.web.api.search import com.dytech.edge.exceptions.BadRequestException import com.tle.beans.entity.DynaCollection -import com.tle.beans.item.{Comment, ItemIdKey} +import com.tle.beans.item.attachments.{Attachment, CustomAttachment, FileAttachment} +import com.tle.beans.item.{Comment, ItemId, ItemIdKey} import com.tle.common.Check import com.tle.common.beans.exception.NotFoundException import com.tle.common.collection.AttachmentConfigConstants import com.tle.common.search.DefaultSearch import com.tle.common.search.whereparser.WhereParser import com.tle.core.freetext.queries.FreeTextBooleanQuery + import com.tle.core.item.security.ItemSecurityConstants import com.tle.core.item.serializer.{ItemSerializerItemBean, ItemSerializerService} import com.tle.core.services.item.{FreetextResult, FreetextSearchResults} @@ -276,17 +278,22 @@ object SearchHelper { beans.asScala // Filter out restricted attachments if the user does not have permissions to view them .filter(a => !a.isRestricted || hasRestrictedAttachmentPrivileges) - .map(att => + .map(att => { + val broken = + recurseBrokenAttachmentCheck( + Option(LegacyGuice.itemService.getNullableAttachmentForUuid(itemKey, att.getUuid))) SearchResultAttachment( attachmentType = att.getRawAttachmentType, id = att.getUuid, description = Option(att.getDescription), + brokenAttachment = broken, preview = att.isPreview, - mimeType = getMimetypeForAttachment(att), + mimeType = getMimetypeForAttachment(att, broken), hasGeneratedThumb = thumbExists(itemKey, att), links = buildAttachmentLinks(att), filePath = getFilePathForAttachment(att) - )) + ) + }) .toList) } @@ -310,10 +317,61 @@ object SearchHelper { } } + /** + * Determines if a given customAttachment is invalid. Required as these attachments can be recursive. + * @param customAttachment The attachment to check. + * @return If true, this attachment is broken. + */ + def getBrokenAttachmentStatusForResourceAttachment( + customAttachment: CustomAttachment): Boolean = { + val key = new ItemId(customAttachment.getData("uuid").asInstanceOf[String], + customAttachment.getData("version").asInstanceOf[Int]) + if (customAttachment.getType != "resource") { + return false; + } + customAttachment.getData("type") match { + case "a" => + // Recurse into child attachment + recurseBrokenAttachmentCheck( + Option( + LegacyGuice.itemService.getNullableAttachmentForUuid(key, customAttachment.getUrl))) + case "p" => + // Get the child item. If it doesn't exist, this is a dead attachment + Option(LegacyGuice.itemService.getUnsecureIfExists(key)).isEmpty + case _ => false + } + } + + /** + * Determines if a given attachment is invalid. + * If it is a resource selector attachment, this gets handled by + * [[getBrokenAttachmentStatusForResourceAttachment(customAttachment: CustomAttachment)]] + * which links back in here to recurse through customAttachments to find the root. + * @param attachment The attachment to check for brokenness. + * @return True if broken, false if intact. + */ + def recurseBrokenAttachmentCheck(attachment: Option[Attachment]): Boolean = { + attachment match { + case Some(fileAttachment: FileAttachment) => + //check if file is present in the filestore + val item = + LegacyGuice.viewableItemFactory.createNewViewableItem(fileAttachment.getItem.getItemId) + !LegacyGuice.fileSystemService.fileExists(item.getFileHandle, fileAttachment.getFilename) + case Some(customAttachment: CustomAttachment) => + getBrokenAttachmentStatusForResourceAttachment(customAttachment) + case None => true + case Some(_) => false + } + } + /** * Extract the mimetype for AbstractExtendableBean. */ - def getMimetypeForAttachment[T <: AbstractExtendableBean](bean: T): Option[String] = { + def getMimetypeForAttachment[T <: AbstractExtendableBean](bean: T, + broken: Boolean): Option[String] = { + if (broken) { + return None + } bean match { case file: AbstractFileAttachmentBean => Some(LegacyGuice.mimeTypeService.getMimeTypeForFilename(file.getFilename)) diff --git a/Source/Plugins/Core/com.equella.core/scalasrc/com/tle/web/api/search/model/SearchResultItem.scala b/Source/Plugins/Core/com.equella.core/scalasrc/com/tle/web/api/search/model/SearchResultItem.scala index 5aa26a0360..b35b5e40f8 100644 --- a/Source/Plugins/Core/com.equella.core/scalasrc/com/tle/web/api/search/model/SearchResultItem.scala +++ b/Source/Plugins/Core/com.equella.core/scalasrc/com/tle/web/api/search/model/SearchResultItem.scala @@ -69,6 +69,9 @@ case class SearchResultItem( * @param attachmentType Attachment's type. * @param id The unique ID of an attachment. * @param description The description of an attachment. + * @param brokenAttachment If true, this attachment is broken or inaccessible. + * For file attachments, this means that it is not accessible from the filestore. + * For resource selector attachments, this means that the linked attachment or item summary does not exist. * @param preview If an attachment can be previewed or not. * @param mimeType Mime Type of file based attachments * @param hasGeneratedThumb Indicates if file based attachments have a generated thumbnail store in filestore @@ -79,6 +82,7 @@ case class SearchResultAttachment( attachmentType: String, id: String, description: Option[String], + brokenAttachment: Boolean, preview: Boolean, mimeType: Option[String], hasGeneratedThumb: Option[Boolean], diff --git a/Source/Plugins/Core/com.equella.core/src/com/tle/core/item/dao/AttachmentDao.java b/Source/Plugins/Core/com.equella.core/src/com/tle/core/item/dao/AttachmentDao.java index 8658cea4ce..a1d34701e1 100644 --- a/Source/Plugins/Core/com.equella.core/src/com/tle/core/item/dao/AttachmentDao.java +++ b/Source/Plugins/Core/com.equella.core/src/com/tle/core/item/dao/AttachmentDao.java @@ -36,8 +36,4 @@ List findByMd5Sum( List findResourceAttachmentsByQuery( String query, boolean liveOnly, String sortHql); - - Attachment findByUuid(String uuid); - - List findAllByUuid(String uuid); } diff --git a/Source/Plugins/Core/com.equella.core/src/com/tle/core/item/dao/impl/AttachmentDaoImpl.java b/Source/Plugins/Core/com.equella.core/src/com/tle/core/item/dao/impl/AttachmentDaoImpl.java index 60799f9af8..5a0444f3c2 100644 --- a/Source/Plugins/Core/com.equella.core/src/com/tle/core/item/dao/impl/AttachmentDaoImpl.java +++ b/Source/Plugins/Core/com.equella.core/src/com/tle/core/item/dao/impl/AttachmentDaoImpl.java @@ -33,7 +33,6 @@ import java.util.Arrays; import java.util.List; import javax.inject.Singleton; -import org.hibernate.Criteria; import org.hibernate.criterion.DetachedCriteria; import org.hibernate.criterion.Restrictions; @@ -137,14 +136,4 @@ private DetachedCriteria criteriaByUuid(String uuid) { .add(Restrictions.eq("i.institution", CurrentInstitution.get())) .add(Restrictions.eq("uuid", uuid)); } - - @Override - public List findAllByUuid(String uuid) { - return (List) findByDetachedCriteria(criteriaByUuid(uuid), Criteria::list); - } - - @Override - public Attachment findByUuid(String uuid) { - return (Attachment) findByDetachedCriteria(criteriaByUuid(uuid), Criteria::uniqueResult); - } } diff --git a/Source/Plugins/Core/com.equella.core/src/com/tle/core/item/service/ItemService.java b/Source/Plugins/Core/com.equella.core/src/com/tle/core/item/service/ItemService.java index 8f47bcfbc0..61ab68fccd 100644 --- a/Source/Plugins/Core/com.equella.core/src/com/tle/core/item/service/ItemService.java +++ b/Source/Plugins/Core/com.equella.core/src/com/tle/core/item/service/ItemService.java @@ -66,13 +66,22 @@ public interface ItemService List getNextLiveItems(List items); /** - * @param itemId - * @param uuid - * @return Never returns null. + * @param itemId the Item for which to return an attachment + * @param uuid The UUID of the attachment to return + * @return Never returns null. Returns attachment or throws. * @throws AttachmentNotFoundException */ Attachment getAttachmentForUuid(ItemKey itemId, String uuid); + /** + * As {@link #getAttachmentForUuid(ItemKey itemId, String uuid)}, without the null check. + * + * @param itemId the Item for which to return an attachment + * @param uuid The UUID of the attachment to return + * @return Returns null if attachment not found. + */ + Attachment getNullableAttachmentForUuid(ItemKey itemId, String uuid); + Multimap getAttachmentsForItems(Collection items); int getLatestVersion(String uuid); diff --git a/Source/Plugins/Core/com.equella.core/src/com/tle/core/item/service/impl/ItemServiceImpl.java b/Source/Plugins/Core/com.equella.core/src/com/tle/core/item/service/impl/ItemServiceImpl.java index 0012b5c4fe..706401770b 100644 --- a/Source/Plugins/Core/com.equella.core/src/com/tle/core/item/service/impl/ItemServiceImpl.java +++ b/Source/Plugins/Core/com.equella.core/src/com/tle/core/item/service/impl/ItemServiceImpl.java @@ -480,6 +480,11 @@ public Attachment getAttachmentForUuid(ItemKey itemId, String uuid) { return attachment; } + @Override + public Attachment getNullableAttachmentForUuid(ItemKey itemId, String uuid) { + return dao.getAttachmentByUuid(itemId, uuid); + } + @Override public Map getItemInfo(ItemId id) { return dao.getItemInfo(id.getUuid(), id.getVersion()); diff --git a/Source/Plugins/Core/com.equella.core/src/com/tle/core/mimetypes/MimeTypeService.java b/Source/Plugins/Core/com.equella.core/src/com/tle/core/mimetypes/MimeTypeService.java index 236e9f8def..589eab1a56 100644 --- a/Source/Plugins/Core/com.equella.core/src/com/tle/core/mimetypes/MimeTypeService.java +++ b/Source/Plugins/Core/com.equella.core/src/com/tle/core/mimetypes/MimeTypeService.java @@ -20,6 +20,7 @@ import com.tle.annotation.NonNullByDefault; import com.tle.annotation.Nullable; +import com.tle.beans.item.ItemId; import com.tle.beans.item.attachments.Attachment; import com.tle.beans.mime.MimeEntry; import com.tle.core.TextExtracterExtension; @@ -39,7 +40,7 @@ public interface MimeTypeService { */ String getMimeTypeForFilename(String filename); - String getMimeTypeForAttachmentUuid(String attachmentUuid); + String getMimeTypeForAttachmentUuid(ItemId key, String attachmentUuid); String getMimeTypeForResourceAttachmentBean(ResourceAttachmentBean resourceAttachmentBean); diff --git a/Source/Plugins/Core/com.equella.core/src/com/tle/core/mimetypes/MimeTypeServiceImpl.java b/Source/Plugins/Core/com.equella.core/src/com/tle/core/mimetypes/MimeTypeServiceImpl.java index fcc117227d..1c988baaf6 100644 --- a/Source/Plugins/Core/com.equella.core/src/com/tle/core/mimetypes/MimeTypeServiceImpl.java +++ b/Source/Plugins/Core/com.equella.core/src/com/tle/core/mimetypes/MimeTypeServiceImpl.java @@ -24,6 +24,7 @@ import com.google.common.cache.CacheLoader; import com.tle.annotation.Nullable; import com.tle.beans.Institution; +import com.tle.beans.item.ItemId; import com.tle.beans.item.attachments.Attachment; import com.tle.beans.item.attachments.AttachmentType; import com.tle.beans.item.attachments.CustomAttachment; @@ -38,7 +39,7 @@ import com.tle.core.guice.Bind; import com.tle.core.institution.InstitutionCache; import com.tle.core.institution.InstitutionService; -import com.tle.core.item.dao.AttachmentDao; +import com.tle.core.item.service.ItemService; import com.tle.core.mimetypes.dao.MimeEntryDao; import com.tle.core.mimetypes.institution.MimeMigrator; import com.tle.core.plugins.AbstractPluginService; @@ -76,7 +77,7 @@ public class MimeTypeServiceImpl implements MimeTypeService, MimeTypesUpdatedLis AbstractPluginService.getMyPluginId(MimeTypeServiceImpl.class) + "."; @Inject private MimeEntryDao mimeEntryDao; @Inject private EventService eventService; - @Inject private AttachmentDao attachmentDao; + @Inject private ItemService itemService; private PluginTracker textExtracterTracker; @@ -167,8 +168,8 @@ public String getMimeTypeForFilename(String filename) { return DEFAULT_MIMETYPE; } - public String getMimeTypeForAttachmentUuid(String attachmentUuid) { - Attachment attachment = attachmentDao.findByUuid(attachmentUuid); + public String getMimeTypeForAttachmentUuid(ItemId key, String attachmentUuid) { + Attachment attachment = itemService.getAttachmentForUuid(key, attachmentUuid); return getMimeEntryForAttachment(attachment); } @@ -176,7 +177,10 @@ public String getMimeTypeForResourceAttachmentBean( ResourceAttachmentBean resourceAttachmentBean) { switch (resourceAttachmentBean.getResourceType()) { case SelectedResource.TYPE_ATTACHMENT: - return getMimeTypeForAttachmentUuid(resourceAttachmentBean.getAttachmentUuid()); + return getMimeTypeForAttachmentUuid( + new ItemId( + resourceAttachmentBean.getItemUuid(), resourceAttachmentBean.getItemVersion()), + resourceAttachmentBean.getAttachmentUuid()); case SelectedResource.TYPE_PATH: return MIME_ITEM; default: @@ -429,12 +433,14 @@ public String getMimeEntryForAttachment(Attachment attachment) { .getData("type") .equals(Character.toString(SelectedResource.TYPE_ATTACHMENT))) { // Recurse to drill into the linked attachment, so we can use the correct viewer. - // If more than one attachment has the linked uuid, - // this is a zip or scorm package and we can let it fall through. - List attachmentList = attachmentDao.findAllByUuid(attachment.getUrl()); - if (attachmentList.size() == 1) { - return getMimeEntryForAttachment(attachmentList.get(0)); - } + // data stored in getData("uuid") and getData("version") for a resource attachment gives the + // child item, which we need to determine the attachment to recurse into. + ItemId childAttachmentItem = + new ItemId((String) attachment.getData("uuid"), (int) attachment.getData("version")); + + Attachment childAttachment = + itemService.getNullableAttachmentForUuid(childAttachmentItem, attachment.getUrl()); + return childAttachment == null ? null : getMimeEntryForAttachment(childAttachment); } Map> map = getExtensionMap(); List extensions = map.get(type); @@ -443,7 +449,6 @@ public String getMimeEntryForAttachment(Attachment attachment) { return attachmentResources.getBeanByExtension(extension).getMimeType(attachment); } } - return null; } diff --git a/autotest/Tests/tests/rest/institution/items/41/94505.xml b/autotest/Tests/tests/rest/institution/items/41/94505.xml new file mode 100644 index 0000000000..4ca4608067 --- /dev/null +++ b/autotest/Tests/tests/rest/institution/items/41/94505.xml @@ -0,0 +1,94 @@ + + 94505 + c833659e-89e6-4693-bcee-cad05b49137e + 1 + TLE_ADMINISTRATOR + 2021-06-03 12:48:57.299 + 2021-06-03 12:48:57.301 + 2021-06-03 12:48:57.299 + -1.0 + false + LIVE + + + + 94511 + 9b5cec43-5283-41d2-b6f4-98c5bc2c1683 + source.gif + source.gif + 234932 + _THUMBS/source.gif.jpeg + 1511d04129a8d594ad2f6eace553e77f + false + false + false + + + + + + + 94508 + 2021-06-03 12:48:57.299 + 2021-06-03 12:48:57.299 + false + + + + + 94512 + TLE_ADMINISTRATOR + 2021-06-03 12:48:57.299 + false + contributed + DRAFT + + + 94513 + TLE_ADMINISTRATOR + 2021-06-03 12:48:57.299 + false + edit + DRAFT + + + 94514 + TLE_ADMINISTRATOR + 2021-06-03 12:48:57.299 + false + statechange + LIVE + + + + + + + + + 94509 + + + en_AU + + 94510 + en_AU + 2 + DeadAttachmentsTest + + + + + + + 94506 + + + 0 + + false + false + false + + default + \ No newline at end of file diff --git a/autotest/Tests/tests/rest/institution/items/41/94505/_ITEM/item.xml b/autotest/Tests/tests/rest/institution/items/41/94505/_ITEM/item.xml new file mode 100644 index 0000000000..572bfa5d7c --- /dev/null +++ b/autotest/Tests/tests/rest/institution/items/41/94505/_ITEM/item.xml @@ -0,0 +1 @@ +DeadAttachmentsTest9b5cec43-5283-41d2-b6f4-98c5bc2c1683 \ No newline at end of file diff --git a/autotest/Tests/tests/rest/institution/items/53/95797.xml b/autotest/Tests/tests/rest/institution/items/53/95797.xml new file mode 100644 index 0000000000..4e37be7b10 --- /dev/null +++ b/autotest/Tests/tests/rest/institution/items/53/95797.xml @@ -0,0 +1,102 @@ + + 95797 + 918a2381-a445-45f5-95b6-809e113c370d + 1 + TLE_ADMINISTRATOR + 2021-06-03 13:50:20.689 + 2021-06-03 13:50:20.69 + 2021-06-03 13:50:20.689 + -1.0 + false + LIVE + + + + 95802 + fcb5cd6f-16d1-4b75-a53f-436e21b1f23d + d3cd2079-a1f8-457c-b4ab-b77a0e87d8e0 + source.gif + resource + + + type + a + + + uuid + dda553d9-8029-488e-8bbc-90012a77935b + + + version + 1 + + + false + false + false + + + + + + + 95799 + 2021-06-03 13:50:20.689 + 2021-06-03 13:50:20.689 + false + + + + + 95803 + TLE_ADMINISTRATOR + 2021-06-03 13:50:20.689 + false + contributed + DRAFT + + + 95804 + TLE_ADMINISTRATOR + 2021-06-03 13:50:20.689 + false + edit + DRAFT + + + 95805 + TLE_ADMINISTRATOR + 2021-06-03 13:50:20.689 + false + statechange + LIVE + + + + + + + + + 95800 + + + en_AU + + 95801 + en_AU + 2 + NestedDeadResourceAttachmentTest - Child 1 + + + + + + 0 + + false + false + false + + default + \ No newline at end of file diff --git a/autotest/Tests/tests/rest/institution/items/53/95797/_ITEM/item.xml b/autotest/Tests/tests/rest/institution/items/53/95797/_ITEM/item.xml new file mode 100644 index 0000000000..3b0b35e442 --- /dev/null +++ b/autotest/Tests/tests/rest/institution/items/53/95797/_ITEM/item.xml @@ -0,0 +1 @@ +fcb5cd6f-16d1-4b75-a53f-436e21b1f23dNestedDeadResourceAttachmentTest - Child 1 \ No newline at end of file diff --git a/autotest/Tests/tests/rest/institution/items/63/95807.xml b/autotest/Tests/tests/rest/institution/items/63/95807.xml new file mode 100644 index 0000000000..907a10476b --- /dev/null +++ b/autotest/Tests/tests/rest/institution/items/63/95807.xml @@ -0,0 +1,102 @@ + + 95807 + a101af01-e13e-4dd3-bb1d-117ae5a92745 + 1 + TLE_ADMINISTRATOR + 2021-06-03 13:50:48.447 + 2021-06-03 13:50:48.448 + 2021-06-03 13:50:48.447 + -1.0 + false + LIVE + + + + 95812 + d185b602-020f-4c64-929d-5524f1b6e21d + fcb5cd6f-16d1-4b75-a53f-436e21b1f23d + source.gif + resource + + + type + a + + + uuid + 918a2381-a445-45f5-95b6-809e113c370d + + + version + 1 + + + false + false + false + + + + + + + 95809 + 2021-06-03 13:50:48.447 + 2021-06-03 13:50:48.447 + false + + + + + 95813 + TLE_ADMINISTRATOR + 2021-06-03 13:50:48.447 + false + contributed + DRAFT + + + 95814 + TLE_ADMINISTRATOR + 2021-06-03 13:50:48.447 + false + edit + DRAFT + + + 95815 + TLE_ADMINISTRATOR + 2021-06-03 13:50:48.447 + false + statechange + LIVE + + + + + + + + + 95810 + + + en_AU + + 95811 + en_AU + 2 + NestedDeadResourceAttachmentTest - Child 2 + + + + + + 0 + + false + false + false + + default + \ No newline at end of file diff --git a/autotest/Tests/tests/rest/institution/items/63/95807/_ITEM/item.xml b/autotest/Tests/tests/rest/institution/items/63/95807/_ITEM/item.xml new file mode 100644 index 0000000000..50ae7d0091 --- /dev/null +++ b/autotest/Tests/tests/rest/institution/items/63/95807/_ITEM/item.xml @@ -0,0 +1 @@ +d185b602-020f-4c64-929d-5524f1b6e21dNestedDeadResourceAttachmentTest - Child 2 \ No newline at end of file diff --git a/autotest/Tests/tests/rest/institution/items/73/95817.xml b/autotest/Tests/tests/rest/institution/items/73/95817.xml new file mode 100644 index 0000000000..320a7a2f0d --- /dev/null +++ b/autotest/Tests/tests/rest/institution/items/73/95817.xml @@ -0,0 +1,102 @@ + + 95817 + c824b750-7dd6-493d-8ba0-c6570615d31b + 1 + TLE_ADMINISTRATOR + 2021-06-03 13:51:40.084 + 2021-06-03 13:51:40.086 + 2021-06-03 13:51:40.084 + -1.0 + false + LIVE + + + + 95822 + 311c3a7a-f4a2-4b21-b7a2-cdfc2789db2d + + NestedDeadResourceAttachmentTest - Root + resource + + + type + p + + + uuid + dda553d9-8029-488e-8bbc-90012a77935b + + + version + 1 + + + false + false + false + + + + + + + 95819 + 2021-06-03 13:51:40.084 + 2021-06-03 13:51:40.084 + false + + + + + 95823 + TLE_ADMINISTRATOR + 2021-06-03 13:51:40.084 + false + contributed + DRAFT + + + 95824 + TLE_ADMINISTRATOR + 2021-06-03 13:51:40.084 + false + edit + DRAFT + + + 95825 + TLE_ADMINISTRATOR + 2021-06-03 13:51:40.084 + false + statechange + LIVE + + + + + + + + + 95820 + + + en_AU + + 95821 + en_AU + 2 + NestedDeadResourceAttachmentTest - Points at root item summary + + + + + + 0 + + false + false + false + + default + \ No newline at end of file diff --git a/autotest/Tests/tests/rest/institution/items/73/95817/_ITEM/item.xml b/autotest/Tests/tests/rest/institution/items/73/95817/_ITEM/item.xml new file mode 100644 index 0000000000..ac334440e8 --- /dev/null +++ b/autotest/Tests/tests/rest/institution/items/73/95817/_ITEM/item.xml @@ -0,0 +1 @@ +311c3a7a-f4a2-4b21-b7a2-cdfc2789db2dNestedDeadResourceAttachmentTest - Points at root item summary \ No newline at end of file diff --git a/oeq-ts-rest-api/src/Search.ts b/oeq-ts-rest-api/src/Search.ts index aa796c8250..d296e5762f 100644 --- a/oeq-ts-rest-api/src/Search.ts +++ b/oeq-ts-rest-api/src/Search.ts @@ -181,6 +181,11 @@ export interface Attachment { * The description of an attachment. */ description?: string; + + /** + * Whether or not the attachment has been determined to be broken by the server. + */ + brokenAttachment: boolean; /** * True if an attachment can be previewed. */ diff --git a/oeq-ts-rest-api/test/Search.test.ts b/oeq-ts-rest-api/test/Search.test.ts index 956983f17e..7227799c71 100644 --- a/oeq-ts-rest-api/test/Search.test.ts +++ b/oeq-ts-rest-api/test/Search.test.ts @@ -164,3 +164,68 @@ describe('Exports search results for the specified search params', function () { ).resolves.toBe(true); }); }); + +describe('Dead attachment handling', () => { + it.each<[string, string, number, number, boolean]>([ + [ + 'an intact file attachment as not broken', + 'Keyword found in attachment test item', + 0, + 0, + false, + ], + [ + 'an intact resource selector attachment as not broken', + 'ItemApiViewTest - All attachments', + 0, + 1, + false, + ], + [ + 'a returned file attachment missing from the filestore as broken', + 'DeadAttachmentsTest', + 0, + 0, + true, + ], + [ + 'a resource attachment pointing at a non-existent attachment as broken', + 'NestedDeadResourceAttachmentTest - Child 1', + 0, + 0, + true, + ], + [ + 'a resource attachment pointing at a non-existent item summary as broken', + 'NestedDeadResourceAttachmentTest - Points at root item summary', + 0, + 0, + true, + ], + [ + 'a nested resource attachment with the end of the chain being a non-existent attachment as broken', + 'NestedDeadResourceAttachmentTest - Child 2', + 0, + 0, + true, + ], + ])( + 'should mark %s', + async ( + _: string, + query: string, + itemResultIndex: number, + attachmentResultIndex: number, + expectBrokenStatus: boolean + ) => { + const searchResult = await doSearch({ query: query }); + const { attachments } = searchResult.results[itemResultIndex]; + if (attachments === undefined) { + throw new Error('Unexpected undefined attachments'); + } + const brokenAttachment = + attachments[attachmentResultIndex].brokenAttachment; + expect(brokenAttachment).toEqual(expectBrokenStatus); + } + ); +}); From 90d819e9492ac03a487e5b15eb7be347cd76dd22 Mon Sep 17 00:00:00 2001 From: SammyIsConfused Date: Tue, 8 Jun 2021 17:23:21 +1000 Subject: [PATCH 07/13] Merge pull request #3090 from SammyIsConfused/bugfix/fix_mistake_in_oeqthumb Fix mistake in OEQThumb and surround with tests and stories --- .../js/tsrc/components/OEQThumb.tsx | 63 +++++++-- .../js/tsrc/util/langstrings.ts | 10 ++ react-front-end/__mocks__/OEQThumb.mock.ts | 133 ++++++++++++++++++ .../components/OEQThumb.stories.tsx | 111 +++++++++++++++ .../tsrc/components/OEQThumb.test.tsx | 105 ++++++++++++++ 5 files changed, 408 insertions(+), 14 deletions(-) create mode 100644 react-front-end/__mocks__/OEQThumb.mock.ts create mode 100644 react-front-end/__stories__/components/OEQThumb.stories.tsx create mode 100644 react-front-end/__tests__/tsrc/components/OEQThumb.test.tsx diff --git a/Source/Plugins/Core/com.equella.core/js/tsrc/components/OEQThumb.tsx b/Source/Plugins/Core/com.equella.core/js/tsrc/components/OEQThumb.tsx index 1baae380e8..e0d6e53bd3 100644 --- a/Source/Plugins/Core/com.equella.core/js/tsrc/components/OEQThumb.tsx +++ b/Source/Plugins/Core/com.equella.core/js/tsrc/components/OEQThumb.tsx @@ -26,6 +26,7 @@ import DefaultFileIcon from "@material-ui/icons/InsertDriveFile"; import WebIcon from "@material-ui/icons/Language"; import Web from "@material-ui/icons/Web"; import PlaceholderIcon from "@material-ui/icons/TextFields"; +import { languageStrings } from "../util/langstrings"; const useStyles = makeStyles((theme: Theme) => { return { @@ -48,7 +49,7 @@ interface ThumbProps { fontSize: "inherit" | "default" | "small" | "large"; } -interface OEQThumbProps { +export interface OEQThumbProps { /** * On object representing an oEQ attachment. If undefined, a placeholder icon is returned */ @@ -70,13 +71,19 @@ export default function OEQThumb({ showPlaceholder, }: OEQThumbProps) { const classes = useStyles(); + const thumbLabels = languageStrings.searchpage.thumbnails; const generalThumbStyles: ThumbProps = { className: `MuiPaper-elevation1 MuiPaper-rounded ${classes.thumbnail} ${classes.placeholderThumbnail}`, fontSize: "large", }; if (!attachment || showPlaceholder) { - return ; + return ( + + ); } const { @@ -89,13 +96,16 @@ export default function OEQThumb({ const oeqProvidedThumb: React.ReactElement = ( {description} ); - const defaultThumb = ; + const defaultThumb = ( + + ); /** * We need to check if a thumbnail has been generated, and return a generic icon if not @@ -106,21 +116,42 @@ export default function OEQThumb({ if (hasGeneratedThumb) { return oeqProvidedThumb; } - if (mimeType === "equella/item") { - return ; - } - if (mimeType === "equella/link") { - return ; - } let result = defaultThumb; if (mimeType?.startsWith("image")) { - result = ; + result = ( + + ); } else if (mimeType?.startsWith("video")) { - result = ; + result = ( + + ); } return result; }; + /** + * Resource attachments point to other attachments or items, so we need to use the MIME type + * to determine the thumbnail to use, rather than the attachment type which will be custom/resource. + * + * @param mimeType The MIME type of the resource attachment's target. + */ + const handleResourceAttachmentThumb = (mimeType?: string) => { + switch (mimeType) { + case "equella/item": + return ; + case "equella/link": + return ( + + ); + case "text/html": + return ( + + ); + default: + return oeqProvidedThumb; + } + }; + let oeqThumb = defaultThumb; if (attachment.brokenAttachment) { return defaultThumb; @@ -130,13 +161,17 @@ export default function OEQThumb({ oeqThumb = handleMimeType(mimeType); break; case "link": - oeqThumb = ; + oeqThumb = ( + + ); break; case "html": - oeqThumb = ; + oeqThumb = ( + + ); break; case "custom/resource": - oeqThumb = handleMimeType(mimeType); + oeqThumb = handleResourceAttachmentThumb(mimeType); break; case "custom/flickr": case "custom/youtube": diff --git a/Source/Plugins/Core/com.equella.core/js/tsrc/util/langstrings.ts b/Source/Plugins/Core/com.equella.core/js/tsrc/util/langstrings.ts index 95caa09d00..6f2752f376 100644 --- a/Source/Plugins/Core/com.equella.core/js/tsrc/util/langstrings.ts +++ b/Source/Plugins/Core/com.equella.core/js/tsrc/util/langstrings.ts @@ -451,6 +451,16 @@ export const languageStrings = { live: "Live", title: "Status", }, + thumbnails: { + html: "HTML Icon", + placeholder: "Placeholder Icon", + provided: "Provided Icon", + file: "Default File Icon", + image: "Image Icon", + video: "Video Icon", + link: "Link Icon", + item: "Item Icon", + }, comments: { zero: "No comments", one: "%d comment", diff --git a/react-front-end/__mocks__/OEQThumb.mock.ts b/react-front-end/__mocks__/OEQThumb.mock.ts new file mode 100644 index 0000000000..d456b0be0e --- /dev/null +++ b/react-front-end/__mocks__/OEQThumb.mock.ts @@ -0,0 +1,133 @@ +/* + * Licensed to The Apereo Foundation under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * The Apereo Foundation licenses this file to you under the Apache License, + * Version 2.0, (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import * as OEQ from "@openequella/rest-api-client"; + +export const fileAttachment: OEQ.Search.Attachment = { + attachmentType: "file", + id: "9e751549-5cba-47dd-bccb-722c48072287", + description: "broken.png", + preview: false, + mimeType: "image/png", + hasGeneratedThumb: true, + brokenAttachment: false, + links: { + view: + "http://localhost:8080/rest/items/72558c1d-8788-4515-86c8-b24a28cc451e/1/?attachment.uuid=78b8af7e-f0f5-4b5c-9f44-16f212583fe8", + thumbnail: "./thumb.jpg", + }, +}; + +export const brokenFileAttachment: OEQ.Search.Attachment = { + attachmentType: "file", + id: "9e751549-5cba-47dd-bccb-722c48072287", + description: "broken.png", + preview: false, + mimeType: "image/png", + hasGeneratedThumb: true, + brokenAttachment: true, + links: { + view: + "http://localhost:8080/rest/items/72558c1d-8788-4515-86c8-b24a28cc451e/1/?attachment.uuid=78b8af7e-f0f5-4b5c-9f44-16f212583fe8", + thumbnail: "./thumb.jpg", + }, +}; + +export const resourceFileAttachment: OEQ.Search.Attachment = { + attachmentType: "custom/resource", + id: "2c663052-a472-4b3e-b4d1-a25a5cd45675", + description: "miss-violet.jpg", + brokenAttachment: false, + preview: false, + mimeType: "image/jpeg", + links: { + view: + "http://localhost:8080/rest/items/9ba9a328-4697-4ae0-9dba-3f82f1876fb8/1/?attachment.uuid=2c663052-a472-4b3e-b4d1-a25a5cd45675", + thumbnail: "./thumb.jpg", + }, +}; +export const linkAttachment: OEQ.Search.Attachment = { + attachmentType: "link", + id: "7d84f75e-1756-4af0-b4e1-2553b52885f0", + description: "https://www.google.com", + brokenAttachment: false, + preview: false, + links: { + view: + "http://localhost:8080/rest/items/1dc04a21-9659-487f-b784-66726fa59bdc/1/?attachment.uuid=7d84f75e-1756-4af0-b4e1-2553b52885f0", + thumbnail: "./thumb.jpg", + }, +}; + +export const resourceLinkAttachment: OEQ.Search.Attachment = { + attachmentType: "custom/resource", + id: "02f8f12e-8222-4c5b-b89a-888cbbbc402d", + description: "https://www.google.com", + brokenAttachment: false, + preview: false, + mimeType: "equella/link", + links: { + view: + "http://localhost:8080/rest/items/a321a2f7-2228-4853-8292-54f390976049/1/?attachment.uuid=02f8f12e-8222-4c5b-b89a-888cbbbc402d", + thumbnail: "./thumb.jpg", + }, +}; + +export const equellaItemAttachment: OEQ.Search.Attachment = { + attachmentType: "custom/resource", + id: "7140295f-7fe0-4b6b-b621-eb2adfbd386f", + description: "a321a2f7-2228-4853-8292-54f390976049", + brokenAttachment: false, + preview: false, + mimeType: "equella/item", + links: { + view: + "http://localhost:8080/rest/items/475a5e1b-4558-43f9-aeb4-9d49408197be/1/?attachment.uuid=7140295f-7fe0-4b6b-b621-eb2adfbd386f", + thumbnail: + "http://localhost:8080/rest/thumbs/475a5e1b-4558-43f9-aeb4-9d49408197be/1/7140295f-7fe0-4b6b-b621-eb2adfbd386f", + }, +}; + +export const htmlAttachment: OEQ.Search.Attachment = { + attachmentType: "html", + id: "ef533d4c-15fc-45d4-91ee-0873e17fa7cb", + description: "New Page", + brokenAttachment: false, + preview: false, + mimeType: "text/html", + links: { + view: + "http://localhost:8080/rest/items/eb099d2a-d1a8-4e4e-98ff-fec42587adb4/1/?attachment.uuid=ef533d4c-15fc-45d4-91ee-0873e17fa7cb", + thumbnail: + "http://localhost:8080/rest/thumbs/eb099d2a-d1a8-4e4e-98ff-fec42587adb4/1/ef533d4c-15fc-45d4-91ee-0873e17fa7cb", + }, +}; + +export const resourceHtmlAttachment: OEQ.Search.Attachment = { + attachmentType: "custom/resource", + id: "1fa39170-ad0a-4607-9e37-bac86a8dea32", + description: "New Page", + brokenAttachment: false, + preview: false, + mimeType: "text/html", + links: { + view: + "http://localhost:8080/rest/items/550a3047-eb17-4db4-8b1e-6adbf6d63f3b/1/?attachment.uuid=1fa39170-ad0a-4607-9e37-bac86a8dea32", + thumbnail: + "http://localhost:8080/rest/thumbs/550a3047-eb17-4db4-8b1e-6adbf6d63f3b/1/1fa39170-ad0a-4607-9e37-bac86a8dea32", + }, +}; diff --git a/react-front-end/__stories__/components/OEQThumb.stories.tsx b/react-front-end/__stories__/components/OEQThumb.stories.tsx new file mode 100644 index 0000000000..57b87321e5 --- /dev/null +++ b/react-front-end/__stories__/components/OEQThumb.stories.tsx @@ -0,0 +1,111 @@ +/* + * Licensed to The Apereo Foundation under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * The Apereo Foundation licenses this file to you under the Apache License, + * Version 2.0, (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Meta, Story } from "@storybook/react"; +import * as React from "react"; +import { + brokenFileAttachment, + resourceFileAttachment, + equellaItemAttachment, + fileAttachment, + htmlAttachment, + linkAttachment, + resourceLinkAttachment, + resourceHtmlAttachment, +} from "../../__mocks__/OEQThumb.mock"; +import OEQThumb, { OEQThumbProps } from "../../tsrc/components/OEQThumb"; + +export default { + title: "Component/OEQThumb", + component: OEQThumb, + argTypes: { + showPlaceholder: { boolean: false }, + }, +} as Meta; + +export const fileAttachmentStory: Story = (args) => ( + +); + +fileAttachmentStory.args = { + attachment: fileAttachment, +}; + +export const brokenFileAttachmentStory: Story = (args) => ( + +); + +brokenFileAttachmentStory.args = { + attachment: brokenFileAttachment, +}; + +export const customResourceAttachmentStory: Story = (args) => ( + +); + +customResourceAttachmentStory.args = { + attachment: resourceFileAttachment, +}; + +export const linkAttachmentStory: Story = (args) => ( + +); + +linkAttachmentStory.args = { + attachment: linkAttachment, +}; + +export const resourceLinkAttachmentStory: Story = (args) => ( + +); + +resourceLinkAttachmentStory.args = { + attachment: resourceLinkAttachment, +}; + +export const equellaItemAttachmentStory: Story = (args) => ( + +); + +equellaItemAttachmentStory.args = { + attachment: equellaItemAttachment, +}; + +export const htmlAttachmentStory: Story = (args) => ( + +); + +htmlAttachmentStory.args = { + attachment: htmlAttachment, +}; + +export const resourceHtmlAttachmentStory: Story = (args) => ( + +); + +resourceHtmlAttachmentStory.args = { + attachment: resourceHtmlAttachment, +}; + +export const placeHolderAttachmentStory: Story = (args) => ( + +); + +placeHolderAttachmentStory.args = { + attachment: fileAttachment, + showPlaceholder: true, +}; diff --git a/react-front-end/__tests__/tsrc/components/OEQThumb.test.tsx b/react-front-end/__tests__/tsrc/components/OEQThumb.test.tsx new file mode 100644 index 0000000000..4f0314431f --- /dev/null +++ b/react-front-end/__tests__/tsrc/components/OEQThumb.test.tsx @@ -0,0 +1,105 @@ +/* + * Licensed to The Apereo Foundation under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * The Apereo Foundation licenses this file to you under the Apache License, + * Version 2.0, (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { queryByLabelText, render } from "@testing-library/react"; +import * as React from "react"; +import "@testing-library/jest-dom/extend-expect"; +import { + brokenFileAttachment, + equellaItemAttachment, + fileAttachment, + htmlAttachment, + linkAttachment, + resourceHtmlAttachment, + resourceLinkAttachment, +} from "../../../__mocks__/OEQThumb.mock"; +import OEQThumb from "../../../tsrc/components/OEQThumb"; +import * as OEQ from "@openequella/rest-api-client"; +import { languageStrings } from "../../../tsrc/util/langstrings"; + +describe("", () => { + const thumbLabels = languageStrings.searchpage.thumbnails; + const buildOEQThumb = ( + attachment: OEQ.Search.Attachment, + showPlaceHolder: boolean + ) => + render( + + ); + + it.each<[string, OEQ.Search.Attachment, boolean, string]>([ + [ + "shows the placeholder icon when showPlaceholder is true", + fileAttachment, + true, + thumbLabels.placeholder, + ], + [ + "shows thumbnail image when showPlaceholder is false", + fileAttachment, + false, + thumbLabels.provided, + ], + [ + "shows default file thumbnail when brokenAttachment is true", + brokenFileAttachment, + false, + thumbLabels.file, + ], + [ + "shows link icon thumbnail for a link attachment", + linkAttachment, + false, + thumbLabels.link, + ], + [ + "shows link icon thumbnail for a resource link attachment", + resourceLinkAttachment, + false, + thumbLabels.link, + ], + [ + "shows equella item thumbnail for a resource attachment pointing at an item summary", + equellaItemAttachment, + false, + thumbLabels.item, + ], + [ + "shows html thumbnail for a web page attachment", + htmlAttachment, + false, + thumbLabels.html, + ], + [ + "shows html thumbnail for a resource web page attachment", + resourceHtmlAttachment, + false, + thumbLabels.html, + ], + ])( + "%s", + ( + _: string, + attachment: OEQ.Search.Attachment, + showPlaceHolder: boolean, + query: string + ) => { + const { container } = buildOEQThumb(attachment, showPlaceHolder); + expect(queryByLabelText(container, query)).toBeInTheDocument(); + } + ); +}); From d5711d76a35666206a8e21b6b5e1d269e0303d1a Mon Sep 17 00:00:00 2001 From: SammyIsConfused Date: Fri, 11 Jun 2021 15:26:23 +1000 Subject: [PATCH 08/13] Merge pull request #3092 from SammyIsConfused/bugfix/make_dead_attachments_not_draggable Make dead attachments not draggable --- .../search/components/SearchResult.test.tsx | 19 +++++++++++++++++++ .../SearchResultAttachmentsList.tsx | 10 ++++++---- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/Source/Plugins/Core/com.equella.core/js/__tests__/tsrc/search/components/SearchResult.test.tsx b/Source/Plugins/Core/com.equella.core/js/__tests__/tsrc/search/components/SearchResult.test.tsx index db3a4dc08d..545e199d7f 100644 --- a/Source/Plugins/Core/com.equella.core/js/__tests__/tsrc/search/components/SearchResult.test.tsx +++ b/Source/Plugins/Core/com.equella.core/js/__tests__/tsrc/search/components/SearchResult.test.tsx @@ -435,6 +435,25 @@ describe("", () => { }); }); + it("should not make broken attachments draggable", async () => { + updateMockGetRenderData(basicRenderData); + await renderSearchResult(mockData.oneDeadOneAliveAttachObj); + + const deadAttachment = mockData.oneDeadOneAliveAttachObj.attachments![0]; + const intactAttachment = mockData.oneDeadOneAliveAttachObj + .attachments![1]; + + //expect intact attachment to be draggable + expect( + getGlobalCourseList().prepareDraggableAndBind + ).toHaveBeenCalledWith(`#${intactAttachment.id}`, false); + + //expect dead attachment not to be draggable + expect( + getGlobalCourseList().prepareDraggableAndBind + ).not.toHaveBeenCalledWith(`#${deadAttachment.id}`, false); + }); + it("should hide All attachment button in Skinny", async () => { updateMockGetRenderData(renderDataForSkinny); const { queryByLabelText } = await renderSearchResult( diff --git a/Source/Plugins/Core/com.equella.core/js/tsrc/search/components/SearchResultAttachmentsList.tsx b/Source/Plugins/Core/com.equella.core/js/tsrc/search/components/SearchResultAttachmentsList.tsx index e7a107a00a..4b04b68ed2 100644 --- a/Source/Plugins/Core/com.equella.core/js/tsrc/search/components/SearchResultAttachmentsList.tsx +++ b/Source/Plugins/Core/com.equella.core/js/tsrc/search/components/SearchResultAttachmentsList.tsx @@ -122,12 +122,14 @@ export const SearchResultAttachmentsList = ({ setAttachmentsAndViewerConfigs, ] = useState([]); - // In Selection Session, make each attachment draggable. + // In Selection Session, make each intact attachment draggable. useEffect(() => { if (inStructured) { - attachmentsAndViewerConfigs.forEach(({ attachment }) => { - prepareDraggable(attachment.id, false); - }); + attachmentsAndViewerConfigs + .filter(({ attachment }) => !attachment.brokenAttachment) + .forEach(({ attachment }) => { + prepareDraggable(attachment.id, false); + }); } }, [attachmentsAndViewerConfigs, inStructured]); From a66e427f2c907138754c8c16a3ad7c90516413e9 Mon Sep 17 00:00:00 2001 From: Penghai Date: Fri, 25 Jun 2021 14:40:25 +1000 Subject: [PATCH 09/13] chore: use Kaltura master branch --- buildspec.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/buildspec.yml b/buildspec.yml index 64ee44cc82..fd10fc9c5f 100644 --- a/buildspec.yml +++ b/buildspec.yml @@ -2,8 +2,7 @@ version: 0.2 env: variables: - # TODO [SpringHib5] keep this in sync with the appropriate oEQ-Kaltura branch before merging into `develop`, and eventually into `master`. - KALTURA_BRANCH: "develop" + KALTURA_BRANCH: "master" AUTOTEST_CONFIG: "autotest/codebuild.conf" EQ_EXIFTOOL_PATH: "/usr/bin/exiftool" OLD_TEST_NEWUI: true From 3000bbd25f5741853631271824767f93fad758d2 Mon Sep 17 00:00:00 2001 From: PenghaiZhang <47203811+PenghaiZhang@users.noreply.github.com> Date: Wed, 23 Jun 2021 09:37:59 +1000 Subject: [PATCH 10/13] Merge pull request #3130 from PenghaiZhang/feature/run-npm-build-for-kaltura-ts-control chore: run npm build for Kaltura TS Upload control on Codebuild --- buildspec.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/buildspec.yml b/buildspec.yml index fd10fc9c5f..4a34717788 100644 --- a/buildspec.yml +++ b/buildspec.yml @@ -19,6 +19,7 @@ phases: build: commands: - (npm ci && cd Source/Plugins/Core/com.equella.core/js && npm ci) + - (cd Source/Plugins/Kaltura/com.tle.web.wizard.controls.kaltura/js && npm ci && npm run build) - (npm run check:ts && npm run check:ts-types-source && npm run check:license) - (cd Source/Plugins/Core/com.equella.core/js && npm test) - sbt -no-colors -Dconfig.file=${HOME}/build.conf test installerZip writeLanguagePack writeScriptingJavadoc From 46532058a9b33d8bbed56b24b9a48843753514c9 Mon Sep 17 00:00:00 2001 From: PenghaiZhang <47203811+PenghaiZhang@users.noreply.github.com> Date: Thu, 24 Jun 2021 14:34:11 +1000 Subject: [PATCH 11/13] Merge pull request #3140 from PenghaiZhang/feature/support-kaltura-css-in-new-ui chore: include Kaltura Upload control CSS in new UI --- .../core/plugins/AbstractPluginService.java | 4 ++++ .../com/tle/core/plugins/PluginService.java | 10 ++++++++++ .../tle/web/template/RenderNewTemplate.scala | 18 ++++++++++++++++-- 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/Platform/Plugins/com.tle.platform.common/src/com/tle/core/plugins/AbstractPluginService.java b/Platform/Plugins/com.tle.platform.common/src/com/tle/core/plugins/AbstractPluginService.java index cd21f92ee1..5baa49663a 100644 --- a/Platform/Plugins/com.tle.platform.common/src/com/tle/core/plugins/AbstractPluginService.java +++ b/Platform/Plugins/com.tle.platform.common/src/com/tle/core/plugins/AbstractPluginService.java @@ -190,6 +190,10 @@ public void ensureActivated(PluginDescriptor plugin) { } } + public boolean isActivated(String pluginId) { + return pluginManager.getRegistry().isPluginDescriptorAvailable(pluginId); + } + @SuppressWarnings("nls") @Override public ExtensionPoint getExtensionPoint(String pluginId, String pointId) { diff --git a/Platform/Plugins/com.tle.platform.common/src/com/tle/core/plugins/PluginService.java b/Platform/Plugins/com.tle.platform.common/src/com/tle/core/plugins/PluginService.java index 4d174cb7f0..7cdb5c37d7 100644 --- a/Platform/Plugins/com.tle.platform.common/src/com/tle/core/plugins/PluginService.java +++ b/Platform/Plugins/com.tle.platform.common/src/com/tle/core/plugins/PluginService.java @@ -52,6 +52,16 @@ public interface PluginService { void ensureActivated(PluginDescriptor plugin); + /** + * Check if a plugin is activated. + * + * @param pluginId The ID of plugin + * @return `true` if plugin available otherwise false + */ + default boolean isActivated(String pluginId) { + throw new UnsupportedOperationException(); + } + void registerExtensionListener( String pluginId, String extensionId, RegistryChangeListener listener); diff --git a/Source/Plugins/Core/com.equella.core/scalasrc/com/tle/web/template/RenderNewTemplate.scala b/Source/Plugins/Core/com.equella.core/scalasrc/com/tle/web/template/RenderNewTemplate.scala index 713014ecd7..310268d9ee 100644 --- a/Source/Plugins/Core/com.equella.core/scalasrc/com/tle/web/template/RenderNewTemplate.scala +++ b/Source/Plugins/Core/com.equella.core/scalasrc/com/tle/web/template/RenderNewTemplate.scala @@ -19,15 +19,15 @@ package com.tle.web.template import java.util.concurrent.ConcurrentHashMap - import com.tle.common.i18n.{CurrentLocale, LocaleUtils} import com.tle.common.settings.standard.QuickContributeAndVersionSettings import com.tle.core.db.RunWithDB import com.tle.core.i18n.LocaleLookup +import com.tle.core.plugins.AbstractPluginService import com.tle.legacy.LegacyGuice import com.tle.web.DebugSettings import com.tle.web.freemarker.FreemarkerFactory -import com.tle.web.resources.ResourcesService +import com.tle.web.resources.{ResourcesService} import com.tle.web.sections._ import com.tle.web.sections.equella.ScalaSectionRenderable import com.tle.web.sections.events._ @@ -37,6 +37,7 @@ import com.tle.web.sections.js.generic.function.IncludeFile import com.tle.web.sections.render._ import com.tle.web.selection.section.RootSelectionSection import com.tle.web.integration.IntegrationSection +import com.tle.web.sections.render.CssInclude.{Priority, include} import com.tle.web.selection.section.RootSelectionSection.Layout import com.tle.web.settings.UISettings import javax.servlet.http.HttpServletRequest @@ -83,11 +84,24 @@ object RenderNewTemplate { supportIEPolyFills(info) info.preRender(bundleJs) links.foreach(l => info.addCss(r.url(l.attr("href")))) + addKalturaCss(info) info.addCss(RenderTemplate.CUSTOMER_CSS) } (prerender, htmlDoc) } + def addKalturaCss(info: PreRenderContext): Unit = { + val kalturaPluginId = "com.tle.web.wizard.controls.kaltura" + val pluginService = AbstractPluginService.get() + if (pluginService.isActivated(kalturaPluginId)) { + info.addCss( + include( + ResourcesService + .getResourceHelper(kalturaPluginId) + .url("js/UploadControlEntry.css")).priority(Priority.LOWEST).make()) + } + } + val NewLayoutKey = "NEW_LAYOUT" // Check if new UI is enabled. From ec4f89d393319d6aaed9d570eac298b54f602f8a Mon Sep 17 00:00:00 2001 From: PenghaiZhang <47203811+PenghaiZhang@users.noreply.github.com> Date: Fri, 25 Jun 2021 12:05:56 +1000 Subject: [PATCH 12/13] Merge pull request #3141 from PenghaiZhang/feature/support-kaltura-css-in-new-ui refactor: update Kaltura TS Upload control CSS styles --- .../resources/web/sass/legacy.scss | 53 +++++++++++-------- 1 file changed, 32 insertions(+), 21 deletions(-) diff --git a/Source/Plugins/Core/com.equella.core/resources/web/sass/legacy.scss b/Source/Plugins/Core/com.equella.core/resources/web/sass/legacy.scss index 1391c1ec36..1e9ddf0eaa 100644 --- a/Source/Plugins/Core/com.equella.core/resources/web/sass/legacy.scss +++ b/Source/Plugins/Core/com.equella.core/resources/web/sass/legacy.scss @@ -280,6 +280,30 @@ $savePanelWidth: 221px; padding-left: $width; } +@mixin standardButton { + @include curvedCorners; + padding: 0 $singleMargin 0 $singleMargin; + display: inline-flex; + position: relative; + align-items: center; + justify-content: center; + box-sizing: border-box; + min-width: 64px; + border: none; + outline: none; + cursor: pointer; + background-color: $primaryColor; + color: set-text-color($primaryColor); + @include typography-button(); + + &:hover { + background-color: transparentize($primaryColor, 0.1); + } + &:active { + background-color: transparentize($primaryColor, 0.3); + } +} + /***************************************************************************** Animations *****************************************************************************/ @@ -420,27 +444,7 @@ Buttons *****************************************************************************/ .btn { - @include curvedCorners; - padding: 0 $singleMargin 0 $singleMargin; - display: inline-flex; - position: relative; - align-items: center; - justify-content: center; - box-sizing: border-box; - min-width: 64px; - border: none; - outline: none; - cursor: pointer; - background-color: $primaryColor; - color: set-text-color($primaryColor); - @include typography-button(); - - &:hover { - background-color: transparentize($primaryColor, 0.1); - } - &:active { - background-color: transparentize($primaryColor, 0.3); - } + @include standardButton; } .button-expandable { @@ -4324,6 +4328,13 @@ a.modal-control.modal-save { padding-right: $singleMargin; margin-left: $singleMargin; } + + #kaltura-simple-uploader { + input[type="submit"], + button { + @include standardButton; + } + } } .fileHandler, From 245fb99528be61964fc8b36b370efa070420f4c2 Mon Sep 17 00:00:00 2001 From: Penghai Date: Fri, 25 Jun 2021 15:23:23 +1000 Subject: [PATCH 13/13] chore: move OEQThumb front-end files to Core project --- .../Plugins/Core/com.equella.core/js}/__mocks__/OEQThumb.mock.ts | 0 .../js}/__stories__/components/OEQThumb.stories.tsx | 0 .../js}/__tests__/tsrc/components/OEQThumb.test.tsx | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename {react-front-end => Source/Plugins/Core/com.equella.core/js}/__mocks__/OEQThumb.mock.ts (100%) rename {react-front-end => Source/Plugins/Core/com.equella.core/js}/__stories__/components/OEQThumb.stories.tsx (100%) rename {react-front-end => Source/Plugins/Core/com.equella.core/js}/__tests__/tsrc/components/OEQThumb.test.tsx (100%) diff --git a/react-front-end/__mocks__/OEQThumb.mock.ts b/Source/Plugins/Core/com.equella.core/js/__mocks__/OEQThumb.mock.ts similarity index 100% rename from react-front-end/__mocks__/OEQThumb.mock.ts rename to Source/Plugins/Core/com.equella.core/js/__mocks__/OEQThumb.mock.ts diff --git a/react-front-end/__stories__/components/OEQThumb.stories.tsx b/Source/Plugins/Core/com.equella.core/js/__stories__/components/OEQThumb.stories.tsx similarity index 100% rename from react-front-end/__stories__/components/OEQThumb.stories.tsx rename to Source/Plugins/Core/com.equella.core/js/__stories__/components/OEQThumb.stories.tsx diff --git a/react-front-end/__tests__/tsrc/components/OEQThumb.test.tsx b/Source/Plugins/Core/com.equella.core/js/__tests__/tsrc/components/OEQThumb.test.tsx similarity index 100% rename from react-front-end/__tests__/tsrc/components/OEQThumb.test.tsx rename to Source/Plugins/Core/com.equella.core/js/__tests__/tsrc/components/OEQThumb.test.tsx