Compare commits

...

8 Commits

Author SHA1 Message Date
Rainer Killinger
c5c9bcf433 docs: update changelogs for release
ci: publish release
2025-07-30 12:44:30 +02:00
Rainer Killinger
b40ba7ad63 refactor: update elasticsearch and audit relevant dependencies 2025-07-30 12:40:59 +02:00
Jovan Krunić
362f6adf07 fix: use modal instead of menu inside of a split pane
Closes #234
2025-07-08 14:05:30 +02:00
Rainer Killinger
bbd6b0f874 fix: app missing chevron buttons in schedule module 2025-07-02 16:44:23 +02:00
Rainer Killinger
b1a9ba44d0 fix: app now requests backend version 4.0.0 2025-07-02 12:04:41 +02:00
Rainer Killinger
bee38d4a59 docs: update changelogs for release
ci: publish release
2025-07-01 16:29:42 +02:00
Rainer Killinger
a5c5c31d09 fix: app cypress e2e tests failing on core major version change 2025-07-01 15:33:27 +02:00
Rainer Killinger
c8e290200f refactor: update to node 22 2025-06-30 16:55:33 +02:00
113 changed files with 15137 additions and 14007 deletions

View File

@@ -1,5 +0,0 @@
---
"@openstapps/app": patch
---
Migrated IonIcons replacer to a custom component

View File

@@ -1,7 +0,0 @@
---
"@openstapps/app": patch
---
Migrated to Ionic standalone components
Angular ESBuild will not work with `IonicModule` apps

View File

@@ -1,5 +0,0 @@
---
"@openstapps/app": patch
---
Migrated to Angular ESBuild

3
.gitignore vendored
View File

@@ -98,7 +98,8 @@ typings/
# ignore ide files
.idea
.vscode
.vscode?*
!.vscode/launch.json
# ignore lib
lib

View File

@@ -21,13 +21,7 @@ include:
- local: /.gitlab/publishing.gitlab-ci.yml
variables:
TURBO_CACHE_BYPASS:
value: ''
options:
- '--force'
- '--no-cache'
- ''
description: Bypass turbo cache
TURBO_CACHE_BYPASS: "--force --no-cache"
default:
image: registry.gitlab.com/openstapps/openstapps/node-builder
@@ -36,10 +30,7 @@ default:
interruptible: true
before_script:
- corepack enable
- corepack prepare pnpm@latest-8 --activate
- echo TURBO_API=$TURBO_API >> .env.local
- echo TURBO_TOKEN=$TURBO_TOKEN >> .env.local
- echo TURBO_TEAM=$TURBO_TEAM >> .env.local
- corepack prepare pnpm@latest-10 --activate
- pnpm config set store-dir .pnpm-store
cache: &pnpm_cache
key:
@@ -63,7 +54,7 @@ build:
# - pnpm i --prefer-offline
# - pnpm build:full:skip || pnpm i -r --prefer-offline
- pnpm install
# TODO: whats the benifit for CI? something doesn't work with git here...
# TODO: whats the benefit for CI? something doesn't work with git here...
# "🦋 error Error: Failed to find where HEAD diverged from master. Does master exist?"
# - pnpm changeset:status
- pnpm build:full $TURBO_CACHE_BYPASS
@@ -105,13 +96,7 @@ unit:
stage: test
needs: ['build']
script:
# - pnpm config set recursive-install false
# - pnpm i --prefer-offline
# - pnpm test:skip || pnpm i -r --prefer-offline
- pnpm install
- cd node_modules/.pnpm/re2*/node_modules/re2
- npm run install
- cd $CI_PROJECT_DIR
- pnpm test $TURBO_CACHE_BYPASS
coverage: '/Statements[^:]*\:[^:]*\s+([\d\.]+)%/'
artifacts:

View File

@@ -27,7 +27,7 @@ publish image:
- tags
needs: ['deploy']
image:
name: gcr.io/kaniko-project/executor:v1.12.1-debug
name: gcr.io/kaniko-project/executor:v1.24.0-debug
entrypoint: [""]
script:
- >

View File

@@ -27,6 +27,7 @@ const config = {
'bin',
'files',
'engines',
'packageManager',
'scripts',
'dependencies',
'devDependencies',

View File

@@ -1,3 +1,3 @@
nodejs 18.19.1
pnpm 8.15.4
python 3.11.5
nodejs 22.16.0
pnpm 10.12.3
python 3.11.5

16
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,16 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type":"node",
"request": "launch",
"name": "Run mocha for nodejs workspace",
"runtimeExecutable": "${fileDirname}/../node_modules/.bin/c8",
"cwd": "${fileDirname}/../",
"runtimeArgs": ["--config", "${workspaceFolder}/.c8rc.json", "${fileDirname}/../node_modules/.bin/mocha", "--exit", "--reporter", "spec"]
},
]
}

View File

@@ -9,8 +9,8 @@ integration:
- name: registry.gitlab.com/openstapps/openstapps/database:latest
alias: elasticsearch
script:
- pnpm --filter=@openstapps/backend install
- pnpm test:integration:backend
- pnpm install
- pnpm test:integration:backend --force --no-cache
artifacts:
when: on_failure
paths:

View File

@@ -1,5 +1,25 @@
# @openstapps/backend
## 4.0.1
### Patch Changes
- b40ba7ad: Updated Elasticsearch dependency
- @openstapps/core@4.0.0
## 4.0.0
### Major Changes
- c8e29020: Update to Node 22
### Patch Changes
- Updated dependencies [c8e29020]
- @openstapps/core@4.0.0
- @openstapps/core-tools@4.0.0
- @openstapps/logger@4.0.0
## 3.3.2
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "@openstapps/backend",
"description": "A reference implementation for a StApps backend",
"version": "3.3.2",
"version": "4.0.1",
"private": true,
"type": "module",
"license": "AGPL-3.0-only",
@@ -43,29 +43,29 @@
"test:unit": "cross-env NODE_CONFIG_ENV=elasticsearch ALLOW_NO_TRANSPORT=true STAPPS_LOG_LEVEL=0 mocha --exit"
},
"dependencies": {
"@elastic/elasticsearch": "8.4.0",
"@elastic/elasticsearch": "8.14.1",
"@openstapps/core": "workspace:*",
"@openstapps/core-tools": "workspace:*",
"@openstapps/logger": "workspace:*",
"@types/body-parser": "1.19.2",
"@types/body-parser": "1.19.6",
"@types/cors": "2.8.13",
"@types/express": "4.17.17",
"@types/geojson": "1.0.6",
"@types/node": "18.15.3",
"@types/node": "22.15.31",
"@types/node-cron": "3.0.7",
"@types/nodemailer": "6.4.7",
"@types/promise-queue": "2.2.0",
"@types/uuid": "8.3.4",
"body-parser": "1.20.2",
"body-parser": "1.20.3",
"cors": "2.8.5",
"cosmiconfig": "8.1.3",
"deepmerge": "4.3.1",
"express": "4.18.2",
"express": "4.21.2",
"express-prom-bundle": "6.6.0",
"express-promise-router": "4.1.1",
"got": "12.6.0",
"moment": "2.30.1",
"morgan": "1.10.0",
"morgan": "1.10.1",
"nock": "13.3.1",
"node-cache": "5.1.2",
"node-cron": "3.0.2",
@@ -80,26 +80,26 @@
"@openstapps/prettier-config": "workspace:*",
"@openstapps/tsconfig": "workspace:*",
"@testdeck/mocha": "0.3.3",
"@types/chai": "4.3.5",
"@types/chai-as-promised": "7.1.5",
"@types/mocha": "10.0.1",
"@types/morgan": "1.9.4",
"@types/chai": "4.3.20",
"@types/chai-as-promised": "7.1.8",
"@types/mocha": "10.0.10",
"@types/morgan": "1.9.10",
"@types/sinon": "10.0.14",
"@types/sinon-express-mock": "1.3.9",
"@types/supertest": "2.0.12",
"c8": "7.14.0",
"chai": "4.3.7",
"chai-as-promised": "7.1.1",
"c8": "10.1.3",
"chai": "4.5.0",
"chai-as-promised": "7.1.2",
"cross-env": "7.0.3",
"get-port": "5.1.1",
"mocha": "10.2.0",
"mocha": "10.8.2",
"mocha-junit-reporter": "2.2.0",
"mocked-env": "1.3.5",
"sinon": "15.0.4",
"sinon-express-mock": "2.2.1",
"supertest": "6.3.3",
"ts-node": "10.9.2",
"tsup": "6.7.0",
"tsup": "8.5.0",
"typescript": "5.4.2"
},
"tsup": {

View File

@@ -25,7 +25,7 @@ import {expect} from 'chai';
import {bulk, DEFAULT_TEST_TIMEOUT} from '../common.js';
import {testApp} from '../tests-setup.js';
import {v4} from 'uuid';
import bookFile from '@openstapps/core/test/resources/indexable/Book.2.json' assert {type: 'json'};
import bookFile from '@openstapps/core/test/resources/indexable/Book.2.json' with {type: 'json'};
const book = bookFile.instance as SCBook;

View File

@@ -30,7 +30,7 @@ import chaiAsPromised from 'chai-as-promised';
import {DEFAULT_TEST_TIMEOUT} from '../common.js';
import {testApp} from '../tests-setup.js';
import {backendConfig} from '../../src/config.js';
import registerRequest from '@openstapps/core/test/resources/PluginRegisterRequest.1.json' assert {type: 'json'};
import registerRequest from '@openstapps/core/test/resources/PluginRegisterRequest.1.json' with {type: 'json'};
// for using promises in expectations (to.eventually.be...)
use(chaiAsPromised);

View File

@@ -18,7 +18,7 @@ import chaiAsPromised from 'chai-as-promised';
import {bulkStorageMock, DEFAULT_TEST_TIMEOUT} from '../common.js';
import {expect, use} from 'chai';
import {testApp} from '../tests-setup.js';
import bookFile from '@openstapps/core/test/resources/indexable/Book.1.json' assert {type: 'json'};
import bookFile from '@openstapps/core/test/resources/indexable/Book.1.json' with {type: 'json'};
use(chaiAsPromised);

View File

@@ -49,8 +49,8 @@ import {
} from '../../../src/storage/elasticsearch/util/index.js';
import cron from 'node-cron';
import {query} from './query.js';
import messageFile from '@openstapps/core/test/resources/indexable/Message.1.json' assert {type: 'json'};
import bookFile from '@openstapps/core/test/resources/indexable/Book.1.json' assert {type: 'json'};
import messageFile from '@openstapps/core/test/resources/indexable/Message.1.json' with {type: 'json'};
import bookFile from '@openstapps/core/test/resources/indexable/Book.1.json' with {type: 'json'};
const message = messageFile.instance as SCMessage;
const book = bookFile.instance as SCBook;

View File

@@ -1,5 +1,17 @@
# @openstapps/database
## 4.0.1
### Patch Changes
- b40ba7ad: Updated Elasticsearch dependency
## 4.0.0
### Major Changes
- c8e29020: Update to Node 22
## 3.2.0
### Patch Changes

View File

@@ -1,4 +1,4 @@
FROM elasticsearch:8.4.2
FROM elasticsearch:8.14.3
EXPOSE 9200
EXPOSE 9300

View File

@@ -1,6 +1,6 @@
{
"name": "@openstapps/database",
"version": "3.2.0",
"version": "4.0.1",
"private": true,
"files": [
"config",

View File

@@ -1,5 +1,16 @@
# @openstapps/proxy
## 4.0.0
### Major Changes
- c8e29020: Update to Node 22
### Patch Changes
- Updated dependencies [c8e29020]
- @openstapps/logger@4.0.0
## 3.0.0
### Major Changes

View File

@@ -1,7 +1,7 @@
{
"name": "@openstapps/proxy",
"description": "NGINX proxy that is dynamically configured by a Node.js script",
"version": "3.0.0",
"version": "4.0.0",
"private": true,
"type": "module",
"license": "AGPL-3.0-only",
@@ -44,7 +44,7 @@
"@openstapps/logger": "workspace:*",
"@types/config": "3.3.0",
"@types/dockerode": "3.3.17",
"@types/node": "18.15.3",
"@types/node": "22.15.31",
"@types/sha1": "1.1.3",
"config": "3.3.9",
"dockerode": "3.3.5",
@@ -58,25 +58,25 @@
"@openstapps/eslint-config": "workspace:*",
"@openstapps/prettier-config": "workspace:*",
"@openstapps/tsconfig": "workspace:*",
"@types/chai": "4.3.5",
"@types/chai": "4.3.20",
"@types/config": "3.3.0",
"@types/dockerode": "3.3.17",
"@types/mocha": "10.0.1",
"@types/mocha": "10.0.10",
"@types/mustache": "4.2.2",
"@types/node": "18.15.3",
"@types/node": "22.15.31",
"@types/proxyquire": "1.3.28",
"@types/semver": "7.5.8",
"@types/sha1": "1.1.3",
"@types/sinon": "10.0.14",
"@types/sinon-chai": "3.2.9",
"c8": "7.14.0",
"chai": "4.3.7",
"mocha": "10.2.0",
"c8": "10.1.3",
"chai": "4.5.0",
"mocha": "10.8.2",
"mocha-junit-reporter": "2.2.0",
"sinon": "15.0.4",
"sinon-chai": "3.7.0",
"ts-node": "10.9.2",
"tsup": "6.7.0"
"tsup": "8.5.0"
},
"tsup": {
"entry": [

View File

@@ -1,5 +1,11 @@
# @openstapps/backend-config
## 4.0.0
### Major Changes
- c8e29020: Update to Node 22
## 3.0.0
### Major Changes
@@ -30,7 +36,7 @@
```js
#!/usr/bin/env node
import './lib/app.js';
import "./lib/app.js";
```
- 64caebaf: Migrate to ESM
@@ -105,7 +111,7 @@
```js
#!/usr/bin/env node
import './lib/app.js';
import "./lib/app.js";
```
- 64caebaf: Migrate to ESM

View File

@@ -1,7 +1,7 @@
{
"name": "@openstapps/backend-config",
"description": "Backend Configuration for OpenStApps",
"version": "3.0.0",
"version": "4.0.0",
"private": true,
"type": "module",
"license": "GPL-3.0-only",

View File

@@ -1,5 +1,11 @@
# @openstapps/eslint-config
## 4.0.0
### Major Changes
- c8e29020: Update to Node 22
## 3.0.0
### Major Changes
@@ -30,7 +36,7 @@
```js
#!/usr/bin/env node
import './lib/app.js';
import "./lib/app.js";
```
- 64caebaf: Migrate to ESM
@@ -75,11 +81,14 @@
- 64caebaf: Migrated changelogs to changeset format
```js
import fs from 'fs';
import fs from "fs";
const path = 'packages/logger/CHANGELOG.md';
const path = "packages/logger/CHANGELOG.md";
fs.writeFileSync(path, fs.readFileSync(path, 'utf8').replace(/^#+\s+\[/gm, '## ['));
fs.writeFileSync(
path,
fs.readFileSync(path, "utf8").replace(/^#+\s+\[/gm, "## ["),
);
```
- 98546a97: Migrate away from @openstapps/configuration
@@ -122,7 +131,7 @@
```js
#!/usr/bin/env node
import './lib/app.js';
import "./lib/app.js";
```
- 64caebaf: Migrate to ESM
@@ -167,11 +176,14 @@
- 64caebaf: Migrated changelogs to changeset format
```js
import fs from 'fs';
import fs from "fs";
const path = 'packages/logger/CHANGELOG.md';
const path = "packages/logger/CHANGELOG.md";
fs.writeFileSync(path, fs.readFileSync(path, 'utf8').replace(/^#+\s+\[/gm, '## ['));
fs.writeFileSync(
path,
fs.readFileSync(path, "utf8").replace(/^#+\s+\[/gm, "## ["),
);
```
- 98546a97: Migrate away from @openstapps/configuration

View File

@@ -1,7 +1,7 @@
{
"name": "@openstapps/eslint-config",
"description": "A collection of configuration base files for StApps projects. Just an (unused) experiment for now.",
"version": "3.0.0",
"version": "4.0.0",
"type": "commonjs",
"license": "GPL-3.0-only",
"repository": "git@gitlab.com:openstapps/eslint-config.git",
@@ -17,7 +17,7 @@
],
"devDependencies": {
"@openstapps/tsconfig": "workspace:*",
"@types/node": "18.15.3",
"@types/node": "22.15.31",
"eslint": "8.57.0",
"typescript": "5.4.2"
},

View File

@@ -1,5 +1,11 @@
# @openstapps/prettier-config
## 4.0.0
### Major Changes
- c8e29020: Update to Node 22
## 3.2.0
### Patch Changes

View File

@@ -12,4 +12,4 @@ const config = {
endOfLine: 'lf'
}
export default config;
module.exports = config;

View File

@@ -1,8 +1,8 @@
{
"name": "@openstapps/prettier-config",
"description": "StApps Prettier Config",
"version": "3.2.0",
"type": "module",
"version": "4.0.0",
"type": "commonjs",
"license": "GPL-3.0-only",
"repository": "git@gitlab.com:openstapps/prettier-config.git",
"author": "Thea Schöbl <dev@theaninova.de>",

View File

@@ -1,5 +1,24 @@
# @openstapps/projectmanagement
## 4.0.1
### Patch Changes
- b40ba7ad: Updated Elasticsearch dependency
## 4.0.0
### Major Changes
- c8e29020: Update to Node 22
### Patch Changes
- Updated dependencies [c8e29020]
- @openstapps/collection-utils@4.0.0
- @openstapps/gitlab-api@4.0.0
- @openstapps/logger@4.0.0
## 3.0.0
### Major Changes

View File

@@ -1,7 +1,7 @@
{
"name": "@openstapps/projectmanagement",
"description": "Main documentation and scripts for maintenance.",
"version": "3.0.0",
"version": "4.0.1",
"private": true,
"type": "module",
"license": "GPL-3.0-only",
@@ -41,7 +41,6 @@
"@openstapps/collection-utils": "workspace:*",
"@openstapps/gitlab-api": "workspace:*",
"@openstapps/logger": "workspace:*",
"@slack/web-api": "6.8.1",
"commander": "10.0.0",
"date-fns": "3.6.0",
"glob": "10.3.10",
@@ -51,20 +50,20 @@
"@openstapps/eslint-config": "workspace:*",
"@openstapps/prettier-config": "workspace:*",
"@openstapps/tsconfig": "workspace:*",
"@types/chai": "4.3.5",
"@types/chai-as-promised": "7.1.5",
"@types/chai": "4.3.20",
"@types/chai-as-promised": "7.1.8",
"@types/glob": "8.1.0",
"@types/mocha": "10.0.1",
"@types/mocha": "10.0.10",
"@types/mustache": "4.2.2",
"@types/node": "18.15.3",
"@types/node": "22.15.31",
"@types/tmp": "0.2.3",
"c8": "7.14.0",
"chai": "4.3.7",
"chai-as-promised": "7.1.1",
"mocha": "10.2.0",
"c8": "10.1.3",
"chai": "4.5.0",
"chai-as-promised": "7.1.2",
"mocha": "10.8.2",
"mocha-junit-reporter": "2.2.0",
"ts-node": "10.9.2",
"tsup": "6.7.0",
"tsup": "8.5.0",
"typescript": "5.4.2"
},
"tsup": {

View File

@@ -22,7 +22,6 @@ import {
User,
} from '@openstapps/gitlab-api';
import {Logger} from '@openstapps/logger';
import {WebClient} from '@slack/web-api';
import {CONCURRENCY, GROUPS, MAX_DEPTH_FOR_REMINDER, NOTE_PREFIX, SLACK_CHANNEL} from '../configuration.js';
import {mapAsyncLimit} from '@openstapps/collection-utils';
@@ -53,8 +52,7 @@ export async function remind(api: Api): Promise<void> {
Logger.info(`Found ${mergeRequests.length} open merge requests.`);
// instantiate slack client
const client =
process.env.SLACK_API_TOKEN === undefined ? undefined : new WebClient(process.env.SLACK_API_TOKEN);
const client = undefined;
// get members of the main group
const members = await api.getMembers(MembershipScope.GROUPS, GROUPS[0]);

View File

@@ -1,5 +1,11 @@
# @openstapps/tsconfig
## 4.0.0
### Major Changes
- c8e29020: Update to Node 22
## 3.3.0
### Minor Changes

View File

@@ -1,7 +1,7 @@
{
"name": "@openstapps/tsconfig",
"description": "The tsconfig for the openstapps project",
"version": "3.3.0",
"version": "4.0.0",
"type": "commonjs",
"license": "GPL-3.0-only",
"repository": "git@gitlab.com:openstapps/eslint-config.git",

View File

@@ -9,7 +9,7 @@ const args = files.map(it => `${it.split('/', 2)[1]}='${it}'`);
console.log(
'Collecting coverage...',
await promisify(exec)(`cobertura-merge -o ./coverage.xml ${args.join(' ')}`),
await promisify(exec)(`merge-cobertura -o ./coverage.xml ${args.join(' ')}`),
);
const reportFiles = await glob('./*/*/coverage/report-junit.xml');

View File

@@ -1,5 +1,26 @@
# @openstapps/minimal-connector
## 4.0.1
### Patch Changes
- Updated dependencies [b40ba7ad]
- @openstapps/api@4.0.1
- @openstapps/core@4.0.0
## 4.0.0
### Major Changes
- c8e29020: Update to Node 22
### Patch Changes
- Updated dependencies [c8e29020]
- @openstapps/api@4.0.0
- @openstapps/core@4.0.0
- @openstapps/logger@4.0.0
## 3.3.2
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "@openstapps/minimal-connector",
"description": "This is a minimal connector which serves as an example",
"version": "3.3.2",
"version": "4.0.1",
"private": true,
"type": "module",
"license": "GPL-3.0-only",
@@ -42,19 +42,19 @@
"@openstapps/eslint-config": "workspace:*",
"@openstapps/prettier-config": "workspace:*",
"@openstapps/tsconfig": "workspace:*",
"@types/chai": "4.3.5",
"@types/chai-as-promised": "7.1.5",
"@types/mocha": "10.0.1",
"@types/node": "18.15.3",
"c8": "7.14.0",
"chai": "4.3.7",
"chai-as-promised": "7.1.1",
"@types/chai": "4.3.20",
"@types/chai-as-promised": "7.1.8",
"@types/mocha": "10.0.10",
"@types/node": "22.15.31",
"c8": "10.1.3",
"chai": "4.5.0",
"chai-as-promised": "7.1.2",
"conventional-changelog-cli": "2.2.2",
"mocha": "10.2.0",
"mocha": "10.8.2",
"mocha-junit-reporter": "2.2.0",
"nock": "13.3.1",
"ts-node": "10.9.2",
"tsup": "6.7.0",
"tsup": "8.5.0",
"typescript": "5.4.2"
},
"tsup": {

View File

@@ -1,5 +1,17 @@
# @openstapps/minimal-deployment
## 4.0.1
### Patch Changes
- b40ba7ad: Updated Elasticsearch dependency
## 4.0.0
### Major Changes
- c8e29020: Update to Node 22
## 3.0.0
### Patch Changes

View File

@@ -1,23 +1,21 @@
version: '3.7'
x-development-variables: &development-variables
NODE_ENV: "development"
ALLOW_NO_TRANSPORT: "true"
services:
database:
image: registry.gitlab.com/openstapps/openstapps/database:3.0.0
image: registry.gitlab.com/openstapps/openstapps/database:4.0.1
# If you need persistence for debugging purposes uncomment the following lines
#volumes:
# volumes:
# - ./database:/usr/share/elasticsearch/data
expose:
- 9200
ports:
- 127.0.0.1:9200:9200
environment:
- bootstrap.memory_lock=true
- "ES_JAVA_OPTS=-Xms2g -Xmx2g"
- discovery.type=single-node
# - bootstrap.memory_lock=true
# - discovery.type=single-node
- "ES_JAVA_OPTS=-Xms4g -Xmx4g"
ulimits:
memlock:
soft: -1
@@ -25,7 +23,7 @@ services:
restart: unless-stopped
backend:
image: registry.gitlab.com/openstapps/openstapps/backend:3.1.0
image: registry.gitlab.com/openstapps/openstapps/backend:4.0.1
environment:
<<: *development-variables
ES_ADDR: "http://database:9200"
@@ -37,10 +35,11 @@ services:
ports:
- 127.0.0.1:3000:3000
labels:
- stapps.version=4.1.0
- stapps.version=4.0.0
restart: unless-stopped
depends_on:
- database
database:
condition: service_healthy
links:
- database

View File

@@ -1,6 +1,6 @@
{
"name": "@openstapps/minimal-deployment",
"version": "3.0.0",
"version": "4.0.1",
"private": true,
"files": [
"database",

View File

@@ -1,5 +1,30 @@
# @openstapps/minimal-plugin
## 4.0.1
### Patch Changes
- b40ba7ad: Updated Elasticsearch dependency
- Updated dependencies [b40ba7ad]
- @openstapps/api-plugin@4.0.1
- @openstapps/api@4.0.1
- @openstapps/core@4.0.0
## 4.0.0
### Major Changes
- c8e29020: Update to Node 22
### Patch Changes
- Updated dependencies [c8e29020]
- @openstapps/api@4.0.0
- @openstapps/api-plugin@4.0.0
- @openstapps/core@4.0.0
- @openstapps/core-tools@4.0.0
- @openstapps/logger@4.0.0
## 3.3.2
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "@openstapps/minimal-plugin",
"description": "Minimal Plugin",
"version": "3.3.2",
"version": "4.0.1",
"private": true,
"type": "module",
"license": "GPL-3.0-only",
@@ -34,7 +34,7 @@
"@openstapps/core-tools": "workspace:*",
"@openstapps/logger": "workspace:*",
"commander": "10.0.0",
"express": "4.18.2",
"express": "4.21.2",
"ts-node": "10.9.2"
},
"devDependencies": {
@@ -42,8 +42,8 @@
"@openstapps/prettier-config": "workspace:*",
"@openstapps/tsconfig": "workspace:*",
"@types/express": "4.17.17",
"@types/node": "18.15.3",
"tsup": "6.7.0",
"@types/node": "22.15.31",
"tsup": "8.5.0",
"typescript": "5.4.2"
},
"tsup": {

12
flake.lock generated
View File

@@ -5,11 +5,11 @@
"systems": "systems"
},
"locked": {
"lastModified": 1709126324,
"narHash": "sha256-q6EQdSeUZOG26WelxqkmR7kArjgWCdw5sfJVHPH/7j8=",
"lastModified": 1710146030,
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "d465f4819400de7c8d874d50b982301f28a84605",
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
"type": "github"
},
"original": {
@@ -20,11 +20,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1709747860,
"narHash": "sha256-RT4zuBy579m+l8VyIQFOR66WXfcs4g1jntZUHjh6eoI=",
"lastModified": 1717112898,
"narHash": "sha256-7R2ZvOnvd9h8fDd65p0JnB7wXfUvreox3xFdYWd1BnY=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "58ae79ea707579c40102ddf62d84b902a987c58b",
"rev": "6132b0f6e344ce2fe34fc051b72fb46e34f668e0",
"type": "github"
},
"original": {

View File

@@ -11,7 +11,7 @@
flake-utils,
}:
let
aapt2buildToolsVersion = "33.0.2";
aapt2buildToolsVersion = "34.0.0";
in
flake-utils.lib.eachDefaultSystem (
system:
@@ -19,7 +19,7 @@
pkgs = import nixpkgs {
inherit system;
overlays = [
(final: prev: rec {
(final: prev: {
fontMin = prev.python311.withPackages (
ps:
with ps;
@@ -30,21 +30,20 @@
++ (with fonttools.optional-dependencies; [ woff ])
);
android = prev.androidenv.composeAndroidPackages {
buildToolsVersions = [
buildToolsVersions = prev.lib.lists.unique [
"34.0.0"
aapt2buildToolsVersion
];
platformVersions = [ "34" ];
};
cypress = prev.cypress.overrideAttrs (cyPrev: rec {
version = "13.2.0";
version = "13.10.0";
src = prev.fetchzip {
url = "https://cdn.cypress.io/desktop/${version}/linux-x64/cypress.zip";
hash = "sha256-9o0nprGcJhudS1LNm+T7Vf0Dwd1RBauYKI+w1FBQ3ZM=";
hash = "sha256-wKNXo2lWndsQaouOiul0rsOWah+RRQ6fljzdeC8/KW4=";
};
});
nodejs = prev.nodejs_18;
corepack = prev.corepack_18;
nodejs = prev.nodejs_22;
})
];
config = {
@@ -54,7 +53,6 @@
};
androidFhs = pkgs.buildFHSUserEnv {
name = "android-env";
targetPkgs = pkgs: with pkgs; [ ];
runScript = "bash";
profile = ''
export ALLOW_NINJA_ENV=true
@@ -68,7 +66,7 @@
nativeBuildInputs = [ androidFhs ];
buildInputs = with pkgs; [
nodejs
corepack
nodePackages.pnpm
# tools
curl
jq
@@ -83,6 +81,8 @@
GRADLE_OPTS = "-Dorg.gradle.project.android.aapt2FromMavenOverride=${ANDROID_SDK_ROOT}/build-tools/${aapt2buildToolsVersion}/aapt2";
CYPRESS_INSTALL_BINARY = "0";
CYPRESS_RUN_BINARY = "${pkgs.cypress}/bin/Cypress";
# https://github.com/nodejs/node/issues/48444#issuecomment-1591882694
UV_USE_IO_URING = "0";
};
}
);

View File

@@ -1,13 +1,10 @@
e2e:
image: registry.gitlab.com/openstapps/openstapps/app-cypress:node-18
image: registry.gitlab.com/openstapps/openstapps/app-cypress
stage: test
script:
- pnpm --filter=@openstapps/app install
- pnpm install
- pnpm --filter=@openstapps/app exec cypress install
- cd node_modules/.pnpm/re2*/node_modules/re2
- npm run install
- cd $CI_PROJECT_DIR
- pnpm test:integration:app
- pnpm test:integration:app --force --no-cache
artifacts:
when: on_failure
paths:

View File

@@ -1,5 +1,33 @@
# @openstapps/app
## 4.0.1
### Patch Changes
- b40ba7ad: Updated Elasticsearch dependency
- Updated dependencies [b40ba7ad]
- @openstapps/api@4.0.1
- @openstapps/core@4.0.0
## 4.0.0
### Major Changes
- c8e29020: Update to Node 22
### Patch Changes
- b8ac30b9: Migrated IonIcons replacer to a custom component
- b8ac30b9: Migrated to Ionic standalone components
Angular ESBuild will not work with `IonicModule` apps
- b8ac30b9: Migrated to Angular ESBuild
- Updated dependencies [c8e29020]
- @openstapps/api@4.0.0
- @openstapps/collection-utils@4.0.0
- @openstapps/core@4.0.0
## 3.3.5
### Patch Changes

View File

@@ -22,6 +22,7 @@ export default defineConfig({
mochaFile: 'coverage/integration-report-junit-[hash].xml',
},
baseUrl: 'http://localhost:4200',
injectDocumentDomain: true,
supportFile: 'cypress/support/index.ts',
videosFolder: 'cypress/videos',
screenshotsFolder: 'cypress/screenshots',

View File

@@ -14,6 +14,8 @@
*/
describe('context menu', function () {
const contextMenuSelector = 'stapps-context-menu-modal';
beforeEach(function () {
cy.interceptSearch({
extends: {query: 'a'},
@@ -33,21 +35,21 @@ describe('context menu', function () {
});
it('should sort', function () {
cy.get('stapps-context').within(() => {
cy.get(contextMenuSelector).within(() => {
cy.contains('ion-item', 'Name').click();
cy.wait('@search');
});
});
it('should filter', function () {
cy.get('stapps-context').within(() => {
cy.get(contextMenuSelector).within(() => {
cy.contains('ion-item', '(17) Akademische Veranstaltung').click();
cy.wait('@search');
});
});
it('should have a working delete button', function () {
cy.get('stapps-context').within(() => {
cy.get(contextMenuSelector).within(() => {
cy.contains('ion-item', '(17) Akademische Veranstaltung').click();
cy.get('.checkbox-checked').should('have.length', 1);
@@ -60,7 +62,7 @@ describe('context menu', function () {
it('should truncate long category items', function () {
cy.contains('ion-list', 'Akademische Veranstaltung / Kategorien').within(() => {
cy.contains('ion-item', '(1) Tutorium').should('not.exist');
cy.get('div > ion-button').click();
cy.get('ion-button').click();
cy.contains('ion-item', '(1) Tutorium').should('exist');
});
});

View File

@@ -16,7 +16,8 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import equal from 'fast-deep-equal';
import {extendsDeepEqual} from '@openstapps/collection-utils';
import {SCSearchRequest, SCSearchResponse} from '@openstapps/core';
import {CORE_VERSION, SCIndexResponse, SCSearchRequest, SCSearchResponse} from '@openstapps/core';
import * as defaultConfig from '../../fixtures/config/default-config.json';
type InterceptArguments = {
fixture?: string | SCSearchResponse | ((request: SCSearchRequest) => SCSearchResponse);
@@ -34,7 +35,12 @@ type InterceptArguments = {
*
*/
export function interceptConfig(config?: string) {
cy.intercept({url: '/', method: 'POST'}, {fixture: config || 'config/default-config.json'}).as('config');
let localConfig: SCIndexResponse = defaultConfig as unknown as SCIndexResponse;
localConfig.backend.SCVersion = CORE_VERSION;
cy.intercept(
{url: '/', method: 'POST'},
config ? {fixture: config} : {body: JSON.stringify(localConfig)},
).as('config');
}
/**

View File

@@ -1,11 +1,11 @@
#!/usr/bin/env bash
rm coverage/integration-report-junit-*.xml
rm coverage/integration-report-junit-*.xml || true
ng e2e --watch=false --headless=true --browser="$BROWSER"
exit_code=$?
jrm coverage/integration-report-junit.xml coverage/integration-report-junit-*.xml
rm coverage/integration-report-junit-*.xml
jrm coverage/integration-report-junit.xml coverage/integration-report-junit-*.xml || true
rm coverage/integration-report-junit-*.xml || true
exit $exit_code

View File

@@ -1,7 +1,7 @@
{
"name": "@openstapps/app",
"description": "The generic app tailored to fulfill needs of German universities, written using Ionic Framework.",
"version": "3.3.5",
"version": "4.0.1",
"private": true,
"license": "GPL-3.0-only",
"author": "Karl-Philipp Wulfert <krlwlfrt@gmail.com>",
@@ -14,7 +14,7 @@
"Thea Schöbl <dev@theaninova.de>"
],
"scripts": {
"build": "pnpm check-icons && ng build --configuration=production",
"build": "pnpm check-icons && ng build --configuration=production --stats-json",
"build:android": "ionic capacitor build android --no-open && cd android && ./gradlew clean assemble && cd ..",
"build:prod": "ng build --configuration=production",
"changelog": "conventional-changelog -p angular -i src/assets/about/CHANGELOG.md -s -r 0",
@@ -36,7 +36,6 @@
"lint": "ng lint && stylelint \"**/*.scss\"",
"lint:fix": "eslint --fix -c .eslintrc.json --ignore-path .eslintignore --ext .ts,.html src/ && stylelint --fix \"**/*.scss\"",
"minify-icons": "node scripts/minify-icon-font.mjs",
"postinstall": "jetify && echo \"skipping jetify in production mode\"",
"preview": "http-server www/browser --p 8101 -o",
"push": "git push && git push origin \"v$npm_package_version\"",
"resources:ios": "capacitor-assets generate --ios --iconBackgroundColor $(grep -oE \"^@include ion-color\\(primary, #[a-fA-F0-9]{3,6}\" src/theme/colors.scss | grep -oE \"#[a-fA-F0-9]{3,6}\") --splashBackgroundColor $(grep -oE \"^@include ion-color\\(primary, #[a-fA-F0-9]{3,6}\" src/theme/colors.scss | grep -oE \"#[a-fA-F0-9]{3,6}\")",
@@ -92,7 +91,7 @@
"cordova-plugin-calendar": "5.1.6",
"date-fns": "3.6.0",
"deepmerge": "4.3.1",
"form-data": "4.0.0",
"form-data": "4.0.4",
"geojson": "0.5.0",
"ionic-appauth": "0.9.0",
"ionicons": "7.4.0",
@@ -102,10 +101,11 @@
"moment": "2.30.1",
"ngx-date-fns": "11.0.0",
"ngx-logger": "5.0.12",
"ngx-markdown": "17.1.1",
"ngx-markdown": "17.2.1",
"ngx-moment": "6.0.2",
"opening_hours": "3.8.0",
"pmtiles": "3.0.3",
"prettier": "3.1.1",
"rxjs": "7.8.1",
"semver": "7.6.0",
"swiper": "8.4.5",
@@ -133,7 +133,7 @@
"@capacitor/cli": "6.2.1",
"@capacitor/ios": "6.2.1",
"@compodoc/compodoc": "1.1.23",
"@cypress/schematic": "2.5.1",
"@cypress/schematic": "3.0.0",
"@ionic/angular-toolkit": "11.0.1",
"@ionic/cli": "7.2.0",
"@openstapps/prettier-config": "workspace:*",
@@ -147,12 +147,12 @@
"@types/karma": "6.3.8",
"@types/karma-coverage": "2.0.3",
"@types/karma-jasmine": "4.0.5",
"@types/node": "18.15.3",
"@types/node": "22.15.31",
"@types/semver": "7.5.8",
"@typescript-eslint/eslint-plugin": "7.2.0",
"@typescript-eslint/parser": "7.2.0",
"cordova-res": "0.15.4",
"cypress": "13.7.0",
"cypress": "14.5.0",
"eslint": "8.57.0",
"eslint-plugin-jsdoc": "48.2.1",
"eslint-plugin-prettier": "5.1.3",
@@ -164,7 +164,6 @@
"is-docker": "2.2.1",
"jasmine-core": "5.1.2",
"jasmine-spec-reporter": "7.0.0",
"jetifier": "2.0.0",
"junit-report-merger": "6.0.3",
"karma": "6.4.3",
"karma-chrome-launcher": "3.2.0",

View File

@@ -21,7 +21,7 @@ import {ModalController, Platform} from '@ionic/angular/standalone';
import {TranslateService} from '@ngx-translate/core';
import {ThingTranslateService} from './translation/thing-translate.service';
import {HttpClientTestingModule} from '@angular/common/http/testing';
import {provideHttpClientTesting} from '@angular/common/http/testing';
import {AppComponent} from './app.component';
import {AuthModule} from './modules/auth/auth.module';
import {ConfigProvider} from './modules/config/config.provider';
@@ -32,6 +32,7 @@ import {ScheduleSyncService} from './modules/background/schedule/schedule-sync.s
import {sampleAuthConfiguration} from './_helpers/data/sample-configuration';
import {StorageProvider} from './modules/storage/storage.provider';
import {SimpleBrowser} from './util/browser.factory';
import {provideHttpClient, withInterceptorsFromDi} from '@angular/common/http';
describe('AppComponent', () => {
let platformReadySpy: any;
@@ -75,8 +76,9 @@ describe('AppComponent', () => {
modalController = jasmine.createSpyObj('ModalController', ['create', 'dismiss', 'getTop']);
TestBed.configureTestingModule({
imports: [RouterTestingModule.withRoutes([]), HttpClientTestingModule, AuthModule],
declarations: [AppComponent],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
imports: [RouterTestingModule.withRoutes([]), AuthModule],
providers: [
{provide: Platform, useValue: platformSpy},
{provide: TranslateService, useValue: translateServiceSpy},
@@ -88,8 +90,9 @@ describe('AppComponent', () => {
{provide: StorageProvider, useValue: storageProvider},
{provide: SimpleBrowser, useValue: simpleBrowser},
{provide: ModalController, useValue: modalController},
provideHttpClient(withInterceptorsFromDi()),
provideHttpClientTesting(),
],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
}).compileComponents();
});

View File

@@ -13,7 +13,7 @@
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {CommonModule, LocationStrategy, PathLocationStrategy, registerLocaleData} from '@angular/common';
import {HTTP_INTERCEPTORS, HttpClient, HttpClientModule} from '@angular/common/http';
import {HTTP_INTERCEPTORS, HttpClient, provideHttpClient, withInterceptorsFromDi} from '@angular/common/http';
import localeDe from '@angular/common/locales/de';
import {APP_INITIALIZER, NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
@@ -158,7 +158,6 @@ export function createTranslateLoader(http: HttpClient) {
JobModule,
FavoritesModule,
LibraryModule,
HttpClientModule,
ProfilePageModule,
FeedbackModule,
MapModule,
@@ -220,6 +219,7 @@ export function createTranslateLoader(http: HttpClient) {
multi: true,
},
provideIonicAngular(),
provideHttpClient(withInterceptorsFromDi()),
],
})
export class AppModule {

View File

@@ -22,7 +22,7 @@ import {Requestor, StorageBackend} from '@openid/appauth';
import {TranslateService} from '@ngx-translate/core';
import {PAIAAuthService} from './paia/paia-auth.service';
import {StAppsWebHttpClient} from '../data/stapps-web-http-client.provider';
import {HttpClientModule} from '@angular/common/http';
import {provideHttpClient, withInterceptorsFromDi} from '@angular/common/http';
import {SimpleBrowser} from '../../util/browser.factory';
import {LoggerTestingModule} from 'ngx-logger/testing';
@@ -54,7 +54,7 @@ describe('AuthHelperService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientModule, LoggerTestingModule],
imports: [LoggerTestingModule],
providers: [
StAppsWebHttpClient,
{
@@ -88,6 +88,7 @@ describe('AuthHelperService', () => {
provide: SimpleBrowser,
useValue: simpleBrowserMock,
},
provideHttpClient(withInterceptorsFromDi()),
],
});
authHelperService = TestBed.inject(AuthHelperService);

View File

@@ -20,7 +20,7 @@ import {Browser} from 'ionic-appauth';
import {nowInSeconds, Requestor, StorageBackend} from '@openid/appauth';
import {TranslateService} from '@ngx-translate/core';
import {StAppsWebHttpClient} from '../data/stapps-web-http-client.provider';
import {HttpClientModule} from '@angular/common/http';
import {provideHttpClient, withInterceptorsFromDi} from '@angular/common/http';
import {IonicStorage} from 'ionic-appauth/lib';
import {RouterModule} from '@angular/router';
import {LoggerTestingModule} from 'ngx-logger/testing';
@@ -35,7 +35,7 @@ describe('AuthService', () => {
storageBackendSpy = jasmine.createSpyObj('StorageBackend', ['getItem']);
TestBed.configureTestingModule({
imports: [HttpClientModule, LoggerTestingModule, RouterModule.forRoot([])],
imports: [LoggerTestingModule, RouterModule.forRoot([])],
providers: [
StAppsWebHttpClient,
{
@@ -54,6 +54,7 @@ describe('AuthService', () => {
useValue: storageBackendSpy,
},
Requestor,
provideHttpClient(withInterceptorsFromDi()),
],
});
defaultAuthService = TestBed.inject(DefaultAuthService);

View File

@@ -37,8 +37,5 @@
{{ 'dashboard.jobs.noJobs' | translate }}
</ion-label>
</ion-item>
<ion-button slot="button-end" fill="clear" color="medium" [routerLink]="['/jobs']">
<ion-icon slot="icon-only" name="search" [size]="24"></ion-icon>
</ion-button>
}
</stapps-section>

View File

@@ -14,7 +14,7 @@
*/
import {ScrollingModule} from '@angular/cdk/scrolling';
import {CommonModule} from '@angular/common';
import {HttpClientModule} from '@angular/common/http';
import {provideHttpClient, withInterceptorsFromDi} from '@angular/common/http';
import {NgModule} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {
@@ -224,12 +224,33 @@ import {IonIconDirective} from 'src/app/util/ion-icon/ion-icon.directive';
PeriodicalDetailContentComponent,
ShareButtonComponent,
],
exports: [
DataDetailComponent,
DataDetailContentComponent,
DataIconPipe,
DataListComponent,
DataListItemComponent,
DateSeriesListItemComponent,
PlaceListItemComponent,
SimpleCardComponent,
SkeletonListItemComponent,
SkeletonSimpleCardComponent,
SearchPageComponent,
SimpleDataListComponent,
OriginDetailComponent,
FavoriteButtonComponent,
TreeListComponent,
ExternalLinkComponent,
ArticleDetailContentComponent,
BookDetailContentComponent,
PeriodicalDetailContentComponent,
TitleCardComponent,
],
imports: [
CommonModule,
DataRoutingModule,
FormsModule,
MapWidgetComponent,
HttpClientModule,
MarkdownModule.forRoot(),
MenuModule,
IonIconDirective,
@@ -301,28 +322,7 @@ import {IonIconDirective} from 'src/app/util/ion-icon/ion-icon.directive';
useFactory: browserFactory,
deps: [Platform],
},
],
exports: [
DataDetailComponent,
DataDetailContentComponent,
DataIconPipe,
DataListComponent,
DataListItemComponent,
DateSeriesListItemComponent,
PlaceListItemComponent,
SimpleCardComponent,
SkeletonListItemComponent,
SkeletonSimpleCardComponent,
SearchPageComponent,
SimpleDataListComponent,
OriginDetailComponent,
FavoriteButtonComponent,
TreeListComponent,
ExternalLinkComponent,
ArticleDetailContentComponent,
BookDetailContentComponent,
PeriodicalDetailContentComponent,
TitleCardComponent,
provideHttpClient(withInterceptorsFromDi()),
],
})
export class DataModule {}

View File

@@ -15,7 +15,12 @@
import {Component, DestroyRef, inject, Input, OnInit} from '@angular/core';
import {ActivatedRoute, Router} from '@angular/router';
import {Keyboard} from '@capacitor/keyboard';
import {AlertController, AnimationBuilder, AnimationController} from '@ionic/angular/standalone';
import {
AlertController,
AnimationBuilder,
AnimationController,
ModalController,
} from '@ionic/angular/standalone';
import {Capacitor} from '@capacitor/core';
import {
SCFacet,
@@ -36,6 +41,8 @@ import {PositionService} from '../../map/position.service';
import {ConfigProvider} from '../../config/config.provider';
import {searchPageSwitchAnimation} from './search-page-switch-animation';
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
import {ContextMenuModalComponent} from '../../menu/context/context-menu-modal.component';
import {enterAnimation, leaveAnimation} from '../../menu/context/context-menu-modal.animations';
/**
* SearchPageComponent queries things and shows list of things as search results and filter as context menu
@@ -173,7 +180,8 @@ export class SearchPageComponent implements OnInit {
private readonly route: ActivatedRoute,
protected positionService: PositionService,
private readonly configProvider: ConfigProvider,
animationController: AnimationController,
protected animationController: AnimationController,
protected modalController: ModalController,
) {
this.routeAnimation = searchPageSwitchAnimation(animationController);
}
@@ -368,4 +376,20 @@ export class SearchPageComponent implements OnInit {
this.searchStringChanged(term);
}
}
async openContextMenu(): Promise<void> {
const modal = await this.modalController.create({
component: ContextMenuModalComponent,
cssClass: 'context-menu-modal',
showBackdrop: true,
backdropDismiss: true,
enterAnimation: (baseElement: HTMLElement) => enterAnimation(baseElement, this.animationController),
leaveAnimation: (baseElement: HTMLElement) => leaveAnimation(baseElement, this.animationController),
componentProps: {
contextMenuService: this.contextMenuService,
},
});
await modal.present();
}
}

View File

@@ -12,10 +12,6 @@
~ You should have received a copy of the GNU General Public License along with
~ this program. If not, see <https://www.gnu.org/licenses/>.
-->
@if (showContextMenu) {
<stapps-context contentId="data-list"></stapps-context>
}
<ion-header>
@if (showDrawer && showTopToolbar) {
<ion-toolbar color="primary" mode="ios">
@@ -41,7 +37,7 @@
>
</ion-searchbar>
@if (showContextMenu) {
<ion-menu-button menu="context" auto-hide="false" slot="end">
<ion-menu-button menu="context" auto-hide="false" slot="end" (click)="openContextMenu()">
<ion-icon name="tune"></ion-icon>
</ion-menu-button>
}

View File

@@ -13,7 +13,7 @@
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {Component, OnInit} from '@angular/core';
import {AlertController, AnimationController} from '@ionic/angular/standalone';
import {AlertController, AnimationController, ModalController} from '@ionic/angular/standalone';
import {ActivatedRoute, Router} from '@angular/router';
import {NGXLogger} from 'ngx-logger';
import {debounceTime, distinctUntilChanged, startWith, take} from 'rxjs/operators';
@@ -55,6 +55,7 @@ export class FavoritesPageComponent extends SearchPageComponent implements OnIni
private favoritesService: FavoritesService,
configProvider: ConfigProvider,
animationController: AnimationController,
modalController: ModalController,
) {
super(
alertController,
@@ -68,6 +69,7 @@ export class FavoritesPageComponent extends SearchPageComponent implements OnIni
positionService,
configProvider,
animationController,
modalController,
);
}

View File

@@ -21,7 +21,7 @@ import {StAppsWebHttpClient} from '../data/stapps-web-http-client.provider';
import {StorageProvider} from '../storage/storage.provider';
import {LoggerTestingModule} from 'ngx-logger/testing';
import {MapModule} from '../map/map.module';
import {HttpClientModule} from '@angular/common/http';
import {provideHttpClient, withInterceptorsFromDi} from '@angular/common/http';
import {StorageModule} from '../storage/storage.module';
import {DaiaHolding, DaiaService} from './protocol/response';
import {Observable, of} from 'rxjs';
@@ -47,7 +47,6 @@ describe('DaiaDataProvider', () => {
imports: [
HebisModule,
MapModule,
HttpClientModule,
StorageModule,
LoggerTestingModule,
TranslateModule.forRoot({
@@ -62,6 +61,7 @@ describe('DaiaDataProvider', () => {
StAppsWebHttpClient,
StorageProvider,
DaiaDataProvider,
provideHttpClient(withInterceptorsFromDi()),
],
});
daiaDataProvider = TestBed.inject(DaiaDataProvider);

View File

@@ -14,7 +14,7 @@
*/
import {ScrollingModule} from '@angular/cdk/scrolling';
import {CommonModule} from '@angular/common';
import {HttpClientModule} from '@angular/common/http';
import {provideHttpClient, withInterceptorsFromDi} from '@angular/common/http';
import {NgModule} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {TranslateModule} from '@ngx-translate/core';
@@ -72,7 +72,6 @@ import {IonIconDirective} from 'src/app/util/ion-icon/ion-icon.directive';
FormsModule,
HebisRoutingModule,
IonIconDirective,
HttpClientModule,
MarkdownModule.forRoot(),
MenuModule,
MomentModule.forRoot({
@@ -103,6 +102,11 @@ import {IonIconDirective} from 'src/app/util/ion-icon/ion-icon.directive';
IonItem,
IonThumbnail,
],
providers: [HebisDataProvider, DaiaDataProvider, StAppsWebHttpClient],
providers: [
HebisDataProvider,
DaiaDataProvider,
StAppsWebHttpClient,
provideHttpClient(withInterceptorsFromDi()),
],
})
export class HebisModule {}

View File

@@ -14,7 +14,7 @@
*/
import {TestBed} from '@angular/core/testing';
import {MapModule} from './map.module';
import {HttpClientModule} from '@angular/common/http';
import {provideHttpClient, withInterceptorsFromDi} from '@angular/common/http';
import {StorageModule} from '../storage/storage.module';
import {MapPosition, PositionService} from './position.service';
import {ConfigProvider} from '../config/config.provider';
@@ -39,12 +39,13 @@ describe('PositionService', () => {
},
});
TestBed.configureTestingModule({
imports: [MapModule, HttpClientModule, StorageModule, LoggerTestingModule],
imports: [MapModule, StorageModule, LoggerTestingModule],
providers: [
{
provider: ConfigProvider,
useValue: configProviderMock,
},
provideHttpClient(withInterceptorsFromDi()),
],
});
positionService = TestBed.inject(PositionService);

View File

@@ -0,0 +1,68 @@
/*
* Copyright (C) 2025 StApps
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {Animation, AnimationController} from '@ionic/angular';
/**
* Defines the animation for showing a modal as a right-hand sidebar.
* @param baseElement The root element of the modal (including Shadow DOM).
* @param animationCtrl The Ionic AnimationController.
* @returns The configured Ionic animation.
*/
export const enterAnimation = (baseElement: HTMLElement, animationCtrl: AnimationController): Animation => {
const root = baseElement.shadowRoot;
const backdrop = root?.querySelector('ion-backdrop');
const wrapper = root?.querySelector('.modal-wrapper');
// The wrapper needs to be positioned on the right side
if (wrapper instanceof HTMLElement) {
Object.assign(wrapper.style, {
position: 'absolute',
top: '0',
right: '0',
height: '100%',
width: '304px',
maxWidth: '75%',
opacity: '1',
});
}
const backdropAnimation = animationCtrl
.create()
.addElement(backdrop!)
.fromTo('opacity', '0.01', 'var(--backdrop-opacity)');
const wrapperAnimation = animationCtrl
.create()
.addElement(wrapper!)
.fromTo('transform', 'translateX(100%)', 'translateX(0)');
return animationCtrl
.create()
.addElement(baseElement)
.duration(400)
.easing('ease-out')
.addAnimation([backdropAnimation, wrapperAnimation]);
};
/**
* Defines the animation for hiding a modal by sliding it out to the right.
* @param baseElement The root element of the modal.
* @param animationCtrl The Ionic AnimationController.
* @returns The configured Ionic animation (reverse of enterAnimation).
*/
export const leaveAnimation = (baseElement: HTMLElement, animationCtrl: AnimationController): Animation => {
return enterAnimation(baseElement, animationCtrl).direction('reverse');
};

View File

@@ -0,0 +1,179 @@
/*
* Copyright (C) 2025 StApps
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
/* eslint-disable @typescript-eslint/no-non-null-assertion,@typescript-eslint/no-explicit-any */
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {FormsModule} from '@angular/forms';
import {CommonModule} from '@angular/common';
import {TranslateModule} from '@ngx-translate/core';
import {IonicModule} from '@ionic/angular';
import {SCThingType} from '@openstapps/core';
import {ContextMenuModalComponent} from './context-menu-modal.component';
import {ContextMenuService} from './context-menu.service';
import {FilterContext, SortContext} from './context-type';
import {provideIonicAngular, ModalController} from '@ionic/angular/standalone';
import {BehaviorSubject, of} from 'rxjs';
import {addIcons} from 'ionicons';
import {swapVertical, trash} from 'ionicons/icons';
describe('ContextMenuModalComponent', () => {
let fixture: ComponentFixture<ContextMenuModalComponent>;
let component: ContextMenuModalComponent;
let modalControllerSpy: jasmine.SpyObj<ModalController>;
let contextMenuServiceMock: Partial<ContextMenuService>;
// Register used icons (suppress warnings)
addIcons({
delete: trash,
sort: swapVertical,
});
beforeEach(async () => {
modalControllerSpy = jasmine.createSpyObj<ModalController>('ModalController', ['dismiss']);
contextMenuServiceMock = {
filterOptions: new BehaviorSubject<FilterContext | undefined>(getFilterContextType()),
sortOptions: new BehaviorSubject<SortContext | undefined>(getSortContextType()),
filterContextChanged$: of(getFilterContextType()),
sortContextChanged$: of(getSortContextType()),
contextFilterChanged: jasmine.createSpy(),
contextSortChanged: jasmine.createSpy(),
};
await TestBed.configureTestingModule({
declarations: [ContextMenuModalComponent],
imports: [CommonModule, FormsModule, TranslateModule.forRoot(), IonicModule.forRoot()],
providers: [
provideIonicAngular(),
{
provide: ModalController,
useValue: modalControllerSpy,
},
{
provide: ContextMenuService,
useValue: contextMenuServiceMock,
},
],
}).compileComponents();
fixture = TestBed.createComponent(ContextMenuModalComponent);
component = fixture.componentInstance;
component.contextMenuService = contextMenuServiceMock as ContextMenuService;
component.translator = {
translatedPropertyValue: () => 'translated',
} as any;
fixture.detectChanges();
});
it('should create the component', () => {
expect(component).toBeTruthy();
});
it('should load initial sort and filter context', () => {
expect(component.sortOption?.value).toBe('relevance');
expect(component.filterOption?.options?.length).toBeGreaterThan(0);
});
it('should display sort items', () => {
const sortItems = fixture.nativeElement.querySelectorAll('.sort-item');
expect(sortItems.length).toBe(component.sortOption.values.length);
});
it('should update and reverse sort value on click', () => {
const value = component.sortOption.values[1]; // "name", reversible
component.sortChanged(component.sortOption, value);
expect(component.sortOption.value).toBe('name');
expect(component.sortOption.reversed).toBeFalse();
component.sortChanged(component.sortOption, value);
expect(component.sortOption.reversed).toBeTrue();
});
it('should call contextFilterChanged when filter is reset', () => {
component.filterOption.options[0].buckets[0].checked = true;
component.resetFilter(component.filterOption);
const allUnchecked = component.filterOption.options.every(opt =>
opt.buckets.every(bucket => !bucket.checked),
);
expect(allUnchecked).toBeTrue();
expect(contextMenuServiceMock.contextFilterChanged).toHaveBeenCalled();
});
it('should dismiss the modal', () => {
component.dismiss();
expect(modalControllerSpy.dismiss).toHaveBeenCalled();
});
});
/**
*
*/
function getSortContextType(): SortContext {
return {
name: 'sort',
reversed: false,
value: 'relevance',
values: [
{value: 'relevance', reversible: false},
{value: 'name', reversible: true},
{value: 'date', reversible: true},
{value: 'type', reversible: true},
],
};
}
/**
*
*/
function getFilterContextType(): FilterContext {
return {
name: 'filter',
compact: false,
options: facetsMock
.filter(facet => facet.buckets.length > 0)
.map((facet, i) => ({
buckets: facet.buckets.map(bucket => ({
count: bucket.count,
key: bucket.key,
checked: false,
})),
compact: false,
field: facet.field,
onlyOnType: facet.onlyOnType,
info: {
onlyOnType: facet.onlyOnType,
field: facet.field,
sortOrder: i,
},
})),
};
}
const facetsMock = [
{
buckets: [
{count: 10, key: 'lecture'},
{count: 5, key: 'seminar'},
],
field: 'type',
onlyOnType: SCThingType.AcademicEvent,
},
{
buckets: [{count: 7, key: 'research'}],
field: 'categories',
onlyOnType: SCThingType.AcademicEvent,
},
];

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023 StApps
* Copyright (C) 2025 StApps
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, version 3.
@@ -12,76 +12,40 @@
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {Component, Input} from '@angular/core';
import {LangChangeEvent, TranslateService} from '@ngx-translate/core';
import {SCLanguage, SCThingTranslator, SCThingType, SCTranslations} from '@openstapps/core';
import {ContextMenuService} from './context-menu.service';
import {FilterContext, FilterFacet, SortContext, SortContextOption} from './context-type.js';
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
import {ModalController} from '@ionic/angular/standalone';
import {Component, Input, OnInit, OnDestroy} from '@angular/core';
import {Subject, takeUntil} from 'rxjs';
/**
* The context menu
*
* It can be configured with sorting types and filtering on facets
*
* Example:<br>
* `<stapps-context (optionChange)="onOptionChange($event)" (settingChange)="onSettingChange($event)"
* [sortOption]="SortContext" [filterOption]="FilterContext"></stapps-context>`
*/
@Component({
selector: 'stapps-context',
templateUrl: 'context-menu.html',
selector: 'stapps-context-menu-modal',
templateUrl: './context-menu-modal.html',
})
export class ContextMenuComponent {
/**
* Id of the content the menu is used for
*/
@Input()
contentId: string;
export class ContextMenuModalComponent implements OnInit, OnDestroy {
@Input() contextMenuService: ContextMenuService;
/**
* Amount of filter options shown on compact view
*/
compactFilterOptionCount = 5;
/**
* Container for the filter context
*/
filterOption: FilterContext;
/**
* Picks facets based on the compact filter option and sorts
* them based on
*
* No specific type => Type name alphabetically => Bucket count
*/
get facets(): FilterFacet[] {
return this.filterOption.options.filter(it => it.buckets.length > 0);
}
/**
* Possible languages to be used for translation
*/
language: keyof SCTranslations<SCLanguage>;
/**
* Mapping of SCThingType
*/
scThingType = SCThingType;
/**
* Container for the sort context
*/
sortOption: SortContext;
/**
* Core translator
*/
language: keyof SCTranslations<SCLanguage>;
translator: SCThingTranslator;
scThingType = SCThingType;
// Using a subject to manage subscriptions for clean-up
private readonly destroy$ = new Subject<void>();
constructor(
private translateService: TranslateService,
private readonly contextMenuService: ContextMenuService,
private readonly modalController: ModalController,
) {
this.language = this.translateService.currentLang as keyof SCTranslations<SCLanguage>;
this.translator = new SCThingTranslator(this.language);
@@ -90,43 +54,56 @@ export class ContextMenuComponent {
this.language = event.lang as keyof SCTranslations<SCLanguage>;
this.translator = new SCThingTranslator(this.language);
});
this.contextMenuService.filterContextChanged$.pipe(takeUntilDestroyed()).subscribe(filterContext => {
this.filterOption = filterContext;
}
ngOnInit(): void {
const initialFilter = this.contextMenuService.filterOptions.getValue();
if (initialFilter) {
this.filterOption = initialFilter;
}
const initialSort = this.contextMenuService.sortOptions.getValue();
if (initialSort) {
this.sortOption = initialSort;
}
// Move the subscription logic here. It's now safe to access this.contextMenuService.
this.contextMenuService.filterContextChanged$.pipe(takeUntil(this.destroy$)).subscribe(fc => {
if (fc) {
this.filterOption = fc;
}
});
this.contextMenuService.sortOptions.pipe(takeUntilDestroyed()).subscribe(sortContext => {
this.sortOption = sortContext;
this.contextMenuService.sortContextChanged$.pipe(takeUntil(this.destroy$)).subscribe(sc => {
if (sc) {
this.sortOption = sc;
}
});
}
/**
* Sets selected filter options and updates listener
*/
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
get facets(): FilterFacet[] {
return this.filterOption?.options?.filter(it => it.buckets.length > 0) || [];
}
resetFilter(option: FilterContext) {
for (const facet of option.options) {
for (const bucket of facet.buckets) {
bucket.checked = false;
}
}
this.contextMenuService.contextFilterChanged(this.filterOption);
}
filterChanged = () => {
this.contextMenuService.contextFilterChanged(this.filterOption);
};
/**
* Returns translated property value
*/
getTranslatedPropertyValue(onlyForType: SCThingType, field: string, key?: string): string | undefined {
return this.translator.translatedPropertyValue(onlyForType, field, key);
}
/**
* Resets filter options
*/
resetFilter = (option: FilterContext) => {
for (const filterFacet of option.options)
for (const filterBucket of filterFacet.buckets) {
filterBucket.checked = false;
}
this.contextMenuService.contextFilterChanged(this.filterOption);
};
/**
* Updates selected sort option and updates listener
*/
sortChanged = (option: SortContext, value: SortContextOption) => {
sortChanged(option: SortContext, value: SortContextOption) {
if (option.value === value.value) {
if (value.reversible) {
option.reversed = !option.reversed;
@@ -138,5 +115,13 @@ export class ContextMenuComponent {
}
}
this.contextMenuService.contextSortChanged(option);
};
}
getTranslatedPropertyValue(onlyForType: SCThingType, field: string, key?: string): string | undefined {
return this.translator.translatedPropertyValue(onlyForType, field, key);
}
dismiss() {
this.modalController.dismiss();
}
}

View File

@@ -0,0 +1,104 @@
<!--
~ Copyright (C) 2025 StApps
~ This program is free software: you can redistribute it and/or modify it
~ under the terms of the GNU General Public License as published by the Free
~ Software Foundation, version 3.
~
~ This program is distributed in the hope that it will be useful, but WITHOUT
~ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
~ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
~ more details.
~
~ You should have received a copy of the GNU General Public License along with
~ this program. If not, see <https://www.gnu.org/licenses/>.
-->
<ion-header>
<ion-toolbar color="primary" mode="ios">
<ion-label class="ion-padding-horizontal">
<h1 class="ion-padding-horizontal">{{ 'menu.context.title' | translate | titlecase }}</h1>
</ion-label>
</ion-toolbar>
</ion-header>
<ion-content>
<!-- Sort Context -->
<ion-list *ngIf="sortOption">
<ion-radio-group class="context-sort" [value]="0">
<ion-list-header>
<ion-icon name="sort"></ion-icon>
<ion-title>{{ 'menu.context.sort.title' | translate | titlecase }}</ion-title>
</ion-list-header>
<ion-item
class="sort-item"
*ngFor="let value of sortOption.values; let i = index"
(click)="sortChanged(sortOption, value)"
>
<ion-radio [value]="i">
{{ 'menu.context.sort.' + value.value | translate | titlecase }}
<span *ngIf="sortOption.value === value.value && value.reversible">
<ion-icon *ngIf="sortOption.reversed" name="arrow_downward"></ion-icon>
<ion-icon *ngIf="!sortOption.reversed" name="arrow_upward"></ion-icon>
</span>
</ion-radio>
</ion-item>
</ion-radio-group>
</ion-list>
<!-- Filter Context -->
<form class="context-filter" *ngIf="filterOption">
<ion-list-header>
<ion-icon name="filter_list"></ion-icon>
<ion-title>{{ 'menu.context.filter.title' | translate | titlecase }}</ion-title>
<ion-button class="resetFilterButton" fill="clear" color="dark" (click)="resetFilter(filterOption)">
<ion-icon name="delete"></ion-icon>
</ion-button>
</ion-list-header>
<ion-list class="filter-group" *ngFor="let facet of facets">
<ion-list-header class="h3">
<ion-label>
<span *ngIf="facet.info.onlyOnType"
><b>{{ facet.info.onlyOnType | titlecase }}</b> /
</span>
{{ facet.info.field | titlecase }}
</ion-label>
</ion-list-header>
<ng-container
*ngFor="
let bucket of !facet.compact ? facet.buckets.slice(0, compactFilterOptionCount) : facet.buckets
"
>
<ion-item>
<ion-checkbox
[(ngModel)]="bucket.checked"
(ngModelChange)="filterChanged()"
[name]="facet.onlyOnType + '-' + facet.field + '-' + bucket.key"
[value]="{
field: facet.field,
value: bucket.key,
onlyOnType: facet.onlyOnType
}"
class="filter-item-label"
>
({{ bucket.count }})
{{
facet.field === 'type'
? (getTranslatedPropertyValue($any(bucket.key), 'type') | titlecase)
: (facet.onlyOnType && getTranslatedPropertyValue(facet.onlyOnType, facet.field, bucket.key)
| titlecase)
}}
</ion-checkbox>
</ion-item>
</ng-container>
<ion-button
*ngIf="!facet.compact && facet.buckets.length > compactFilterOptionCount"
fill="clear"
(click)="facet.compact = true"
>
{{ 'menu.context.filter.showAll' | translate }}
</ion-button>
</ion-list>
</form>
</ion-content>

View File

@@ -1,303 +0,0 @@
/*
* Copyright (C) 2023 StApps
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
/* eslint-disable @typescript-eslint/no-non-null-assertion, @typescript-eslint/ban-ts-comment */
import {APP_BASE_HREF, CommonModule, Location, LocationStrategy, PathLocationStrategy} from '@angular/common';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {FormsModule} from '@angular/forms';
import {ChildrenOutletContexts, RouterModule, UrlSerializer} from '@angular/router';
import {TranslateModule} from '@ngx-translate/core';
import {SCFacet, SCThingType} from '@openstapps/core';
import {ContextMenuComponent} from './context-menu.component';
import {SettingsModule} from '../../settings/settings.module';
import {ContextMenuService} from './context-menu.service';
import {FilterContext, SortContext} from './context-type';
import {Component} from '@angular/core';
import {By} from '@angular/platform-browser';
import {provideIonicAngular} from '@ionic/angular/standalone';
@Component({
template: `<ion-content id="foo"></ion-content><stapps-context contentId="foo"></stapps-context> `,
})
class ContextMenuContainerComponent {}
describe('ContextMenuComponent', async () => {
let fixture: ComponentFixture<ContextMenuContainerComponent>;
let instance: ContextMenuComponent;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [ContextMenuComponent, ContextMenuContainerComponent],
providers: [
provideIonicAngular(),
ChildrenOutletContexts,
Location,
UrlSerializer,
ContextMenuService,
{provide: LocationStrategy, useClass: PathLocationStrategy},
{provide: APP_BASE_HREF, useValue: '/'},
],
imports: [
FormsModule,
TranslateModule.forRoot(),
CommonModule,
SettingsModule,
RouterModule.forRoot([]),
],
}).compileComponents();
fixture = TestBed.createComponent(ContextMenuContainerComponent);
instance = fixture.debugElement.query(By.directive(ContextMenuComponent)).componentInstance;
});
it('should show items in sort context', () => {
instance.sortOption = getSortContextType();
fixture.detectChanges();
const sort: HTMLElement = fixture.debugElement.nativeElement.querySelector('.context-sort');
expect(sort!.querySelector('ion-radio')?.textContent).toContain('relevance');
});
it('should show items in filter context', () => {
instance.filterOption = getFilterContextType();
fixture.detectChanges();
const filter: HTMLElement = fixture.debugElement.nativeElement.querySelector('.context-filter');
const filterItem = filter.querySelector('.filter-group');
expect(filterItem!.querySelector('ion-list-header')!.textContent).toContain('Type');
});
it('should set sort context value and reverse on click', () => {
instance.sortOption = getSortContextType();
fixture.detectChanges();
const sort: HTMLElement = fixture.debugElement.nativeElement.querySelector('.context-sort');
// @ts-expect-error not relevant for this case
const sortItem: HTMLElement = sort.querySelectorAll('.sort-item')[1];
sortItem!.click();
expect(instance.sortOption.value).toEqual('name');
expect(instance.sortOption.reversed).toBe(false);
// click again for reverse
sortItem!.click();
expect(instance.sortOption.reversed).toBe(true);
});
it('should show all filterable facets', () => {
// get set facets with non empty buckets
const facets: SCFacet[] = getFilterContextType().options;
instance.filterOption = getFilterContextType();
fixture.detectChanges();
// get filter context div
const filter: HTMLElement = fixture.debugElement.nativeElement.querySelector('.context-filter');
// get all filter groups that represent a facet
const filterGroups = filter.querySelectorAll('.filter-group');
expect(filterGroups.length).toEqual(facets.length);
for (const facet of facets) {
let filterGroup;
// get filter option for facets field
// eslint-disable-next-line unicorn/no-array-for-each
filterGroups.forEach(element => {
if (
element
.querySelector('ion-list-header')!
.textContent!.toString()
.toLowerCase()
.includes(facet.field)
) {
filterGroup = element;
return;
}
});
expect(filterGroup).toBeDefined();
const filterItems = filterGroup!.querySelectorAll('.filter-item-label');
if (filterItems.length !== facet.buckets.length) {
console.log(JSON.stringify(facet));
}
expect(filterItems.length).toEqual(facet.buckets.length);
// check all buckets are shown
for (const bucket of facet.buckets) {
let filterItem;
for (let i = 0; i < filterItems.length; i++) {
if (
filterItems.item(i).textContent!.toString().toLowerCase().indexOf(bucket.key.toLowerCase()) > 0
) {
filterItem = filterItems.item(i);
break;
}
}
expect(filterItem).toBeDefined();
}
}
});
it('should reset filter', () => {
instance.filterOption = getFilterContextType();
instance.filterOption.options = [
{
field: 'type',
buckets: [{count: 10, key: 'date series', checked: true}],
info: {
onlyOnType: SCThingType.AcademicEvent,
field: 'date series',
sortOrder: 0,
},
},
];
fixture.detectChanges();
// click reset button
const resetButton: HTMLElement = fixture.debugElement.nativeElement.querySelector('.resetFilterButton');
resetButton.click();
expect(instance.filterOption.options[0].buckets[0].checked).toEqual(false);
});
});
/**
*
*/
function getSortContextType(): SortContext {
return {
name: 'sort',
reversed: false,
value: 'relevance',
values: [
{
reversible: false,
value: 'relevance',
},
{
reversible: true,
value: 'name',
},
{
reversible: true,
value: 'date',
},
{
reversible: true,
value: 'type',
},
],
};
}
/**
*
*/
function getFilterContextType(): FilterContext {
return {
name: 'filter',
compact: false,
options: facetsMock
.filter(facet => facet.buckets.length > 0)
.map((facet, i) => {
return {
buckets: facet.buckets.map(bucket => {
return {
count: bucket.count,
key: bucket.key,
checked: false,
};
}),
compact: false,
field: facet.field,
onlyOnType: facet.onlyOnType,
info: {
onlyOnType: facet.onlyOnType,
field: facet.field,
sortOrder: i,
},
};
}),
};
}
const facetsMock: SCFacet[] = [
{
buckets: [
{
count: 60,
key: 'academic event',
},
{
count: 160,
key: 'message',
},
{
count: 151,
key: 'date series',
},
{
count: 106,
key: 'dish',
},
{
count: 20,
key: 'building',
},
],
field: 'type',
},
{
buckets: [
{
count: 12,
key: 'Max Mustermann',
},
{
count: 2,
key: 'Foo Bar',
},
],
field: 'performers',
onlyOnType: SCThingType.AcademicEvent,
},
{
buckets: [
{
count: 5,
key: 'colloquium',
},
{
count: 15,
key: 'course',
},
],
field: 'categories',
onlyOnType: SCThingType.AcademicEvent,
},
{
buckets: [
{
count: 5,
key: 'employees',
},
{
count: 15,
key: 'students',
},
],
field: 'audiences',
onlyOnType: SCThingType.Message,
},
];

View File

@@ -1,114 +0,0 @@
<!--
~ Copyright (C) 2023 StApps
~ This program is free software: you can redistribute it and/or modify it
~ under the terms of the GNU General Public License as published by the Free
~ Software Foundation, version 3.
~
~ This program is distributed in the hope that it will be useful, but WITHOUT
~ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
~ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
~ more details.
~
~ You should have received a copy of the GNU General Public License along with
~ this program. If not, see <https://www.gnu.org/licenses/>.
-->
<ion-menu type="overlay" menuId="context" contentId="{{ contentId }}" maxEdgeStart="0" side="end">
<ion-toolbar color="primary" mode="ios">
<ion-label class="ion-padding-horizontal">
<h1 class="ion-padding-horizontal">{{ 'menu.context.title' | translate | titlecase }}</h1>
</ion-label>
</ion-toolbar>
<ion-content>
<!-- Sort Context -->
<ion-list>
@if (sortOption) {
<ion-radio-group class="context-sort" [value]="0">
<ion-list-header>
<ion-icon name="sort"></ion-icon>
<ion-title>{{ 'menu.context.sort.title' | translate | titlecase }}</ion-title>
</ion-list-header>
@for (value of sortOption.values; track value; let i = $index) {
<ion-item class="sort-item" (click)="sortChanged(sortOption, sortOption.values[i])">
<ion-radio [value]="i">
{{ 'menu.context.sort.' + value.value | translate | titlecase }}
@if (sortOption.value === value.value && value.reversible) {
<span>
@if (sortOption.reversed) {
<ion-icon name="arrow_downward"></ion-icon>
}
@if (!sortOption.reversed) {
<ion-icon name="arrow_upward"></ion-icon>
}
</span>
}
</ion-radio>
</ion-item>
}
</ion-radio-group>
}
</ion-list>
<!-- Filter Context -->
@if (filterOption) {
<form class="context-filter">
<ion-list-header>
<ion-icon name="filter_list"></ion-icon>
<ion-title>{{ 'menu.context.filter.title' | translate | titlecase }}</ion-title>
<ion-button class="resetFilterButton" fill="clear" color="dark" (click)="resetFilter(filterOption)">
<ion-icon name="delete"></ion-icon>
</ion-button>
</ion-list-header>
@for (facet of facets; track facet) {
<ion-list class="filter-group">
<div>
<ion-list-header class="h3">
<ion-label>
@if (facet.info.onlyOnType) {
<span
><b>{{ facet.info.onlyOnType | titlecase }}</b> /
</span>
}
{{ facet.info.field | titlecase }}
</ion-label>
</ion-list-header>
<div>
@for (
bucket of !facet.compact ? facet.buckets.slice(0, compactFilterOptionCount) : facet.buckets;
track bucket
) {
<ion-item>
<ion-checkbox
[(ngModel)]="bucket.checked"
(ngModelChange)="filterChanged()"
[name]="facet.onlyOnType + '-' + facet.field + '-' + bucket.key"
[value]="{
field: facet.field,
value: bucket.key,
onlyOnType: facet.onlyOnType
}"
class="filter-item-label"
>
({{ bucket.count }})
{{
facet.field === 'type'
? (getTranslatedPropertyValue($any(bucket.key), 'type') | titlecase)
: (facet.onlyOnType &&
getTranslatedPropertyValue(facet.onlyOnType, facet.field, bucket.key)
| titlecase)
}}
</ion-checkbox>
</ion-item>
}
@if (!facet.compact && facet.buckets.length > compactFilterOptionCount) {
<ion-button fill="clear" (click)="facet.compact = true">
{{ 'menu.context.filter.showAll' | translate }}
</ion-button>
}
</div>
</div>
</ion-list>
}
</form>
}
</ion-content>
</ion-menu>

View File

@@ -12,14 +12,13 @@
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {TestBed} from '@angular/core/testing';
import {ContextMenuService} from './context-menu.service';
import {SCFacet} from '@openstapps/core';
import {FilterContext, SortContext} from './context-type';
import {ThingTranslateModule} from '../../../translation/thing-translate.module';
import {TranslateModule} from '@ngx-translate/core';
import {firstValueFrom, filter} from 'rxjs';
describe('ContextMenuService', () => {
let service: ContextMenuService;
@@ -36,39 +35,39 @@ describe('ContextMenuService', () => {
expect(service).toBeTruthy();
});
it('should update filterOptions', done => {
service.filterContextChanged$.subscribe(result => {
expect(result).toBeDefined();
done();
});
it('should update filterOptions', async () => {
service.updateContextFilter(facetsMock);
const result = await firstValueFrom(service.filterContextChanged$.pipe(filter(Boolean)));
expect(result).toBeDefined();
});
it('should update filterQuery', done => {
service.filterContextChanged$.subscribe(result => {
expect(result).toBeDefined();
expect(service.contextFilter.options[0].buckets.length).toEqual(
filterContext.options[0].buckets.length,
);
done();
});
it('should update filterQuery', async () => {
service.updateContextFilter(facetsMock);
const result = await firstValueFrom(service.filterContextChanged$.pipe(filter(Boolean)));
expect(result).toBeDefined();
const current = service.contextFilter;
expect(current.options[0].buckets.length).toEqual(filterContext.options[0].buckets.length);
});
it('should update sortOptions', done => {
service.sortContextChanged$.subscribe(result => {
expect(result).toBeDefined();
done();
});
it('should update sortOptions', async () => {
service.setContextSort(sortContext);
const result = await firstValueFrom(service.sortContextChanged$.pipe(filter(Boolean)));
expect(result).toBeDefined();
});
it('should update sortQuery', done => {
service.sortContextChanged$.subscribe(result => {
expect(result).toBeDefined();
done();
});
it('should update sortQuery', async () => {
service.setContextSort(sortContext);
const result = await firstValueFrom(service.sortContextChanged$.pipe(filter(Boolean)));
expect(result).toBeDefined();
});
});

View File

@@ -21,7 +21,7 @@ import {
SCThingType,
SCTranslations,
} from '@openstapps/core';
import {Subject} from 'rxjs';
import {BehaviorSubject} from 'rxjs';
import {FilterBucket, FilterContext, FilterFacet, SortContext, TransformedFacet} from './context-type';
import {TranslateService} from '@ngx-translate/core';
import {ThingTranslateService} from '../../../translation/thing-translate.service';
@@ -40,7 +40,7 @@ export class ContextMenuService {
/**
* Container for the filter context
*/
filterOptions = new Subject<FilterContext>();
filterOptions = new BehaviorSubject<FilterContext | undefined>(undefined);
/**
* Observable filterContext streams
@@ -50,7 +50,7 @@ export class ContextMenuService {
/**
* Container for the filter query (SCSearchFilter)
*/
filterQuery = new Subject<SCSearchFilter | undefined>();
filterQuery = new BehaviorSubject<SCSearchFilter | undefined>(undefined);
/**
* Observable filterContext streams
@@ -65,7 +65,7 @@ export class ContextMenuService {
/**
* Container for the sort context
*/
sortOptions = new Subject<SortContext>();
sortOptions = new BehaviorSubject<SortContext | undefined>(undefined);
/**
* Observable SortContext streams
@@ -75,7 +75,7 @@ export class ContextMenuService {
/**
* Container for the sort query
*/
sortQuery = new Subject<SCSearchSort[] | undefined>();
sortQuery = new BehaviorSubject<SCSearchSort[] | undefined>(undefined);
/**
* Observable SortContext streams

View File

@@ -19,12 +19,13 @@ import {RouterModule} from '@angular/router';
import {LayoutModule} from '@angular/cdk/layout';
import {TranslateModule} from '@ngx-translate/core';
import {SettingsModule} from '../settings/settings.module';
import {ContextMenuComponent} from './context/context-menu.component';
import {ContextMenuService} from './context/context-menu.service';
import {
IonButton,
IonButtons,
IonCheckbox,
IonContent,
IonHeader,
IonItem,
IonLabel,
IonList,
@@ -39,13 +40,14 @@ import {
IonToolbar,
} from '@ionic/angular/standalone';
import {IonIconDirective} from 'src/app/util/ion-icon/ion-icon.directive';
import {ContextMenuModalComponent} from './context/context-menu-modal.component';
/**
* Menu module
*/
@NgModule({
declarations: [ContextMenuComponent],
exports: [ContextMenuComponent],
declarations: [ContextMenuModalComponent],
exports: [ContextMenuModalComponent],
imports: [
CommonModule,
IonIconDirective,
@@ -69,6 +71,8 @@ import {IonIconDirective} from 'src/app/util/ion-icon/ion-icon.directive';
IonRadioGroup,
IonContent,
IonToolbar,
IonButtons,
IonHeader,
],
providers: [ContextMenuService],
})

View File

@@ -15,7 +15,7 @@
import {CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {HttpClientTestingModule} from '@angular/common/http/testing';
import {provideHttpClientTesting} from '@angular/common/http/testing';
import {RouterTestingModule} from '@angular/router/testing';
import {AuthModule} from '../../auth/auth.module';
import {ProfilePageComponent} from './profile-page.component';
@@ -27,6 +27,7 @@ import {ScheduleProvider} from '../../calendar/schedule.provider';
import {DataProvider} from '../../data/data.provider';
import {StAppsWebHttpClient} from '../../data/stapps-web-http-client.provider';
import {SimpleBrowser} from '../../../util/browser.factory';
import {provideHttpClient, withInterceptorsFromDi} from '@angular/common/http';
describe('ProfilePage', () => {
let component: ProfilePageComponent;
@@ -47,7 +48,8 @@ describe('ProfilePage', () => {
TestBed.configureTestingModule({
declarations: [ProfilePageComponent],
imports: [HttpClientTestingModule, RouterTestingModule, AuthModule, TranslateModule.forRoot()],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
imports: [RouterTestingModule, AuthModule, TranslateModule.forRoot()],
providers: [
{provide: ConfigProvider, useValue: configProvider},
{provide: StorageProvider, useValue: storageProvider},
@@ -55,8 +57,9 @@ describe('ProfilePage', () => {
{provide: SimpleBrowser, useValue: simpleBrowser},
ScheduleProvider,
DataProvider,
provideHttpClient(withInterceptorsFromDi()),
provideHttpClientTesting(),
],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
}).compileComponents();
});

View File

@@ -14,10 +14,10 @@
-->
<div class="header">
<ion-button fill="clear" class="left-button" (click)="mainSwiper.pageBackwards()">
<ion-icon slot="icon-only" name="navigate_before"></ion-icon>
<ion-icon slot="icon-only" name="chevron_left"></ion-icon>
</ion-button>
<ion-button fill="clear" class="right-button" (click)="mainSwiper.pageForward()">
<ion-icon slot="icon-only" name="navigate_next"></ion-icon>
<ion-icon slot="icon-only" name="chevron_right"></ion-icon>
</ion-button>
<infinite-swiper
class="header-swiper"

View File

@@ -14,10 +14,10 @@
-->
<div class="header">
<ion-button fill="clear" class="left-button" (click)="mainSwiper.swiperRef.slidePrev()">
<ion-icon slot="icon-only" name="navigate_before"></ion-icon>
<ion-icon slot="icon-only" name="chevron_left"></ion-icon>
</ion-button>
<ion-button fill="clear" class="right-button" (click)="mainSwiper.swiperRef.slideNext()">
<ion-icon slot="icon-only" name="navigate_next"></ion-icon>
<ion-icon slot="icon-only" name="chevron_right"></ion-icon>
</ion-button>
<swiper
class="header-swiper"

File diff suppressed because one or more lines are too long

View File

@@ -21,7 +21,7 @@ export const environment = {
backend_url: 'https://mobile.server.uni-frankfurt.de',
app_host: 'mobile.app.uni-frankfurt.de',
custom_url_scheme: 'de.anyschool.app',
backend_version: '3.3.0',
backend_version: '4.0.0',
production: true,
};

View File

@@ -1,5 +1,11 @@
# @openstapps/app-builder-image
## 4.0.0
### Major Changes
- c8e29020: Update to Node 22
## 3.1.0
### Minor Changes

View File

@@ -1,5 +1,5 @@
### Set base image
FROM ubuntu:20.04
FROM ubuntu:22.04
LABEL version="2.0.0" \
description="Build environment for the StApps app." \
@@ -8,18 +8,19 @@ LABEL version="2.0.0" \
### Configure versions to install
ENV ANDROID_APIS="android-34" \
ANDROID_BUILD_TOOLS_VERSION="34.0.0" \
NODE_VERSION="22.x" \
NPM_VERSION="^10.0.0" \
IONIC_VERSION="^6.0.0" \
IONIC_VERSION="^7.0.0" \
CAPACITOR_VERSION="^6.0.0" \
CORDOVA_RES_VERSION="latest" \
### Configure download URLs
ANDROID_SDK_TOOLS_DOWNLOAD_URL="https://dl.google.com/android/repository/commandlinetools-linux-6858069_latest.zip" \
ANDROID_SDK_TOOLS_DOWNLOAD_URL="https://dl.google.com/android/repository/commandlinetools-linux-13114758_latest.zip" \
GOOGLE_SIGNING_KEY_URL="https://dl-ssl.google.com/linux/linux_signing_key.pub" \
GOOGLE_CHROME_REPOSITORY_URL="http://dl.google.com/linux/chrome/deb/" \
### Android SDK path
ANDROID_SDK_ROOT="/opt/android-sdk" \
### Installation files
SCRIPTS_DIRECTORY="scripts" \
NODE_SETUP_SCRIPT="node_setup.sh" \
TMP_PROJECT_NAME="tmp-project"
### Set $PATH
@@ -43,7 +44,7 @@ RUN apt-get update && \
git \
gradle \
ca-certificates-java \
python \
python3 \
python3-pip \
software-properties-common \
ssh \
@@ -54,9 +55,9 @@ RUN apt-get update && \
musl-dev \
&& rm -rf /var/lib/apt/lists/*
### Install Java Development Kit 17
### Install Java Development Kit 21
RUN add-apt-repository -y ppa:openjdk-r/ppa && apt-get update && \
apt-get install --no-install-recommends -y openjdk-17-jdk \
apt-get install --no-install-recommends -y openjdk-21-jdk \
&& rm -rf /var/lib/apt/lists/*
### Setup the locale
@@ -96,25 +97,25 @@ RUN curl $ANDROID_SDK_TOOLS_DOWNLOAD_URL > /tmp/android-sdk.zip && \
sdkmanager "platforms;$ANDROID_APIS" "build-tools;$ANDROID_BUILD_TOOLS_VERSION"
### Copy scripts directory into the tmp folder, so it's available to the following commands
COPY $SCRIPTS_DIRECTORY/$NODE_SETUP_SCRIPT /tmp/
RUN bash /tmp/$NODE_SETUP_SCRIPT && apt-get install -y nodejs && \
RUN curl -fsSL https://deb.nodesource.com/setup_${NODE_VERSION} | bash && \
apt-get install -y nodejs && \
### Install wanted npm version
npm install -g --unsafe-perm npm@$NPM_VERSION && \
### Install needed global npm packages
npm install -g --unsafe-perm @ionic/cli@$IONIC_VERSION cordova-res@$CORDOVA_RES_VERSION
npm install -g --unsafe-perm @ionic/cli@$IONIC_VERSION cordova-res@$CORDOVA_RES_VERSION @capacitor/cli@$CAPACITOR_VERSION
RUN cd / && ionic start $TMP_PROJECT_NAME blank --type=angular --capacitor --no-git --no-interactive && \
cd $TMP_PROJECT_NAME && ionic capacitor add android && export NG_CLI_ANALYTICS=ci && ionic capacitor build android --no-open && \
cd android && ./gradlew assembleDebug && \
cd / && rm -rf $TMP_PROJECT_NAME
cd $TMP_PROJECT_NAME && ionic capacitor add android && export NG_CLI_ANALYTICS=ci && ionic capacitor build android --no-open && \
cd android && ./gradlew assembleDebug && \
cd / && rm -rf $TMP_PROJECT_NAME
### Compatibility with musl libc
RUN ln -s /usr/lib/x86_64-linux-musl/libc.so /lib/libc.musl-x86_64.so.1
### PNPM
RUN corepack enable && corepack prepare pnpm@latest-8 --activate
RUN corepack enable && corepack prepare pnpm@latest-10 --activate
### Set working directory
WORKDIR /app

View File

@@ -1,6 +1,6 @@
{
"name": "@openstapps/app-builder-image",
"version": "3.1.0",
"version": "4.0.0",
"private": true,
"type": "module",
"license": "GPL-3.0-only",

View File

@@ -1,108 +0,0 @@
#!/bin/bash
# Logger Function
log() {
local message="$1"
local type="$2"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
local color
local endcolor="\033[0m"
case "$type" in
"info") color="\033[38;5;79m" ;;
"success") color="\033[1;32m" ;;
"error") color="\033[1;31m" ;;
*) color="\033[1;34m" ;;
esac
echo -e "${color}${timestamp} - ${message}${endcolor}"
}
# Error handler function
handle_error() {
local exit_code=$1
local error_message="$2"
log "Error: $error_message (Exit Code: $exit_code)" "error"
exit $exit_code
}
# Function to check for command availability
command_exists() {
command -v "$1" &> /dev/null
}
check_os() {
if ! [ -f "/etc/debian_version" ]; then
echo "Error: This script is only supported on Debian-based systems."
exit 1
fi
}
# Function to Install the script pre-requisites
install_pre_reqs() {
log "Installing pre-requisites" "info"
# Run 'apt-get update'
if ! apt-get update -y; then
handle_error "$?" "Failed to run 'apt-get update'"
fi
# Run 'apt-get install'
if ! apt-get install -y apt-transport-https ca-certificates curl gnupg; then
handle_error "$?" "Failed to install packages"
fi
if ! mkdir -p /usr/share/keyrings; then
handle_error "$?" "Makes sure the path /usr/share/keyrings exist or run ' mkdir -p /usr/share/keyrings' with sudo"
fi
rm -f /usr/share/keyrings/nodesource.gpg || true
rm -f /etc/apt/sources.list.d/nodesource.list || true
# Run 'curl' and 'gpg'
if ! curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /usr/share/keyrings/nodesource.gpg; then
handle_error "$?" "Failed to download and import the NodeSource signing key"
fi
}
# Function to configure the Repo
configure_repo() {
local node_version=$1
arch=$(dpkg --print-architecture)
if [ "$arch" != "amd64" ] && [ "$arch" != "arm64" ] && [ "$arch" != "armhf" ]; then
handle_error "1" "Unsupported architecture: $arch. Only amd64, arm64, and armhf are supported."
fi
echo "deb [arch=$arch signed-by=/usr/share/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$node_version nodistro main" | tee /etc/apt/sources.list.d/nodesource.list > /dev/null
# N|solid Config
echo "Package: nsolid" | tee /etc/apt/preferences.d/nsolid > /dev/null
echo "Pin: origin deb.nodesource.com" | tee -a /etc/apt/preferences.d/nsolid > /dev/null
echo "Pin-Priority: 600" | tee -a /etc/apt/preferences.d/nsolid > /dev/null
# Nodejs Config
echo "Package: nodejs" | tee /etc/apt/preferences.d/nodejs > /dev/null
echo "Pin: origin deb.nodesource.com" | tee -a /etc/apt/preferences.d/nodejs > /dev/null
echo "Pin-Priority: 600" | tee -a /etc/apt/preferences.d/nodejs > /dev/null
# Run 'apt-get update'
if ! apt-get update -y; then
handle_error "$?" "Failed to run 'apt-get update'"
else
log "Repository configured successfully."
log "To install Node.js, run: apt-get install nodejs -y" "info"
log "You can use N|solid Runtime as a node.js alternative" "info"
log "To install N|solid Runtime, run: apt-get install nsolid -y \n" "success"
fi
}
# Define Node.js version
NODE_VERSION="18.x"
# Check OS
check_os
# Main execution
install_pre_reqs || handle_error $? "Failed installing pre-requisites"
configure_repo "$NODE_VERSION" || handle_error $? "Failed configuring repository"

View File

@@ -1,3 +1,9 @@
# @openstapps/app-cypress
## 4.0.0
### Major Changes
- c8e29020: Update to Node 22
## 3.0.0

View File

@@ -1,5 +1,5 @@
### Set base image
FROM cypress/base:18.16.1
FROM cypress/base:22.16.0
USER root
@@ -61,9 +61,9 @@ ENV DBUS_SESSION_BUS_ADDRESS=/dev/null
# install Firefox browser
RUN export FIREFOX_VERSION=$(curl -fsSL https://product-details.mozilla.org/1.0/firefox_versions.json | jq -r '.LATEST_FIREFOX_VERSION') && \
wget --no-verbose -O /tmp/firefox.tar.bz2 "https://download-installer.cdn.mozilla.net/pub/firefox/releases/${FIREFOX_VERSION}/linux-x86_64/en-US/firefox-${FIREFOX_VERSION}.tar.bz2" && \
tar -C /opt -xjf /tmp/firefox.tar.bz2 && \
rm /tmp/firefox.tar.bz2 && \
wget --no-verbose -O /tmp/firefox.tar.xz "https://download-installer.cdn.mozilla.net/pub/firefox/releases/${FIREFOX_VERSION}/linux-x86_64/en-US/firefox-${FIREFOX_VERSION}.tar.xz" && \
tar -C /opt -xJf /tmp/firefox.tar.xz && \
rm /tmp/firefox.tar.xz && \
ln -fs /opt/firefox/firefox /usr/bin/firefox
RUN echo "Downloading Latest Edge version..."
@@ -83,6 +83,9 @@ RUN apt-get update && \
# Add a link to the browser that allows Cypress to find it
RUN ln -s /usr/bin/microsoft-edge /usr/bin/edge
### PNPM
RUN corepack enable && corepack prepare pnpm@latest-10 --activate
# versions of local tools
RUN echo " node version: $(node -v) \n" \
"npm version: $(npm -v) \n" \

View File

@@ -1,6 +1,6 @@
{
"name": "@openstapps/app-cypress",
"version": "3.0.0",
"version": "4.0.0",
"private": true,
"type": "module",
"license": "GPL-3.0-only",

View File

@@ -1,5 +1,11 @@
# @openstapps/node-base
## 4.0.0
### Major Changes
- c8e29020: Update to Node 22
## 3.2.0
### Patch Changes

View File

@@ -1,7 +1,9 @@
FROM node:18-alpine3.18
FROM node:22-alpine
RUN apk update && apk add git curl jq && mkdir -p /opt
RUN corepack enable && corepack prepare pnpm@latest-10 --activate
COPY --chown=root:root wait-for.sh /opt/wait-for
RUN chmod +x /opt/wait-for && ln -s /opt/wait-for /usr/local/bin/wait-for

View File

@@ -1,6 +1,6 @@
{
"name": "@openstapps/node-base",
"version": "3.2.0",
"version": "4.0.0",
"private": true,
"type": "module",
"license": "GPL-3.0-only",

View File

@@ -1,5 +1,11 @@
# @openstapps/node-builder
## 4.0.0
### Major Changes
- c8e29020: Update to Node 22
## 3.2.0
### Patch Changes

View File

@@ -1,7 +1,7 @@
FROM node:18-alpine3.18
FROM node:22-alpine
RUN apk update && apk add git jq curl python3 build-base
RUN npm install turbo pnpm --global
RUN npm install turbo --global && corepack enable && corepack prepare pnpm@latest-10 --activate
CMD []

View File

@@ -1,6 +1,6 @@
{
"name": "@openstapps/node-builder",
"version": "3.2.0",
"version": "4.0.0",
"private": true,
"type": "module",
"license": "GPL-3.0-only",

View File

@@ -2,9 +2,10 @@
"name": "@openstapps/openstapps",
"private": true,
"engines": {
"node": "^18.19.1",
"pnpm": "^8.15.4"
"node": "^22.16.0",
"pnpm": "^10.12.3"
},
"packageManager": "pnpm@10.12.3+sha512.467df2c586056165580ad6dfb54ceaad94c5a30f80893ebdec5a44c5aa73c205ae4a5bb9d5ed6bb84ea7c249ece786642bbb49d06a307df218d03da41c317417",
"scripts": {
"build": "dotenv -c -- turbo run build",
"build:full": "dotenv -c -- turbo run syncpack build lint format --summarize",
@@ -21,7 +22,7 @@
"publish-packages": "pnpm changeset version && pnpm syncpack:fix && pnpm install && pnpm format:fix && git add . && git commit -m \"docs: update changelogs for release\" -m \"ci: publish release\" && git push && pnpm changeset tag && git push --follow-tags",
"syncpack": "syncpack list-mismatches && syncpack lint-semver-ranges --types dev,peer,prod",
"syncpack:fix": "syncpack format && syncpack fix-mismatches",
"test": "trap 'node coverage.mjs' EXIT && dotenv -c -- turbo run test",
"test": "trap 'node coverage.mjs' EXIT && dotenv -c -- turbo run test --continue",
"test:integration": "dotenv -c -- turbo run test:integration",
"test:integration:app": "dotenv -c -- turbo run test:integration --filter=@openstapps/app",
"test:integration:backend": "dotenv -c -- turbo run test:integration --filter=@openstapps/backend",
@@ -31,16 +32,16 @@
"devDependencies": {
"@changesets/changelog-git": "0.1.14",
"@changesets/cli": "2.26.1",
"cobertura-merge": "1.0.4",
"deepmerge": "4.3.1",
"dotenv-cli": "7.2.1",
"glob": "10.3.10",
"http-server": "14.1.1",
"junit-report-merger": "6.0.3",
"merge-cobertura": "1.0.1",
"prettier": "3.1.1",
"syncpack": "12.3.0",
"turbo": "1.10.16",
"turbo-ignore": "1.10.16",
"turbo": "1.13.4",
"turbo-ignore": "1.13.4",
"typedoc": "0.25.12",
"typescript": "5.4.2"
}

View File

@@ -1,5 +1,27 @@
# @openstapps/api-cli
## 4.0.1
### Patch Changes
- Updated dependencies [b40ba7ad]
- @openstapps/api@4.0.1
- @openstapps/core@4.0.0
## 4.0.0
### Major Changes
- c8e29020: Update to Node 22
### Patch Changes
- Updated dependencies [c8e29020]
- @openstapps/api@4.0.0
- @openstapps/core@4.0.0
- @openstapps/core-tools@4.0.0
- @openstapps/logger@4.0.0
## 3.3.2
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "@openstapps/api-cli",
"description": "CLI client for @openstapps/api",
"version": "3.3.2",
"version": "4.0.1",
"type": "module",
"license": "GPL-3.0-only",
"repository": "git@gitlab.com:openstapps/api.git",
@@ -38,15 +38,14 @@
"@openstapps/api": "workspace:*",
"@openstapps/core": "workspace:*",
"@openstapps/core-tools": "workspace:*",
"@openstapps/eslint-config": "workspace:*",
"@openstapps/logger": "workspace:*",
"@types/cli-progress": "3.11.0",
"@types/express": "4.17.17",
"@types/fs-extra": "9.0.13",
"@types/json-schema": "7.0.11",
"@types/json-schema": "7.0.15",
"@types/junit-report-builder": "3.0.0",
"@types/mocha": "10.0.1",
"@types/node": "18.15.3",
"@types/mocha": "10.0.10",
"@types/node": "22.15.31",
"@types/wait-on": "5.3.1",
"cli-progress": "3.12.0",
"commander": "10.0.0",
@@ -55,20 +54,21 @@
"wait-on": "6.0.1"
},
"devDependencies": {
"@openstapps/eslint-config": "workspace:*",
"@openstapps/prettier-config": "workspace:*",
"@openstapps/tsconfig": "workspace:*",
"@types/chai": "4.3.5",
"@types/chai-as-promised": "7.1.5",
"@types/chai-spies": "1.0.3",
"c8": "7.14.0",
"chai": "4.3.7",
"chai-as-promised": "7.1.1",
"chai-spies": "1.0.0",
"mocha": "10.2.0",
"@types/chai": "4.3.20",
"@types/chai-as-promised": "7.1.8",
"@types/chai-spies": "1.0.6",
"c8": "10.1.3",
"chai": "4.5.0",
"chai-as-promised": "7.1.2",
"chai-spies": "1.1.0",
"mocha": "10.8.2",
"mocha-junit-reporter": "2.2.0",
"nock": "13.3.1",
"ts-node": "10.9.2",
"tsup": "6.7.0",
"tsup": "8.5.0",
"typescript": "5.4.2"
},
"tsup": {

View File

@@ -162,7 +162,11 @@ describe('e2e Connector', function () {
await e2eRun(httpClient, {
to: 'http://localhost',
samplesLocation: './node_modules/@openstapps/core/test/resources',
}).should.eventually.include('Unexpected difference between original and retrieved sample');
}).should.eventually.satisfy((errors: string[]) => {
return errors.every(error => {
return error.startsWith('Unexpected difference between original and retrieved sample');
});
});
});
it('should fail to index', async function () {

View File

@@ -1,5 +1,28 @@
# @openstapps/api-plugin
## 4.0.1
### Patch Changes
- b40ba7ad: Updated Elasticsearch dependency
- Updated dependencies [b40ba7ad]
- @openstapps/api@4.0.1
- @openstapps/core@4.0.0
## 4.0.0
### Major Changes
- c8e29020: Update to Node 22
### Patch Changes
- Updated dependencies [c8e29020]
- @openstapps/api@4.0.0
- @openstapps/core@4.0.0
- @openstapps/core-tools@4.0.0
- @openstapps/logger@4.0.0
## 3.3.2
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "@openstapps/api-plugin",
"description": "Node.js library to interact with the StApps backend service",
"version": "3.3.2",
"version": "4.0.1",
"type": "module",
"license": "GPL-3.0-only",
"repository": "git@gitlab.com:openstapps/api.git",
@@ -36,33 +36,33 @@
"@openstapps/core": "workspace:*",
"@openstapps/core-tools": "workspace:*",
"@openstapps/logger": "workspace:*",
"@types/body-parser": "1.19.2",
"@types/body-parser": "1.19.6",
"@types/express": "4.17.17",
"@types/json-schema": "7.0.11",
"@types/morgan": "1.9.4",
"body-parser": "1.20.2",
"express": "4.18.2",
"@types/json-schema": "7.0.15",
"@types/morgan": "1.9.10",
"body-parser": "1.20.3",
"express": "4.21.2",
"json-schema": "0.4.0",
"morgan": "1.10.0"
"morgan": "1.10.1"
},
"devDependencies": {
"@openstapps/eslint-config": "workspace:*",
"@openstapps/prettier-config": "workspace:*",
"@openstapps/tsconfig": "workspace:*",
"@types/chai": "4.3.5",
"@types/chai-as-promised": "7.1.5",
"@types/chai-spies": "1.0.3",
"@types/mocha": "10.0.1",
"c8": "7.14.0",
"chai": "4.3.7",
"chai-as-promised": "7.1.1",
"chai-spies": "1.0.0",
"@types/chai": "4.3.20",
"@types/chai-as-promised": "7.1.8",
"@types/chai-spies": "1.0.6",
"@types/mocha": "10.0.10",
"c8": "10.1.3",
"chai": "4.5.0",
"chai-as-promised": "7.1.2",
"chai-spies": "1.1.0",
"conventional-changelog-cli": "2.2.2",
"mocha": "10.2.0",
"mocha": "10.8.2",
"mocha-junit-reporter": "2.2.0",
"nock": "13.3.1",
"ts-node": "10.9.2",
"tsup": "6.7.0",
"tsup": "8.5.0",
"typedoc": "0.25.12",
"typescript": "5.4.2"
},

View File

@@ -1,5 +1,23 @@
# @openstapps/api
## 4.0.1
### Patch Changes
- b40ba7ad: Updated Elasticsearch dependency
- @openstapps/core@4.0.0
## 4.0.0
### Major Changes
- c8e29020: Update to Node 22
### Patch Changes
- Updated dependencies [c8e29020]
- @openstapps/core@4.0.0
## 3.3.2
### Patch Changes

View File

@@ -1,7 +1,7 @@
{
"name": "@openstapps/api",
"description": "Node.js library to interact with the StApps backend service",
"version": "3.3.2",
"version": "4.0.1",
"type": "module",
"license": "GPL-3.0-only",
"repository": "git@gitlab.com:openstapps/api.git",
@@ -40,25 +40,25 @@
"@openstapps/eslint-config": "workspace:*",
"@openstapps/prettier-config": "workspace:*",
"@openstapps/tsconfig": "workspace:*",
"@types/chai": "4.3.5",
"@types/chai-as-promised": "7.1.5",
"@types/chai-spies": "1.0.3",
"@types/mocha": "10.0.1",
"@types/chai": "4.3.20",
"@types/chai-as-promised": "7.1.8",
"@types/chai-spies": "1.0.6",
"@types/mocha": "10.0.10",
"@types/traverse": "0.6.32",
"c8": "7.14.0",
"chai": "4.3.7",
"chai-as-promised": "7.1.1",
"chai-spies": "1.0.0",
"c8": "10.1.3",
"chai": "4.5.0",
"chai-as-promised": "7.1.2",
"chai-spies": "1.1.0",
"conventional-changelog-cli": "2.2.2",
"date-fns": "3.6.0",
"mocha": "10.2.0",
"mocha": "10.8.2",
"mocha-junit-reporter": "2.2.0",
"traverse": "0.6.7",
"ts-node": "10.9.2",
"tsup": "6.7.0",
"tsup": "8.5.0",
"typedoc": "0.25.12",
"typescript": "5.4.2",
"undici": "5.22.1"
"undici": "6.21.3"
},
"peerDependencies": {
"@openstapps/core": "workspace:*"

View File

@@ -1,5 +1,11 @@
# @openstapps/collection-utils
## 4.0.0
### Major Changes
- c8e29020: Update to Node 22
## 3.0.0
### Major Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@openstapps/collection-utils",
"version": "3.0.0",
"version": "4.0.0",
"type": "module",
"license": "GPL-3.0-only",
"main": "lib/index.js",
@@ -22,15 +22,15 @@
"@openstapps/eslint-config": "workspace:*",
"@openstapps/prettier-config": "workspace:*",
"@openstapps/tsconfig": "workspace:*",
"@types/chai": "4.3.5",
"@types/mocha": "10.0.1",
"@types/node": "18.15.3",
"c8": "7.14.0",
"chai": "4.3.7",
"mocha": "10.2.0",
"@types/chai": "4.3.20",
"@types/mocha": "10.0.10",
"@types/node": "22.15.31",
"c8": "10.1.3",
"chai": "4.5.0",
"mocha": "10.8.2",
"mocha-junit-reporter": "2.2.0",
"ts-node": "10.9.2",
"tsup": "6.7.0",
"tsup": "8.5.0",
"typedoc": "0.25.12",
"typescript": "5.4.2"
},

View File

@@ -1,5 +1,18 @@
# @openstapps/core-tools
## 4.0.0
### Major Changes
- c8e29020: Update to Node 22
### Patch Changes
- Updated dependencies [c8e29020]
- @openstapps/collection-utils@4.0.0
- @openstapps/easy-ast@4.0.0
- @openstapps/logger@4.0.0
## 3.3.2
### Patch Changes

Some files were not shown because too many files have changed in this diff Show More