Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
8d5baec
6871: Added screen list optimatizations
tuj Mar 26, 2026
ecb5928
6871: Added playlist list optimizations
tuj Mar 27, 2026
3f88af7
Merge branch 'release/3.0.0' into feature/list-optimizations
tuj Mar 30, 2026
0a47efd
6871: Added release.json generation to builds
tuj Mar 30, 2026
660246b
6871: Added release variables to GitHub workflows
tuj Mar 30, 2026
5c0ae1c
6871: Fixed releaseVersion value
tuj Mar 30, 2026
b6e8a79
6871: Added screen dto campaigns length
tuj Mar 30, 2026
631e7ef
6871: Fixed screen group list button
tuj Mar 31, 2026
968e0ac
6871: Fixed release loader to only fetch every 5 minutes and avoid mu…
tuj Mar 31, 2026
e44056c
6871: Updated API specification and generated api
tuj Mar 31, 2026
a9e48bb
6871: Added tests for API additions
tuj Mar 31, 2026
f669a57
6871: Fixed test
tuj Mar 31, 2026
8610bf3
6871: Fixed disabled button when no results
tuj Mar 31, 2026
c7ba7c6
6871: Moved campaign count logic to sql query
jekuaitk Apr 1, 2026
6aa6b4a
6871: Fixed campaigns button
tuj Apr 1, 2026
6e01f09
6871: Moved button code to separate files
tuj Apr 1, 2026
3d2b183
6871: Applied coding standards
tuj Apr 1, 2026
a47c9d4
6871: Generated API specification
tuj Apr 1, 2026
2d598a7
6871: Changed release values to be variables set in GitHub action
tuj Apr 2, 2026
7c860de
6871: Add "testCampaignsLengthViaScreenGroupAndDeduplication" to Scre…
turegjorup Apr 9, 2026
0f031ae
6871: Fixed bugs in releaseLoader
tuj Apr 9, 2026
fe42298
6871: Added extra lazy to all collections doctrine mappings
tuj Apr 9, 2026
820d749
6871: Changed to a testable class-based release loader and added tests
tuj Apr 10, 2026
eeab191
Merge branch 'feature/list-optimizations' into feature/extra-lazy-col…
tuj Apr 10, 2026
f48a607
6871: Removed fetch EXTRA_LAZY from ManyToOne associations
tuj Apr 10, 2026
85d937b
Merge pull request #373 from os2display/feature/extra-lazy-collections
tuj Apr 10, 2026
1ff2f23
6871: Refactored to use recursive functions to get all pages
tuj Apr 11, 2026
7bc36d2
Merge branch 'feature/list-optimizations' of github.com:os2display/di…
tuj Apr 11, 2026
8fd12a6
6871: Cleaned up getAllPages implementation and added tests
tuj Apr 11, 2026
0c2b112
6871: Applied coding standards
tuj Apr 11, 2026
d767735
6871: Fixed relations checksum test
tuj Apr 11, 2026
bfd69e7
6871: Added test for campaignsLength on ScreenGroup
turegjorup Apr 13, 2026
679147a
6871: Added tests for length fields on collection endpoints
turegjorup Apr 13, 2026
757daab
6871: Fixed unhandled promise rejections in CampaignsButton onClick
turegjorup Apr 13, 2026
a970672
6871: Applied coding standards
turegjorup Apr 13, 2026
7a4b507
Merge pull request #372 from os2display/feature/list-optimizations
tuj Apr 13, 2026
5c11225
Merge branch 'release/3.0.0' into feature/v3-fix-relations-checksum-l…
tuj Apr 13, 2026
1faaa13
Merge pull request #375 from os2display/feature/v3-fix-relations-chec…
tuj Apr 13, 2026
cf079df
6871: Fixed merge
tuj Apr 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion .github/workflows/docker_build_stg_images.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,20 @@ jobs:
with:
images: os2display/display-api-service

- name: Set release timestamp
run: |
echo "APP_RELEASE_TIMESTAMP=$(echo $(date +%s))" >> $GITHUB_ENV
echo "APP_RELEASE_TIME=$(echo $(date))" >> $GITHUB_ENV

- name: Build and push (API)
uses: docker/build-push-action@v6
with:
context: ./infrastructure/display-api-service/
file: ./infrastructure/display-api-service/Dockerfile
build-args: |
VERSION=${{ env.APP_VERSION }}
APP_VERSION=${{ env.APP_VERSION }}
APP_RELEASE_TIMESTAMP=${{ env.APP_RELEASE_TIMESTAMP }}
APP_RELEASE_TIME=${{ env.APP_RELEASE_TIME }}
push: true
tags: ${{ steps.meta-api.outputs.tags }}
labels: ${{ steps.meta-api.outputs.labels }}
Expand Down
19 changes: 19 additions & 0 deletions .github/workflows/github_build_release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,16 @@ jobs:
docker compose run --rm node npm install
docker compose run --rm node npm run build

- name: Set release timestamp
run: |
echo "APP_RELEASE_TIMESTAMP=$(echo $(date +%s))" >> $GITHUB_ENV
echo "APP_RELEASE_TIME=$(echo $(date))" >> $GITHUB_ENV

- name: Create release file
run: |
printf "{\n \"releaseTimestamp\": ${{ env.APP_RELEASE_TIMESTAMP }},\n \"releaseTime\": \"${{ env.APP_RELEASE_TIME }}\",\n \"releaseVersion\": \"${{ github.ref_name }}\"\n}" > public/release.json
cat public/release.json

- name: Cleanup after install
run: |
sudo chown -R runner:runner .
Expand Down Expand Up @@ -102,6 +112,11 @@ jobs:
tags: |
type=raw,value=${{ github.ref_name }}

- name: Set release timestamp
run: |
echo "APP_RELEASE_TIMESTAMP=$(echo $(date +%s))" >> $GITHUB_ENV
echo "APP_RELEASE_TIME=$(echo $(date))" >> $GITHUB_ENV

- name: Build and push Docker image (main)
id: push-main
uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4
Expand All @@ -110,6 +125,8 @@ jobs:
file: ./infrastructure/display-api-service/Dockerfile
build-args: |
APP_VERSION=${{ github.ref_name }}
APP_RELEASE_TIMESTAMP=${{ env.APP_RELEASE_TIMESTAMP }}
APP_RELEASE_TIME=${{ env.APP_RELEASE_TIME }}
push: true
tags: ${{ steps.meta-main.outputs.tags }}
labels: ${{ steps.meta-main.outputs.labels }}
Expand All @@ -132,6 +149,8 @@ jobs:
file: ./infrastructure/nginx/Dockerfile
build-args: |
APP_VERSION=${{ github.ref_name }}
APP_RELEASE_TIMESTAMP=${{ env.APP_RELEASE_TIMESTAMP }}
APP_RELEASE_TIME=${{ env.APP_RELEASE_TIME }}
APP_IMAGE=${{ env.IMAGE_NAME_MAIN }}
push: true
pull: true
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,5 @@ phpstan.neon
###> vincentlanglet/twig-cs-fixer ###
/.twig-cs-fixer.cache
###< vincentlanglet/twig-cs-fixer ###

.claude/
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ All notable changes to this project will be documented in this file.
- Added relations checksum feature flag.
- Fixes saving issues described in issue where saving resulted in infinite spinner.
- Fixed loading of routes containing null string values.
- Fixed release.json creation in v3.
- Fixed relations checksum test.
- Optimized release data fetching.
- Optimized list loading.

### NB! Prior to 3.x the project was split into separate repositories

Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -509,9 +509,11 @@ CLIENT_DEBUG=false
waiting for being activated in the administration.

**Default**: 20 s.
- CLIENT_REFRESH_TOKEN_TIMEOUT: How often (milliseconds) should it be checked whether the token needs to be refreshed?
- CLIENT_RELEASE_TIMESTAMP_INTERVAL_TIMEOUT: How often (milliseconds) should it be checked whether a new release is
available?
Value should not be lower than 5 minutes, since release.json is only fetched with a minimum of 5 minutes interval.

**Default**: 30 s.
**Default**: 10 m.
- CLIENT_REFRESH_TOKEN_TIMEOUT: How often (milliseconds) should it be checked whether the token needs to be refreshed?

**Default**: 60 s.
Expand Down
2 changes: 1 addition & 1 deletion Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ tasks:
desc: "Runs API tests (PHPUnit)."
cmds:
- task composer -- test-setup
- task compose -- exec --env SYMFONY_DEPRECATIONS_HELPER=disabled phpfpm composer test
- task compose -- exec --env SYMFONY_DEPRECATIONS_HELPER=disabled phpfpm vendor/bin/phpunit --stop-on-failure {{.CLI_ARGS}}

test:frontend-built:
desc: "Runs frontend tests (Playwright) on the built files. This temporarily stops the node container."
Expand Down
61 changes: 52 additions & 9 deletions assets/admin/components/groups/groups-columns.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,57 @@
import { useTranslation } from "react-i18next";
import ListButton from "../util/list/list-button";
import ColumnHoc from "../util/column-hoc";
import SelectColumnHoc from "../util/select-column-hoc";
import useModal from "../../context/modal-context/modal-context-hook.jsx";
import { useDispatch } from "react-redux";
import { enhancedApi } from "../../../shared/redux/enhanced-api.ts";
import idFromUrl from "../util/helpers/id-from-url.jsx";
import getAllPages from "../util/helpers/get-all-pages.js";
import { Link } from "react-router-dom";
import { Button } from "react-bootstrap";

function ScreensButton({ group }) {
const { t } = useTranslation("common", { keyPrefix: "groups-columns" });
const { setModal } = useModal();
const dispatch = useDispatch();

const onClick = () => {
getAllPages(dispatch, enhancedApi.endpoints.getV2ScreenGroupsByIdScreens, {
id: idFromUrl(group.id),
}).then((screens) => {
const content = (
<ul>
{screens.map((screen) => (
<li key={screen["@id"]}>
<Link
to={`screen/edit/${idFromUrl(screen["@id"])}`}
target="_blank"
>
{screen.title}
</Link>
</li>
))}
</ul>
);

setModal({
info: true,
modalTitle: t("screens-modal-title"),
content,
});
});
};

return (
<Button
variant="secondary"
type="button"
onClick={onClick}
disabled={group.screensLength === 0}
>
{group.screensLength}
</Button>
);
}

/**
* Columns for group lists.
Expand All @@ -18,14 +68,7 @@ function getGroupColumns({ apiCall, infoModalRedirect, infoModalTitle }) {
const columns = [
{
// eslint-disable-next-line react/prop-types
content: ({ screens }) => (
<ListButton
redirectTo={infoModalRedirect}
displayData={screens}
modalTitle={infoModalTitle}
apiCall={apiCall}
/>
),
content: (group) => <ScreensButton group={group} />,
key: "screens",
label: t("screens"),
},
Expand Down
84 changes: 54 additions & 30 deletions assets/admin/components/playlist/playlists-columns.jsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,59 @@
import { useContext } from "react";
import { useTranslation } from "react-i18next";
import ColumnHoc from "../util/column-hoc";
import SelectColumnHoc from "../util/select-column-hoc";
import UserContext from "../../context/user-context";
import ListButton from "../util/list/list-button";
import DateValue from "../util/date-value";
import PublishingStatus from "../util/publishingStatus";
import useModal from "../../context/modal-context/modal-context-hook.jsx";
import { useDispatch } from "react-redux";
import { enhancedApi } from "../../../shared/redux/enhanced-api.ts";
import idFromUrl from "../util/helpers/id-from-url.jsx";
import getAllPages from "../util/helpers/get-all-pages.js";
import { Link } from "react-router-dom";
import { Button } from "react-bootstrap";

function SlidesButton({ playlist }) {
const { t } = useTranslation("common", { keyPrefix: "playlists-columns" });
const { setModal } = useModal();
const dispatch = useDispatch();

const onClick = () => {
getAllPages(dispatch, enhancedApi.endpoints.getV2PlaylistsByIdSlides, {
id: idFromUrl(playlist.id),
}).then((playlistSlides) => {
const content = (
<ul>
{playlistSlides.map((playlistSlide) => (
<li key={playlistSlide?.slide["@id"]}>
<Link
to={`slide/edit/${idFromUrl(playlistSlide?.slide["@id"])}`}
target="_blank"
>
{playlistSlide?.slide.title}
</Link>
</li>
))}
</ul>
);

setModal({
info: true,
modalTitle: t("playlist-slide-modal-title"),
content,
});
});
};

return (
<Button
variant="secondary"
type="button"
onClick={onClick}
disabled={playlist.slidesLength === 0}
>
{playlist.slidesLength}
</Button>
);
}

/**
* Columns for playlists lists.
Expand All @@ -17,40 +65,16 @@ import PublishingStatus from "../util/publishingStatus";
* @param {string} props.dataKey The data key for mapping the data.
* @returns {object} The columns for the playlists lists.
*/
function getPlaylistColumns({
apiCall,
infoModalRedirect,
infoModalTitle,
dataKey,
}) {
const context = useContext(UserContext);
function getPlaylistColumns() {
const { t } = useTranslation("common", {
keyPrefix: "playlists-columns",
});

const columns = [
{
key: "slides",
key: "playlist",
label: t("number-of-slides"),
render: ({ tenants }) => {
return (
tenants?.length === 0 ||
!tenants.find(
(tenant) =>
tenant.tenantKey === context.selectedTenant.get.tenantKey,
)
);
},
// eslint-disable-next-line react/prop-types
content: ({ slides, playlistSlides }) => (
<ListButton
apiCall={apiCall}
redirectTo={infoModalRedirect}
displayData={slides || playlistSlides}
modalTitle={infoModalTitle}
dataKey={dataKey}
/>
),
content: (playlist) => <SlidesButton playlist={playlist} />,
},
{
key: "publishing-from",
Expand Down
5 changes: 0 additions & 5 deletions assets/admin/components/screen/screen-list.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import useModal from "../../context/modal-context/modal-context-hook";
import {
useGetV2ScreensQuery,
useDeleteV2ScreensByIdMutation,
useGetV2ScreensByIdScreenGroupsQuery,
} from "../../../shared/redux/enhanced-api.ts";
import {
displaySuccess,
Expand Down Expand Up @@ -128,10 +127,6 @@ function ScreenList() {

// The columns for the table.
const columns = ScreenColumns({
handleDelete,
apiCall: useGetV2ScreensByIdScreenGroupsQuery,
infoModalRedirect: "/group/edit",
infoModalTitle: t("info-modal.screen-in-groups"),
displayStatus: showScreenStatus,
});

Expand Down
7 changes: 2 additions & 5 deletions assets/admin/components/screen/screen-status.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { enhancedApi } from "../../../shared/redux/enhanced-api.ts";
import { displayError } from "../util/list/toast-component/display-toast";
import FormInput from "../util/forms/form-input";
import AdminConfigLoader from "../util/admin-config-loader.js";
import ReleaseLoader from "../../../shared/release-loader.js";

/**
* Displays screen status.
Expand Down Expand Up @@ -92,11 +93,7 @@ function ScreenStatus({ screen, handleInput = () => {}, mode = "default" }) {

useEffect(() => {
if (status) {
const now = dayjs().startOf("minute").valueOf();

fetch(`/release.json?ts=${now}`)
.then((res) => res.json())
.then((data) => setClientRelease(data));
ReleaseLoader.loadRelease().then((data) => setClientRelease(data));
}
}, [status]);

Expand Down
Loading
Loading