From 0ab20b210ad0b9ea83460cf7a960c000f10b290e Mon Sep 17 00:00:00 2001 From: PenghaiZhang <47203811+PenghaiZhang@users.noreply.github.com> Date: Thu, 9 Jan 2020 13:30:46 +1100 Subject: [PATCH 01/11] Enable Cloud Provider Proxy to handle Delete requests * Add a REST endpoint in oEQ to handle Cloud Provider Proxy Delete requests * Add a REST endpoint in IntegTester to respond to Delete requests PR #1405 --- .../com/tle/web/api/wizard/WizardApi.scala | 10 ++++++++++ autotest/IntegTester/ps/www/control.tsx | 14 ++++++++++++++ .../testprovider/TestingCloudProvider.scala | 2 ++ 3 files changed, 26 insertions(+) diff --git a/Source/Plugins/Core/com.equella.core/scalasrc/com/tle/web/api/wizard/WizardApi.scala b/Source/Plugins/Core/com.equella.core/scalasrc/com/tle/web/api/wizard/WizardApi.scala index af2af88cd0..5ce1c9f316 100644 --- a/Source/Plugins/Core/com.equella.core/scalasrc/com/tle/web/api/wizard/WizardApi.scala +++ b/Source/Plugins/Core/com.equella.core/scalasrc/com/tle/web/api/wizard/WizardApi.scala @@ -211,6 +211,16 @@ class WizardApi { .streamBody(streamedBody) } } + + @DELETE + @Path("provider/{providerId}/{serviceId}") + def proxyDELETE(@PathParam("wizid") wizid: String, + @PathParam("providerId") providerId: UUID, + @PathParam("serviceId") serviceId: String, + @Context uriInfo: UriInfo, + @Context req: HttpServletRequest): Response = { + proxyRequest(wizid, req, providerId, serviceId, uriInfo)(sttp.delete) + } } class SimpleWorkflowOperation extends WorkflowOperation { diff --git a/autotest/IntegTester/ps/www/control.tsx b/autotest/IntegTester/ps/www/control.tsx index dc76171b5e..63943a8660 100644 --- a/autotest/IntegTester/ps/www/control.tsx +++ b/autotest/IntegTester/ps/www/control.tsx @@ -182,6 +182,20 @@ function TestControl(p: ControlApi) { > Execute + + ); diff --git a/autotest/IntegTester/src/main/scala/integtester/testprovider/TestingCloudProvider.scala b/autotest/IntegTester/src/main/scala/integtester/testprovider/TestingCloudProvider.scala index e33aebef1d..34a0fcb4ac 100644 --- a/autotest/IntegTester/src/main/scala/integtester/testprovider/TestingCloudProvider.scala +++ b/autotest/IntegTester/src/main/scala/integtester/testprovider/TestingCloudProvider.scala @@ -135,6 +135,8 @@ class TestingCloudProvider(implicit val cs: ContextShift[IO]) extends Http4sDsl[ case authReq @ GET -> Root / "myService" as user => val req = authReq.req Ok(ServiceResponse(user, "", req.queryString).asJson) + case authReq @ DELETE -> Root / "myService" as user => + Ok(ServiceResponse(user, "", authReq.req.queryString).asJson) } val oauthService = publicServices <+> middleware(protectedService) From 891dcf6981453182973f39cee42a2112d380437e Mon Sep 17 00:00:00 2001 From: PenghaiZhang <47203811+PenghaiZhang@users.noreply.github.com> Date: Wed, 15 Jan 2020 08:43:30 +1100 Subject: [PATCH 02/11] Support Cloud Provider Proxy PUT requests * Make sure headers are correctly sent to Cloud Provider services PR #1419 --- .../com/tle/web/api/wizard/WizardApi.scala | 53 ++++++++++-- autotest/IntegTester/ps/www/control.tsx | 86 ++++++++++--------- .../testprovider/TestingCloudProvider.scala | 31 +++++-- 3 files changed, 121 insertions(+), 49 deletions(-) diff --git a/Source/Plugins/Core/com.equella.core/scalasrc/com/tle/web/api/wizard/WizardApi.scala b/Source/Plugins/Core/com.equella.core/scalasrc/com/tle/web/api/wizard/WizardApi.scala index 5ce1c9f316..a19ea25440 100644 --- a/Source/Plugins/Core/com.equella.core/scalasrc/com/tle/web/api/wizard/WizardApi.scala +++ b/Source/Plugins/Core/com.equella.core/scalasrc/com/tle/web/api/wizard/WizardApi.scala @@ -184,6 +184,30 @@ class WizardApi { } + private def getStreamedBody(content: InputStream): Stream[IO, ByteBuffer] = { + readInputStream(IO(content), 4096, Implicits.global).chunks.map(_.toByteBuffer) + } + + private def getRequestHeaders(req: HttpServletRequest): Map[String, String] = { + val headers = (for { + headerName <- req.getHeaderNames.asScala + } yield (headerName, req.getHeader(headerName))).toMap + + val filterCookies = { + val filterList = List("JSESSIONID") + val cookies = + req.getCookies.filter(cookie => filterList.exists(!_.startsWith(cookie.getName))) + // Generate a string which include cookie pairs separated by a semi-colon + cookies.map(cookie => s"${cookie.getName}=${cookie.getValue}").mkString(";") + } + // If have cookies apart from those unneeded then reset cookie in the header; otherwise remove cookie from the header. + if (!filterCookies.isEmpty) { + headers + ("cookie" -> filterCookies) + } else { + headers - "cookie" + } + } + @NoCache @GET @Path("provider/{providerId}/{serviceId}") @@ -192,7 +216,9 @@ class WizardApi { @PathParam("serviceId") serviceId: String, @Context uriInfo: UriInfo, @Context req: HttpServletRequest): Response = { - proxyRequest(wizid, req, providerId, serviceId, uriInfo)(sttp.get) + proxyRequest(wizid, req, providerId, serviceId, uriInfo) { uri => + sttp.get(uri).headers(getRequestHeaders(req)) + } } @POST @@ -203,12 +229,27 @@ class WizardApi { @Context uriInfo: UriInfo, @Context req: HttpServletRequest, content: InputStream): Response = { - val streamedBody = - readInputStream(IO(content), 4096, Implicits.global).chunks.map(_.toByteBuffer) proxyRequest(wizid, req, providerId, serviceId, uriInfo) { uri => sttp .post(uri) - .streamBody(streamedBody) + .headers(getRequestHeaders(req)) + .streamBody(getStreamedBody(content)) + } + } + + @PUT + @Path("provider/{providerId}/{serviceId}") + def proxyPUT(@PathParam("wizid") wizid: String, + @PathParam("providerId") providerId: UUID, + @PathParam("serviceId") serviceId: String, + @Context uriInfo: UriInfo, + @Context req: HttpServletRequest, + content: InputStream): Response = { + proxyRequest(wizid, req, providerId, serviceId, uriInfo) { uri => + sttp + .put(uri) + .headers(getRequestHeaders(req)) + .streamBody(getStreamedBody(content)) } } @@ -219,7 +260,9 @@ class WizardApi { @PathParam("serviceId") serviceId: String, @Context uriInfo: UriInfo, @Context req: HttpServletRequest): Response = { - proxyRequest(wizid, req, providerId, serviceId, uriInfo)(sttp.delete) + proxyRequest(wizid, req, providerId, serviceId, uriInfo) { uri => + sttp.delete(uri).headers(getRequestHeaders(req)) + } } } diff --git a/autotest/IntegTester/ps/www/control.tsx b/autotest/IntegTester/ps/www/control.tsx index 63943a8660..6452b3fb2e 100644 --- a/autotest/IntegTester/ps/www/control.tsx +++ b/autotest/IntegTester/ps/www/control.tsx @@ -1,6 +1,6 @@ import * as ReactDOM from "react-dom"; import * as React from "react"; -import axios from "axios"; +import axios, { AxiosResponse } from "axios"; import { ControlApi, @@ -93,6 +93,28 @@ function TestControl(p: ControlApi) { [failValidation, required] ); + interface TestingButtonProps { + buttonName: string; + onClick: () => Promise>; + } + const TestingButton = TestingButtonProps => ( + + ); + + const requestUrl = (query: String = queryString) => { + return p.providerUrl(serviceId) + "?" + query; + }; + React.useEffect(() => { p.registerValidator(validator); return () => p.deregisterValidator(validator); @@ -135,14 +157,6 @@ function TestControl(p: ControlApi) { onChange={e => setQueryString(e.target.value)} /> -
- POST Request?{" "} - setPostRequest(e.target.checked)} - /> -
{postRequest && (
Payload: @@ -165,37 +179,31 @@ function TestControl(p: ControlApi) { />
)} - - + axios.get(requestUrl())} /> + + + axios.post(requestUrl(), { + data: serviceContent + }) + } + /> + + axios.delete(requestUrl("param1=test_param_one"))} + /> + + + axios.put(requestUrl(), { + data: serviceContent + }) + } + /> ); diff --git a/autotest/IntegTester/src/main/scala/integtester/testprovider/TestingCloudProvider.scala b/autotest/IntegTester/src/main/scala/integtester/testprovider/TestingCloudProvider.scala index 34a0fcb4ac..6a15c7dbcd 100644 --- a/autotest/IntegTester/src/main/scala/integtester/testprovider/TestingCloudProvider.scala +++ b/autotest/IntegTester/src/main/scala/integtester/testprovider/TestingCloudProvider.scala @@ -11,6 +11,7 @@ import org.http4s._ import org.http4s.circe._ import org.http4s.dsl.Http4sDsl import org.http4s.server.AuthMiddleware +import org.http4s.util.CaseInsensitiveString import scalaoauth2.provider._ import scala.collection.JavaConverters._ @@ -127,16 +128,36 @@ class TestingCloudProvider(implicit val cs: ContextShift[IO]) extends Http4sDsl[ case req @ POST -> Root / "itemNotification" as user => System.err.println(req.req.queryString) Ok() + case authReq @ POST -> Root / "myService" as user => - val req = authReq.req + createResponse(true, authReq.req, user) + + case authReq @ PUT -> Root / "myService" as user => + createResponse(true, authReq.req, user) + + case authReq @ GET -> Root / "myService" as user => + createResponse(false, authReq.req, user) + + case authReq @ DELETE -> Root / "myService" as user => + createResponse(false, authReq.req, user) + } + def createResponse(decode: Boolean, req: Request[IO], user: TestUser): IO[Response[IO]] = { + val cookies = req.headers.get(CaseInsensitiveString("cookie")) + + // If header includes cookies then check if JSESSIONID exists; if yes then respond with a bad request. + if (cookies.isDefined) { + val jSessionId = cookies.get.value.split(";").exists(value => value.startsWith("JSESSIONID")) + if (jSessionId) { + return BadRequest("The unexpected cookie name (JSESSIONID) was found.") + } + } + if (decode) { req.decode[String] { serviceData => Ok(ServiceResponse(user, serviceData, req.queryString).asJson) } - case authReq @ GET -> Root / "myService" as user => - val req = authReq.req + } else { Ok(ServiceResponse(user, "", req.queryString).asJson) - case authReq @ DELETE -> Root / "myService" as user => - Ok(ServiceResponse(user, "", authReq.req.queryString).asJson) + } } val oauthService = publicServices <+> middleware(protectedService) From aca3a43826205dd9bf31d9749d838d5b1d007efd Mon Sep 17 00:00:00 2001 From: PenghaiZhang <47203811+PenghaiZhang@users.noreply.github.com> Date: Thu, 16 Jan 2020 18:16:29 +1100 Subject: [PATCH 03/11] fix functional tests (#1429) Use the beta version of Chrome and Chromedrive; Make some flaky tests more stable; --- .travis.yml | 105 +++++++++++++----- .../services/user/impl/UserServiceImpl.java | 4 +- .../test/cal/CALActivationsRolloverTest.java | 12 ++ .../asc/AdvancedScriptControlTests.java | 10 +- .../webtests/test/dashboard/PortalsTest.java | 8 +- .../test/viewing/ItemSummaryTest.java | 1 + .../tle/webtests/pageobject/AbstractPage.java | 8 ++ .../portal/AbstractPortalSection.java | 10 +- .../portal/RecentContributionsEditPage.java | 7 ++ .../portal/RecentContributionsSection.java | 22 ++-- .../pageobject/tasklist/ModerationView.java | 2 + .../pageobject/viewitem/PackageViewer.java | 9 +- .../wizard/AbstractWizardControlPage.java | 12 +- .../pageobject/wizard/AbstractWizardTab.java | 2 +- .../equellatests/pages/LoginNoticePage.scala | 10 +- autotest/travis.conf | 2 +- 16 files changed, 167 insertions(+), 57 deletions(-) diff --git a/.travis.yml b/.travis.yml index c542bc650e..eebde54af4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,7 @@ branches: - master - /^release\/.+/ - /^stable-.+/ + - /^travis\/.+/ env: global: ### AWS START ### @@ -27,10 +28,9 @@ services: - postgresql addons: postgresql: "9.6" + chrome: beta apt: packages: - - chromium-browser - - chromium-chromedriver - ffmpeg - libimage-exiftool-perl - openjdk-8-jdk @@ -51,6 +51,10 @@ before_install: - sudo ln -s /usr/bin/ffplay /usr/bin/avplay - sudo ln -s /usr/bin/ffprobe /usr/bin/avprobe install: + - curl -OL https://chromedriver.storage.googleapis.com/80.0.3987.16/chromedriver_linux64.zip + - unzip chromedriver_linux64.zip + - sudo apt-get purge google-chrome-stable # have to delete the stable version to force chromedriver to use the beta + - echo `google-chrome --version` # retrieve full git history for automatic versioning - git fetch --unshallow before_script: @@ -65,7 +69,6 @@ before_script: stages: - name: build and check - name: functional test - if: false # Currently disabled due to Travis now running with Chromium 79 which is having issues headless - name: finalise if: fork = false # No access to S3, so skip for forks @@ -90,6 +93,15 @@ jobs: - ci/s3cp.sh Installer/target/equella-installer*.zip $S3_DEST_BUILD - ci/s3cp.sh target/reference-language-pack.zip $S3_DEST_BUILD - ci/s3cp.sh target/scriptingapi-javadoc-*.zip $S3_DEST_BUILD + - stage: build and check + script: sbt "project IntegTester" package + workspaces: + create: + name: prebuilt-integtester + paths: + - autotest/IntegTester/target/ + - autotest/IntegTester/ps/ + name: Pre-build IntegTester - stage: build and check script: sbt test name: Unit test @@ -101,11 +113,14 @@ jobs: name: Build the Import/Export tool - stage: functional test - script: sbt -jvm-opts autotest/.jvmopts "project autotest" \ - installEquella startEquella configureInstall setupForTests \ - Tests/test dumpCoverage + script: + - sbt -jvm-opts autotest/.jvmopts "project autotest" \ + installEquella startEquella configureInstall setupForTests \ + Tests/test dumpCoverage workspaces: - use: oeq-installer + use: + - oeq-installer + - prebuilt-integtester name: Scalacheck after_script: ci/scalacheck-save-results.sh Tests - stage: functional test @@ -113,21 +128,27 @@ jobs: installEquella startEquella configureInstall setupForTests \ Tests/Serial/test dumpCoverage workspaces: - use: oeq-installer + use: + - oeq-installer + - prebuilt-integtester name: Scalacheck Serial after_script: ci/scalacheck-save-results.sh Tests-Serial # Admin - stage: functional test script: ci/oldtests-run.sh workspaces: - use: oeq-installer + use: + - oeq-installer + - prebuilt-integtester name: TestNG (admin) env: OLD_TEST_SUITE=admin after_script: ci/oldtests-save-results.sh - stage: functional test script: ci/oldtests-run.sh workspaces: - use: oeq-installer + use: + - oeq-installer + - prebuilt-integtester name: TestNG (admin - new UI) env: - OLD_TEST_SUITE=admin @@ -137,14 +158,18 @@ jobs: - stage: functional test script: ci/oldtests-run.sh workspaces: - use: oeq-installer + use: + - oeq-installer + - prebuilt-integtester name: TestNG (advanced script controls - ASC) env: OLD_TEST_SUITE=asc after_script: ci/oldtests-save-results.sh - stage: functional test script: ci/oldtests-run.sh workspaces: - use: oeq-installer + use: + - oeq-installer + - prebuilt-integtester name: TestNG (advanced script controls - ASC - new UI) env: - OLD_TEST_SUITE=asc @@ -154,14 +179,18 @@ jobs: - stage: functional test script: ci/oldtests-run.sh workspaces: - use: oeq-installer + use: + - oeq-installer + - prebuilt-integtester name: TestNG (contribution) env: OLD_TEST_SUITE=contribution after_script: ci/oldtests-save-results.sh - stage: functional test script: ci/oldtests-run.sh workspaces: - use: oeq-installer + use: + - oeq-installer + - prebuilt-integtester name: TestNG (contribution - new UI) env: - OLD_TEST_SUITE=contribution @@ -171,14 +200,18 @@ jobs: - stage: functional test script: ci/oldtests-run.sh workspaces: - use: oeq-installer + use: + - oeq-installer + - prebuilt-integtester name: TestNG (controls) env: OLD_TEST_SUITE=controls after_script: ci/oldtests-save-results.sh - stage: functional test script: ci/oldtests-run.sh workspaces: - use: oeq-installer + use: + - oeq-installer + - prebuilt-integtester name: TestNG (controls - new UI) env: - OLD_TEST_SUITE=controls @@ -188,14 +221,18 @@ jobs: - stage: functional test script: ci/oldtests-run.sh workspaces: - use: oeq-installer + use: + - oeq-installer + - prebuilt-integtester name: TestNG (searching) env: OLD_TEST_SUITE=searching after_script: ci/oldtests-save-results.sh - stage: functional test script: ci/oldtests-run.sh workspaces: - use: oeq-installer + use: + - oeq-installer + - prebuilt-integtester name: TestNG (searching - new UI) env: - OLD_TEST_SUITE=searching @@ -205,14 +242,18 @@ jobs: - stage: functional test script: ci/oldtests-run.sh workspaces: - use: oeq-installer + use: + - oeq-installer + - prebuilt-integtester name: TestNG (users) env: OLD_TEST_SUITE=users after_script: ci/oldtests-save-results.sh - stage: functional test script: ci/oldtests-run.sh workspaces: - use: oeq-installer + use: + - oeq-installer + - prebuilt-integtester name: TestNG (users - new UI) env: - OLD_TEST_SUITE=users @@ -222,14 +263,18 @@ jobs: - stage: functional test script: ci/oldtests-run.sh workspaces: - use: oeq-installer + use: + - oeq-installer + - prebuilt-integtester name: TestNG (viewing) env: OLD_TEST_SUITE=viewing after_script: ci/oldtests-save-results.sh - stage: functional test script: ci/oldtests-run.sh workspaces: - use: oeq-installer + use: + - oeq-installer + - prebuilt-integtester name: TestNG (viewing - new UI) env: - OLD_TEST_SUITE=viewing @@ -239,14 +284,18 @@ jobs: - stage: functional test script: ci/oldtests-run.sh workspaces: - use: oeq-installer + use: + - oeq-installer + - prebuilt-integtester name: TestNG (webservices) env: OLD_TEST_SUITE=webservices after_script: ci/oldtests-save-results.sh - stage: functional test script: ci/oldtests-run.sh workspaces: - use: oeq-installer + use: + - oeq-installer + - prebuilt-integtester name: TestNG (webservices - new UI) env: - OLD_TEST_SUITE=webservices @@ -256,14 +305,18 @@ jobs: - stage: functional test script: ci/oldtests-run.sh workspaces: - use: oeq-installer + use: + - oeq-installer + - prebuilt-integtester name: TestNG (workflow) env: OLD_TEST_SUITE=workflow after_script: ci/oldtests-save-results.sh - stage: functional test script: ci/oldtests-run.sh workspaces: - use: oeq-installer + use: + - oeq-installer + - prebuilt-integtester name: TestNG (workflow - new UI) env: - OLD_TEST_SUITE=workflow diff --git a/Source/Plugins/Core/com.equella.core/src/com/tle/core/services/user/impl/UserServiceImpl.java b/Source/Plugins/Core/com.equella.core/src/com/tle/core/services/user/impl/UserServiceImpl.java index 918a0c21c4..e1eece3aa2 100644 --- a/Source/Plugins/Core/com.equella.core/src/com/tle/core/services/user/impl/UserServiceImpl.java +++ b/Source/Plugins/Core/com.equella.core/src/com/tle/core/services/user/impl/UserServiceImpl.java @@ -492,7 +492,9 @@ public void login(UserState userState, boolean forceSession) { public void userSessionCreatedEvent(UserSessionLoginEvent event) { UserState userState = event.getUserState(); if (!userState.isGuest()) { - saveUserInfoBackup(userState.getUserBean()); + synchronized (this) { + saveUserInfoBackup(userState.getUserBean()); + } } } diff --git a/autotest/OldTests/src/test/java/com/tle/webtests/test/cal/CALActivationsRolloverTest.java b/autotest/OldTests/src/test/java/com/tle/webtests/test/cal/CALActivationsRolloverTest.java index fa902dbc2b..ad255d04ba 100644 --- a/autotest/OldTests/src/test/java/com/tle/webtests/test/cal/CALActivationsRolloverTest.java +++ b/autotest/OldTests/src/test/java/com/tle/webtests/test/cal/CALActivationsRolloverTest.java @@ -7,6 +7,7 @@ import com.tle.webtests.pageobject.cal.CALRolloverDialog; import com.tle.webtests.pageobject.cal.CALSummaryPage; import com.tle.webtests.pageobject.cal.ManageActivationsPage; +import org.openqa.selenium.support.ui.ExpectedConditions; import org.testng.annotations.Test; public class CALActivationsRolloverTest extends AbstractActivationsTest { @@ -40,7 +41,18 @@ public void testActivationsRollover() { rolloverDialog.selectCourse(ROLLOVER_COURSE); assertTrue(rolloverDialog.execute(activationResults)); + activations + .getWaiter() + .until( + ExpectedConditions.textToBePresentInElement( + activationResults.getResultsDiv(), ORIGINAL_COURSE)); assertTrue(activationResults.isShowing(portionName, ORIGINAL_COURSE)); + + activations + .getWaiter() + .until( + ExpectedConditions.textToBePresentInElement( + activationResults.getResultsDiv(), ROLLOVER_COURSE)); assertTrue(activationResults.isShowing(portionName, ROLLOVER_COURSE)); // bulk all activations rollover using same course + dates diff --git a/autotest/OldTests/src/test/java/com/tle/webtests/test/contribute/controls/asc/AdvancedScriptControlTests.java b/autotest/OldTests/src/test/java/com/tle/webtests/test/contribute/controls/asc/AdvancedScriptControlTests.java index b2ea97247a..9fb40255a2 100644 --- a/autotest/OldTests/src/test/java/com/tle/webtests/test/contribute/controls/asc/AdvancedScriptControlTests.java +++ b/autotest/OldTests/src/test/java/com/tle/webtests/test/contribute/controls/asc/AdvancedScriptControlTests.java @@ -39,6 +39,7 @@ import org.openqa.selenium.WebElement; import org.openqa.selenium.support.ui.ExpectedConditions; import org.openqa.selenium.support.ui.Select; +import org.openqa.selenium.support.ui.WebDriverWait; import org.testng.annotations.Test; @TestInstitution("asc") @@ -1065,7 +1066,10 @@ private List getDivsByPrefix(String prefix) { * @return */ private WebElement getAscMessage() { - return context.getDriver().findElement(By.xpath("//div[@id='ascMessage']/pre")); + By ascMessageXpath = By.xpath("//div[@id='ascMessage']/pre"); + WebDriverWait wait = new WebDriverWait(context.getDriver(), 30); + wait.until(ExpectedConditions.visibilityOfElementLocated(ascMessageXpath)); + return context.getDriver().findElement(ascMessageXpath); } private WebElement getAscMessage1() { @@ -1082,6 +1086,8 @@ private String getDivMessageForId(String id) { private void ascEditbox(int ctrlNum, String suffix, String text) { WebElement field = context.getDriver().findElement(By.name("c" + ctrlNum + suffix)); + WebDriverWait wait = new WebDriverWait(context.getDriver(), 30); + wait.until(ExpectedConditions.visibilityOf(field)); field.clear(); field.sendKeys(text); } @@ -1093,6 +1099,8 @@ private void ascEditbox(int ctrlNum, String suffix, String text) { * @param text */ private void ascSelectDropdown(String id, String optText) { + WebDriverWait wait = new WebDriverWait(context.getDriver(), 30); + wait.until(ExpectedConditions.presenceOfElementLocated(By.id(id))); Select dropdown = new Select(context.getDriver().findElement(By.id(id))); dropdown.selectByVisibleText(optText); } diff --git a/autotest/OldTests/src/test/java/com/tle/webtests/test/dashboard/PortalsTest.java b/autotest/OldTests/src/test/java/com/tle/webtests/test/dashboard/PortalsTest.java index 9fd28d60cc..716e508aa0 100644 --- a/autotest/OldTests/src/test/java/com/tle/webtests/test/dashboard/PortalsTest.java +++ b/autotest/OldTests/src/test/java/com/tle/webtests/test/dashboard/PortalsTest.java @@ -322,6 +322,7 @@ public void testRecentPortal() { // Edit the portal RecentContributionsEditPage edit = recent.edit(portal); edit.setStatus("draft"); + edit.checkSelectedCollection(); edit.save(new HomePage(context)); // Check that the draft item is displayed @@ -344,23 +345,26 @@ public void testRecentPortal() { edit = recent.edit(portal); edit.setQuery("query item"); edit.setStatus("live"); + edit.checkSelectedCollection(); edit.save(new HomePage(context)); // Check that the queried item is displayed home = new MenuSection(context).home(); recent = new RecentContributionsSection(context, recentName).get(); assertTrue(recent.recentContributionExists(itemToQuery)); - assertTrue(recent.descriptionExists(description)); + assertTrue(recent.descriptionExists(description, true)); // Edit portlet for description option home = new MenuSection(context).home(); + recent = new RecentContributionsSection(context, recentName).get(); edit = recent.edit(portal); edit.setDisplayTitleOnly(true); + edit.checkSelectedCollection(); edit.save(new HomePage(context)); // Check that the description not displayed home = new MenuSection(context).home(); recent = new RecentContributionsSection(context, recentName).get(); - assertFalse(recent.descriptionExists(description)); + assertFalse(recent.descriptionExists(description, false)); } @Test diff --git a/autotest/OldTests/src/test/java/com/tle/webtests/test/viewing/ItemSummaryTest.java b/autotest/OldTests/src/test/java/com/tle/webtests/test/viewing/ItemSummaryTest.java index 34a72fd011..497cf1ee82 100644 --- a/autotest/OldTests/src/test/java/com/tle/webtests/test/viewing/ItemSummaryTest.java +++ b/autotest/OldTests/src/test/java/com/tle/webtests/test/viewing/ItemSummaryTest.java @@ -68,6 +68,7 @@ public void lastOwnerTest() { logon(); SearchPage searchPage = new SearchPage(context).load(); ItemListPage itemList = searchPage.resultsPageObject(); + searchPage.exactQuery(LAST_KNOWN_OWNER_ITEM_NAME); SummaryPage summaryPage = itemList.getResultForTitle(LAST_KNOWN_OWNER_ITEM_NAME).viewSummary(); // Check if LAST_KNOWN_OWNER_ITEM_NAME is displayed instead of 'unknown user' diff --git a/autotest/Tests/src/main/java/com/tle/webtests/pageobject/AbstractPage.java b/autotest/Tests/src/main/java/com/tle/webtests/pageobject/AbstractPage.java index 56217ff74f..cb3ee7f641 100644 --- a/autotest/Tests/src/main/java/com/tle/webtests/pageobject/AbstractPage.java +++ b/autotest/Tests/src/main/java/com/tle/webtests/pageobject/AbstractPage.java @@ -414,6 +414,14 @@ protected WebElement waitForHiddenElement(final WebElement element) { @Override public T get() { refreshTime = System.currentTimeMillis(); + // Due to some of the pages using various Sections AJAXy stuff, we need + // a bit more thorough wait checking then the plain old check by Selenium. + getWaiter() + .until( + driver -> + ((JavascriptExecutor) driver) + .executeScript("return document.readyState") + .equals("complete")); return getWaiter() .until( new ExpectedCondition() { diff --git a/autotest/Tests/src/main/java/com/tle/webtests/pageobject/portal/AbstractPortalSection.java b/autotest/Tests/src/main/java/com/tle/webtests/pageobject/portal/AbstractPortalSection.java index b4bfc8e746..c67121f9ad 100644 --- a/autotest/Tests/src/main/java/com/tle/webtests/pageobject/portal/AbstractPortalSection.java +++ b/autotest/Tests/src/main/java/com/tle/webtests/pageobject/portal/AbstractPortalSection.java @@ -8,6 +8,7 @@ import org.openqa.selenium.JavascriptExecutor; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.ui.ExpectedCondition; +import org.openqa.selenium.support.ui.ExpectedConditions; public abstract class AbstractPortalSection> extends AbstractPage { @@ -74,7 +75,14 @@ public boolean isCloseable() { public

> P edit(P portal) { showButtons(); - getBoxHead().findElement(By.className("box_edit")).click(); + WebElement editButton = + driver.findElement( + By.xpath( + "//div[contains(@title," + + quoteXPath(getTitle()) + + ")]/following-sibling::img[contains(@class, 'box_edit')]")); + waiter.until(ExpectedConditions.elementToBeClickable(editButton)); + editButton.click(); return portal.get(); } diff --git a/autotest/Tests/src/main/java/com/tle/webtests/pageobject/portal/RecentContributionsEditPage.java b/autotest/Tests/src/main/java/com/tle/webtests/pageobject/portal/RecentContributionsEditPage.java index 9147ea75f5..56141cf54b 100644 --- a/autotest/Tests/src/main/java/com/tle/webtests/pageobject/portal/RecentContributionsEditPage.java +++ b/autotest/Tests/src/main/java/com/tle/webtests/pageobject/portal/RecentContributionsEditPage.java @@ -3,6 +3,7 @@ import com.tle.webtests.framework.PageContext; import com.tle.webtests.pageobject.generic.component.EquellaSelect; import org.openqa.selenium.By; +import org.openqa.selenium.WebElement; public class RecentContributionsEditPage extends AbstractPortalEditPage { @@ -23,6 +24,12 @@ public void checkLoaded() throws Error { displayList = new EquellaSelect(context, driver.findElement(By.id("rct_d"))); } + public void checkSelectedCollection() { + WebElement allResourceOption = + driver.findElement(By.xpath("//input[@id=//label[text()='All resources']/@for]")); + if (!allResourceOption.isSelected()) allResourceOption.click(); + } + @Override public String getType() { return "Recent contributions"; diff --git a/autotest/Tests/src/main/java/com/tle/webtests/pageobject/portal/RecentContributionsSection.java b/autotest/Tests/src/main/java/com/tle/webtests/pageobject/portal/RecentContributionsSection.java index 6dd8a561fd..0995f6f63a 100644 --- a/autotest/Tests/src/main/java/com/tle/webtests/pageobject/portal/RecentContributionsSection.java +++ b/autotest/Tests/src/main/java/com/tle/webtests/pageobject/portal/RecentContributionsSection.java @@ -2,6 +2,7 @@ import com.tle.webtests.framework.PageContext; import org.openqa.selenium.By; +import org.openqa.selenium.support.ui.ExpectedConditions; public class RecentContributionsSection extends AbstractPortalSection { @@ -10,20 +11,21 @@ public RecentContributionsSection(PageContext context, String title) { } public boolean recentContributionExists(String itemName) { - return isPresent( - getBoxContent(), + By recentContributionXpath = By.xpath( ".//div[normalize-space(@class)='recent-items']//a[normalize-space(text())=" + quoteXPath(itemName) - + "]")); + + "]"); + waiter.until(ExpectedConditions.visibilityOfElementLocated(recentContributionXpath)); + return isPresent(recentContributionXpath); } - public boolean descriptionExists(String description) { - return isPresent( - getBoxContent(), - By.xpath( - ".//div[normalize-space(@class)='recent-items']//p[normalize-space(text())=" - + quoteXPath(description) - + "]")); + public boolean descriptionExists(String description, boolean expectedExisting) { + By descriptionQuoteXpath = + By.xpath("//p[normalize-space(text())=" + quoteXPath(description) + "]"); + if (expectedExisting) { + waiter.until(ExpectedConditions.visibilityOfElementLocated(descriptionQuoteXpath)); + } + return isPresent(descriptionQuoteXpath); } } diff --git a/autotest/Tests/src/main/java/com/tle/webtests/pageobject/tasklist/ModerationView.java b/autotest/Tests/src/main/java/com/tle/webtests/pageobject/tasklist/ModerationView.java index a3557e260f..b1d13125df 100644 --- a/autotest/Tests/src/main/java/com/tle/webtests/pageobject/tasklist/ModerationView.java +++ b/autotest/Tests/src/main/java/com/tle/webtests/pageobject/tasklist/ModerationView.java @@ -11,6 +11,7 @@ import org.openqa.selenium.By; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.FindBy; +import org.openqa.selenium.support.ui.ExpectedConditions; public class ModerationView extends AbstractPage { @FindBy(className = "moderate-reject") @@ -48,6 +49,7 @@ public void accept() { } public ModerationMessagePage acceptToMessagePage() { + waiter.until(ExpectedConditions.elementToBeClickable(approveButton)); approveButton.click(); return new ApproveMessagePage(context).get(); } diff --git a/autotest/Tests/src/main/java/com/tle/webtests/pageobject/viewitem/PackageViewer.java b/autotest/Tests/src/main/java/com/tle/webtests/pageobject/viewitem/PackageViewer.java index 4ded1f9506..2f5db5d483 100644 --- a/autotest/Tests/src/main/java/com/tle/webtests/pageobject/viewitem/PackageViewer.java +++ b/autotest/Tests/src/main/java/com/tle/webtests/pageobject/viewitem/PackageViewer.java @@ -11,6 +11,7 @@ import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.ui.ExpectedCondition; +import org.openqa.selenium.support.ui.ExpectedConditions; public class PackageViewer extends AbstractPage { @@ -91,11 +92,9 @@ public String tabText(String attachment, String tabName, int split) { clickAttachment(attachment); switchToFrame(split); driver.findElement(By.xpath("//a/span[text()=" + quoteXPath(tabName) + "]")).click(); - driver.switchTo().frame("iframe-content"); - - waitForBody(); - - String text = driver.findElement(By.tagName("body")).getText(); + waiter.until(ExpectedConditions.frameToBeAvailableAndSwitchToIt("iframe-content")); + By body = By.cssSelector("body"); + String text = waiter.until(ExpectedConditions.visibilityOfElementLocated(body)).getText(); driver.switchTo().defaultContent(); return text; } diff --git a/autotest/Tests/src/main/java/com/tle/webtests/pageobject/wizard/AbstractWizardControlPage.java b/autotest/Tests/src/main/java/com/tle/webtests/pageobject/wizard/AbstractWizardControlPage.java index f2e78ef207..c0a7d503ec 100644 --- a/autotest/Tests/src/main/java/com/tle/webtests/pageobject/wizard/AbstractWizardControlPage.java +++ b/autotest/Tests/src/main/java/com/tle/webtests/pageobject/wizard/AbstractWizardControlPage.java @@ -345,11 +345,13 @@ public RepeaterControl repeater(int ctrlNum) { } public String getErrorMessage(int ctrlNum) { - return driver - .findElement( + WebElement errorMessage = + driver.findElement( By.xpath( - "id(" + quoteXPath(getControlId(ctrlNum)) + ")/div/p[@class='ctrlinvalidmessage']")) - .getText() - .trim(); + "id(" + + quoteXPath(getControlId(ctrlNum)) + + ")/div/p[@class='ctrlinvalidmessage']")); + waiter.until(ExpectedConditions.visibilityOf(errorMessage)); + return errorMessage.getText().trim(); } } diff --git a/autotest/Tests/src/main/java/com/tle/webtests/pageobject/wizard/AbstractWizardTab.java b/autotest/Tests/src/main/java/com/tle/webtests/pageobject/wizard/AbstractWizardTab.java index 17449dbdb8..880d72dc5e 100644 --- a/autotest/Tests/src/main/java/com/tle/webtests/pageobject/wizard/AbstractWizardTab.java +++ b/autotest/Tests/src/main/java/com/tle/webtests/pageobject/wizard/AbstractWizardTab.java @@ -46,8 +46,8 @@ protected void clickCommand(String command) { } else { xpath = "//div[@id='wizard-actions']/a[normalize-space(text())=" + quoteXPath(command) + "]"; } - WebElement elem = driver.findElement(By.xpath(xpath)); ((JavascriptExecutor) driver).executeScript("window.scrollTo(0, -document.body.scrollHeight)"); + WebElement elem = driver.findElement(By.xpath(xpath)); waiter.until(ExpectedConditions.elementToBeClickable(elem)); elem.click(); } diff --git a/autotest/Tests/src/test/scala/equellatests/pages/LoginNoticePage.scala b/autotest/Tests/src/test/scala/equellatests/pages/LoginNoticePage.scala index 538e9d3e03..4e19dd0a9c 100644 --- a/autotest/Tests/src/test/scala/equellatests/pages/LoginNoticePage.scala +++ b/autotest/Tests/src/test/scala/equellatests/pages/LoginNoticePage.scala @@ -39,16 +39,19 @@ case class LoginNoticePage(ctx: PageContext) private def switchToTinyMCEIFrame(): Unit = { driver.switchTo().frame(preNoticeIFrame) + waiter.until(ExpectedConditions.presenceOfElementLocated(By.id("tinymce"))) } private def switchFromTinyMCEIFrame(): Unit = { driver.switchTo().defaultContent() + waiter.until( + ExpectedConditions.presenceOfElementLocated(By.xpath("//h5[text()='Login notice editor']"))) } private def clearAndPopulatePreNoticeField(notice: String): Unit = { switchToTinyMCEIFrame() - preNoticeField.sendKeys(Keys.chord(Keys.CONTROL, "a")) - preNoticeField.sendKeys(Keys.DELETE) + preNoticeField.sendKeys(Keys.chord(Keys.CONTROL, "a", Keys.BACK_SPACE)) + preNoticeField.sendKeys(notice) waitFor(ExpectedConditions.textToBePresentInElement(preNoticeField, notice)) } @@ -124,8 +127,7 @@ case class LoginNoticePage(ctx: PageContext) def populatePostNoticeField(notice: String): Unit = { gotoPostNoticeTab() - postNoticeField.sendKeys(Keys.chord(Keys.CONTROL, "a")) - postNoticeField.sendKeys(Keys.DELETE) + postNoticeField.sendKeys(Keys.chord(Keys.CONTROL, "a", Keys.BACK_SPACE)) postNoticeField.sendKeys(notice) } diff --git a/autotest/travis.conf b/autotest/travis.conf index 203579ec9f..948020c5d3 100644 --- a/autotest/travis.conf +++ b/autotest/travis.conf @@ -2,7 +2,7 @@ server.url = "http://localhost:8080/" server.password = autotestpassword webdriver.chrome { - driver = "/usr/lib/chromium-browser/chromedriver" + driver = ${TRAVIS_BUILD_DIR}"/chromedriver" headless = true } From 0f6215040a18f198d67e26218b4b4e90316ae5f8 Mon Sep 17 00:00:00 2001 From: PenghaiZhang <47203811+PenghaiZhang@users.noreply.github.com> Date: Thu, 6 Feb 2020 12:36:59 +1100 Subject: [PATCH 04/11] Don't copy host from Headers for Cloud Provider requests PR #1467 --- .../core/cloudproviders/CloudProviderService.scala | 12 ++++++++++-- .../scalasrc/com/tle/web/api/wizard/WizardApi.scala | 11 +++++++---- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/Source/Plugins/Core/com.equella.core/scalasrc/com/tle/core/cloudproviders/CloudProviderService.scala b/Source/Plugins/Core/com.equella.core/scalasrc/com/tle/core/cloudproviders/CloudProviderService.scala index 8986705b86..c0f5998e32 100644 --- a/Source/Plugins/Core/com.equella.core/scalasrc/com/tle/core/cloudproviders/CloudProviderService.scala +++ b/Source/Plugins/Core/com.equella.core/scalasrc/com/tle/core/cloudproviders/CloudProviderService.scala @@ -36,6 +36,7 @@ import com.tle.core.db._ import com.tle.core.httpclient._ import com.tle.core.oauthclient.OAuthClientService import fs2.Stream +import org.apache.commons.lang.RandomStringUtils import org.slf4j.LoggerFactory import scala.collection.JavaConverters._ @@ -87,7 +88,10 @@ object CloudProviderService { IO.fromEither( UriTemplateService.replaceVariables(serviceUri.url, provider.baseUrl, cparams ++ params)) } - req = f(uri) + req = f(uri) + requestContext = "[" + RandomStringUtils.randomAlphanumeric(6) + "] provider: " + provider.id + ", vendor: " + provider.vendorId + _ = Logger.debug( + requestContext + ", method: " + req.method.m + ", request: " + uri.toString.split('?')(0)) auth = provider.providerAuth response <- if (serviceUri.authenticated) { dbLiftIO.liftIO(tokenUrlForProvider(provider)).flatMap { oauthUrl => @@ -95,7 +99,11 @@ object CloudProviderService { .authorizedRequest(oauthUrl.toString, auth.clientId, auth.clientSecret, req) } } else dbLiftIO.liftIO(req.send()) - } yield response + + } yield { + Logger.debug(requestContext + ", response status: " + response.code) + response + } case class ControlListCacheValue( expiry: Instant, diff --git a/Source/Plugins/Core/com.equella.core/scalasrc/com/tle/web/api/wizard/WizardApi.scala b/Source/Plugins/Core/com.equella.core/scalasrc/com/tle/web/api/wizard/WizardApi.scala index a19ea25440..454f1124fd 100644 --- a/Source/Plugins/Core/com.equella.core/scalasrc/com/tle/web/api/wizard/WizardApi.scala +++ b/Source/Plugins/Core/com.equella.core/scalasrc/com/tle/web/api/wizard/WizardApi.scala @@ -201,11 +201,14 @@ class WizardApi { cookies.map(cookie => s"${cookie.getName}=${cookie.getValue}").mkString(";") } // If have cookies apart from those unneeded then reset cookie in the header; otherwise remove cookie from the header. - if (!filterCookies.isEmpty) { - headers + ("cookie" -> filterCookies) - } else { - headers - "cookie" + val filterHeaders = { + if (!filterCookies.isEmpty) { + headers + ("cookie" -> filterCookies) + } else { + headers - "cookie" + } } + filterHeaders - "host" } @NoCache From c6ac728b39117b5f4996f9e0fb1649d24f61ff87 Mon Sep 17 00:00:00 2001 From: Penghai Date: Thu, 6 Feb 2020 15:15:02 +1100 Subject: [PATCH 05/11] Update the version to 2019.2.1 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 99555ac1d6..8b5fca989d 100644 --- a/build.sbt +++ b/build.sbt @@ -116,7 +116,7 @@ name := "Equella" equellaMajor in ThisBuild := 2019 equellaMinor in ThisBuild := 2 -equellaPatch in ThisBuild := 0 +equellaPatch in ThisBuild := 1 equellaStream in ThisBuild := "Stable" equellaBuild in ThisBuild := buildConfig.value.getString("build.buildname") From ea2dbd1c82d5ed08b41cf8af32978cdcc4a25122 Mon Sep 17 00:00:00 2001 From: PenghaiZhang <47203811+PenghaiZhang@users.noreply.github.com> Date: Thu, 9 Jan 2020 10:15:21 +1100 Subject: [PATCH 06/11] Do not use 'cd -' after building import-export-tool in Codebuild PR #1408 --- buildspec.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildspec.yml b/buildspec.yml index 14f4f41913..d7b2e01c1a 100644 --- a/buildspec.yml +++ b/buildspec.yml @@ -20,7 +20,7 @@ phases: - (cd Source/Plugins/Core/com.equella.core/js && npm install && npm test) - sbt -no-colors -Dconfig.file=${HOME}/build.conf test installerZip writeLanguagePack writeScriptingJavadoc - sbt -mem 2048 -no-colors "project autotest" installEquella startEquella configureInstall setupForTests Tests/test Tests/Serial/test OldTests/test coverageReport - - (cd import-export-tool && ./gradlew build); cd - + - (cd import-export-tool && ./gradlew build) post_build: commands: - publishBuildResults From 3f67d03104429da50302876ec8b87818b306be26 Mon Sep 17 00:00:00 2001 From: mrblippy Date: Thu, 6 Feb 2020 14:42:25 +1100 Subject: [PATCH 07/11] force redirect for hierarchy topics, to get the back button working properly in old and new ui --- .../com/tle/web/hierarchy/section/TopicDisplaySection.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Source/Plugins/Core/com.equella.core/src/com/tle/web/hierarchy/section/TopicDisplaySection.java b/Source/Plugins/Core/com.equella.core/src/com/tle/web/hierarchy/section/TopicDisplaySection.java index 0e4c2feec3..3ffbbf82c9 100644 --- a/Source/Plugins/Core/com.equella.core/src/com/tle/web/hierarchy/section/TopicDisplaySection.java +++ b/Source/Plugins/Core/com.equella.core/src/com/tle/web/hierarchy/section/TopicDisplaySection.java @@ -191,7 +191,6 @@ public SectionResult renderHtml(RenderEventContext context) { hideZero = topic.isHideSubtopicsWithNoResults(); } model.setName(getLabelForTopic(topic, topicVirtValue)); - // We now need the current topic and value to be in the "values" map so // that the counts and URLs for the child topics are generated // correctly. @@ -235,7 +234,6 @@ childTopic, new ExtraTopicMap(values, childTopic, childValue)), HtmlLinkState link = new HtmlLinkState(events.getNamedHandler("changeTopic", dynamicHierarchyId)); - List dynamicKeyResources = hierarchyService.getDynamicKeyResource(dynamicHierarchyId); @@ -253,7 +251,6 @@ childTopic, new ExtraTopicMap(values, childTopic, childValue)), } } model.setSubTopics(subNodes); - // for the benefit of skinny sessions ... if (skinnySearchActions != null) { // If there's no topic, we're showing the topic display and so don't @@ -294,6 +291,7 @@ public void doAdvanced(SectionInfo info) { public void changeTopic(SectionInfo info, String topicUuid) { getModel(info).setTopicId(topicUuid); pager.resetToFirst(info); + info.forceRedirect(); } @Override From b3f0917e0bce9399fe8fdab2dec12aceeed59250 Mon Sep 17 00:00:00 2001 From: PenghaiZhang <47203811+PenghaiZhang@users.noreply.github.com> Date: Mon, 17 Feb 2020 14:31:25 +1100 Subject: [PATCH 08/11] Merge pull request #1491 from openequella/component/accessibility_changes --- .../tle/web/sections/render/TagRenderer.java | 19 + .../com/tle/web/sections/render/TagState.java | 12 + .../js/tsrc/mainui/ScreenOptions.tsx | 2 + .../js/tsrc/mainui/Template.tsx | 5 +- .../js/tsrc/theme/ThemePage.tsx | 4 +- .../js/tsrc/util/langstrings.ts | 4 + .../lang/i18n-resource-centre.properties | 2 +- .../resources/view/attachmentlisttoggle.ftl | 4 +- .../resources/view/editbox.ftl | 4 +- .../resources/view/flickrquery.ftl | 10 +- .../resources/view/itemadmin-query.ftl | 26 +- .../resources/view/layouts/inner/dialog.ftl | 2 +- .../resources/view/logon/logon.ftl | 14 +- .../resources/view/macro/receipt.ftl | 2 +- .../resources/view/manage-query.ftl | 14 +- .../resources/view/managequery.ftl | 12 +- .../com.equella.core/resources/view/query.ftl | 28 +- .../resources/view/title/standardTitle.ftl | 10 +- .../view/universalattachmentlist.ftl | 4 +- .../resources/view/wizard/pagessection.ftl | 10 +- .../web/css/component/zebratable.css | 190 +++-- .../resources/web/css/styles.css | 4 +- .../resources/web/jquerylib/jquery.stars.js | 703 +++++++++--------- .../resources/web/sass/legacy.scss | 95 ++- .../web/scripts/component/richdropdown.js | 7 +- .../web/scripts/itemlistattachments.js | 35 +- .../resources/web/scripts/resultactions.js | 84 ++- .../com/tle/web/api/LegacyContentApi.scala | 24 +- .../src/com/tle/legacy/LegacyGuice.java | 3 + .../web/api/newuitheme/impl/NewUITheme.java | 2 +- ...tItemlikeListAttachmentDisplaySection.java | 9 +- .../MyResourceContributeSection.java | 8 +- .../renderer/SearchPortletRenderer.java | 2 + .../web/search/itemlist/ThumbnailDisplay.java | 1 + .../searching/section/SearchQuerySection.java | 4 + .../ajax/freemarker/AjaxCaptureDirective.java | 3 + .../equella/dialog/EquellaDialog.java | 9 +- .../BootstrapSplitDropDownRenderer.java | 9 + .../equella/search/PagingSection.java | 6 +- .../search/SearchResultsActionsSection.java | 5 +- .../renderers/AbstractElementRenderer.java | 2 +- .../standard/renderers/ImageRenderer.java | 19 +- .../standard/renderers/LabelTagRenderer.java | 4 +- .../standard/renderers/TextFieldRenderer.java | 1 + .../fancybox/FancyBoxDialogRenderer.java | 1 + .../com/tle/web/template/DialogTemplate.java | 17 +- .../tle/web/wizard/controls/WebControl.java | 1 - .../web/wizard/standard/controls/EditBox.java | 11 + .../wizard/standard/controls/Repeater.java | 4 + .../pageobject/searching/SearchResult.java | 4 + 50 files changed, 843 insertions(+), 612 deletions(-) diff --git a/Platform/Plugins/com.tle.web.sections/src/com/tle/web/sections/render/TagRenderer.java b/Platform/Plugins/com.tle.web.sections/src/com/tle/web/sections/render/TagRenderer.java index f93677e58a..98e827f69a 100644 --- a/Platform/Plugins/com.tle.web.sections/src/com/tle/web/sections/render/TagRenderer.java +++ b/Platform/Plugins/com.tle.web.sections/src/com/tle/web/sections/render/TagRenderer.java @@ -56,6 +56,14 @@ public class TagRenderer extends AbstractWrappedElementId @Nullable protected Set styleClasses; private String tag; + public static final String ARIA_LABEL = "aria-label"; + public static final String ARIA_CONTROL = "aria-controls"; + public static final String ARIA_EXPANDED = "aria-expanded"; + public static final String ARIA_HIDDEN = "aria-hidden"; + public static final String ARIA_REQUIRED = "aria-required"; + public static final String ARIA_LABELLEDBY = "aria-labelledby"; + public static final String ARIA_DESCRIBEDBY = "aria-describedby"; + public TagRenderer(String tag, TagState state) { super(state); this.tag = tag; @@ -273,6 +281,12 @@ protected Map prepareAttributes(SectionWriter writer) throws IOE attrs.put("class", sbuf.toString()); } attrs.put("style", getStyle()); + if (this.attrs != null) { + String roleAttr = (String) this.attrs.get("role"); + if (roleAttr != null) { + attrs.put("role", roleAttr); + } + } final Map allData = getData(); if (allData != null) { for (Entry d : allData.entrySet()) { @@ -288,6 +302,11 @@ protected Map prepareAttributes(SectionWriter writer) throws IOE } } prepareLastAttributes(writer, attrs); + + // Add aria-xxx attributes + if (tagState.getAccessibilityAttrs() != null) { + attrs.putAll(tagState.getAccessibilityAttrs()); + } return attrs; } diff --git a/Platform/Plugins/com.tle.web.sections/src/com/tle/web/sections/render/TagState.java b/Platform/Plugins/com.tle.web.sections/src/com/tle/web/sections/render/TagState.java index 425e41934d..d7dab3b02d 100644 --- a/Platform/Plugins/com.tle.web.sections/src/com/tle/web/sections/render/TagState.java +++ b/Platform/Plugins/com.tle.web.sections/src/com/tle/web/sections/render/TagState.java @@ -53,6 +53,7 @@ public class TagState extends AbstractWrappedElementId { private List preRenderables; private List processors; private final HandlerMap handlerMap = new HandlerMap(); + private Map accessibilityAttrs; public TagState() { super(new PageUniqueId()); @@ -116,6 +117,17 @@ public T getAttribute(Object key) { return (T) attrs.get(key); } + public void setAccessibilityAttr(String key, String value) { + if (accessibilityAttrs == null) { + accessibilityAttrs = new HashMap(); + } + accessibilityAttrs.put(key, value); + } + + public Map getAccessibilityAttrs() { + return accessibilityAttrs; + } + /** * Used for data-xxx attributes. They should always be rendered. * diff --git a/Source/Plugins/Core/com.equella.core/js/tsrc/mainui/ScreenOptions.tsx b/Source/Plugins/Core/com.equella.core/js/tsrc/mainui/ScreenOptions.tsx index 68c354801e..547a820fec 100644 --- a/Source/Plugins/Core/com.equella.core/js/tsrc/mainui/ScreenOptions.tsx +++ b/Source/Plugins/Core/com.equella.core/js/tsrc/mainui/ScreenOptions.tsx @@ -3,6 +3,7 @@ import { makeStyles } from "@material-ui/styles"; import { IconButton, Popover } from "@material-ui/core"; import MoreVertIcon from "@material-ui/icons/MoreVert"; import JQueryDiv from "../legacycontent/JQueryDiv"; +import { languageStrings } from "../util/langstrings"; const useStyles = makeStyles(t => ({ screenOptions: { @@ -22,6 +23,7 @@ export default React.memo(function ScreenOptions(props: { setOptionsAnchor(e.currentTarget)} + aria-label={languageStrings.screenoptions.description} > diff --git a/Source/Plugins/Core/com.equella.core/js/tsrc/mainui/Template.tsx b/Source/Plugins/Core/com.equella.core/js/tsrc/mainui/Template.tsx index f71853399f..76bab4ed6b 100644 --- a/Source/Plugins/Core/com.equella.core/js/tsrc/mainui/Template.tsx +++ b/Source/Plugins/Core/com.equella.core/js/tsrc/mainui/Template.tsx @@ -378,10 +378,11 @@ export const Template = React.memo(function Template(props: TemplateProps) { ); }} key={ind} + button={true} > {item.iconUrl ? ( - + {item.title} ) : ( {item.systemIcon ? item.systemIcon : "folder"} @@ -409,7 +410,7 @@ export const Template = React.memo(function Template(props: TemplateProps) { const menuContent = React.useMemo( () => (

- + {"Logo"} {hasMenu && (