mirror of
https://github.com/opf/openproject.git
synced 2026-06-14 03:30:14 +00:00
Merge remote-tracking branch 'origin/dev' into merge-release/17.5-20260605050041
This commit is contained in:
+48
-2
@@ -13,16 +13,40 @@ updates:
|
||||
groups:
|
||||
angular:
|
||||
patterns:
|
||||
- '@angular*'
|
||||
- '@angular/*'
|
||||
- '@angular-devkit/*'
|
||||
- '@angular-builders/*'
|
||||
angular-eslint:
|
||||
patterns:
|
||||
- 'angular-eslint'
|
||||
- '@angular-eslint/*'
|
||||
blocknote:
|
||||
patterns:
|
||||
- '@blocknote/*'
|
||||
fullcalendar:
|
||||
patterns:
|
||||
- '@fullcalendar/*'
|
||||
eslint:
|
||||
patterns:
|
||||
- 'eslint'
|
||||
- 'eslint-plugin-*'
|
||||
- '@eslint/*'
|
||||
- '@stylistic/eslint-plugin'
|
||||
- 'globals'
|
||||
html-eslint:
|
||||
patterns:
|
||||
- '@html-eslint/*'
|
||||
hotwired:
|
||||
patterns:
|
||||
- '@hotwired/turbo'
|
||||
- '@hotwired/turbo-rails'
|
||||
jquery:
|
||||
patterns:
|
||||
- 'jquery'
|
||||
- 'jquery-migrate'
|
||||
mantine:
|
||||
patterns:
|
||||
- '@mantine/*'
|
||||
ng-select:
|
||||
patterns:
|
||||
- '@ng-select/*'
|
||||
@@ -35,8 +59,30 @@ updates:
|
||||
- 'react-dom'
|
||||
- '@types/react'
|
||||
- '@types/react-dom'
|
||||
testing-library:
|
||||
patterns:
|
||||
- '@testing-library/*'
|
||||
typescript-eslint:
|
||||
patterns:
|
||||
- 'typescript-eslint'
|
||||
- '@typescript-eslint/*'
|
||||
uirouter:
|
||||
patterns:
|
||||
- '@uirouter/*'
|
||||
vitest:
|
||||
patterns:
|
||||
- 'vitest'
|
||||
- '@vitest/*'
|
||||
ignore:
|
||||
- dependency-name: "@angular*"
|
||||
- dependency-name: "@angular/*"
|
||||
update-types: ["version-update:semver-major"]
|
||||
- dependency-name: "@angular-builders/*"
|
||||
update-types: ["version-update:semver-major"]
|
||||
- dependency-name: "@angular-devkit/*"
|
||||
update-types: ["version-update:semver-major"]
|
||||
- dependency-name: "angular-eslint"
|
||||
update-types: ["version-update:semver-major"]
|
||||
- dependency-name: "@angular-eslint/*"
|
||||
update-types: ["version-update:semver-major"]
|
||||
- dependency-name: "@openproject/octicons"
|
||||
- dependency-name: "@openproject/primer-view-components"
|
||||
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Ruby
|
||||
uses: ruby/setup-ruby@c4e5b1316158f92e3d49443a9d58b31d25ac0f8f # v1
|
||||
uses: ruby/setup-ruby@afeafc3d1ab54a631816aba4c914a0081c12ff2f # v1
|
||||
|
||||
- name: Setup Brakeman
|
||||
run: |
|
||||
@@ -46,6 +46,6 @@ jobs:
|
||||
--output output.sarif.json
|
||||
|
||||
- name: Upload SARIF
|
||||
uses: github/codeql-action/upload-sarif@e46ed2cbd01164d986452f91f178727624ae40d7 # v4
|
||||
uses: github/codeql-action/upload-sarif@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4
|
||||
with:
|
||||
sarif_file: output.sarif.json
|
||||
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@e46ed2cbd01164d986452f91f178727624ae40d7 # v4
|
||||
uses: github/codeql-action/init@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4
|
||||
with:
|
||||
config-file: ./.github/codeql/config.yml
|
||||
languages: ${{ matrix.language }}
|
||||
@@ -44,6 +44,6 @@ jobs:
|
||||
queries: security-extended,security-and-quality
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@e46ed2cbd01164d986452f91f178727624ae40d7 # v4
|
||||
uses: github/codeql-action/analyze@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
|
||||
@@ -10,40 +10,28 @@ jobs:
|
||||
if: github.repository == 'opf/openproject'
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
previous_release_branch: ${{ steps.find_previous_release.outputs.branch }}
|
||||
latest_release_branch: ${{ steps.find_latest_release.outputs.branch }}
|
||||
previous_release_branch: ${{ steps.find_release_branches.outputs.previous_branch }}
|
||||
latest_release_branch: ${{ steps.find_release_branches.outputs.latest_branch }}
|
||||
steps:
|
||||
- id: find_previous_release
|
||||
- id: find_release_branches
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.OPENPROJECTCI_GH_CORE_PAT }}
|
||||
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||
run: |
|
||||
BRANCH=$(curl -H "Authorization: token $GITHUB_TOKEN" \
|
||||
https://api.github.com/repos/$GITHUB_REPOSITORY/branches?protected=true | \
|
||||
jq -r '.[].name' | grep '^release/' | sort --version-sort | tail -2 | head -1
|
||||
LATEST_BRANCHES=$(gh api --paginate \
|
||||
"repos/$GITHUB_REPOSITORY/branches?protected=true&per_page=100" \
|
||||
--jq '.[].name' | grep '^release/' | sort --version-sort | tail -2
|
||||
)
|
||||
if [ "$BRANCH" = "" ]; then
|
||||
echo "Invalid release branch found: $BRANCH"
|
||||
LATEST_BRANCH=$(echo "$LATEST_BRANCHES" | tail -1)
|
||||
PREVIOUS_BRANCH=$(echo "$LATEST_BRANCHES" | head -1)
|
||||
if [ -z "$LATEST_BRANCH" ] || [ -z "$PREVIOUS_BRANCH" ]; then
|
||||
echo "Invalid release branches found: latest=$LATEST_BRANCH, previous=$PREVIOUS_BRANCH"
|
||||
exit 1
|
||||
fi
|
||||
echo "Found previous release branch: $BRANCH"
|
||||
echo "branch=${BRANCH}" >> $GITHUB_OUTPUT
|
||||
- id: find_latest_release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.OPENPROJECTCI_GH_CORE_PAT }}
|
||||
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||
run: |
|
||||
BRANCH=$(curl -H "Authorization: token $GITHUB_TOKEN" \
|
||||
https://api.github.com/repos/$GITHUB_REPOSITORY/branches?protected=true | \
|
||||
jq -r '.[].name' | grep '^release/' | sort --version-sort | tail -1
|
||||
)
|
||||
if [ "$BRANCH" = "" ]; then
|
||||
echo "Invalid release branch found: $BRANCH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Found current release branch: $BRANCH"
|
||||
echo "branch=${BRANCH}" >> $GITHUB_OUTPUT
|
||||
echo "Found previous release branch: $PREVIOUS_BRANCH"
|
||||
echo "previous_branch=${PREVIOUS_BRANCH}" >> $GITHUB_OUTPUT
|
||||
echo "Found latest release branch: $LATEST_BRANCH"
|
||||
echo "latest_branch=${LATEST_BRANCH}" >> $GITHUB_OUTPUT
|
||||
merge-or-create-pr:
|
||||
if: github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && github.ref_name == needs.setup.outputs.previous_release_branch)
|
||||
env:
|
||||
|
||||
@@ -20,9 +20,9 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.OPENPROJECTCI_GH_CORE_PAT }}
|
||||
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||
run: |
|
||||
BRANCH=$(curl -H "Authorization: token $GITHUB_TOKEN" \
|
||||
https://api.github.com/repos/$GITHUB_REPOSITORY/branches?protected=true | \
|
||||
jq -r '.[].name' | grep '^release/' | sort --version-sort | tail -1
|
||||
BRANCH=$(gh api --paginate \
|
||||
"repos/$GITHUB_REPOSITORY/branches?protected=true&per_page=100" --jq '.[].name' | \
|
||||
grep '^release/' | sort --version-sort | tail -1
|
||||
)
|
||||
if [ "$BRANCH" = "" ]; then
|
||||
echo "Invalid release branch found: $BRANCH"
|
||||
|
||||
@@ -17,9 +17,9 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||
run: |
|
||||
BRANCH=$(curl -H "Authorization: token $GITHUB_TOKEN" \
|
||||
https://api.github.com/repos/$GITHUB_REPOSITORY/branches?protected=true | \
|
||||
jq -r '.[].name' | grep '^release/' | sort --version-sort | tail -1
|
||||
BRANCH=$(gh api --paginate \
|
||||
"repos/$GITHUB_REPOSITORY/branches?protected=true&per_page=100" --jq '.[].name' | \
|
||||
grep '^release/' | sort --version-sort | tail -1
|
||||
)
|
||||
if [ "$BRANCH" = "" ]; then
|
||||
echo "Invalid release branch found: $BRANCH"
|
||||
@@ -46,7 +46,7 @@ jobs:
|
||||
with:
|
||||
ref: ${{ matrix.branch }}
|
||||
fetch-depth: 1
|
||||
- uses: ruby/setup-ruby@c4e5b1316158f92e3d49443a9d58b31d25ac0f8f # v1
|
||||
- uses: ruby/setup-ruby@afeafc3d1ab54a631816aba4c914a0081c12ff2f # v1
|
||||
with:
|
||||
bundler-cache: true
|
||||
- name: "Set crowdin branch name"
|
||||
|
||||
@@ -22,7 +22,7 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
- uses: ruby/setup-ruby@c4e5b1316158f92e3d49443a9d58b31d25ac0f8f # v1
|
||||
- uses: ruby/setup-ruby@afeafc3d1ab54a631816aba4c914a0081c12ff2f # v1
|
||||
with:
|
||||
ruby-version: .ruby-version
|
||||
bundler-cache: true
|
||||
|
||||
@@ -106,7 +106,7 @@ jobs:
|
||||
STEPS_EXTRACT_VERSION_OUTPUTS_DOCKER_TAGS: ${{ steps.extract_version.outputs.docker_tags }}
|
||||
STEPS_EXTRACT_VERSION_OUTPUTS_REGISTRY_IMAGE: ${{ steps.extract_version.outputs.registry_image }}
|
||||
- name: Cache NPM
|
||||
uses: runs-on/cache@bd330c5a5f6cbb837823ee25864f3c71a211c2e3 # v5
|
||||
uses: runs-on/cache@88d90644011a3a9957fd141a106f5a94f9794203 # v5
|
||||
with:
|
||||
path: |
|
||||
frontend/node_modules
|
||||
@@ -114,12 +114,12 @@ jobs:
|
||||
key: nodejs-x64-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: nodejs-x64-
|
||||
- name: Cache angular
|
||||
uses: runs-on/cache@bd330c5a5f6cbb837823ee25864f3c71a211c2e3 # v5
|
||||
uses: runs-on/cache@88d90644011a3a9957fd141a106f5a94f9794203 # v5
|
||||
with:
|
||||
path: frontend/.angular
|
||||
key: angular-${{ github.ref }}
|
||||
restore-keys: angular-
|
||||
- uses: ruby/setup-ruby@c4e5b1316158f92e3d49443a9d58b31d25ac0f8f # v1
|
||||
- uses: ruby/setup-ruby@afeafc3d1ab54a631816aba4c914a0081c12ff2f # v1
|
||||
with:
|
||||
bundler-cache: true
|
||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
||||
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: ruby/setup-ruby@c4e5b1316158f92e3d49443a9d58b31d25ac0f8f # v1
|
||||
- uses: ruby/setup-ruby@afeafc3d1ab54a631816aba4c914a0081c12ff2f # v1
|
||||
with:
|
||||
bundler-cache: true
|
||||
- run: bundle exec ./script/docs/check_links
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: ruby/setup-ruby@c4e5b1316158f92e3d49443a9d58b31d25ac0f8f # v1
|
||||
- uses: ruby/setup-ruby@afeafc3d1ab54a631816aba4c914a0081c12ff2f # v1
|
||||
with:
|
||||
bundler-cache: true
|
||||
- run: script/docs/check_readme_yaml_header_syntax
|
||||
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Send mail
|
||||
uses: dawidd6/action-send-mail@d38f3f7cd391cdebfe0d38efc3998b935e951c4f # v16
|
||||
uses: dawidd6/action-send-mail@42942bc2f8fba4e611b459a018967a6a7c78c68c # v17
|
||||
with:
|
||||
subject: ${{ inputs.subject }}
|
||||
body: ${{ inputs.body }}
|
||||
|
||||
@@ -161,7 +161,7 @@ jobs:
|
||||
|
||||
- name: Deploy EDGE
|
||||
if: github.ref == 'refs/heads/dev' && github.repository == 'opf/openproject'
|
||||
uses: benc-uk/workflow-dispatch@7a027648b88c2413826b6ddd6c76114894dc5ec4
|
||||
uses: benc-uk/workflow-dispatch@31e2b3319479a63f0ab15bf800eff9e913504e26
|
||||
with:
|
||||
workflow: edge-deploy-shards.yml
|
||||
repo: opf/saas-deploy
|
||||
@@ -172,7 +172,7 @@ jobs:
|
||||
- name: Deploy STAGE
|
||||
# make sure to always use the latest release branch here
|
||||
if: github.ref == 'refs/heads/release/17.5' && github.repository == 'opf/openproject'
|
||||
uses: benc-uk/workflow-dispatch@7a027648b88c2413826b6ddd6c76114894dc5ec4
|
||||
uses: benc-uk/workflow-dispatch@31e2b3319479a63f0ab15bf800eff9e913504e26
|
||||
with:
|
||||
workflow: stage-deploy-shards.yml
|
||||
repo: opf/saas-deploy
|
||||
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Ruby
|
||||
uses: ruby/setup-ruby@c4e5b1316158f92e3d49443a9d58b31d25ac0f8f # v1
|
||||
uses: ruby/setup-ruby@afeafc3d1ab54a631816aba4c914a0081c12ff2f # v1
|
||||
|
||||
- name: Setup i18n-tasks
|
||||
run: |
|
||||
|
||||
@@ -0,0 +1,224 @@
|
||||
name: npm audit fix
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "23 4 * * 1"
|
||||
|
||||
env:
|
||||
BASE_BRANCH: dev
|
||||
BRANCH_PREFIX: npm-audit-fix
|
||||
|
||||
permissions: {}
|
||||
|
||||
concurrency:
|
||||
group: npm-audit-fix
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
audit-fix:
|
||||
name: npm audit fix
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
if: github.repository == 'opf/openproject' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)
|
||||
|
||||
permissions:
|
||||
contents: write # for git push
|
||||
pull-requests: write # for creating pull requests
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
||||
with:
|
||||
ref: ${{ env.BASE_BRANCH }}
|
||||
token: ${{ secrets.OPENPROJECTCI_GH_CORE_PAT }}
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
||||
with:
|
||||
node-version: '22.21'
|
||||
package-manager-cache: false
|
||||
|
||||
- name: Required git config
|
||||
run: |
|
||||
git config user.name "OpenProject Actions CI"
|
||||
git config user.email "operations+ci@openproject.com"
|
||||
|
||||
- name: Run npm audit fix
|
||||
id: audit_fix
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
mkdir -p reports
|
||||
|
||||
print_audit_report() {
|
||||
local report="$1"
|
||||
local label="$2"
|
||||
|
||||
node - "$report" "$label" <<'NODE'
|
||||
const fs = require('fs');
|
||||
|
||||
const reportPath = process.argv[2];
|
||||
const label = process.argv[3];
|
||||
const report = JSON.parse(fs.readFileSync(reportPath, 'utf8'));
|
||||
const vulnerabilities = Object.entries(report.vulnerabilities || {});
|
||||
const counts = report.metadata?.vulnerabilities || {};
|
||||
|
||||
console.log(`${label}: ${vulnerabilities.length} vulnerable package(s)`);
|
||||
console.log(`severity counts: ${JSON.stringify(counts)}`);
|
||||
|
||||
for (const [name, details] of vulnerabilities) {
|
||||
const via = (details.via || [])
|
||||
.map((finding) => typeof finding === 'string' ? finding : finding.title)
|
||||
.filter(Boolean)
|
||||
.join('; ');
|
||||
|
||||
console.log([
|
||||
`- ${name}`,
|
||||
`severity: ${details.severity}`,
|
||||
`range: ${details.range || 'n/a'}`,
|
||||
`fixAvailable: ${JSON.stringify(details.fixAvailable)}`,
|
||||
via ? `via: ${via}` : null
|
||||
].filter(Boolean).join(' | '));
|
||||
}
|
||||
NODE
|
||||
}
|
||||
|
||||
run_for_package() {
|
||||
local name="$1"
|
||||
local directory="$2"
|
||||
local before_report="$GITHUB_WORKSPACE/reports/npm-audit-${name}-before.json"
|
||||
local after_report="$GITHUB_WORKSPACE/reports/npm-audit-${name}-after.json"
|
||||
|
||||
echo "::group::npm audit before fix (${name})"
|
||||
set +e
|
||||
npm --prefix "$directory" audit --audit-level=high --json > "$before_report"
|
||||
audit_before_exit=$?
|
||||
set -e
|
||||
print_audit_report "$before_report" "${name} before fix"
|
||||
echo "npm audit before fix exited with ${audit_before_exit}"
|
||||
echo "::endgroup::"
|
||||
|
||||
echo "::group::npm audit fix (${name})"
|
||||
set +e
|
||||
npm --prefix "$directory" audit fix --package-lock-only --ignore-scripts --audit-level=high
|
||||
audit_fix_exit=$?
|
||||
set -e
|
||||
echo "npm audit fix exited with ${audit_fix_exit}"
|
||||
echo "::endgroup::"
|
||||
|
||||
echo "::group::npm audit after fix (${name})"
|
||||
set +e
|
||||
npm --prefix "$directory" audit --audit-level=high --json > "$after_report"
|
||||
audit_after_exit=$?
|
||||
set -e
|
||||
print_audit_report "$after_report" "${name} after fix"
|
||||
echo "npm audit after fix exited with ${audit_after_exit}"
|
||||
echo "::endgroup::"
|
||||
}
|
||||
|
||||
run_for_package root .
|
||||
run_for_package frontend frontend
|
||||
|
||||
if git diff --quiet -- package.json package-lock.json frontend/package.json frontend/package-lock.json; then
|
||||
echo "has_changes=false" >> "$GITHUB_OUTPUT"
|
||||
echo "npm audit fix produced no package or lockfile changes."
|
||||
else
|
||||
echo "has_changes=true" >> "$GITHUB_OUTPUT"
|
||||
echo "npm audit fix produced package or lockfile changes."
|
||||
fi
|
||||
|
||||
- name: Show diff
|
||||
if: steps.audit_fix.outputs.has_changes == 'true'
|
||||
run: git diff -- package.json package-lock.json frontend/package.json frontend/package-lock.json
|
||||
|
||||
- name: Create pull request
|
||||
if: steps.audit_fix.outputs.has_changes == 'true'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.OPENPROJECTCI_GH_CORE_PAT }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
pr_numbers=$(gh pr list \
|
||||
--base "$BASE_BRANCH" \
|
||||
--state open \
|
||||
--limit 100 \
|
||||
--json number,headRefName \
|
||||
--jq '.[] | select(.headRefName | startswith("'"$BRANCH_PREFIX"'-")) | .number')
|
||||
|
||||
for pr_number in $pr_numbers; do
|
||||
gh pr close "$pr_number" --delete-branch
|
||||
done
|
||||
|
||||
pr_body_file=$(mktemp)
|
||||
|
||||
{
|
||||
echo 'Created by GitHub action.'
|
||||
echo
|
||||
echo '## Impacted packages'
|
||||
} > "$pr_body_file"
|
||||
|
||||
node <<'NODE' >> "$pr_body_file"
|
||||
const fs = require('fs');
|
||||
|
||||
const reports = [
|
||||
['root', 'reports/npm-audit-root-before.json'],
|
||||
['frontend', 'reports/npm-audit-frontend-before.json']
|
||||
];
|
||||
let foundPackages = false;
|
||||
|
||||
for (const [label, reportPath] of reports) {
|
||||
if (!fs.existsSync(reportPath)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const report = JSON.parse(fs.readFileSync(reportPath, 'utf8'));
|
||||
const vulnerabilities = Object.entries(report.vulnerabilities || {})
|
||||
.sort(([left], [right]) => left.localeCompare(right));
|
||||
|
||||
if (vulnerabilities.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foundPackages = true;
|
||||
console.log('');
|
||||
console.log(`### ${label}`);
|
||||
|
||||
for (const [name, details] of vulnerabilities) {
|
||||
console.log(`- \`${name}\` (${details.severity})`);
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundPackages) {
|
||||
console.log('');
|
||||
console.log('No vulnerable packages were reported before the fix.');
|
||||
}
|
||||
NODE
|
||||
|
||||
{
|
||||
for pr_number in $pr_numbers; do
|
||||
if [ "$pr_number" = "$(echo "$pr_numbers" | head -1)" ]; then
|
||||
echo
|
||||
echo '## Replaced PRs'
|
||||
echo
|
||||
fi
|
||||
echo "Replaces #$pr_number"
|
||||
done
|
||||
} >> "$pr_body_file"
|
||||
|
||||
temp_branch="$BRANCH_PREFIX-$(date "+%Y%m%d%H%M%S")"
|
||||
|
||||
git switch -c "$temp_branch"
|
||||
git add package.json package-lock.json frontend/package.json frontend/package-lock.json
|
||||
git commit -m "Run npm audit fix"
|
||||
gh auth setup-git
|
||||
git push origin "$temp_branch"
|
||||
|
||||
gh pr create \
|
||||
--base "$BASE_BRANCH" \
|
||||
--head "$temp_branch" \
|
||||
--title "Run npm audit fix" \
|
||||
--body-file "$pr_body_file"
|
||||
|
||||
echo "Created a PR with npm audit fixes (${temp_branch}) against ${BASE_BRANCH}"
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: ruby/setup-ruby@c4e5b1316158f92e3d49443a9d58b31d25ac0f8f # v1
|
||||
- uses: ruby/setup-ruby@afeafc3d1ab54a631816aba4c914a0081c12ff2f # v1
|
||||
with:
|
||||
bundler-cache: false
|
||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Set up Ruby
|
||||
uses: ruby/setup-ruby@c4e5b1316158f92e3d49443a9d58b31d25ac0f8f # v1
|
||||
uses: ruby/setup-ruby@afeafc3d1ab54a631816aba4c914a0081c12ff2f # v1
|
||||
- name: Run Rubocop
|
||||
uses: reviewdog/action-rubocop@b6d5e953a5fc0bf3ab65254e77730ea2174d6d6d # v2
|
||||
with:
|
||||
|
||||
@@ -98,7 +98,7 @@ jobs:
|
||||
run: sudo apt-get update && sudo apt-get install -y libpq-dev
|
||||
|
||||
- name: Setup Ruby
|
||||
uses: ruby/setup-ruby@c4e5b1316158f92e3d49443a9d58b31d25ac0f8f # v1
|
||||
uses: ruby/setup-ruby@afeafc3d1ab54a631816aba4c914a0081c12ff2f # v1
|
||||
with:
|
||||
bundler-cache: true
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
- name: Cache DOCKER
|
||||
id: cache_docker
|
||||
uses: runs-on/cache@bd330c5a5f6cbb837823ee25864f3c71a211c2e3 # v5
|
||||
uses: runs-on/cache@88d90644011a3a9957fd141a106f5a94f9794203 # v5
|
||||
with:
|
||||
path: cache/docker
|
||||
# Note: no restore keys since whenever the files below change, we want to rebuild the full image from scratch
|
||||
@@ -47,28 +47,28 @@ jobs:
|
||||
if: steps.cache_docker.outputs.cache-hit == 'true'
|
||||
run: docker load -i cache/docker/image.tar
|
||||
- name: Cache GEM
|
||||
uses: runs-on/cache@bd330c5a5f6cbb837823ee25864f3c71a211c2e3 # v5
|
||||
uses: runs-on/cache@88d90644011a3a9957fd141a106f5a94f9794203 # v5
|
||||
with:
|
||||
path: cache/bundle
|
||||
key: gem-trixie-${{ hashFiles('.ruby-version') }}-${{ hashFiles('Gemfile.lock') }}
|
||||
restore-keys: |
|
||||
gem-trixie-${{ hashFiles('.ruby-version') }}-
|
||||
- name: Cache NPM
|
||||
uses: runs-on/cache@bd330c5a5f6cbb837823ee25864f3c71a211c2e3 # v5
|
||||
uses: runs-on/cache@88d90644011a3a9957fd141a106f5a94f9794203 # v5
|
||||
with:
|
||||
path: cache/node
|
||||
key: node-${{ hashFiles('package.json', 'frontend/package-lock.json') }}
|
||||
restore-keys: |
|
||||
node-
|
||||
- name: Cache ANGULAR
|
||||
uses: runs-on/cache@bd330c5a5f6cbb837823ee25864f3c71a211c2e3 # v5
|
||||
uses: runs-on/cache@88d90644011a3a9957fd141a106f5a94f9794203 # v5
|
||||
with:
|
||||
path: cache/angular
|
||||
key: angular-${{ hashFiles('package.json', 'frontend/package-lock.json') }}
|
||||
restore-keys: |
|
||||
angular-
|
||||
- name: Cache TEST RUNTIME
|
||||
uses: runs-on/cache@bd330c5a5f6cbb837823ee25864f3c71a211c2e3 # v5
|
||||
uses: runs-on/cache@88d90644011a3a9957fd141a106f5a94f9794203 # v5
|
||||
with:
|
||||
path: cache/runtime-logs
|
||||
key: runtime-logs-${{ github.head_ref || github.ref }}-${{ github.sha }}
|
||||
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Ruby
|
||||
uses: ruby/setup-ruby@c4e5b1316158f92e3d49443a9d58b31d25ac0f8f # v1
|
||||
uses: ruby/setup-ruby@afeafc3d1ab54a631816aba4c914a0081c12ff2f # v1
|
||||
|
||||
- name: Verify linked version matches core version
|
||||
id: version-check
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
name: yamllint
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- '.yamllint.yml'
|
||||
- 'config/locales/en.yml'
|
||||
- 'config/locales/js-en.yml'
|
||||
- 'modules/*/config/locales/en.yml'
|
||||
- 'modules/*/config/locales/js-en.yml'
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
rubocop:
|
||||
name: yamllint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Run Yamllint
|
||||
uses: reviewdog/action-yamllint@f01d8a48fd8d89f89895499fca2cff09f9e9e8c0 # v1.21.0
|
||||
with:
|
||||
github_token: ${{ secrets.github_token }}
|
||||
fail_level: error
|
||||
yamllint_flags: >
|
||||
.yamllint.yml
|
||||
config/locales/en.yml
|
||||
config/locales/js-en.yml
|
||||
modules/*/config/locales/en.yml
|
||||
modules/*/config/locales/js-en.yml
|
||||
@@ -27,4 +27,4 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Run zizmor 🌈
|
||||
uses: zizmorcore/zizmor-action@b1d7e1fb5de872772f31590499237e7cce841e8e # v0.5.3
|
||||
uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # v0.5.6
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
extends: default
|
||||
|
||||
rules:
|
||||
comments:
|
||||
require-starting-space: false
|
||||
key-ordering: {}
|
||||
line-length: disable
|
||||
@@ -162,7 +162,7 @@ gem "ttfunk", "~> 1.7.0" # remove after https://github.com/prawnpdf/prawn/issues
|
||||
# prawn implicitly depends on matrix gem no longer in ruby core with 3.1
|
||||
gem "matrix", "~> 0.4.3"
|
||||
|
||||
gem "mcp", "~> 0.14.0"
|
||||
gem "mcp", "~> 0.17.0"
|
||||
|
||||
gem "meta-tags", "~> 2.23.0"
|
||||
|
||||
@@ -200,7 +200,7 @@ gem "rack-timeout", "~> 0.7.0", require: "rack/timeout/base"
|
||||
|
||||
gem "nokogiri", "~> 1.19.2"
|
||||
|
||||
gem "carrierwave", "~> 2.2.6"
|
||||
gem "carrierwave", "~> 2.2.7"
|
||||
gem "carrierwave_direct", "~> 3.0.0"
|
||||
gem "fog-aws"
|
||||
gem "ssrf_filter", "~> 1.3"
|
||||
@@ -238,10 +238,10 @@ gem "yabeda-rails"
|
||||
|
||||
# opentelemetry
|
||||
gem "opentelemetry-exporter-otlp", "~> 0.34.0", require: false
|
||||
gem "opentelemetry-instrumentation-all", "~> 0.93.0", require: false
|
||||
gem "opentelemetry-instrumentation-all", "~> 0.94.0", require: false
|
||||
gem "opentelemetry-sdk", "~> 1.10", require: false
|
||||
|
||||
gem "view_component", "~> 4.10.0"
|
||||
gem "view_component", "~> 4.11.0"
|
||||
# Lookbook
|
||||
gem "lookbook", "2.3.14"
|
||||
|
||||
@@ -265,11 +265,10 @@ group :test do
|
||||
gem "rack-test", "~> 2.2.0"
|
||||
gem "shoulda-context", "~> 2.0"
|
||||
|
||||
gem "parallel_tests", "~> 5.7"
|
||||
# Test prof provides factories from code
|
||||
# and other niceties
|
||||
gem "test-prof", "~> 1.6.0"
|
||||
gem "turbo_tests", github: "opf/turbo_tests", ref: "2_2_5_with_patches"
|
||||
gem "turbo_tests", github: "opf/turbo_tests", ref: "with-patches"
|
||||
|
||||
gem "rack_session_access"
|
||||
gem "rspec", "~> 3.13.2"
|
||||
@@ -296,7 +295,7 @@ group :test do
|
||||
gem "rails-controller-testing", "~> 1.0.2"
|
||||
|
||||
gem "capybara", "~> 3.40.0"
|
||||
gem "capybara_accessible_selectors", git: "https://github.com/citizensadvice/capybara_accessible_selectors", tag: "v0.15.0"
|
||||
gem "capybara_accessible_selectors", git: "https://github.com/citizensadvice/capybara_accessible_selectors", tag: "v0.16.0"
|
||||
gem "capybara-screenshot", "~> 1.0.17"
|
||||
gem "cuprite", "~> 0.17.0"
|
||||
gem "rspec-wait"
|
||||
@@ -318,6 +317,8 @@ group :test do
|
||||
gem "equivalent-xml", "~> 0.6"
|
||||
gem "json_spec", "~> 1.1.4"
|
||||
gem "shoulda-matchers", "~> 7.0", require: nil
|
||||
|
||||
gem "parallel_tests", "~> 4.0"
|
||||
end
|
||||
|
||||
group :ldap do
|
||||
@@ -366,7 +367,7 @@ group :development, :test do
|
||||
gem "rubocop-factory_bot", require: false
|
||||
gem "rubocop-openproject", require: false
|
||||
gem "rubocop-performance", require: false
|
||||
gem "rubocop-rails", "~> 2.35.1"
|
||||
gem "rubocop-rails", "~> 2.35.2"
|
||||
gem "rubocop-rspec", require: false
|
||||
gem "rubocop-rspec_rails", require: false
|
||||
|
||||
|
||||
+102
-102
@@ -1,7 +1,7 @@
|
||||
GIT
|
||||
remote: https://github.com/citizensadvice/capybara_accessible_selectors
|
||||
revision: 5b9ce7840d04270e99f4f0cb03989e05437326a6
|
||||
tag: v0.15.0
|
||||
revision: 568699fc71b6648e7186a4ac77bba072447c131e
|
||||
tag: v0.16.0
|
||||
specs:
|
||||
capybara_accessible_selectors (0.15.0)
|
||||
capybara (~> 3.36)
|
||||
@@ -54,11 +54,11 @@ GIT
|
||||
|
||||
GIT
|
||||
remote: https://github.com/opf/turbo_tests.git
|
||||
revision: 4208e7372a7987c972dda634eb4f29ad9f1448e1
|
||||
ref: 2_2_5_with_patches
|
||||
revision: c1c4707f536a5642a168650d273d714dfb62d842
|
||||
ref: with-patches
|
||||
specs:
|
||||
turbo_tests (2.2.5)
|
||||
parallel_tests (>= 3.3.0, < 6)
|
||||
turbo_tests (2.2.0)
|
||||
parallel_tests (>= 3.3.0, < 5)
|
||||
rspec (>= 3.10)
|
||||
|
||||
GIT
|
||||
@@ -217,7 +217,7 @@ PATH
|
||||
remote: modules/two_factor_authentication
|
||||
specs:
|
||||
openproject-two_factor_authentication (1.0.0)
|
||||
aws-sdk-sns (>= 1.101, < 1.114)
|
||||
aws-sdk-sns (~> 1.116)
|
||||
messagebird-rest (>= 1.4.2, < 5.1.0)
|
||||
rotp (~> 6.1)
|
||||
webauthn (~> 3.0)
|
||||
@@ -361,8 +361,8 @@ GEM
|
||||
awesome_nested_set (3.9.0)
|
||||
activerecord (>= 4.0.0, < 8.2)
|
||||
aws-eventstream (1.4.0)
|
||||
aws-partitions (1.1249.0)
|
||||
aws-sdk-core (3.247.0)
|
||||
aws-partitions (1.1255.0)
|
||||
aws-sdk-core (3.250.0)
|
||||
aws-eventstream (~> 1, >= 1.3.0)
|
||||
aws-partitions (~> 1, >= 1.992.0)
|
||||
aws-sigv4 (~> 1.9)
|
||||
@@ -370,15 +370,15 @@ GEM
|
||||
bigdecimal
|
||||
jmespath (~> 1, >= 1.6.1)
|
||||
logger
|
||||
aws-sdk-kms (1.125.0)
|
||||
aws-sdk-core (~> 3, >= 3.247.0)
|
||||
aws-sdk-kms (1.128.0)
|
||||
aws-sdk-core (~> 3, >= 3.248.0)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sdk-s3 (1.222.0)
|
||||
aws-sdk-core (~> 3, >= 3.247.0)
|
||||
aws-sdk-s3 (1.224.0)
|
||||
aws-sdk-core (~> 3, >= 3.248.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sdk-sns (1.113.0)
|
||||
aws-sdk-core (~> 3, >= 3.244.0)
|
||||
aws-sdk-sns (1.116.0)
|
||||
aws-sdk-core (~> 3, >= 3.248.0)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sigv4 (1.12.1)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
@@ -425,7 +425,7 @@ GEM
|
||||
capybara-screenshot (1.0.27)
|
||||
capybara (>= 1.0, < 4)
|
||||
launchy
|
||||
carrierwave (2.2.6)
|
||||
carrierwave (2.2.7)
|
||||
activemodel (>= 5.0.0)
|
||||
activesupport (>= 5.0.0)
|
||||
addressable (~> 2.6)
|
||||
@@ -479,7 +479,7 @@ GEM
|
||||
capybara (~> 3.0)
|
||||
ferrum (~> 0.17.0)
|
||||
daemons (1.4.1)
|
||||
dalli (5.0.4)
|
||||
dalli (5.0.5)
|
||||
logger
|
||||
date (3.5.1)
|
||||
date_validator (0.12.0)
|
||||
@@ -497,7 +497,7 @@ GEM
|
||||
disposable (0.6.3)
|
||||
declarative (>= 0.0.9, < 1.0.0)
|
||||
representable (>= 3.1.1, < 4)
|
||||
doorkeeper (5.9.0)
|
||||
doorkeeper (5.9.1)
|
||||
railties (>= 5)
|
||||
dotenv (3.2.0)
|
||||
dotenv-rails (3.2.0)
|
||||
@@ -587,7 +587,7 @@ GEM
|
||||
logger
|
||||
faraday-follow_redirects (0.5.0)
|
||||
faraday (>= 1, < 3)
|
||||
faraday-net_http (3.4.2)
|
||||
faraday-net_http (3.4.3)
|
||||
net-http (~> 0.5)
|
||||
ferrum (0.17.2)
|
||||
addressable (~> 2.5)
|
||||
@@ -625,13 +625,13 @@ GEM
|
||||
friendly_id (5.7.0)
|
||||
activerecord (>= 4.0.0)
|
||||
front_matter_parser (1.0.1)
|
||||
fugit (1.12.1)
|
||||
fugit (1.12.2)
|
||||
et-orbi (~> 1.4)
|
||||
raabro (~> 1.4)
|
||||
fuubar (2.5.1)
|
||||
rspec-core (~> 3.0)
|
||||
ruby-progressbar (~> 1.4)
|
||||
glob (0.4.0)
|
||||
glob (0.5.0)
|
||||
globalid (1.3.0)
|
||||
activesupport (>= 6.1)
|
||||
good_job (4.18.2)
|
||||
@@ -649,34 +649,34 @@ GEM
|
||||
mini_mime (~> 1.1)
|
||||
representable (~> 3.0)
|
||||
retriable (~> 3.1)
|
||||
google-apis-gmail_v1 (0.49.0)
|
||||
google-apis-gmail_v1 (0.51.0)
|
||||
google-apis-core (>= 0.15.0, < 2.a)
|
||||
google-cloud-env (2.3.1)
|
||||
base64 (~> 0.2)
|
||||
faraday (>= 1.0, < 3.a)
|
||||
google-logging-utils (0.2.0)
|
||||
google-protobuf (4.34.1)
|
||||
google-protobuf (4.35.0)
|
||||
bigdecimal
|
||||
rake (~> 13.3)
|
||||
google-protobuf (4.34.1-aarch64-linux-gnu)
|
||||
google-protobuf (4.35.0-aarch64-linux-gnu)
|
||||
bigdecimal
|
||||
rake (~> 13.3)
|
||||
google-protobuf (4.34.1-aarch64-linux-musl)
|
||||
google-protobuf (4.35.0-aarch64-linux-musl)
|
||||
bigdecimal
|
||||
rake (~> 13.3)
|
||||
google-protobuf (4.34.1-arm64-darwin)
|
||||
google-protobuf (4.35.0-arm64-darwin)
|
||||
bigdecimal
|
||||
rake (~> 13.3)
|
||||
google-protobuf (4.34.1-x86_64-darwin)
|
||||
google-protobuf (4.35.0-x86_64-darwin)
|
||||
bigdecimal
|
||||
rake (~> 13.3)
|
||||
google-protobuf (4.34.1-x86_64-linux-gnu)
|
||||
google-protobuf (4.35.0-x86_64-linux-gnu)
|
||||
bigdecimal
|
||||
rake (~> 13.3)
|
||||
google-protobuf (4.34.1-x86_64-linux-musl)
|
||||
google-protobuf (4.35.0-x86_64-linux-musl)
|
||||
bigdecimal
|
||||
rake (~> 13.3)
|
||||
googleapis-common-protos-types (1.22.0)
|
||||
googleapis-common-protos-types (1.23.0)
|
||||
google-protobuf (~> 4.26)
|
||||
googleauth (1.16.2)
|
||||
faraday (>= 1.0, < 3.a)
|
||||
@@ -755,8 +755,8 @@ GEM
|
||||
jmespath (1.6.2)
|
||||
job-iteration (1.14.0)
|
||||
activejob (>= 7.1)
|
||||
json (2.19.5)
|
||||
json-jwt (1.17.0)
|
||||
json (2.19.7)
|
||||
json-jwt (1.17.1)
|
||||
activesupport (>= 4.2)
|
||||
aes_key_wrap
|
||||
base64
|
||||
@@ -829,7 +829,7 @@ GEM
|
||||
marcel (1.0.4)
|
||||
markly (0.16.0)
|
||||
matrix (0.4.3)
|
||||
mcp (0.14.0)
|
||||
mcp (0.17.0)
|
||||
json-schema (>= 4.1)
|
||||
messagebird-rest (5.0.0)
|
||||
jwt (< 4)
|
||||
@@ -846,7 +846,7 @@ GEM
|
||||
minitest (6.0.6)
|
||||
drb (~> 2.0)
|
||||
prism (~> 1.5)
|
||||
msgpack (1.8.0)
|
||||
msgpack (1.8.1)
|
||||
multi_json (1.20.1)
|
||||
mustermann (4.0.0)
|
||||
mustermann-grape (1.1.0)
|
||||
@@ -885,7 +885,7 @@ GEM
|
||||
oj (3.17.1)
|
||||
bigdecimal (>= 3.0)
|
||||
ostruct (>= 0.2)
|
||||
okcomputer (1.19.1)
|
||||
okcomputer (1.19.2)
|
||||
benchmark
|
||||
omniauth-saml (1.10.6)
|
||||
omniauth (~> 1.3, >= 1.3.2)
|
||||
@@ -954,7 +954,7 @@ GEM
|
||||
opentelemetry-instrumentation-active_support (~> 0.10)
|
||||
opentelemetry-instrumentation-active_support (0.12.0)
|
||||
opentelemetry-instrumentation-base (~> 0.25)
|
||||
opentelemetry-instrumentation-all (0.93.0)
|
||||
opentelemetry-instrumentation-all (0.94.0)
|
||||
opentelemetry-instrumentation-active_model_serializers (~> 0.25.0)
|
||||
opentelemetry-instrumentation-anthropic (~> 0.5.0)
|
||||
opentelemetry-instrumentation-aws_lambda (~> 0.7.0)
|
||||
@@ -991,14 +991,14 @@ GEM
|
||||
opentelemetry-instrumentation-ruby_kafka (~> 0.25.0)
|
||||
opentelemetry-instrumentation-sidekiq (~> 0.29.0)
|
||||
opentelemetry-instrumentation-sinatra (~> 0.30.0)
|
||||
opentelemetry-instrumentation-trilogy (~> 0.68.0)
|
||||
opentelemetry-instrumentation-trilogy (~> 0.69.0)
|
||||
opentelemetry-instrumentation-anthropic (0.5.0)
|
||||
opentelemetry-instrumentation-base (~> 0.25)
|
||||
opentelemetry-instrumentation-aws_lambda (0.7.0)
|
||||
opentelemetry-instrumentation-base (~> 0.25)
|
||||
opentelemetry-instrumentation-aws_sdk (0.12.0)
|
||||
opentelemetry-instrumentation-base (~> 0.25)
|
||||
opentelemetry-instrumentation-base (0.26.0)
|
||||
opentelemetry-instrumentation-base (0.26.1)
|
||||
opentelemetry-api (~> 1.7)
|
||||
opentelemetry-common (~> 0.21)
|
||||
opentelemetry-registry (~> 0.1)
|
||||
@@ -1020,9 +1020,9 @@ GEM
|
||||
opentelemetry-instrumentation-rack (~> 0.29)
|
||||
opentelemetry-instrumentation-graphql (0.32.0)
|
||||
opentelemetry-instrumentation-base (~> 0.25)
|
||||
opentelemetry-instrumentation-grpc (0.5.0)
|
||||
opentelemetry-instrumentation-grpc (0.5.1)
|
||||
opentelemetry-instrumentation-base (~> 0.25)
|
||||
opentelemetry-instrumentation-gruf (0.6.0)
|
||||
opentelemetry-instrumentation-gruf (0.6.1)
|
||||
opentelemetry-instrumentation-base (~> 0.25)
|
||||
opentelemetry-instrumentation-http (0.30.0)
|
||||
opentelemetry-instrumentation-base (~> 0.25)
|
||||
@@ -1051,7 +1051,7 @@ GEM
|
||||
opentelemetry-instrumentation-base (~> 0.25)
|
||||
opentelemetry-instrumentation-racecar (0.7.0)
|
||||
opentelemetry-instrumentation-base (~> 0.25)
|
||||
opentelemetry-instrumentation-rack (0.31.0)
|
||||
opentelemetry-instrumentation-rack (0.31.1)
|
||||
opentelemetry-instrumentation-base (~> 0.25)
|
||||
opentelemetry-instrumentation-rails (0.42.0)
|
||||
opentelemetry-instrumentation-action_mailer (~> 0.7)
|
||||
@@ -1078,7 +1078,7 @@ GEM
|
||||
opentelemetry-instrumentation-base (~> 0.25)
|
||||
opentelemetry-instrumentation-sinatra (0.30.0)
|
||||
opentelemetry-instrumentation-rack (~> 0.29)
|
||||
opentelemetry-instrumentation-trilogy (0.68.0)
|
||||
opentelemetry-instrumentation-trilogy (0.69.0)
|
||||
opentelemetry-helpers-mysql
|
||||
opentelemetry-helpers-sql
|
||||
opentelemetry-helpers-sql-processor
|
||||
@@ -1092,14 +1092,14 @@ GEM
|
||||
opentelemetry-common (~> 0.20)
|
||||
opentelemetry-registry (~> 0.2)
|
||||
opentelemetry-semantic_conventions
|
||||
opentelemetry-semantic_conventions (1.37.1)
|
||||
opentelemetry-semantic_conventions (1.38.0)
|
||||
opentelemetry-api (~> 1.0)
|
||||
optimist (3.2.1)
|
||||
os (1.1.4)
|
||||
ostruct (0.6.3)
|
||||
ox (2.14.26)
|
||||
bigdecimal (>= 3.0)
|
||||
pagy (43.5.4)
|
||||
pagy (43.5.5)
|
||||
json
|
||||
uri
|
||||
yaml
|
||||
@@ -1107,7 +1107,7 @@ GEM
|
||||
activerecord (>= 7.1)
|
||||
request_store (~> 1.4)
|
||||
parallel (2.1.0)
|
||||
parallel_tests (5.7.0)
|
||||
parallel_tests (4.10.1)
|
||||
parallel
|
||||
parser (3.3.11.1)
|
||||
ast (~> 2.4.1)
|
||||
@@ -1197,7 +1197,7 @@ GEM
|
||||
eventmachine_httpserver
|
||||
http_parser.rb (~> 0.8.0)
|
||||
multi_json
|
||||
puma (8.0.1)
|
||||
puma (8.0.2)
|
||||
nio4r (~> 2.0)
|
||||
puma-plugin-statsd (2.8.0)
|
||||
puma (>= 5.0, < 9)
|
||||
@@ -1335,7 +1335,7 @@ GEM
|
||||
rspec-support (3.13.7)
|
||||
rspec-wait (1.0.2)
|
||||
rspec (>= 3.4)
|
||||
rubocop (1.86.2)
|
||||
rubocop (1.87.0)
|
||||
json (~> 2.3)
|
||||
language_server-protocol (~> 3.17.0.2)
|
||||
lint_roller (~> 1.1.0)
|
||||
@@ -1355,13 +1355,13 @@ GEM
|
||||
rubocop-factory_bot (2.28.0)
|
||||
lint_roller (~> 1.1)
|
||||
rubocop (~> 1.72, >= 1.72.1)
|
||||
rubocop-openproject (0.4.0)
|
||||
rubocop-openproject (0.5.0)
|
||||
rubocop
|
||||
rubocop-performance (1.26.1)
|
||||
lint_roller (~> 1.1)
|
||||
rubocop (>= 1.75.0, < 2.0)
|
||||
rubocop-ast (>= 1.47.1, < 2.0)
|
||||
rubocop-rails (2.35.1)
|
||||
rubocop-rails (2.35.3)
|
||||
activesupport (>= 4.2.0)
|
||||
lint_roller (~> 1.1)
|
||||
rack (>= 1.1)
|
||||
@@ -1425,7 +1425,7 @@ GEM
|
||||
bigdecimal
|
||||
logger
|
||||
ruby-ole
|
||||
spring (4.5.0)
|
||||
spring (4.6.0)
|
||||
spring-commands-rspec (1.0.4)
|
||||
spring (>= 0.9.1)
|
||||
spring-commands-rubocop (0.4.0)
|
||||
@@ -1493,7 +1493,7 @@ GEM
|
||||
public_suffix
|
||||
vcr (6.4.0)
|
||||
vernier (1.10.1)
|
||||
view_component (4.10.0)
|
||||
view_component (4.11.0)
|
||||
actionview (>= 7.1.0)
|
||||
activesupport (>= 7.1.0)
|
||||
concurrent-ruby (~> 1)
|
||||
@@ -1554,8 +1554,8 @@ GEM
|
||||
railties
|
||||
yabeda (~> 0.8)
|
||||
yaml (0.4.0)
|
||||
yard (0.9.43)
|
||||
zeitwerk (2.7.5)
|
||||
yard (0.9.44)
|
||||
zeitwerk (2.8.2)
|
||||
|
||||
PLATFORMS
|
||||
aarch64-linux
|
||||
@@ -1594,7 +1594,7 @@ DEPENDENCIES
|
||||
capybara (~> 3.40.0)
|
||||
capybara-screenshot (~> 1.0.17)
|
||||
capybara_accessible_selectors!
|
||||
carrierwave (~> 2.2.6)
|
||||
carrierwave (~> 2.2.7)
|
||||
carrierwave_direct (~> 3.0.0)
|
||||
climate_control
|
||||
closure_tree (~> 9.7.0)
|
||||
@@ -1656,7 +1656,7 @@ DEPENDENCIES
|
||||
mail (= 2.9.0)
|
||||
markly (~> 0.15)
|
||||
matrix (~> 0.4.3)
|
||||
mcp (~> 0.14.0)
|
||||
mcp (~> 0.17.0)
|
||||
md_to_pdf!
|
||||
meta-tags (~> 2.23.0)
|
||||
mini_magick (~> 5.3.0)
|
||||
@@ -1700,13 +1700,13 @@ DEPENDENCIES
|
||||
openproject-wikis!
|
||||
openproject-xls_export!
|
||||
opentelemetry-exporter-otlp (~> 0.34.0)
|
||||
opentelemetry-instrumentation-all (~> 0.93.0)
|
||||
opentelemetry-instrumentation-all (~> 0.94.0)
|
||||
opentelemetry-sdk (~> 1.10)
|
||||
overviews!
|
||||
ox
|
||||
pagy
|
||||
paper_trail (~> 17.0.0)
|
||||
parallel_tests (~> 5.7)
|
||||
parallel_tests (~> 4.0)
|
||||
pdf-inspector (~> 1.2)
|
||||
pg (~> 1.6.2)
|
||||
plaintext (~> 0.3.7)
|
||||
@@ -1746,7 +1746,7 @@ DEPENDENCIES
|
||||
rubocop-factory_bot
|
||||
rubocop-openproject
|
||||
rubocop-performance
|
||||
rubocop-rails (~> 2.35.1)
|
||||
rubocop-rails (~> 2.35.2)
|
||||
rubocop-rspec
|
||||
rubocop-rspec_rails
|
||||
ruby-duration (~> 3.2.0)
|
||||
@@ -1784,7 +1784,7 @@ DEPENDENCIES
|
||||
validate_url
|
||||
vcr
|
||||
vernier
|
||||
view_component (~> 4.10.0)
|
||||
view_component (~> 4.11.0)
|
||||
warden (~> 1.2)
|
||||
warden-basic_auth (~> 0.2.1)
|
||||
webmock (~> 3.26)
|
||||
@@ -1830,11 +1830,11 @@ CHECKSUMS
|
||||
auto_strip_attributes (2.6.0) sha256=a7e2e0cf744de2bcd947fd68014220702bcc88c81274c1cd9ce6f7316aae39b0
|
||||
awesome_nested_set (3.9.0) sha256=3ce99e816550f97f4de118e621630070aacf24928b920fe4a68846578a8daaed
|
||||
aws-eventstream (1.4.0) sha256=116bf85c436200d1060811e6f5d2d40c88f65448f2125bc77ffce5121e6e183b
|
||||
aws-partitions (1.1249.0) sha256=f0bed070aba353e6ffe439953d6c354b3f51d16770386d2b198e71d34612f2e9
|
||||
aws-sdk-core (3.247.0) sha256=789864594ce8cef05ee3d81fa8ed506099280bda6ea12a7612b8b7c5e5e62851
|
||||
aws-sdk-kms (1.125.0) sha256=23f81bc0838ae6ec2e8de3eae88af521d0e29d3a59b6c9dbb4b21343ba476bc8
|
||||
aws-sdk-s3 (1.222.0) sha256=bae4b06fccf0b81b8d77e7abfc56e5e2146590d43c4ab58db0cee3ff5bbfb7f1
|
||||
aws-sdk-sns (1.113.0) sha256=15fe37d010e86f4c28b4c2f2133c463ce5c14189ec3673a1f43c30dfee511b0f
|
||||
aws-partitions (1.1255.0) sha256=490ffc1f032d3eac771cf4a94ab7a3c7999c5edbe6fb7660904e702e291c93b5
|
||||
aws-sdk-core (3.250.0) sha256=8df71164c7e5f2ff411782a3dc3a1ab5c0a73170284409ead111f385e9992963
|
||||
aws-sdk-kms (1.128.0) sha256=20ce3957f80c7b40b3115a7e902af52b15a6eef42fe6b2e19db72f8e8c9e5658
|
||||
aws-sdk-s3 (1.224.0) sha256=7d443c4ae9eda795b60e081d41f49b498e2483f1fc204f3239176ec922ed7879
|
||||
aws-sdk-sns (1.116.0) sha256=5536532b8f9d54c92a44d8f38fae84333e5eea9b53fdf30bee18cba99e29a9c2
|
||||
aws-sigv4 (1.12.1) sha256=6973ff95cb0fd0dc58ba26e90e9510a2219525d07620c8babeb70ef831826c00
|
||||
axe-core-api (4.11.3) sha256=f5f6e802743644a50e2d8ef24c22aefbb6df49dd169024ff0144b47f37e652ba
|
||||
axe-core-rspec (4.11.3) sha256=246c8d443517354e9a9962a10a8cc456bcef4c617516c0924b051a9af9d7da99
|
||||
@@ -1853,7 +1853,7 @@ CHECKSUMS
|
||||
capybara (3.40.0) sha256=42dba720578ea1ca65fd7a41d163dd368502c191804558f6e0f71b391054aeef
|
||||
capybara-screenshot (1.0.27) sha256=afa1896cc23df77be1774e8d3b3ce3953bf060aeaa04ff87607b5daf689174f2
|
||||
capybara_accessible_selectors (0.15.0)
|
||||
carrierwave (2.2.6) sha256=cd9b6108fc7544e97e7fbcc561bd319a09f23c96816fdd0df8f2f45ffdc0dac3
|
||||
carrierwave (2.2.7) sha256=1ede8f502f420e8a34ca1ccbbc9dc92ea159e94583e1194b4ea720a5ccc2d855
|
||||
carrierwave_direct (3.0.0) sha256=da4105ec7beea2687817b95ad95181be3d657248ec1cb5a0e5c35a45b176fc2f
|
||||
cbor (0.5.10.2) sha256=df5104f7a62c881123e6505441b1e276208be1771540c2cc3b1de8a210a7c52c
|
||||
cgi (0.5.1) sha256=e93fcafc69b8a934fe1e6146121fa35430efa8b4a4047c4893764067036f18e9
|
||||
@@ -1884,7 +1884,7 @@ CHECKSUMS
|
||||
csv (3.3.5) sha256=6e5134ac3383ef728b7f02725d9872934f523cb40b961479f69cf3afa6c8e73f
|
||||
cuprite (0.17) sha256=b140d5dc70d08b97ad54bcf45cd95d0bd430e291e9dffe76fff851fddd57c12b
|
||||
daemons (1.4.1) sha256=8fc76d76faec669feb5e455d72f35bd4c46dc6735e28c420afb822fac1fa9a1d
|
||||
dalli (5.0.4) sha256=3b258f100b8e3acd128a095fbd090c5a1324a6f5238fd1f86a2547473e6d49fc
|
||||
dalli (5.0.5) sha256=5f51502200999278806654c846054856d6d0b5607cc1d14d06c617042f71e6f9
|
||||
date (3.5.1) sha256=750d06384d7b9c15d562c76291407d89e368dda4d4fff957eb94962d325a0dc0
|
||||
date_validator (0.12.0) sha256=68c9834da240347b9c17441c553a183572508617ebfbe8c020020f3192ce3058
|
||||
deckar01-task_list (2.3.4) sha256=66abdc7e009ea759732bb53867e1ea42de550e2aa03ac30a015cbf42a04c1667
|
||||
@@ -1893,7 +1893,7 @@ CHECKSUMS
|
||||
descendants_tracker (0.0.4) sha256=e9c41dd4cfbb85829a9301ea7e7c48c2a03b26f09319db230e6479ccdc780897
|
||||
diff-lcs (1.6.2) sha256=9ae0d2cba7d4df3075fe8cd8602a8604993efc0dfa934cff568969efb1909962
|
||||
disposable (0.6.3) sha256=7f2a3fb251bff6cd83f25b164043d4ec3531209b51b066ed476a9df9c2d384cc
|
||||
doorkeeper (5.9.0) sha256=edad053b3dcb6bf51e3181fc423333e7fba28863beb2122379643ce6463b6336
|
||||
doorkeeper (5.9.1) sha256=f74a35af9da40e59cac7cfd72c6d927421ef171b6d6d9ca0babf66e3fa8b9c90
|
||||
dotenv (3.2.0) sha256=e375b83121ea7ca4ce20f214740076129ab8514cd81378161f11c03853fe619d
|
||||
dotenv-rails (3.2.0) sha256=657e25554ba622ffc95d8c4f1670286510f47f2edda9f68293c3f661b303beab
|
||||
drb (2.2.3) sha256=0b00d6fdb50995fe4a45dea13663493c841112e4068656854646f418fda13373
|
||||
@@ -1925,7 +1925,7 @@ CHECKSUMS
|
||||
factory_bot_rails (6.5.1) sha256=d3cc4851eae4dea8a665ec4a4516895045e710554d2b5ac9e68b94d351bc6d68
|
||||
faraday (2.14.2) sha256=73ccb9994a9e8648f010e32eca2ae82e41c57860aa10932cda29418b9e0223ad
|
||||
faraday-follow_redirects (0.5.0) sha256=5cde93c894b30943a5d2b93c2fe9284216a6b756f7af406a1e55f211d97d10ad
|
||||
faraday-net_http (3.4.2) sha256=f147758260d3526939bf57ecf911682f94926a3666502e24c69992765875906c
|
||||
faraday-net_http (3.4.3) sha256=9db13becec9312f345a769eeeecf9049c9287d54c0ae053d7235228993a4eec1
|
||||
ferrum (0.17.2) sha256=2c2540a850b211a46f4d81de21bfd62048f507e4c327d1807225c3823c17e6ee
|
||||
ffi (1.17.4-aarch64-linux-gnu) sha256=b208f06f91ffd8f5e1193da3cae3d2ccfc27fc36fba577baf698d26d91c080df
|
||||
ffi (1.17.4-aarch64-linux-musl) sha256=9286b7a615f2676245283aef0a0a3b475ae3aae2bb5448baace630bb77b91f39
|
||||
@@ -1943,23 +1943,23 @@ CHECKSUMS
|
||||
formatador (1.2.3) sha256=19fa898133c2c26cdbb5d09f6998c1e137ad9427a046663e55adfe18b950d894
|
||||
friendly_id (5.7.0) sha256=b8994416ebaceebc7c60bc556e84ef32a3a663e2e582eb04c429216cc98de4c4
|
||||
front_matter_parser (1.0.1) sha256=bae298bda01db95788a4d6452f1670a3d198c6716c8d3727db9a95533deb7b7b
|
||||
fugit (1.12.1) sha256=5898f478ede9b415f0804e42b8f3fd53f814bd85eebffceebdbc34e1107aaf68
|
||||
fugit (1.12.2) sha256=643f2bf28db263bd400cbf8e0dd8b76b2c9b94bdb130e12d2394de04d9c20e5e
|
||||
fuubar (2.5.1) sha256=b272a7804b282661c7fab583a3764f92543cb482c365ae39c685cd218fdd4880
|
||||
glob (0.4.0) sha256=893dc9e2d24abe13dda907ce0cda576f680ff382f2a6cf9e543f98ecbe29238c
|
||||
glob (0.5.0) sha256=6397ae620b2f71b00424ec6c880c92d5ddcf6c44e6035c0b610a59efe16418fd
|
||||
globalid (1.3.0) sha256=05c639ad6eb4594522a0b07983022f04aa7254626ab69445a0e493aa3786ff11
|
||||
good_job (4.18.2) sha256=7e557a15865fc7b7ad4ab71644cf1d4189a2a1869d3b381e5e88741c540beca6
|
||||
google-apis-core (1.0.2) sha256=ba4579aaadc902d6cc7bc8db88f566ab00f5e31ea87ab41e9f9a032c470f2629
|
||||
google-apis-gmail_v1 (0.49.0) sha256=f5b5e597c8018d9e4b8df37fb06a8df429bd2de5f0fc94fb21a4d32b139f6ff1
|
||||
google-apis-gmail_v1 (0.51.0) sha256=cec89406ee645e71697a0eb0237e972df213d4dde86a772b05979175c48a6d11
|
||||
google-cloud-env (2.3.1) sha256=0faac01eb27be78c2591d64433663b1a114f8f7af55a4f819755426cac9178e7
|
||||
google-logging-utils (0.2.0) sha256=675462b4ea5affa825a3442694ca2d75d0069455a1d0956127207498fca3df7b
|
||||
google-protobuf (4.34.1) sha256=347181542b8d659c60f028fa3791c9cccce651a91ad27782dbc5c5e374796cdc
|
||||
google-protobuf (4.34.1-aarch64-linux-gnu) sha256=f9c07607dc139c895f2792a7740fcd01cd94d4d7b0e0a939045b50d7999f0b1d
|
||||
google-protobuf (4.34.1-aarch64-linux-musl) sha256=db58e5a4a492b43c6614486aea31b7fb86955b175d1d48f28ebf388f058d78a9
|
||||
google-protobuf (4.34.1-arm64-darwin) sha256=2745061f973119e6e7f3c81a0c77025d291a3caa6585a2cd24a25bbc7bedb267
|
||||
google-protobuf (4.34.1-x86_64-darwin) sha256=4dc498376e218871613589c4d872400d42ad9ae0c700bdb2606fe1c77a593075
|
||||
google-protobuf (4.34.1-x86_64-linux-gnu) sha256=87088c9fd8e47b5b40ca498fc1195add6149e941ff7e81c532a5b0b8876d4cc9
|
||||
google-protobuf (4.34.1-x86_64-linux-musl) sha256=8c0e91436fbe504ffc64f0bd621f2e69adbcce8ed2c58439d7a21117069cfdd7
|
||||
googleapis-common-protos-types (1.22.0) sha256=f97492b77bd6da0018c860d5004f512fe7cd165554d7019a8f4df6a56fbfc4c7
|
||||
google-protobuf (4.35.0) sha256=95346162c792ed78c9a28cbf2d937a53f706de6df36a27471582f63f03c30c0d
|
||||
google-protobuf (4.35.0-aarch64-linux-gnu) sha256=2cabd61b420918aec1564f9f7414e455bf922d20c5f8139d62783df5558a7aea
|
||||
google-protobuf (4.35.0-aarch64-linux-musl) sha256=f6b11f3420a4564f68e8233c95eac6924f6a727dfc2f81a56af2e09add551501
|
||||
google-protobuf (4.35.0-arm64-darwin) sha256=66ab26d3fc82b8950702e53ab16c198e3c0ea3f2a38aaaf1f32152da45593ac5
|
||||
google-protobuf (4.35.0-x86_64-darwin) sha256=05eb5c8bc9899135befff496fc0a3642e7ff3d0943f043841dcc456f5654fea0
|
||||
google-protobuf (4.35.0-x86_64-linux-gnu) sha256=999226f3b00cd9fddb1b26851d16060212fa1d90c406aaad47e574682b716059
|
||||
google-protobuf (4.35.0-x86_64-linux-musl) sha256=be0218520d77b2aee898b363514b03819f6f63f9c041ae0d0d79b4ce5247bffd
|
||||
googleapis-common-protos-types (1.23.0) sha256=992e740a523794d9fc5f29a504465d8fc737aaa16c930fe7228e3346860faf0a
|
||||
googleauth (1.16.2) sha256=15009502e2e38af71948cda918f230e27d327f6882a1e47967a5a4664930a638
|
||||
grape (3.2.1) sha256=448072f55904e5a4dca2e3781f0a373942514be65402cafb6177f5bc73db1b94
|
||||
grape_logging (3.0.0) sha256=7b62d984ce96df15d120508668debe307e6a59ac1c511f1d9b5f3b4bea793e13
|
||||
@@ -1991,8 +1991,8 @@ CHECKSUMS
|
||||
iso8601 (0.13.0) sha256=298c2b15b7be5fa95a1372813d36a2257656cd8e906dfbc1f5cb409851425aa2
|
||||
jmespath (1.6.2) sha256=238d774a58723d6c090494c8879b5e9918c19485f7e840f2c1c7532cf84ebcb1
|
||||
job-iteration (1.14.0) sha256=f154f978109acc838c0359ecde2fdd4dccc3382f95a22e03a58ac561a3615224
|
||||
json (2.19.5) sha256=218a18553e4801d579ca7e0f5bc72bafd776d7397238a1fb4e74db5b0a812c59
|
||||
json-jwt (1.17.0) sha256=6ff99026b4c54281a9431179f76ceb81faa14772d710ef6169785199caadc4cc
|
||||
json (2.19.7) sha256=fe432c8639f6efff69f9d73b518a3705d9581ab93156f981ea72806e1e5bcc3e
|
||||
json-jwt (1.17.1) sha256=5e1ced0f7b206b4c567efee19e6503c1426a819749132926cda579ec013d1f46
|
||||
json-schema (6.2.0) sha256=e8bff46ed845a22c1ab2bd0d7eccf831c01fe23bb3920caa4c74db4306813666
|
||||
json_schemer (2.5.0) sha256=2f01fb4cce721a4e08dd068fc2030cffd0702a7f333f1ea2be6e8991f00ae396
|
||||
json_spec (1.1.5) sha256=7a77b97a92c787e2aa3fbc4a1239afc3342c781151dc98cfb81461b3b7cad10f
|
||||
@@ -2013,7 +2013,7 @@ CHECKSUMS
|
||||
marcel (1.0.4) sha256=0d5649feb64b8f19f3d3468b96c680bae9746335d02194270287868a661516a4
|
||||
markly (0.16.0) sha256=6f70d79e385b1efc9e171f74c81628826259039fe6c778e03c3924c71dac5511
|
||||
matrix (0.4.3) sha256=a0d5ab7ddcc1973ff690ab361b67f359acbb16958d1dc072b8b956a286564c5b
|
||||
mcp (0.14.0) sha256=9e3ca2e6b5e568739e8c07090982829896f2e4d884ffbb668d06f0fe758489e1
|
||||
mcp (0.17.0) sha256=a66b71254cd8c49c471cfa55f11a9f050817ab7168cadf961555784d365b8474
|
||||
md_to_pdf (0.2.6)
|
||||
messagebird-rest (5.0.0) sha256=da4cc1efba3d5e4aa021fad07426c2cb6b326ce5670da5104bb8f6056a39d59c
|
||||
meta-tags (2.23.0) sha256=ffe78b5bee398de4ff5ac3316f5a786049538a651643b8476def06c3acc762c1
|
||||
@@ -2023,7 +2023,7 @@ CHECKSUMS
|
||||
mini_magick (5.3.1) sha256=29395dfd76badcabb6403ee5aff6f681e867074f8f28ce08d78661e9e4a351c4
|
||||
mini_mime (1.1.5) sha256=8681b7e2e4215f2a159f9400b5816d85e9d8c6c6b491e96a12797e798f8bccef
|
||||
minitest (6.0.6) sha256=153ea36d1d987a62942382b61075745042a2b3123b1cd48f4c3675af9cc7d6f1
|
||||
msgpack (1.8.0) sha256=e64ce0212000d016809f5048b48eb3a65ffb169db22238fb4b72472fecb2d732
|
||||
msgpack (1.8.1) sha256=3fef787cd3965fd119c08a22724a56a93ca25008c3421fc15039f603a8b7c86c
|
||||
multi_json (1.20.1) sha256=2f3934e805cc45ef91b551a1f89d0e9191abd06a5e04a2ef09a6a036c452ca6d
|
||||
mustermann (4.0.0) sha256=91f67411bb208d1d93c41e6128cb3b0f8ddd9ec7c45966f1007e1c43c08040d7
|
||||
mustermann-grape (1.1.0) sha256=8d258a986004c8f01ce4c023c0b037c168a9ed889cf5778068ad54398fa458c5
|
||||
@@ -2044,7 +2044,7 @@ CHECKSUMS
|
||||
nokogiri (1.19.3-x86_64-linux-gnu) sha256=2f5078620fe12e83669b5b17311b32532a8153d02eee7ad06948b926d6080976
|
||||
nokogiri (1.19.3-x86_64-linux-musl) sha256=248c906d2166eca5efb56d52fdee5f9a1f51d69a72e2b64fdac647b4ce39ea3f
|
||||
oj (3.17.1) sha256=b00687f10bf68a32bfb633b87624174faf0989a5c96aff2f3f96f992717ce782
|
||||
okcomputer (1.19.1) sha256=7df770e768434816d228407f0786563827cbf34cb379933578829720cb4f1e77
|
||||
okcomputer (1.19.2) sha256=d069aedf1e31b8ebe7e1fdf9e327dee158ea49b9fbdebebc2f1bed4690cb7a6d
|
||||
omniauth (1.9.2)
|
||||
omniauth-openid-connect (0.5.0)
|
||||
omniauth-openid_connect-providers (0.2.0)
|
||||
@@ -2096,11 +2096,11 @@ CHECKSUMS
|
||||
opentelemetry-instrumentation-active_record (0.13.0) sha256=239fceaae5a42e82dd9dd87bc63b1888bea32058dac0779b5ea36110fcb3a299
|
||||
opentelemetry-instrumentation-active_storage (0.5.0) sha256=39920d405fd111cd98c01a90a24b19413f4cb5bc8de2a24d7882f785e8f02c19
|
||||
opentelemetry-instrumentation-active_support (0.12.0) sha256=29a2cbdcb3aad4a42f4c9e829dab11167b71ed8a5205ad54587fe4d59d8ee704
|
||||
opentelemetry-instrumentation-all (0.93.0) sha256=e1f918add0d5ec48502cb2fbb49c122457fc2bd2cb54ee85e2db872308bfcb24
|
||||
opentelemetry-instrumentation-all (0.94.0) sha256=f1d1bb66638c57a4a38bf8f1a26eaa80094a35c26a839ab990b88f238809936f
|
||||
opentelemetry-instrumentation-anthropic (0.5.0) sha256=a6b1e1f324d35323d4714a7f204c9d34c46da07529877a725829420a4a44bb13
|
||||
opentelemetry-instrumentation-aws_lambda (0.7.0) sha256=50e5a32c454f2d38ecb53cc94e77dc646b33c47294cc6e6363e7c226097fa132
|
||||
opentelemetry-instrumentation-aws_sdk (0.12.0) sha256=e2f48bf471cefe4d4bd9cfdafabffce65790b73381040e82d337933ac8bfb366
|
||||
opentelemetry-instrumentation-base (0.26.0) sha256=fdec8bff9a8de04d113bd4e8d490b17414c92d6c79dd457dfa079c97ba922be0
|
||||
opentelemetry-instrumentation-base (0.26.1) sha256=49e8d35d565e1a13b4a6b794a4cca4ed8c590d9b33bda5244249bd98c8673775
|
||||
opentelemetry-instrumentation-bunny (0.25.0) sha256=a8b20b7b4cdbfcfd64036b41c160061b164da2938aa7a7621849ee9f7ecb81b8
|
||||
opentelemetry-instrumentation-concurrent_ruby (0.25.0) sha256=722912e7078e3025a84a25d0b6085702417598c58187c19a762234702cbf7b2d
|
||||
opentelemetry-instrumentation-dalli (0.30.0) sha256=5e2fc0ef1f7eb684c6a987789ad0bad22ea9350376e134cd7c803e5eb02776ee
|
||||
@@ -2110,8 +2110,8 @@ CHECKSUMS
|
||||
opentelemetry-instrumentation-faraday (0.33.0) sha256=f4320bece35997b8ce2ba520eaf52499b89a0c048fce9ce0a10c1ae5d783f801
|
||||
opentelemetry-instrumentation-grape (0.7.0) sha256=1b7dddd8e2baad62de6cd20fc924089fb5b8953e23ba41b83b4116ad5bcc03bf
|
||||
opentelemetry-instrumentation-graphql (0.32.0) sha256=c3af73b42ac5ac873476f2c4c4cf46de2fb81bb74612cea61214a9911708afaa
|
||||
opentelemetry-instrumentation-grpc (0.5.0) sha256=09bd9ccbedea4668e80546a24dbc1b148fd3775658228050f7378fc0e03b863d
|
||||
opentelemetry-instrumentation-gruf (0.6.0) sha256=b2a2455b2c622962fc27a943572ee0c660297206bc5eb51b61095d5eb37eae0c
|
||||
opentelemetry-instrumentation-grpc (0.5.1) sha256=e7a40d1b394745bf638aba27f2c207b439c9e7fd1196f713c731e91697d19bc1
|
||||
opentelemetry-instrumentation-gruf (0.6.1) sha256=875407e0063f874a01768ed567b13913ab8d03115bb9ff76f179149abc597dd5
|
||||
opentelemetry-instrumentation-http (0.30.0) sha256=36d2639eab81d386b25e99e0d91fe31888a255159a1213b9648e8359751055c8
|
||||
opentelemetry-instrumentation-http_client (0.29.0) sha256=92363f0aa7a4286cb02e551c483078a2d5323e9f4ca2b706f2066834f7793d3d
|
||||
opentelemetry-instrumentation-httpx (0.8.0) sha256=694b6e3eb6df04f1534d7713b5bd67ab5c2e2f2d5438f2c972542b1617378fec
|
||||
@@ -2123,7 +2123,7 @@ CHECKSUMS
|
||||
opentelemetry-instrumentation-pg (0.36.0) sha256=f7346f8c4377c053ca2720ac112eacef884840e3f97621ec6f553f3cb23baec7
|
||||
opentelemetry-instrumentation-que (0.13.0) sha256=95e04b8c17e89eaa446f5dff7975cf72c2e74f7e1e84717be47facf510613112
|
||||
opentelemetry-instrumentation-racecar (0.7.0) sha256=93a991917687aa0a6e785ccd8d0de5746af110122552b2bf1e3abe12ab04584a
|
||||
opentelemetry-instrumentation-rack (0.31.0) sha256=bae9f424a2bb1dde2aa5e2c7e02dbbb80c972e7f4964ab820431229f276e0bfc
|
||||
opentelemetry-instrumentation-rack (0.31.1) sha256=29a9c145ce6d3fa2938f30334209787c29b581a51fdb472e2098a1d69e15536a
|
||||
opentelemetry-instrumentation-rails (0.42.0) sha256=5ea3808373ca73ee9fa4ecf337471bad1c28e98ea64460b89ab225f5b6eaf8b3
|
||||
opentelemetry-instrumentation-rake (0.6.0) sha256=71746e4e172560f8ccf1d3c91354196f5aa0fd9b0c477d6ff17a451cef901822
|
||||
opentelemetry-instrumentation-rdkafka (0.10.0) sha256=ad1a4aa78c0ab43c1f130d961f54e1d6ac20c674b0180b87d4364770b81ec209
|
||||
@@ -2133,19 +2133,19 @@ CHECKSUMS
|
||||
opentelemetry-instrumentation-ruby_kafka (0.25.0) sha256=33ceccd5cef4f648e652fa45896d0b014da1a71b3a80f17064829ee5aa84e285
|
||||
opentelemetry-instrumentation-sidekiq (0.29.0) sha256=b1d2a0cb9041a5e14239fe7c94d99e3dd07f870e2759460ab63592d7cdd8aadc
|
||||
opentelemetry-instrumentation-sinatra (0.30.0) sha256=b67301153420f43264a0c68cdb3ca5bd77467cf5054e57b83a2bf891aaaa0361
|
||||
opentelemetry-instrumentation-trilogy (0.68.0) sha256=24b31efdf21a08644ad26038b574f4b0876195b1502f3f64b1065eff3fe0f588
|
||||
opentelemetry-instrumentation-trilogy (0.69.0) sha256=0676dd720eeab284abfa52f273967442156fcac7084a1e1411373cf14ec026ad
|
||||
opentelemetry-registry (0.6.0) sha256=5d3ed32ab9eee0fbdb30d4f0d0bb61ad11a4040b267b475ae815b80a8498a728
|
||||
opentelemetry-sdk (1.12.0) sha256=a224abe0c59023d41cb7ac1c634d9d28843907efcd045ed1ae320796c48b864b
|
||||
opentelemetry-semantic_conventions (1.37.1) sha256=74e87b31ab88c38410c5447d3bc5e692dcc0a73ee2114910c0142fc4546531c1
|
||||
opentelemetry-semantic_conventions (1.38.0) sha256=f0c2275a9c921ad7b5e70b658e269bfc6d46e3471d0636e1529fd7f34ae9a663
|
||||
optimist (3.2.1) sha256=8cf8a0fd69f3aa24ab48885d3a666717c27bc3d9edd6e976e18b9d771e72e34e
|
||||
os (1.1.4) sha256=57816d6a334e7bd6aed048f4b0308226c5fb027433b67d90a9ab435f35108d3f
|
||||
ostruct (0.6.3) sha256=95a2ed4a4bd1d190784e666b47b2d3f078e4a9efda2fccf18f84ddc6538ed912
|
||||
overviews (1.0.0)
|
||||
ox (2.14.26) sha256=d4a9cb49fb55751631d469623cfb2e7dad7cea89119c93749c1e31a3fb2f4896
|
||||
pagy (43.5.4) sha256=2bdf3fa6b1e0cac5bbafe5d077fb24eb971f72f3194f8c6863a0f3867261ce59
|
||||
pagy (43.5.5) sha256=86790336da3a1966e3503c226989577f1f56e3353dad10353445e8c988412548
|
||||
paper_trail (17.0.0) sha256=1c2842061d3874ca7015908e821e2aa14f9b982af2acb2a7974713bf79021c85
|
||||
parallel (2.1.0) sha256=b35258865c2e31134c5ecb708beaaf6772adf9d5efae28e93e99260877b09356
|
||||
parallel_tests (5.7.0) sha256=3f1762c46ca2c223b8af8ef877217f9d76974e191bfa934f2580b58bcf1d005c
|
||||
parallel_tests (4.10.1) sha256=df05458c691462b210f7a41fc2651d4e4e8a881e8190e6d1e122c92c07735d70
|
||||
parser (3.3.11.1) sha256=d17ace7aabe3e72c3cc94043714be27cc6f852f104d81aa284c2281aecc65d54
|
||||
pdf-core (0.9.0) sha256=4f368b2f12b57ec979872d4bf4bd1a67e8648e0c81ab89801431d2fc89f4e0bb
|
||||
pdf-inspector (1.3.0) sha256=fc107579d6f29b636e2da3d6743479b2624d9e390bf2d84beef8fd4ebe1a05bd
|
||||
@@ -2177,7 +2177,7 @@ CHECKSUMS
|
||||
psych (5.3.1) sha256=eb7a57cef10c9d70173ff74e739d843ac3b2c019a003de48447b2963d81b1974
|
||||
public_suffix (7.0.5) sha256=1a8bb08f1bbea19228d3bed6e5ed908d1cb4f7c2726d18bd9cadf60bc676f623
|
||||
puffing-billy (4.0.4) sha256=87015b0c41e0722b2171a0c5aa8130fd3f58aa1c016a1dc6dc569b2028aa846f
|
||||
puma (8.0.1) sha256=7b94e50c07655718c1fb8ae41a11fc06c7d61293208b3aa608ff71a46d3ad37c
|
||||
puma (8.0.2) sha256=c8ed871dfbbe66448ea9ffd46692342d9804d4071522b52b5331b7b6e7b686fb
|
||||
puma-plugin-statsd (2.8.0) sha256=e515445f93232b6b3571a23b832f93a776d4ce0fc8a5edee798013b82f3488f3
|
||||
raabro (1.4.0) sha256=d4fa9ff5172391edb92b242eed8be802d1934b1464061ae5e70d80962c5da882
|
||||
racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f
|
||||
@@ -2230,13 +2230,13 @@ CHECKSUMS
|
||||
rspec-retry (0.6.2) sha256=6101ba23a38809811ae3484acde4ab481c54d846ac66d5037ccb40131a60d858
|
||||
rspec-support (3.13.7) sha256=0640e5570872aafefd79867901deeeeb40b0c9875a36b983d85f54fb7381c47c
|
||||
rspec-wait (1.0.2) sha256=865f921239325d3d26fc10ded4bdd485d8b58bcaaad1a28dd85ed15266b5a912
|
||||
rubocop (1.86.2) sha256=bb2e97f635eda42c448f2588f4a6ff78f221b8bdfdf65b1e9b07fbd57521b45d
|
||||
rubocop (1.87.0) sha256=b9d9ddf55116a513f8ef2c7ae660662d8b49301f118d3f0df61865b33a5c188d
|
||||
rubocop-ast (1.49.1) sha256=4412f3ee70f6fe4546cc489548e0f6fcf76cafcfa80fa03af67098ffed755035
|
||||
rubocop-capybara (2.23.0) sha256=f9ea1ba3a7561ee8e88cf76fc378ce517ce5327155f305ee7b5c2500e5aee357
|
||||
rubocop-factory_bot (2.28.0) sha256=4b17fc02124444173317e131759d195b0d762844a71a29fe8139c1105d92f0cb
|
||||
rubocop-openproject (0.4.0) sha256=ce56d9e591f9be5a4d98125b10a73564b0557a5e408f97918f9630fb15ae66ae
|
||||
rubocop-openproject (0.5.0) sha256=564d4a0d240bec8543c96d1cb38facba9afd3fbe7e91ff28d254d3ebf329c323
|
||||
rubocop-performance (1.26.1) sha256=cd19b936ff196df85829d264b522fd4f98b6c89ad271fa52744a8c11b8f71834
|
||||
rubocop-rails (2.35.1) sha256=4ad270f5fb968ce86ac19f53c4b97354e4f91bc5334177478238674f51568377
|
||||
rubocop-rails (2.35.3) sha256=6edd45410866912b9b2e90ae3aeafd31d576df2bb2a9c9408f1667a50c32c7de
|
||||
rubocop-rspec (3.9.0) sha256=8fa70a3619408237d789aeecfb9beef40576acc855173e60939d63332fdb55e2
|
||||
rubocop-rspec_rails (2.32.0) sha256=4a0d641c72f6ebb957534f539d9d0a62c47abd8ce0d0aeee1ef4701e892a9100
|
||||
ruby-duration (3.2.3) sha256=eb3d13b1df85067a015a8fb2ed8f1eec842a3b721e47c9b6fd74d2f356069784
|
||||
@@ -2262,7 +2262,7 @@ CHECKSUMS
|
||||
simpleidn (0.2.3) sha256=08ce96f03fa1605286be22651ba0fc9c0b2d6272c9b27a260bc88be05b0d2c29
|
||||
smart_properties (1.17.0) sha256=f9323f8122e932341756ddec8e0ac9ec6e238408a7661508be99439ca6d6384b
|
||||
spreadsheet (1.3.5) sha256=cd83ea66803d9cae4ac258dfe16cd8c2b85da33eec18a6d7b48fd4a45840ab7d
|
||||
spring (4.5.0) sha256=9124b954f6d079cb9aa610d7069cbada7c146c9368599557806a9ff7067b2e1b
|
||||
spring (4.6.0) sha256=e9e41f9a56fa5f111df7c85110a7d606ca731dc116c906ba395fd74c9fd474e3
|
||||
spring-commands-rspec (1.0.4) sha256=6202e54fa4767452e3641461a83347645af478bf45dddcca9737b43af0dd1a2c
|
||||
spring-commands-rubocop (0.4.0) sha256=3e677a2c8a27ae8a986f04bfb69e66d5d55b017541e8be93bf0dc48a7f5690c1
|
||||
sprockets (3.7.5) sha256=72c20f256548f8a37fe7db41d96be86c3262fddaf4ebe9d69ec8317394fed383
|
||||
@@ -2291,7 +2291,7 @@ CHECKSUMS
|
||||
ttfunk (1.7.0) sha256=2370ba484b1891c70bdcafd3448cfd82a32dd794802d81d720a64c15d3ef2a96
|
||||
turbo-rails (2.0.23) sha256=ee0d90733aafff056cf51ff11e803d65e43cae258cc55f6492020ec1f9f9315f
|
||||
turbo_power (0.7.0) sha256=ad95d147e0fa761d0023ad9ca00528c7b7ddf6bba8ca2e23755d5b21b290d967
|
||||
turbo_tests (2.2.5)
|
||||
turbo_tests (2.2.0)
|
||||
tzinfo (2.0.6) sha256=8daf828cc77bcf7d63b0e3bdb6caa47e2272dcfaf4fbfe46f8c3a9df087a829b
|
||||
tzinfo-data (1.2026.2) sha256=7db0d3d3d53b8d7601fc183fccc8c6d056a3004e14eb59ea995bf6aec4ae10bc
|
||||
uber (0.1.0) sha256=5beeb407ff807b5db994f82fa9ee07cfceaa561dad8af20be880bc67eba935dc
|
||||
@@ -2303,7 +2303,7 @@ CHECKSUMS
|
||||
validate_url (1.0.15) sha256=72fe164c0713d63a9970bd6700bea948babbfbdcec392f2342b6704042f57451
|
||||
vcr (6.4.0) sha256=077ac92cc16efc5904eb90492a18153b5e6ca5398046d8a249a7c96a9ea24ae6
|
||||
vernier (1.10.1) sha256=f2ab2e163b75ac3ef4e3173262d27d27da6219cf04b1b4a7171e4798114275ce
|
||||
view_component (4.10.0) sha256=9e86960ee7ea4da3d1d07f65b1a66e4a9fb656be5434f1a649eb6fce6ac9baf9
|
||||
view_component (4.11.0) sha256=92df960575b8fa5e984522532df225ec333015a07a8105b3b1c200c655065517
|
||||
virtus (2.0.0) sha256=8841dae4eb7fcc097320ba5ea516bf1839e5d056c61ee27138aa4bddd6e3d1c2
|
||||
warden (1.2.9) sha256=46684f885d35a69dbb883deabf85a222c8e427a957804719e143005df7a1efd0
|
||||
warden-basic_auth (0.2.1) sha256=bfc752e0109c0182c3e69e930284c5e1e81e7b4a354aeb2b5914ead1391f3c6e
|
||||
@@ -2323,8 +2323,8 @@ CHECKSUMS
|
||||
yabeda-puma-plugin (0.9.0) sha256=b78673ecc7ee30bc50691ddc41b7022c1c1801843900d5101418f4a14b550bc8
|
||||
yabeda-rails (0.11.0) sha256=afa2581bd44c8f419cb3f2bbf9f6fb40f817c30476f7caf5d1c55c48d69a5b29
|
||||
yaml (0.4.0) sha256=240e69d1e6ce3584d6085978719a0faa6218ae426e034d8f9b02fb54d3471942
|
||||
yard (0.9.43) sha256=cf8733a8f0485df2a162927e9b5f182215a61f6d22de096b8f402c726a1c5821
|
||||
zeitwerk (2.7.5) sha256=d8da92128c09ea6ec62c949011b00ed4a20242b255293dd66bf41545398f73dd
|
||||
yard (0.9.44) sha256=eb087e9b631ccd887b049f303d489963945452d5e2a7eb49a5a74a7cf6887f28
|
||||
zeitwerk (2.8.2) sha256=7212a61311083c604184b1ea2574b9aa05cd14f855a0841c06985cabe9181d12
|
||||
|
||||
RUBY VERSION
|
||||
ruby 4.0.2
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
@import "enterprise_edition/banner_component"
|
||||
@import "filter/filters_component"
|
||||
@import "open_project/common/border_box_list_component"
|
||||
@import "op_primer/border_box_table_component"
|
||||
@import "op_primer/full_page_prompt_component"
|
||||
@import "op_primer/form_helpers"
|
||||
@@ -9,6 +7,7 @@
|
||||
@import "open_project/common/attribute_help_text_component"
|
||||
@import "open_project/common/attribute_help_text_caption_component"
|
||||
@import "open_project/common/attribute_label_component"
|
||||
@import "open_project/common/border_box_list_component"
|
||||
@import "open_project/common/inplace_edit_fields/index"
|
||||
@import "open_project/common/submenu_component"
|
||||
@import "open_project/common/main_menu_toggle_component"
|
||||
|
||||
@@ -27,7 +27,7 @@ See COPYRIGHT and LICENSE files for more details.
|
||||
|
||||
++#%>
|
||||
|
||||
<%= component_wrapper(tag: "tr", class: row_css_class, data: { turbo: true }) do %>
|
||||
<%= component_wrapper(tag: "tr", class: row_css_class) do %>
|
||||
<% columns.each do |column| %>
|
||||
<td class="<%= column_css_class(column) %>">
|
||||
<%= column_value(column) %>
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
<%=
|
||||
render(
|
||||
Primer::OpenProject::DangerDialog.new(
|
||||
id: DIALOG_ID,
|
||||
title: t("working_days.dialog.title"),
|
||||
confirm_button_text: t("working_days.dialog.confirm_button"),
|
||||
form_arguments:,
|
||||
size: :medium_portrait
|
||||
)
|
||||
) do |dialog|
|
||||
dialog.with_confirmation_message do |message|
|
||||
message.with_heading(tag: :h2) { t("working_days.dialog.heading") }
|
||||
message.with_description_content(t("working_days.dialog.description"))
|
||||
end
|
||||
|
||||
dialog.with_additional_details do
|
||||
concat hidden_settings_fields
|
||||
concat(
|
||||
tag.div do
|
||||
if removed_non_working_days.any?
|
||||
concat tag.p(t("working_days.dialog.removed_title"))
|
||||
concat(
|
||||
render(
|
||||
Primer::BaseComponent.new(
|
||||
tag: :ul,
|
||||
mb: 3
|
||||
)
|
||||
) do
|
||||
safe_join(removed_non_working_days.map { |non_working_day| tag.li(non_working_day) })
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
concat tag.p(t("working_days.dialog.warning"))
|
||||
end
|
||||
)
|
||||
end
|
||||
end
|
||||
%>
|
||||
@@ -0,0 +1,78 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# 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; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# 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, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
module Admin
|
||||
module Settings
|
||||
module WorkingDays
|
||||
class ConfirmDialogComponent < ApplicationComponent
|
||||
include OpTurbo::Streamable
|
||||
|
||||
DIALOG_ID = "working-days-change-dialog"
|
||||
|
||||
attr_reader :form_values, :removed_non_working_days
|
||||
|
||||
def initialize(form_values: {}, removed_non_working_days: [])
|
||||
super
|
||||
@form_values = form_values
|
||||
@removed_non_working_days = removed_non_working_days
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def form_arguments
|
||||
{
|
||||
action: helpers.admin_settings_working_days_and_hours_path,
|
||||
method: :patch,
|
||||
data: { turbo: false }
|
||||
}
|
||||
end
|
||||
|
||||
def hidden_settings_fields
|
||||
hidden_field_tags_for("settings", form_values)
|
||||
end
|
||||
|
||||
def hidden_field_tags_for(name, value)
|
||||
case value
|
||||
when Hash
|
||||
safe_join(
|
||||
value.flat_map do |key, nested_value|
|
||||
hidden_field_tags_for("#{name}[#{key}]", nested_value)
|
||||
end
|
||||
)
|
||||
when Array
|
||||
safe_join(value.map { |array_value| hidden_field_tag("#{name}[]", array_value) })
|
||||
else
|
||||
hidden_field_tag(name, value)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -26,134 +26,30 @@
|
||||
<% else %>
|
||||
|
||||
<%= helpers.turbo_frame_tag TURBO_FRAME_ID do %>
|
||||
<%= form_tag(
|
||||
{},
|
||||
<%= primer_form_with(
|
||||
url: {},
|
||||
method: :get,
|
||||
data: {
|
||||
action: "submit->filter--filters-form#sendForm:prevent"
|
||||
}
|
||||
) do %>
|
||||
<fieldset class="advanced-filters--container">
|
||||
<%=
|
||||
render(
|
||||
Primer::Beta::IconButton.new(
|
||||
icon: :x,
|
||||
scheme: :invisible,
|
||||
classes: "advanced-filters--close",
|
||||
tooltip_direction: :se,
|
||||
aria: { label: t("js.close_form_title") },
|
||||
data: { action: "filter--filters-form#toggleDisplayFilters" }
|
||||
)
|
||||
)
|
||||
%>
|
||||
<legend><%= t(:label_filter_plural) %></legend>
|
||||
<ul class="advanced-filters--filters">
|
||||
<% each_filter do |filter, filter_active, additional_options| %>
|
||||
<% filter_boolean = filter.is_a?(Queries::Filters::Shared::BooleanFilter) %>
|
||||
<% autocomplete_filter = additional_options.key?(:autocomplete_options) %>
|
||||
|
||||
<li class="advanced-filters--filter <%= filter_active ? "" : "hidden" %>"
|
||||
data-filter-name="<%= filter.name %>"
|
||||
data-filter-type="<%= filter.type %>"
|
||||
data-filter--filters-form-target="filter">
|
||||
<label class="advanced-filters--filter-name" for="<%= filter.name %>">
|
||||
<%= filter.human_name %>
|
||||
</label>
|
||||
<% selected_operator = filter.operator || filter.default_operator.symbol %>
|
||||
<%= content_tag :div, class: "advanced-filters--filter-operator", style: filter_boolean ? "display:none" : "" do %>
|
||||
<%= select_tag :operator,
|
||||
options_from_collection_for_select(
|
||||
filter.available_operators,
|
||||
:symbol,
|
||||
:human_name,
|
||||
selected_operator
|
||||
),
|
||||
class: "advanced-filters--select",
|
||||
data: {
|
||||
action: "change->filter--filters-form#setValueVisibility",
|
||||
"filter--filters-form-filter-name-param": filter.name,
|
||||
"filter--filters-form-target": "operator",
|
||||
"filter-name": filter.name
|
||||
} %>
|
||||
<% end %>
|
||||
<% if autocomplete_filter %>
|
||||
<%= render partial: "filters/autocomplete",
|
||||
locals: { value_visibility: value_hidden_class(selected_operator),
|
||||
filter: filter,
|
||||
autocomplete_options: additional_options[:autocomplete_options] } %>
|
||||
<% elsif filter_boolean %>
|
||||
<%= render partial: "filters/boolean",
|
||||
locals: { value_visibility: value_hidden_class(selected_operator),
|
||||
filter: filter } %>
|
||||
<% elsif %i(list list_optional list_all).include? filter.type %>
|
||||
<%= render partial: "filters/list/input_options",
|
||||
locals: { value_visibility: value_hidden_class(selected_operator),
|
||||
filter: filter } %>
|
||||
<% elsif [:datetime_past, :date].include? filter.type %>
|
||||
<%= render partial: "filters/date/input_options",
|
||||
locals: { value_visibility: value_hidden_class(selected_operator),
|
||||
filter: filter,
|
||||
selected_operator: selected_operator } %>
|
||||
<% else %>
|
||||
<%# All other simple types %>
|
||||
<%= render partial: "filters/text",
|
||||
locals: { value_visibility: value_hidden_class(selected_operator),
|
||||
filter: filter } %>
|
||||
<% end %>
|
||||
|
||||
<div class="advanced-filters--remove-filter">
|
||||
<a href=""
|
||||
class="filter_rem"
|
||||
data-action="click->filter--filters-form#removeFilter"
|
||||
data-filter--filters-form-filter-name-param="<%= filter.name %>">
|
||||
<%= helpers.op_icon("icon-close advanced-filters--remove-filter-icon", title: I18n.t("js.button_delete")) %>
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
<% end %>
|
||||
|
||||
<li class="advanced-filters--spacer <%= query.filters.blank? ? "hidden" : "" %>"
|
||||
data-filter--filters-form-target="spacer"></li>
|
||||
|
||||
<li class="advanced-filters--add-filter">
|
||||
<!-- Add filters -->
|
||||
<label for="add_filter_select" aria-hidden="true" class="advanced-filters--add-filter-label ng-binding">
|
||||
<%= helpers.op_icon("icon-add icon4") %>
|
||||
<%= t(:label_filter_add) %>:
|
||||
</label>
|
||||
<label for="add_filter_select" class="sr-only ng-binding">
|
||||
<%= t(:label_filter_add) %>
|
||||
<%= t("js.filter.description.text_open_filter") %>
|
||||
<%= t("js.filter.description.text_close_filter") %>
|
||||
</label>
|
||||
|
||||
<div class="advanced-filters--add-filter-value">
|
||||
<%= select_tag "add_filter_select",
|
||||
options_from_collection_for_select(
|
||||
allowed_filters,
|
||||
:name,
|
||||
:human_name,
|
||||
disabled: query.filters.map(&:name)
|
||||
),
|
||||
prompt: t(:actionview_instancetag_blank_option),
|
||||
class: "advanced-filters--select",
|
||||
focus: "false",
|
||||
"aria-invalid": "false",
|
||||
data: {
|
||||
"filter--filters-form-target": "addFilterSelect",
|
||||
action: "change->filter--filters-form#addFilter:prevent"
|
||||
} %>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
) do |f| %>
|
||||
<%= render(Primer::Beta::BorderBox.new) do |box| %>
|
||||
<% box.with_body do %>
|
||||
<%= render(filter_form(f)) %>
|
||||
<% unless turbo_requests? %>
|
||||
<li class="advanced-filters--controls">
|
||||
<%= submit_tag t("button_apply"), class: "button -small -primary", name: nil %>
|
||||
</li>
|
||||
<div class="advanced-filters--actions">
|
||||
<%= render(
|
||||
Primer::Beta::Button.new(
|
||||
scheme: :secondary,
|
||||
type: :button,
|
||||
data: { action: "filter--filters-form#toggleDisplayFilters" }
|
||||
)
|
||||
) { t("js.close_form_title") } %>
|
||||
<%= render(Primer::Beta::Button.new(type: :submit, scheme: :primary)) { t("button_apply") } %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
</ul>
|
||||
</fieldset>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
@@ -29,7 +29,6 @@
|
||||
# ++
|
||||
module Filter
|
||||
class FilterComponent < ApplicationComponent
|
||||
OPERATORS_WITHOUT_VALUES = %w[* !* t w].freeze
|
||||
TURBO_FRAME_ID = "filter_component"
|
||||
|
||||
options :query
|
||||
@@ -38,29 +37,14 @@ module Filter
|
||||
options lazy_loaded_path: false
|
||||
options initially_expanded: false
|
||||
|
||||
# Returns filters, active and inactive.
|
||||
# In case a filter is active, the active one will be preferred over the inactive one.
|
||||
def each_filter
|
||||
allowed_filters.each do |allowed_filter|
|
||||
active_filter = query.find_active_filter(allowed_filter.name)
|
||||
filter = active_filter || allowed_filter
|
||||
|
||||
yield filter, active_filter.present?, additional_filter_attributes(filter)
|
||||
end
|
||||
def filter_form(form)
|
||||
Filters::FilterFormComponent.new(builder: form, query:, allowed_filters:)
|
||||
end
|
||||
|
||||
def allowed_filters
|
||||
query.available_advanced_filters
|
||||
end
|
||||
|
||||
def value_hidden_class(selected_operator)
|
||||
operator_without_value?(selected_operator) ? "hidden" : ""
|
||||
end
|
||||
|
||||
def operator_without_value?(operator)
|
||||
OPERATORS_WITHOUT_VALUES.include?(operator)
|
||||
end
|
||||
|
||||
def lazy_loaded? = !!lazy_loaded_path
|
||||
|
||||
def initially_expanded? = initially_expanded
|
||||
@@ -75,105 +59,16 @@ module Filter
|
||||
end
|
||||
|
||||
def filter_classes
|
||||
"op-filters-form op-filters-form_top-margin #{'-expanded' if initially_expanded?}"
|
||||
[
|
||||
"op-filters-form",
|
||||
"op-filters-form_top-margin",
|
||||
("-expanded" if initially_expanded?),
|
||||
("op-filters-form--with-footer" unless turbo_requests?)
|
||||
].compact.join(" ")
|
||||
end
|
||||
|
||||
def lazy_turbo_frame_src
|
||||
public_send(lazy_loaded_path, **params.permit(:filters, :columns, :sortBy, :id, :query_id))
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# With this method we can pass additional options for each type of filter into the frontend. This is especially
|
||||
# useful when we want to pass options for the autocompleter components.
|
||||
#
|
||||
# When the method is overwritten in a subclass, the subclass should call super(filter) to get the default attributes.
|
||||
#
|
||||
# @param filter [QueryFilter] the filter for which we want to pass additional attributes
|
||||
# @return [Hash] the additional attributes for the filter, that will be yielded in the each_filter method
|
||||
def additional_filter_attributes(filter)
|
||||
case filter
|
||||
when Queries::Filters::Shared::ProjectFilter::Required,
|
||||
Queries::Filters::Shared::ProjectFilter::Optional
|
||||
{ autocomplete_options: project_autocomplete_options }
|
||||
when Queries::Filters::Shared::CustomFields::User
|
||||
{ autocomplete_options: user_autocomplete_options }
|
||||
when Queries::Filters::Shared::CustomFields::ListOptional
|
||||
{ autocomplete_options: custom_field_list_autocomplete_options(filter) }
|
||||
when Queries::Filters::Shared::CustomFields::Hierarchy
|
||||
{ autocomplete_options: custom_field_hierarchy_autocomplete_options(filter) }
|
||||
when Queries::Projects::Filters::ProjectStatusFilter,
|
||||
Queries::Projects::Filters::TypeFilter
|
||||
{ autocomplete_options: list_autocomplete_options(filter) }
|
||||
else
|
||||
{}
|
||||
end
|
||||
end
|
||||
|
||||
def custom_field_list_autocomplete_options(filter)
|
||||
all_items = if filter.custom_field.version?
|
||||
filter.allowed_values.map { |name, id, project_name| { name:, id:, project_name: } }
|
||||
else
|
||||
filter.allowed_values.map { |name, id| { name:, id: } }
|
||||
end
|
||||
selected = filter.values
|
||||
options = { items: all_items }
|
||||
options[:groupBy] = "project_name" if filter.custom_field.version?
|
||||
autocomplete_options.merge(options).merge(model: all_items.select { |item| selected.include?(item[:id]) })
|
||||
end
|
||||
|
||||
def custom_field_hierarchy_autocomplete_options(filter)
|
||||
items = filter.allowed_values.map do |name, id|
|
||||
path = name.split(" / ")
|
||||
{ name: path.last, id:, depth: path.length - 1 }
|
||||
end
|
||||
selected = filter.values
|
||||
|
||||
autocomplete_options.merge({ items: }).merge(model: items.select { |item| selected.include?(item[:id]) })
|
||||
end
|
||||
|
||||
def list_autocomplete_options(filter)
|
||||
all_items = filter.allowed_values.map { |name, id| { name:, id: } }
|
||||
selected = filter.values
|
||||
autocomplete_options.merge(
|
||||
items: all_items,
|
||||
model: all_items.select { |item| selected.include?(item[:id]) }
|
||||
)
|
||||
end
|
||||
|
||||
def autocomplete_options
|
||||
{
|
||||
component: "opce-autocompleter",
|
||||
bindValue: "id",
|
||||
bindLabel: "name",
|
||||
hideSelected: true
|
||||
}
|
||||
end
|
||||
|
||||
def project_autocomplete_options
|
||||
{
|
||||
component: "opce-project-autocompleter",
|
||||
resource: "projects",
|
||||
filters: [
|
||||
{ name: "active", operator: "=", values: ["t"] }
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
def user_autocomplete_options
|
||||
{
|
||||
component: "opce-user-autocompleter",
|
||||
hideSelected: true,
|
||||
defaultData: false,
|
||||
placeholder: I18n.t(:label_user_search),
|
||||
resource: "principals",
|
||||
url: ::API::V3::Utilities::PathHelper::ApiV3Path.principals,
|
||||
filters: [
|
||||
{ name: "status", operator: "!", values: [Principal.statuses["locked"].to_s] }
|
||||
],
|
||||
searchKey: "any_name_attribute",
|
||||
focusDirectly: false
|
||||
}
|
||||
public_send(lazy_loaded_path, **params.permit(:filters, :columns, :sortBy, :id, :query_id, :project_id))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
.op-filters
|
||||
&-form
|
||||
display: none
|
||||
&.-expanded
|
||||
display: block
|
||||
.advanced-filters--filter-value
|
||||
// visibility based on operator type
|
||||
&.hidden
|
||||
visibility: hidden
|
||||
height: 55px
|
||||
|
||||
// visibility for list value selectors
|
||||
.multi-select
|
||||
display: none
|
||||
.single-select
|
||||
display: block
|
||||
&.multi-value
|
||||
.multi-select
|
||||
display: block
|
||||
.single-select
|
||||
display: none
|
||||
|
||||
// visibility for datetime_past value selectors
|
||||
&.between-dates
|
||||
>.on-date,
|
||||
>.days
|
||||
display: none
|
||||
&.on-date
|
||||
>.between-dates,
|
||||
>.days
|
||||
display: none
|
||||
&.days
|
||||
>.on-date,
|
||||
>.between-dates
|
||||
display: none
|
||||
|
||||
// Special input field styles
|
||||
&.between-dates
|
||||
input[type="text"]
|
||||
display: inline-block
|
||||
max-width: 6rem
|
||||
|
||||
.advanced-filters--controls
|
||||
margin-top: 1rem
|
||||
|
||||
&_top-margin
|
||||
margin-top: 1rem
|
||||
@@ -0,0 +1,11 @@
|
||||
<%=
|
||||
render(
|
||||
Primer::ConditionalWrapper.new(
|
||||
condition: @wrap_with_controller,
|
||||
**@wrapper_arguments
|
||||
)
|
||||
) do
|
||||
%>
|
||||
<%= hidden_filters_input if @hidden_input_name %>
|
||||
<%= render(form_list) %>
|
||||
<% end %>
|
||||
@@ -0,0 +1,172 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# 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; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# 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, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
# Renders the list of filter input fields (one row per available filter plus an
|
||||
# "add filter" select) for a given query as part of a Primer form.
|
||||
#
|
||||
# The set of inputs depends on the query's available and active filters and is
|
||||
# built dynamically at render time. The component receives the builder of the
|
||||
# surrounding `primer_form_with` so that the emitted field names match what the
|
||||
# controller expects (top-level `operator_<filter>` and `<filter>_value` fields).
|
||||
#
|
||||
# Embed it in any primer form:
|
||||
#
|
||||
# <%= primer_form_with(url: ...) do |f| %>
|
||||
# <%= f.text_field(name: :title) %>
|
||||
# <%= render(Filters::FilterFormComponent.new(builder: f, query: @query)) %>
|
||||
# <% end %>
|
||||
#
|
||||
# Customise the set of advertised filters by passing `allowed_filters:` (used
|
||||
# by `Filter::FilterComponent` subclasses that restrict or reorder the list).
|
||||
#
|
||||
# By default the component does *not* attach the `filter--filters-form` Stimulus
|
||||
# controller, because in the standard layout (e.g. `Projects::IndexSubHeaderComponent`)
|
||||
# the controller has to sit on a common ancestor of the advanced filter form
|
||||
# *and* the inline quick filter input so that `sendForm()` can collect values
|
||||
# from both. For standalone embeds with no co-located quick filter, pass
|
||||
# `wrap_with_controller: true` and the component will emit its own controller
|
||||
# wrapper.
|
||||
#
|
||||
# Pass `hidden_input_name:` (e.g. `"filters"`) to also emit a hidden input
|
||||
# bound to the Stimulus controller's `filtersInput` target. The controller
|
||||
# keeps the field value in sync with the serialized filter selections so
|
||||
# that a normal form submit carries the canonical filter string in
|
||||
# `params[:<hidden_input_name>]` — no `sendForm` redirect needed.
|
||||
#
|
||||
# `output_format:` selects how the filter selection is serialized into the
|
||||
# hidden field (and into the URL when `sendForm` redirects). Supported values:
|
||||
# * `:params` (default) — URL-style string: `name ~ "foo"&login ! "bar"`.
|
||||
# * `:json` — JSON array: `[{"name":{"operator":"~","values":["foo"]}}, ...]`.
|
||||
# Only meaningful when this component owns the controller
|
||||
# (`wrap_with_controller: true`); otherwise the host's controller wrapper
|
||||
# decides.
|
||||
#
|
||||
# `autocomplete_append_to:` forwards an `appendTo` selector (or DOM reference
|
||||
# string ng-select understands, e.g. `"#my-dialog"` or `"body"`) to every
|
||||
# autocompleter the component renders. Use this when the component is embedded
|
||||
# in a Primer dialog or another container that clips overflow, so the dropdown
|
||||
# portal renders outside that container instead of being clipped.
|
||||
class Filters::FilterFormComponent < ApplicationComponent
|
||||
include OpPrimer::AttributesHelper
|
||||
include Primer::FetchOrFallbackHelper
|
||||
|
||||
OUTPUT_FORMATS = %i[params json].freeze
|
||||
|
||||
def initialize(builder:,
|
||||
query:,
|
||||
allowed_filters: nil,
|
||||
wrap_with_controller: false,
|
||||
hidden_input_name: nil,
|
||||
output_format: nil,
|
||||
autocomplete_append_to: nil,
|
||||
**wrapper_arguments)
|
||||
super()
|
||||
@builder = builder
|
||||
@query = query
|
||||
@allowed_filters = allowed_filters || query.available_advanced_filters
|
||||
@wrap_with_controller = wrap_with_controller
|
||||
@hidden_input_name = hidden_input_name
|
||||
@output_format = fetch_or_fallback(OUTPUT_FORMATS, output_format.to_sym) if output_format
|
||||
@autocomplete_append_to = autocomplete_append_to
|
||||
@wrapper_arguments = wrapper_arguments
|
||||
@wrapper_arguments[:tag] ||= :div
|
||||
@wrapper_arguments[:classes] = class_names(
|
||||
"op-filters-form -expanded",
|
||||
@wrapper_arguments[:classes]
|
||||
)
|
||||
@wrapper_arguments[:data] = merge_data(
|
||||
@wrapper_arguments,
|
||||
{
|
||||
data: {
|
||||
controller: "filter--filters-form",
|
||||
filter__filters_form_output_format_value: @output_format&.to_s
|
||||
}
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :query, :allowed_filters
|
||||
|
||||
def form_list
|
||||
Primer::Forms::FormList.new(*sub_forms)
|
||||
end
|
||||
|
||||
def hidden_filters_input
|
||||
hidden_field_tag(
|
||||
@hidden_input_name,
|
||||
"",
|
||||
data: { filter__filters_form_target: "filtersInput" }
|
||||
)
|
||||
end
|
||||
|
||||
def sub_forms
|
||||
forms = map_filter do |filter, active, additional_attributes|
|
||||
filter_form_class(filter)
|
||||
.new(@builder, filter:, additional_attributes:, active:)
|
||||
end
|
||||
|
||||
forms << Filters::Inputs::AddFilterForm.new(
|
||||
@builder,
|
||||
allowed_filters:,
|
||||
active_filter_names: query.filters.map(&:name)
|
||||
)
|
||||
end
|
||||
|
||||
def map_filter
|
||||
allowed_filters.map do |allowed_filter|
|
||||
active_filter = query.find_active_filter(allowed_filter.name)
|
||||
filter = active_filter || allowed_filter
|
||||
|
||||
yield filter, active_filter.present?, additional_filter_attributes(filter)
|
||||
end
|
||||
end
|
||||
|
||||
def additional_filter_attributes(filter)
|
||||
opts = filter.autocomplete_options
|
||||
opts = opts.merge(appendTo: @autocomplete_append_to) if @autocomplete_append_to
|
||||
opts.any? ? { autocomplete_options: opts } : {}
|
||||
end
|
||||
|
||||
def filter_form_class(filter)
|
||||
if filter.is_a?(Queries::Filters::Shared::BooleanFilter)
|
||||
Filters::Inputs::BooleanForm
|
||||
elsif filter.autocomplete_options.any?
|
||||
Filters::Inputs::AutocompleteForm
|
||||
elsif filter.type.in? %i[list list_optional list_all]
|
||||
Filters::Inputs::ListForm
|
||||
elsif filter.type.in? %i[datetime_past date]
|
||||
Filters::Inputs::DateForm
|
||||
else
|
||||
Filters::Inputs::TextForm
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,39 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# -- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# 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; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# 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, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
# ++
|
||||
|
||||
module Homescreen
|
||||
module Blocks
|
||||
class FavoriteProjects < Grids::WidgetComponent
|
||||
def call
|
||||
render(Grids::Widgets::FavoriteProjects.new(current_user:))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,57 +0,0 @@
|
||||
<%= widget_wrapper do %>
|
||||
<% if @favorite_projects.any? %>
|
||||
<p class="widget-box--additional-info"><%= t("projects.lists.favorited") %></p>
|
||||
<ul class="widget-box--arrow-links">
|
||||
<% @favorite_projects.each do |project| %>
|
||||
<li>
|
||||
<%= render(
|
||||
Primer::Beta::Octicon.new(
|
||||
icon: "star-fill",
|
||||
classes: "op-primer--star-icon",
|
||||
"aria-label": I18n.t(:label_favorite)
|
||||
)
|
||||
) %>
|
||||
|
||||
<%= link_to project, project_path(project),
|
||||
title: short_project_description(project),
|
||||
data: { test_selector: "favorite-project" } %>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
<% end %>
|
||||
|
||||
<% if @newest_projects.empty? %>
|
||||
<p class="widget-box--additional-info">
|
||||
<%= t("homescreen.additional.no_visible_projects") %>
|
||||
</p>
|
||||
<% else %>
|
||||
<p class="widget-box--additional-info"><%= t("homescreen.additional.projects") %></p>
|
||||
<ul class="widget-box--arrow-links">
|
||||
<% @newest_projects.each do |project| %>
|
||||
<li>
|
||||
<%= link_to project, project_path(project), title: short_project_description(project) %>
|
||||
<small>(<%= format_date(project.created_at) %>)</small>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
<% end %>
|
||||
|
||||
<div class="widget-box--blocks--buttons">
|
||||
<% if current_user.allowed_globally?(:add_project) %>
|
||||
<%= link_to new_project_path,
|
||||
{ class: "button -primary",
|
||||
aria: { label: t(:label_project_new) },
|
||||
title: t(:label_project_new) } do %>
|
||||
<%= op_icon("button--icon icon-add") %>
|
||||
<span class="button--text"><%= Project.model_name.human %></span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<%# If any project exists %>
|
||||
<% unless @newest_projects.empty? %>
|
||||
<%= link_to t(:label_project_view_all), projects_path,
|
||||
class: "button -highlight-inverted",
|
||||
title: t(:label_project_view_all) %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
@@ -0,0 +1,47 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# 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; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# 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, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
module OpPrimer
|
||||
# Drop-in replacement for `Primer::AttributesHelper` that treats the Stimulus
|
||||
# `controller` data attribute as plural. Stimulus supports multiple
|
||||
# controllers on one element (space-separated `data-controller="a b"`), but
|
||||
# upstream Primer omits `controller` from its plural data attributes, so
|
||||
# `merge_data` would silently drop a caller's controller when a component
|
||||
# merges in its own. Treating it as plural concatenates them instead.
|
||||
module AttributesHelper
|
||||
include Primer::AttributesHelper
|
||||
|
||||
PLURAL_DATA_ATTRIBUTES = (Primer::AttributesHelper::PLURAL_DATA_ATTRIBUTES + %i[controller]).freeze
|
||||
|
||||
def merge_data(*hashes)
|
||||
merge_prefixed_attribute_hashes(*hashes, prefix: :data, plural_keys: PLURAL_DATA_ATTRIBUTES)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -27,13 +27,15 @@ See COPYRIGHT and LICENSE files for more details.
|
||||
|
||||
++#%>
|
||||
|
||||
<%= render(Primer::Alpha::SegmentedControl.new("aria-label": @name, full_width: false)) do |control| %>
|
||||
<% items.each do |item| %>
|
||||
<% control.with_item(
|
||||
tag: :a,
|
||||
href: href_for(item.value),
|
||||
label: item.label,
|
||||
selected: current_value == item.value
|
||||
) %>
|
||||
<%= component_wrapper do %>
|
||||
<%= render(Primer::Alpha::SegmentedControl.new("aria-label": @name, full_width: false)) do |control| %>
|
||||
<% items.each do |item| %>
|
||||
<% control.with_item(
|
||||
tag: :a,
|
||||
href: href_for(item.value),
|
||||
label: item.label,
|
||||
selected: current_value == item.value
|
||||
) %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
@@ -32,6 +32,7 @@ module OpPrimer
|
||||
module QuickFilter
|
||||
class SegmentedComponent < ApplicationComponent
|
||||
include ApplicationHelper
|
||||
include OpTurbo::Streamable
|
||||
|
||||
renders_many :items, Item
|
||||
|
||||
@@ -45,9 +46,9 @@ module OpPrimer
|
||||
@orders = orders
|
||||
end
|
||||
|
||||
def render?
|
||||
items.any?
|
||||
end
|
||||
def wrapper_key = "quick-filter-#{@filter_key}"
|
||||
|
||||
def render? = items.any?
|
||||
|
||||
private
|
||||
|
||||
|
||||
@@ -84,10 +84,8 @@ module Projects
|
||||
|
||||
def allowed_new_workspace_types
|
||||
@allowed_new_workspace_types ||= [].tap do |types|
|
||||
if OpenProject::FeatureDecisions.portfolio_models_active?
|
||||
types << "portfolio" if @current_user.allowed_globally?(:add_portfolios)
|
||||
types << "program" if @current_user.allowed_globally?(:add_programs)
|
||||
end
|
||||
types << "portfolio" if @current_user.allowed_globally?(:add_portfolios)
|
||||
types << "program" if @current_user.allowed_globally?(:add_programs)
|
||||
types << "project" if @current_user.allowed_globally?(:add_project)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -172,7 +172,6 @@ module Projects
|
||||
end
|
||||
|
||||
def workspace_type_badge
|
||||
return unless OpenProject::FeatureDecisions.portfolio_models_active?
|
||||
# Only show icon and type for non-project workspaces
|
||||
return unless project.workspace_type.in?(["portfolio", "program"])
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ module Projects
|
||||
end
|
||||
|
||||
def render_view_all_link(widget)
|
||||
widget.with_row do
|
||||
widget.with_footer do
|
||||
helpers.link_to(
|
||||
I18n.t("projects.settings.versions.show_work_packages"),
|
||||
helpers.project_work_packages_version_path(version)
|
||||
|
||||
@@ -89,8 +89,12 @@ class RowComponent < ApplicationComponent
|
||||
:default
|
||||
end
|
||||
|
||||
def checkmark(condition)
|
||||
if condition
|
||||
def checkmark(condition, primerized: false)
|
||||
return unless condition
|
||||
|
||||
if primerized
|
||||
render(Primer::Beta::Octicon.new(icon: :check))
|
||||
else
|
||||
helpers.op_icon "icon icon-checkmark"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -42,7 +42,7 @@ See COPYRIGHT and LICENSE files for more details.
|
||||
<% if sortable_column?(name) %>
|
||||
<%= helpers.sort_header_tag(name, **options) %>
|
||||
<% else %>
|
||||
<th>
|
||||
<th aria-label="<%= options[:caption] %>">
|
||||
<div class="generic-table--sort-header-outer">
|
||||
<div class="generic-table--sort-header">
|
||||
<span>
|
||||
|
||||
@@ -38,28 +38,5 @@ module Users
|
||||
.grep_v(Queries::Users::Filters::BlockedFilter)
|
||||
.sort_by(&:human_name)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def additional_filter_attributes(filter)
|
||||
case filter
|
||||
when Queries::Users::Filters::GroupFilter
|
||||
{
|
||||
autocomplete_options: {
|
||||
component: "opce-user-autocompleter",
|
||||
resource: "principals",
|
||||
url: ::API::V3::Utilities::PathHelper::ApiV3Path.principals,
|
||||
filters: [
|
||||
{ name: "type", operator: "=", values: %w[Group] }
|
||||
],
|
||||
searchKey: "any_name_attribute",
|
||||
inputValue: filter.values,
|
||||
bindValue: "id"
|
||||
}
|
||||
}
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -36,6 +36,7 @@ module Attachments
|
||||
attribute :digest
|
||||
attribute :description
|
||||
attribute :content_type
|
||||
attribute :charset
|
||||
attribute :container
|
||||
attribute :container_type
|
||||
attribute :author
|
||||
|
||||
@@ -36,11 +36,9 @@ module OAuthClients
|
||||
validates :client_id, presence: true, length: { maximum: 255 }
|
||||
|
||||
attribute :client_secret, writable: true
|
||||
validates :client_secret, presence: true, if: :client_secret_required?
|
||||
validates :client_secret, presence: true
|
||||
validates :client_secret, length: { maximum: 255 }
|
||||
|
||||
def client_secret_required? = true
|
||||
|
||||
attribute :integration_type, writable: true
|
||||
validates :integration_type, presence: true
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# 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; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# 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, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
module Admin::Settings
|
||||
class ExportsSettingsController < ::Admin::SettingsController
|
||||
menu_item :settings_exports
|
||||
end
|
||||
end
|
||||
@@ -30,8 +30,25 @@
|
||||
|
||||
module Admin::Settings
|
||||
class WorkingDaysAndHoursSettingsController < ::Admin::SettingsController
|
||||
include OpTurbo::ComponentStream
|
||||
|
||||
menu_item :working_days_and_hours
|
||||
|
||||
def confirm_changes
|
||||
return update unless working_days_changed? || non_working_days_changed?
|
||||
|
||||
removed_days = non_working_days_params
|
||||
.select { |nwd| nwd["_destroy"].present? }
|
||||
.filter_map { |nwd| removed_non_working_day_date(nwd) }
|
||||
|
||||
component = Admin::Settings::WorkingDays::ConfirmDialogComponent.new(
|
||||
form_values: params.expect(settings: {}).to_h,
|
||||
removed_non_working_days: removed_days
|
||||
)
|
||||
|
||||
respond_with_dialog(component)
|
||||
end
|
||||
|
||||
def failure_callback(call)
|
||||
@modified_non_working_days = modified_non_working_days_for(call.result)
|
||||
flash[:error] = call.message || I18n.t(:notice_internal_server_error)
|
||||
@@ -53,15 +70,31 @@ module Admin::Settings
|
||||
|
||||
private
|
||||
|
||||
def working_days_changed?
|
||||
working_days_params(params.expect(settings: {})) != Setting.working_days.map(&:to_i)
|
||||
end
|
||||
|
||||
def non_working_days_changed?
|
||||
non_working_days_params.any?
|
||||
end
|
||||
|
||||
def working_days_params(settings)
|
||||
settings[:working_days] ? settings[:working_days].compact_blank.map(&:to_i).uniq : []
|
||||
end
|
||||
|
||||
def non_working_days_params
|
||||
non_working_days = params[:settings].to_unsafe_hash[:non_working_days_attributes] || {}
|
||||
non_working_days = params.expect(settings: {})[:non_working_days_attributes] || {}
|
||||
non_working_days.to_h.values
|
||||
end
|
||||
|
||||
def removed_non_working_day_date(non_working_day_params)
|
||||
date = NonWorkingDay.find_by(id: non_working_day_params["id"])&.date || non_working_day_params["date"]
|
||||
|
||||
I18n.l(date.to_date, format: :long)
|
||||
rescue Date::Error, NoMethodError
|
||||
nil
|
||||
end
|
||||
|
||||
def modified_non_working_days_for(result)
|
||||
return if result.nil?
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# 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; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# 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, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
class OpenprojectMetadataController < ApplicationController
|
||||
no_authorization_required! :show
|
||||
|
||||
skip_before_action :check_if_login_required
|
||||
|
||||
def show
|
||||
render json: {
|
||||
installation_uuid: Setting.installation_uuid
|
||||
}
|
||||
end
|
||||
end
|
||||
@@ -31,7 +31,6 @@ module Portfolios
|
||||
class MenusController < ApplicationController
|
||||
authorization_checked! :show
|
||||
|
||||
before_action :not_authorized_on_feature_flag_inactive
|
||||
before_action :authorize_portfolio_access, only: %i[show]
|
||||
|
||||
def show
|
||||
@@ -47,9 +46,5 @@ module Portfolios
|
||||
render_403 unless User.current.allowed_globally?(:add_portfolios) ||
|
||||
Project.portfolio.allowed_to(User.current, :view_project).any?
|
||||
end
|
||||
|
||||
def not_authorized_on_feature_flag_inactive
|
||||
render_403 unless OpenProject::FeatureDecisions.portfolio_models_active?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -34,7 +34,6 @@
|
||||
# projects in these cases.
|
||||
class PortfoliosController < ProjectsController
|
||||
before_action :authorize_portfolio_access, only: %i[index]
|
||||
before_action :not_authorized_on_feature_flag_inactive
|
||||
|
||||
skip_before_action :load_query_or_deny_access # skip using the superclass's before action because the next must be called first
|
||||
before_action :set_default_query, only: %i[index] # Must be called before `load_query_or_deny_access`
|
||||
|
||||
@@ -29,5 +29,4 @@
|
||||
# ++
|
||||
|
||||
class ProgramsController < ProjectsController
|
||||
before_action :not_authorized_on_feature_flag_inactive
|
||||
end
|
||||
|
||||
@@ -330,10 +330,6 @@ class ProjectsController < ApplicationController
|
||||
::Exports::Register.list_formats(Project).map(&:to_s)
|
||||
end
|
||||
|
||||
def not_authorized_on_feature_flag_inactive
|
||||
render_403 unless OpenProject::FeatureDecisions.portfolio_models_active?
|
||||
end
|
||||
|
||||
def layout_for_new
|
||||
if portfolio_management_feature_missing?
|
||||
"global"
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# 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; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# 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, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
module Admin
|
||||
module Settings
|
||||
class ExportsSettingsForm < ApplicationForm
|
||||
settings_form do |sf|
|
||||
sf.text_field(
|
||||
name: :work_packages_projects_export_limit,
|
||||
type: :number,
|
||||
input_width: :xsmall,
|
||||
caption: I18n.t(:setting_work_packages_projects_export_limit_text)
|
||||
)
|
||||
|
||||
sf.check_box(
|
||||
name: :csv_escape_formulas,
|
||||
caption: I18n.t(:setting_csv_escape_formulas_text)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -82,12 +82,6 @@ module Admin
|
||||
input_width: :xsmall
|
||||
)
|
||||
|
||||
sf.text_field(
|
||||
name: :work_packages_projects_export_limit,
|
||||
type: :number,
|
||||
input_width: :xsmall
|
||||
)
|
||||
|
||||
sf.text_field(
|
||||
name: :file_max_size_displayed,
|
||||
type: :number,
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# 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; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# 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, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
class Filters::Inputs::AddFilterForm < ApplicationForm
|
||||
def initialize(allowed_filters:, active_filter_names:)
|
||||
super()
|
||||
@allowed_filters = allowed_filters
|
||||
@active_filter_names = active_filter_names
|
||||
end
|
||||
|
||||
form do |form|
|
||||
form.select_list(
|
||||
name: :add_filter_select,
|
||||
label: I18n.t(:label_filter_add),
|
||||
scope_name_to_model: false,
|
||||
prompt: I18n.t(:actionview_instancetag_blank_option),
|
||||
data: {
|
||||
"filter--filters-form-target": "addFilterSelect",
|
||||
action: "change->filter--filters-form#addFilter:prevent"
|
||||
}
|
||||
) do |select|
|
||||
@allowed_filters.each do |filter|
|
||||
select.option(
|
||||
label: filter.human_name,
|
||||
value: filter.name,
|
||||
disabled: @active_filter_names.include?(filter.name)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,54 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# 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; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# 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, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
class Filters::Inputs::AutocompleteForm < Filters::Inputs::BaseFilterForm
|
||||
def add_operand(group)
|
||||
group.autocompleter(
|
||||
name: operand_name,
|
||||
label: @filter.human_name,
|
||||
visually_hide_label: true,
|
||||
required: true,
|
||||
wrapper_classes: ["advanced-filters--filter-value"],
|
||||
wrapper_data_attributes: {
|
||||
"filter--filters-form-target": "filterValueContainer",
|
||||
"filter-name": @filter.name,
|
||||
"filter-autocomplete": "true"
|
||||
},
|
||||
autocomplete_options: @additional_attributes[:autocomplete_options].merge(
|
||||
id: operand_name,
|
||||
multiple: true,
|
||||
multipleAsSeparateInputs: false,
|
||||
inputName: "value",
|
||||
inputValue: @filter.values,
|
||||
hiddenFieldAction: "change->filter--filters-form#autocompleteSendForm"
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,135 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# 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; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# 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, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
class Filters::Inputs::BaseFilterForm < ApplicationForm
|
||||
def initialize(filter:, additional_attributes:, active:)
|
||||
super()
|
||||
@filter = filter
|
||||
@additional_attributes = additional_attributes
|
||||
@active = active
|
||||
end
|
||||
|
||||
def self.inherited(subclass)
|
||||
super
|
||||
subclass.form do |form|
|
||||
form.group(**filter_row_arguments) do |group|
|
||||
add_label(group)
|
||||
add_operator(group)
|
||||
add_operand(group)
|
||||
add_delete_button(group)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def add_label(group)
|
||||
filter_human_name = @filter.human_name
|
||||
for_id = operator_hidden? ? operand_input_id : "operator_#{@filter.name}"
|
||||
group.html_content do
|
||||
content_tag(:label, filter_human_name, class: "advanced-filters--filter-name", for: for_id)
|
||||
end
|
||||
end
|
||||
|
||||
def operator_hidden?
|
||||
@filter.is_a?(Queries::Filters::Shared::BooleanFilter)
|
||||
end
|
||||
|
||||
def operand_input_id
|
||||
nil
|
||||
end
|
||||
|
||||
def add_operand(_group)
|
||||
raise SubclassResponsibilityError
|
||||
end
|
||||
|
||||
def filter_row_arguments
|
||||
args = {
|
||||
layout: :horizontal,
|
||||
classes: "advanced-filters--filter",
|
||||
data: {
|
||||
"filter--filters-form-target": "filter",
|
||||
"filter-name": @filter.name,
|
||||
"filter-type": @filter.type
|
||||
}
|
||||
}
|
||||
args[:hidden] = "hidden" unless @active
|
||||
args
|
||||
end
|
||||
|
||||
def operand_name
|
||||
"#{@filter.name}_value"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def add_operator(group)
|
||||
selected_operator = @filter.operator || @filter.default_operator.symbol
|
||||
|
||||
group.select_list(
|
||||
name: :"operator_#{@filter.name}",
|
||||
label: @filter.human_name,
|
||||
visually_hide_label: true,
|
||||
scope_name_to_model: false,
|
||||
hidden: @filter.is_a?(Queries::Filters::Shared::BooleanFilter),
|
||||
data: {
|
||||
action: "change->filter--filters-form#setValueVisibility",
|
||||
"filter--filters-form-filter-name-param": @filter.name,
|
||||
"filter--filters-form-target": "operator",
|
||||
"filter-name": @filter.name
|
||||
}
|
||||
) do |select|
|
||||
@filter.available_operators.each do |operator|
|
||||
select.option(
|
||||
label: operator.human_name,
|
||||
value: operator.symbol,
|
||||
selected: operator.symbol == selected_operator,
|
||||
**(operator.requires_value? ? {} : { "data-no-value": true })
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def add_delete_button(group)
|
||||
filter_name = @filter.name
|
||||
group.html_content do
|
||||
render(Primer::Beta::IconButton.new(
|
||||
icon: :x,
|
||||
scheme: :invisible,
|
||||
classes: "advanced-filters--remove-filter",
|
||||
aria: { label: I18n.t("button_delete") },
|
||||
data: {
|
||||
action: "click->filter--filters-form#removeFilter",
|
||||
"filter--filters-form-filter-name-param": filter_name
|
||||
}
|
||||
))
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,48 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# 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; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# 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, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
class Filters::Inputs::BooleanForm < Filters::Inputs::BaseFilterForm
|
||||
def add_operand(group)
|
||||
group.segmented_control(
|
||||
name: operand_name,
|
||||
label: @filter.human_name,
|
||||
visually_hide_label: true,
|
||||
value: @filter.values.first,
|
||||
items: [
|
||||
{ value: "f", label: I18n.t("general_text_No") },
|
||||
{ value: "t", label: I18n.t("general_text_Yes") }
|
||||
],
|
||||
wrapper_data_attributes: {
|
||||
"filter--filters-form-target": "filterValueContainer",
|
||||
"filter-name": @filter.name
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,116 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# 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; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# 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, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
class Filters::Inputs::DateForm < Filters::Inputs::BaseFilterForm
|
||||
DAYS_OPERATORS = %w[>t- <t- t- <t+ >t+ t+].freeze
|
||||
|
||||
def add_operand(group)
|
||||
filter_name = @filter.name
|
||||
filter_values = @filter.values
|
||||
fmt = date_format
|
||||
|
||||
days_value = fmt == "days" ? filter_values.fetch(0, "") : nil
|
||||
on_date_value = fmt == "on-date" ? filter_values.fetch(0, "") : nil
|
||||
from_value = fmt == "between-dates" ? filter_values.fetch(0, "") : nil
|
||||
to_value = fmt == "between-dates" ? filter_values.fetch(1, "") : nil
|
||||
|
||||
# The multi name intentionally uses @filter.name (not operand_name) because
|
||||
# parseDateFilterValue in filters-form.controller.ts locates the datepicker
|
||||
# inputs via findTargetById(filterName, …), which matches the id derived from
|
||||
# the multi name. Switching to operand_name would require migrating that
|
||||
# lookup to findTargetByName and adding data-filter-name to the picker inputs.
|
||||
group.multi(name: filter_name, label: filter_name, visually_hide_label: true,
|
||||
class: ["advanced-filters--filter-value"],
|
||||
data: {
|
||||
"filter--filters-form-target": "filterValueContainer",
|
||||
"filter-name": filter_name
|
||||
}) do |builder|
|
||||
days_div(builder, filter_name, days_value)
|
||||
on_date_div(builder, filter_name, on_date_value)
|
||||
between_dates_div(builder, filter_name, from_value, to_value)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def date_format
|
||||
op = @filter.operator || @filter.default_operator.symbol
|
||||
@date_format ||= if DAYS_OPERATORS.include?(op)
|
||||
"days"
|
||||
elsif op == "=d"
|
||||
"on-date"
|
||||
elsif op == "<>d"
|
||||
"between-dates"
|
||||
end
|
||||
end
|
||||
|
||||
def days_div(builder, filter_name, value)
|
||||
field_arguments = {
|
||||
name: :days,
|
||||
label: I18n.t("datetime.units.day.other"),
|
||||
visually_hide_label: true,
|
||||
trailing_visual: { text: { text: I18n.t("datetime.units.day.other") } },
|
||||
scope_name_to_model: false,
|
||||
value:,
|
||||
hidden: value.nil?,
|
||||
data: {
|
||||
"filter--filters-form-target": "days",
|
||||
"filter-name": filter_name
|
||||
}
|
||||
}
|
||||
|
||||
builder.text_field(**field_arguments, type: :number, step: "any", class: "days")
|
||||
end
|
||||
|
||||
def on_date_div(builder, filter_name, value)
|
||||
builder.single_date_picker(
|
||||
name: :singleDay,
|
||||
label: :singleDay,
|
||||
hidden: value.nil?,
|
||||
leading_visual: { icon: :calendar },
|
||||
value: value || "",
|
||||
datepicker_options: { input_attributes: { "data-filter--filters-form-target" => "singleDay" } },
|
||||
data: { "filter-name": filter_name }
|
||||
)
|
||||
end
|
||||
|
||||
def between_dates_div(builder, filter_name, from_value, to_value)
|
||||
value = [from_value, to_value].compact.join(" - ").presence
|
||||
builder.range_date_picker(
|
||||
name: :dateRange,
|
||||
label: :dateRange,
|
||||
hidden: value.nil?,
|
||||
leading_visual: { icon: :calendar },
|
||||
value: value || "-",
|
||||
datepicker_options: { input_attributes: { "data-filter--filters-form-target" => "dateRange" } },
|
||||
data: { "filter-name": filter_name }
|
||||
)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,64 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# 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; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# 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, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
class Filters::Inputs::ListForm < Filters::Inputs::BaseFilterForm
|
||||
def add_operand(group)
|
||||
filter_name = @filter.name
|
||||
filter_values = @filter.values || []
|
||||
items = @filter.allowed_values.map { |name, id| { name:, id: } }
|
||||
|
||||
group.autocompleter(
|
||||
name: operand_name,
|
||||
label: :value,
|
||||
visually_hide_label: true,
|
||||
wrapper_classes: ["advanced-filters--filter-value"],
|
||||
wrapper_data_attributes: {
|
||||
"filter--filters-form-target": "filterValueContainer",
|
||||
"filter-name": filter_name,
|
||||
"filter-autocomplete": "true"
|
||||
},
|
||||
autocomplete_options: {
|
||||
component: "opce-autocompleter",
|
||||
id: operand_name,
|
||||
multiple: true,
|
||||
multipleAsSeparateInputs: false,
|
||||
inputName: "value",
|
||||
inputValue: filter_values,
|
||||
items:,
|
||||
model: items.select { |item| filter_values.include?(item[:id]) },
|
||||
bindLabel: "name",
|
||||
bindValue: "id",
|
||||
hideSelected: true,
|
||||
defaultData: false,
|
||||
hiddenFieldAction: "change->filter--filters-form#autocompleteSendForm"
|
||||
}.merge(@additional_attributes[:autocomplete_options] || {})
|
||||
)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,50 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# 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; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# 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, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
class Filters::Inputs::TextForm < Filters::Inputs::BaseFilterForm
|
||||
def add_operand(group)
|
||||
field_arguments = {
|
||||
name: operand_name,
|
||||
label: @filter.human_name,
|
||||
visually_hide_label: true,
|
||||
scope_name_to_model: false,
|
||||
value: @filter.values.first,
|
||||
data: {
|
||||
"filter--filters-form-target": "filterValueContainer simpleValue",
|
||||
"filter-name": @filter.name
|
||||
}
|
||||
}
|
||||
if @filter.type.in? %i[integer float]
|
||||
group.text_field(**field_arguments, type: :number, step: "any")
|
||||
else
|
||||
group.text_field(**field_arguments)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -49,8 +49,6 @@ module FrontendAssetHelper
|
||||
# or referencing the running CLI proxy that hosts the assets in memory.
|
||||
def include_frontend_assets
|
||||
capture do
|
||||
concat nonced_javascript_include_tag variable_asset_path("jquery.js"), skip_pipeline: true
|
||||
|
||||
%w(polyfills.js main.js).each do |file|
|
||||
concat nonced_javascript_include_tag variable_asset_path(file), skip_pipeline: true, type: "module"
|
||||
end
|
||||
|
||||
@@ -338,7 +338,7 @@ module SortHelper
|
||||
|
||||
# Extracts the given `options` and provides them to a block.
|
||||
# See #sort_header_tag and #sort_header_with_action_menu for usage examples.
|
||||
def with_sort_header_options(column, allowed_params: nil, with_title: false, **options)
|
||||
def with_sort_header_options(column, allowed_params: nil, with_title: false, **options) # rubocop:disable Metrics/AbcSize
|
||||
caption = get_caption(column, options)
|
||||
|
||||
default_order = options.delete(:default_order) || "asc"
|
||||
@@ -349,6 +349,12 @@ module SortHelper
|
||||
options[:title] = sort_header_title(column, caption, options) if with_title
|
||||
options[:icon_only_header] = column == :favorited
|
||||
|
||||
# Give the header cell an explicit accessible name equal to the plain caption.
|
||||
# Without it, the column header's accessible name is derived from its contents,
|
||||
# which can include the action menu trigger text and is uppercased by the
|
||||
# `text-transform` styling, neither of which should be part of the name.
|
||||
options[:"aria-label"] = caption if caption.present?
|
||||
|
||||
within_sort_header_tag_hierarchy(options, sort_class(column)) do
|
||||
yield(column, caption, default_order, allowed_params:, param:, lang:, title: options[:title],
|
||||
sortable: options.fetch(:sortable, false), data:)
|
||||
|
||||
@@ -93,7 +93,9 @@ class Attachment < ApplicationRecord
|
||||
# specifically when using S3 for attachments. In the case of S3 the file name for the downloaded
|
||||
# file will still be correct as it's part of the URL before the query.
|
||||
def external_url_options(expires_in: nil)
|
||||
{ content_disposition: content_disposition(include_filename: false), expires_in: }
|
||||
{ content_disposition: content_disposition(include_filename: false),
|
||||
content_type: served_content_type,
|
||||
expires_in: }
|
||||
end
|
||||
|
||||
def external_storage?
|
||||
@@ -119,6 +121,30 @@ class Attachment < ApplicationRecord
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the Content-Type to use when serving this file inline in a browser.
|
||||
# Text files are normalised to text/plain (prevents script execution) with an
|
||||
# explicit charset. Non-inlineable files get application/octet-stream so the
|
||||
# browser is forced to download them.
|
||||
def served_content_type
|
||||
if is_text?
|
||||
"text/plain; charset=#{charset.presence || Setting.attachment_default_charset}"
|
||||
elsif inlineable?
|
||||
content_type
|
||||
else
|
||||
"application/octet-stream"
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the content type to use when serving the file to a browser.
|
||||
# For text files, ensures a charset is always present so browsers don't
|
||||
# fall back to ISO-8859-1. Preserves the real MIME subtype (e.g. text/x-ruby)
|
||||
# unlike served_content_type which normalises to text/plain for security.
|
||||
def serving_content_type
|
||||
return content_type unless is_text?
|
||||
|
||||
"#{content_type}; charset=#{charset.presence || Setting.attachment_default_charset}"
|
||||
end
|
||||
|
||||
def visible?(user = User.current)
|
||||
allowed_or_author?(user) do
|
||||
container.attachments_visible?(user)
|
||||
@@ -227,7 +253,7 @@ class Attachment < ApplicationRecord
|
||||
end
|
||||
|
||||
def set_content_type(file)
|
||||
self.content_type = self.class.content_type_for(file.path)
|
||||
self.content_type, self.charset = OpenProject::ContentTypeDetector.new(file.path).detect_with_charset
|
||||
end
|
||||
|
||||
def set_digest(file)
|
||||
|
||||
@@ -47,7 +47,8 @@ module Exports
|
||||
|
||||
def encode_csv_columns(columns, encoding = I18n.t(:general_csv_encoding))
|
||||
columns.map do |cell|
|
||||
Redmine::CodesetUtil.from_utf8(cell.to_s, encoding)
|
||||
sanitized = Exports::Concerns::CSVFormulaSanitization.sanitize(cell)
|
||||
Redmine::CodesetUtil.from_utf8(sanitized, encoding)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# 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; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# 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, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
module Exports
|
||||
module Concerns
|
||||
# Escape cells that begin with a spreadsheet formula-triggering character,
|
||||
# if the +csv_escape_formulas+ setting is active.
|
||||
module CSVFormulaSanitization
|
||||
module_function
|
||||
|
||||
# Leading characters that always indicate a formula and will always be escaped
|
||||
ALWAYS_ESCAPE = %W[= @ \t \r].freeze
|
||||
|
||||
# Leading characters that trigger formula evaluation but may also legitimately
|
||||
# be a number (negative/positive values).
|
||||
POSSIBLE_NUMBER_START = %w[- +].freeze
|
||||
|
||||
# A single, optionally signed number as it appears in exported cells:
|
||||
# thousands/decimal separators, optional surrounding whitespace and an
|
||||
# optional currency symbol or percent sign (e.g. "-5.00", "-1.234,56 €").
|
||||
# Anything with an internal operator (e.g. "+1+1") or letters/parentheses
|
||||
# fails this match and will be treated as a formula again
|
||||
PLAIN_NUMBER = /\A[+-]?[\p{Sc}\s]*\d[\d.,'\s]*[\p{Sc}%]?\z/u
|
||||
|
||||
# Escape a single CSV cell value if the setting is active
|
||||
#
|
||||
# @param value [Object] the raw cell value
|
||||
# @return [String] the (possibly escaped) string value
|
||||
def sanitize(value)
|
||||
str = value.to_s
|
||||
return str unless needs_escaping?(str)
|
||||
|
||||
"'#{str}"
|
||||
end
|
||||
|
||||
def needs_escaping?(str)
|
||||
return false unless Setting.csv_escape_formulas?
|
||||
return false if str.empty?
|
||||
|
||||
first = str[0]
|
||||
return true if ALWAYS_ESCAPE.include?(first)
|
||||
return false unless POSSIBLE_NUMBER_START.include?(first)
|
||||
|
||||
# Leading - or +: only escape when the value is not a plain signed number.
|
||||
!str.match?(PLAIN_NUMBER)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -31,7 +31,7 @@
|
||||
class PersistedView < ApplicationRecord
|
||||
belongs_to :project, optional: true
|
||||
belongs_to :principal, optional: true, inverse_of: :persisted_views
|
||||
belongs_to :query, polymorphic: true, optional: true
|
||||
belongs_to :query, polymorphic: true, optional: true, autosave: true
|
||||
|
||||
belongs_to :parent, class_name: "PersistedView", optional: true
|
||||
has_many :children, class_name: "PersistedView", foreign_key: "parent_id", dependent: :destroy, inverse_of: :parent
|
||||
@@ -49,6 +49,7 @@ class PersistedView < ApplicationRecord
|
||||
|
||||
scope :public_views, -> { where(public: true) }
|
||||
scope :private_views, ->(principal = User.current) { where(public: false, principal_id: principal.id) }
|
||||
scope :with_children, -> { includes(:children) }
|
||||
|
||||
scope :visible, (lambda do |principal = User.current|
|
||||
public_views.or(private_views(principal))
|
||||
@@ -68,6 +69,13 @@ class PersistedView < ApplicationRecord
|
||||
attr_writer :allowed_children
|
||||
end
|
||||
|
||||
# Resolves a (potentially user-supplied) class name to a view class, but only
|
||||
# if it is an allowed child of this view. Returns nil for any unknown or
|
||||
# forbidden name.
|
||||
def self.allowed_child_class(name)
|
||||
allowed_children.index_with(&:constantize)[name.to_s]
|
||||
end
|
||||
|
||||
# Returns the query of this view or, if not set, the query of the parent view.
|
||||
def effective_query
|
||||
query || parent&.effective_query
|
||||
|
||||
@@ -85,6 +85,8 @@ class Project < ApplicationRecord
|
||||
}, dependent: :destroy
|
||||
has_many :time_entries, dependent: :delete_all
|
||||
has_many :time_entry_activities_projects, dependent: :delete_all
|
||||
has_many :cost_types_projects, dependent: :delete_all
|
||||
has_many :cost_types, through: :cost_types_projects
|
||||
has_many :queries, dependent: :destroy
|
||||
has_many :news, -> { includes(:author) }, dependent: :destroy
|
||||
has_many :categories, -> { order("#{Category.table_name}.name") }, dependent: :delete_all
|
||||
@@ -193,7 +195,8 @@ class Project < ApplicationRecord
|
||||
scope :templated, -> { where(templated: true) }
|
||||
|
||||
scopes :activated_time_activity,
|
||||
:visible_with_activated_time_activity
|
||||
:visible_with_activated_time_activity,
|
||||
:available_cost_types
|
||||
|
||||
enum :status_code, {
|
||||
on_track: 0,
|
||||
|
||||
@@ -182,6 +182,10 @@ class Queries::Filters::Base
|
||||
errors.full_message(human_name, messages)
|
||||
end
|
||||
|
||||
def autocomplete_options
|
||||
{}
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def type_strategy
|
||||
|
||||
@@ -37,6 +37,23 @@ module Queries
|
||||
true
|
||||
end
|
||||
|
||||
def autocomplete_options
|
||||
items = allowed_values.map do |name, id|
|
||||
path = name.split(" / ")
|
||||
{ name: path.last, id:, depth: path.length - 1 }
|
||||
end
|
||||
|
||||
{
|
||||
component: "opce-autocompleter",
|
||||
bindValue: "id",
|
||||
bindLabel: "name",
|
||||
hideSelected: true,
|
||||
defaultData: false,
|
||||
items:,
|
||||
model: items.select { |item| values.include?(item[:id]) }
|
||||
}
|
||||
end
|
||||
|
||||
def value_objects
|
||||
CustomField::Hierarchy::Item
|
||||
.where(id: @values)
|
||||
|
||||
@@ -59,6 +59,24 @@ module Queries::Filters::Shared
|
||||
:list_optional
|
||||
end
|
||||
|
||||
def autocomplete_options # rubocop:disable Metrics/AbcSize
|
||||
all_items = if custom_field.version?
|
||||
allowed_values.map { |name, id, project_name| { name:, id:, project_name: } }
|
||||
else
|
||||
allowed_values.map { |name, id| { name:, id: } }
|
||||
end
|
||||
options = { items: all_items }
|
||||
options[:groupBy] = "project_name" if custom_field.version?
|
||||
|
||||
{
|
||||
component: "opce-autocompleter",
|
||||
bindValue: "id",
|
||||
bindLabel: "name",
|
||||
hideSelected: true,
|
||||
defaultData: false
|
||||
}.merge(options).merge(model: all_items.select { |item| values.include?(item[:id]) })
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def condition
|
||||
@@ -75,8 +93,7 @@ module Queries::Filters::Shared
|
||||
end
|
||||
|
||||
def customized_strategy?
|
||||
operator_strategy == Queries::Operators::CustomFields::EqualsAll ||
|
||||
operator_strategy == Queries::Operators::CustomFields::NotEqualsAll
|
||||
[Queries::Operators::CustomFields::EqualsAll, Queries::Operators::CustomFields::NotEqualsAll].include?(operator_strategy)
|
||||
end
|
||||
|
||||
def type_strategy_class
|
||||
|
||||
@@ -48,6 +48,22 @@ module Queries::Filters::Shared
|
||||
vals + user_groups_added(vals)
|
||||
end
|
||||
|
||||
def autocomplete_options
|
||||
{
|
||||
component: "opce-user-autocompleter",
|
||||
hideSelected: true,
|
||||
defaultData: false,
|
||||
placeholder: I18n.t(:label_user_search),
|
||||
resource: "principals",
|
||||
url: ::API::V3::Utilities::PathHelper::ApiV3Path.principals,
|
||||
filters: [
|
||||
{ name: "status", operator: "!", values: [Principal.statuses["locked"].to_s] }
|
||||
],
|
||||
searchKey: "any_name_attribute",
|
||||
focusDirectly: false
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def group_members_added(vals)
|
||||
|
||||
@@ -51,6 +51,14 @@ module Queries::Filters::Shared::ProjectFilter::Optional
|
||||
# harm.
|
||||
@type_strategy ||= ::Queries::Filters::Strategies::IntegerListOptional.new(self)
|
||||
end
|
||||
|
||||
def autocomplete_options
|
||||
{
|
||||
component: "opce-project-autocompleter",
|
||||
resource: "projects",
|
||||
filters: [{ name: "active", operator: "=", values: ["t"] }]
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
|
||||
@@ -51,6 +51,14 @@ module Queries::Filters::Shared::ProjectFilter::Required
|
||||
# harm.
|
||||
@type_strategy ||= ::Queries::Filters::Strategies::IntegerList.new(self)
|
||||
end
|
||||
|
||||
def autocomplete_options
|
||||
{
|
||||
component: "opce-project-autocompleter",
|
||||
resource: "projects",
|
||||
filters: [{ name: "active", operator: "=", values: ["t"] }]
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
|
||||
@@ -41,6 +41,19 @@ class Queries::Projects::Filters::ProjectStatusFilter < Queries::Projects::Filte
|
||||
:list_optional
|
||||
end
|
||||
|
||||
def autocomplete_options
|
||||
all_items = allowed_values.map { |name, id| { name:, id: } }
|
||||
{
|
||||
component: "opce-autocompleter",
|
||||
bindValue: "id",
|
||||
bindLabel: "name",
|
||||
hideSelected: true,
|
||||
defaultData: false,
|
||||
items: all_items,
|
||||
model: all_items.select { |item| values.include?(item[:id]) }
|
||||
}
|
||||
end
|
||||
|
||||
def where
|
||||
operator_strategy.sql_for_field(values, model.table_name, :status_code)
|
||||
end
|
||||
|
||||
@@ -45,6 +45,19 @@ class Queries::Projects::Filters::TypeFilter < Queries::Projects::Filters::Base
|
||||
:list
|
||||
end
|
||||
|
||||
def autocomplete_options
|
||||
all_items = allowed_values.map { |name, id| { name:, id: } }
|
||||
{
|
||||
component: "opce-autocompleter",
|
||||
bindValue: "id",
|
||||
bindLabel: "name",
|
||||
hideSelected: true,
|
||||
defaultData: false,
|
||||
items: all_items,
|
||||
model: all_items.select { |item| values.include?(item[:id]) }
|
||||
}
|
||||
end
|
||||
|
||||
def self.key
|
||||
:type_id
|
||||
end
|
||||
|
||||
@@ -34,4 +34,16 @@ class Queries::Users::Filters::GroupFilter < Queries::Users::Filters::UserFilter
|
||||
def human_name
|
||||
I18n.t(:label_group)
|
||||
end
|
||||
|
||||
def autocomplete_options
|
||||
{
|
||||
component: "opce-user-autocompleter",
|
||||
resource: "principals",
|
||||
url: ::API::V3::Utilities::PathHelper::ApiV3Path.principals,
|
||||
filters: [{ name: "type", operator: "=", values: %w[Group] }],
|
||||
searchKey: "any_name_attribute",
|
||||
inputValue: values,
|
||||
bindValue: "id"
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -37,6 +37,27 @@ module Queries::WorkPackages::Filter::FilterForWpMixin
|
||||
raise NotImplementedError, "There would be too many candidates"
|
||||
end
|
||||
|
||||
# Tell `Filters::FilterFormComponent`'s dispatch to render these filters with a
|
||||
# server-side autocompleter (the candidate set is too large for an inline
|
||||
# `<select>`). Mirrors what the legacy Angular WP filter UI does — see
|
||||
# `filter-searchable-multiselect-value.component.html`, which renders an
|
||||
# `op-autocompleter` against the `work_packages` resource using the
|
||||
# `typeahead` filter for search.
|
||||
#
|
||||
# Subclasses that override `type` to something other than `:list` (e.g.
|
||||
# `SearchFilter` / `SubjectOrIdFilter` use `:search`, `RelatableFilter`
|
||||
# uses `:relation`) get an empty hash so the dispatch falls back to the
|
||||
# appropriate non-autocomplete input.
|
||||
def autocomplete_options
|
||||
return {} unless type == :list
|
||||
|
||||
{
|
||||
component: "opce-autocompleter",
|
||||
resource: "work_packages",
|
||||
searchKey: "typeahead"
|
||||
}
|
||||
end
|
||||
|
||||
def value_objects
|
||||
objects = visible_scope.find(no_templated_values)
|
||||
|
||||
|
||||
@@ -63,6 +63,7 @@ class Query < ApplicationRecord
|
||||
validate :validate_timestamps
|
||||
|
||||
include Scopes::Scoped
|
||||
|
||||
scopes :visible,
|
||||
:having_views
|
||||
|
||||
@@ -208,6 +209,25 @@ class Query < ApplicationRecord
|
||||
filters.delete_if { |f| f.field.to_s == name.to_s }
|
||||
end
|
||||
|
||||
# Mirrors `Queries::BaseQuery#find_active_filter` so that consumers built
|
||||
# on top of the modern query API (e.g. `Filters::FilterFormComponent`) can ask any
|
||||
# query — including this legacy work-package one — for its active filter
|
||||
# by name. Signature kept identical to BaseQuery's (symbol arg in, filter
|
||||
# or nil out).
|
||||
def find_active_filter(name)
|
||||
filters.detect { |f| f.name == name }
|
||||
end
|
||||
|
||||
# The manual-sort filter is added programmatically when the user drags
|
||||
# work packages to reorder them — it has no operator/value UI of its own
|
||||
# (type `:empty_value`), so it doesn't belong in the picker that
|
||||
# `Filters::FilterFormComponent` builds. Mirrors how
|
||||
# `Queries::Filters::AvailableFilters#available_advanced_filters` already
|
||||
# excludes the inline `name_and_identifier` quick-filter on projects.
|
||||
def available_advanced_filters
|
||||
super.grep_v(::Queries::WorkPackages::Filter::ManualSortFilter)
|
||||
end
|
||||
|
||||
def normalized_name
|
||||
name.parameterize.underscore
|
||||
end
|
||||
|
||||
@@ -85,6 +85,8 @@ module Authorization
|
||||
|
||||
if perms.blank?
|
||||
if !OpenProject::AccessControl.disabled_permission?(action)
|
||||
# See https://www.openproject.org/docs/development/concepts/permissions/#definition-of-permissions
|
||||
# if you are wondering where to define permissions
|
||||
Rails.logger.debug { "Used permission \"#{action}\" that is not defined. It will never return true." }
|
||||
raise UnknownPermissionError.new(action) if raise_on_unknown
|
||||
end
|
||||
|
||||
@@ -52,6 +52,8 @@ class WorkPackages::ActivitiesTab::Paginator
|
||||
include Pagy::Method
|
||||
include WorkPackages::ActivitiesTab::JournalSortingInquirable
|
||||
|
||||
MAX_PAGES = 100
|
||||
|
||||
def self.paginate(work_package, params = {})
|
||||
new(work_package, params).call
|
||||
end
|
||||
@@ -72,7 +74,7 @@ class WorkPackages::ActivitiesTab::Paginator
|
||||
@filter = :all # Ignore filter when jumping to specific journal
|
||||
pagy_array_for_target_journal(anchor_type, target_record_id)
|
||||
else
|
||||
pagy(:offset, base_journals, **pagy_options)
|
||||
pagy(:offset, capped_journals, **pagy_options)
|
||||
end
|
||||
|
||||
# For UI display: if user wants "oldest first" UI, reverse the array
|
||||
@@ -87,7 +89,6 @@ class WorkPackages::ActivitiesTab::Paginator
|
||||
{
|
||||
page: params[:page] || 1,
|
||||
limit: params[:limit] || Pagy::DEFAULT[:limit],
|
||||
max_pages: 100,
|
||||
request: { params: }
|
||||
}.compact
|
||||
end
|
||||
@@ -127,6 +128,11 @@ class WorkPackages::ActivitiesTab::Paginator
|
||||
combine_and_sort_records(fetch_journals, fetch_revisions)
|
||||
end
|
||||
|
||||
def capped_journals
|
||||
max_records = (params[:limit] || Pagy::DEFAULT[:limit]) * MAX_PAGES
|
||||
base_journals.first(max_records)
|
||||
end
|
||||
|
||||
def fetch_journals
|
||||
API::V3::Activities::ActivityEagerLoadingWrapper.wrap(fetch_ar_journals)
|
||||
end
|
||||
|
||||
@@ -74,6 +74,7 @@ class FogFileUploader < CarrierWave::Uploader::Base
|
||||
#
|
||||
# @param options [Hash] Options hash.
|
||||
# @option options [String] :content_disposition Pass this content disposition to S3 so that it serves the file with it.
|
||||
# @option options [String] :content_type Pass this content type to S3 so that it serves the file with it.
|
||||
# @option options [DateTime] :expires_at Date at which the link should expire (default: now + 5 minutes)
|
||||
# @option options [ActiveSupport::Duration] :expires_in Duration in which the link should expire.
|
||||
#
|
||||
@@ -82,6 +83,7 @@ class FogFileUploader < CarrierWave::Uploader::Base
|
||||
url_options = {}
|
||||
|
||||
set_content_disposition!(url_options, options:)
|
||||
set_content_type!(url_options, options:)
|
||||
set_expires_at!(url_options, options:)
|
||||
|
||||
remote_file.url url_options
|
||||
@@ -103,15 +105,17 @@ class FogFileUploader < CarrierWave::Uploader::Base
|
||||
private
|
||||
|
||||
def set_content_disposition!(url_options, options:)
|
||||
if options[:content_disposition].present?
|
||||
url_options[:query] = {
|
||||
# Passing this option to S3 will make it serve the file with the
|
||||
# respective content disposition. Without it no content disposition
|
||||
# header is sent. This only works for S3 but we don't support
|
||||
# anything else anyway (see carrierwave.rb).
|
||||
"response-content-disposition" => options[:content_disposition]
|
||||
}
|
||||
end
|
||||
return if options[:content_disposition].blank?
|
||||
|
||||
(url_options[:query] ||= {})["response-content-disposition"] = options[:content_disposition]
|
||||
end
|
||||
|
||||
def set_content_type!(url_options, options:)
|
||||
return if options[:content_type].blank?
|
||||
|
||||
# Like the content disposition above, this makes S3 serve the file with the
|
||||
# given Content-Type, overriding the stored object type.
|
||||
(url_options[:query] ||= {})["response-content-type"] = options[:content_type]
|
||||
end
|
||||
|
||||
def set_expires_at!(url_options, options:)
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
<%#-- copyright
|
||||
OpenProject is an open source project management software.
|
||||
Copyright (C) the OpenProject GmbH
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License version 3.
|
||||
|
||||
OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
Copyright (C) 2010-2013 the ChiliProject Team
|
||||
|
||||
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; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
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, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
See COPYRIGHT and LICENSE files for more details.
|
||||
|
||||
++#%>
|
||||
|
||||
<% html_title t(:label_administration), t(:label_export_plural) %>
|
||||
|
||||
<%=
|
||||
render(Primer::OpenProject::PageHeader.new) do |header|
|
||||
header.with_title { t(:label_export_plural) }
|
||||
header.with_breadcrumbs(
|
||||
[{ href: admin_index_path, text: t("label_administration") },
|
||||
{ href: admin_settings_general_path, text: t(:label_system_settings) },
|
||||
t(:label_export_plural)]
|
||||
)
|
||||
end
|
||||
%>
|
||||
|
||||
<%=
|
||||
settings_primer_form_with(
|
||||
url: admin_settings_exports_path,
|
||||
scope: :settings,
|
||||
method: :patch
|
||||
) do |f|
|
||||
render(
|
||||
Primer::Forms::FormList.new(
|
||||
Admin::Settings::ExportsSettingsForm.new(f),
|
||||
Admin::Settings::Save.new(f)
|
||||
)
|
||||
)
|
||||
end
|
||||
%>
|
||||
@@ -41,9 +41,9 @@ See COPYRIGHT and LICENSE files for more details.
|
||||
%>
|
||||
|
||||
<%= styled_form_tag(
|
||||
admin_settings_working_days_and_hours_path,
|
||||
method: :patch,
|
||||
class: "op-working-days-admin-settings"
|
||||
confirm_changes_admin_settings_working_days_and_hours_path,
|
||||
class: "op-working-days-admin-settings",
|
||||
data: { turbo_stream: true }
|
||||
) do %>
|
||||
|
||||
<section class="form--section">
|
||||
|
||||
@@ -13,20 +13,20 @@
|
||||
<% if body_subheader %>
|
||||
<tr>
|
||||
<td style="<%= placeholder_text_styles("font-weight": "bold") %>">
|
||||
<%= format_text body_subheader %>
|
||||
<%= format_mail_html body_subheader %>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
<% if body_header %>
|
||||
<tr>
|
||||
<td style="<%= placeholder_text_styles(color: "#333333", "line-height": "36px", "font-size": "18px", "font-weight": "bold") %>">
|
||||
<%= format_text body_header %>
|
||||
<%= format_mail_html body_header %>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
<tr>
|
||||
<td style="<%= placeholder_text_styles %>">
|
||||
<%= format_text body %>
|
||||
<%= format_mail_html body %>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
<%= content_tag :div,
|
||||
class: "advanced-filters--filter-value #{value_visibility}",
|
||||
data: {
|
||||
filter_autocomplete: true,
|
||||
filter__filters_form_target: "filterValueContainer",
|
||||
filter_name: filter.name
|
||||
} do %>
|
||||
<%= angular_component_tag autocomplete_options[:component],
|
||||
inputs: {
|
||||
inputName: "value",
|
||||
multiple: true,
|
||||
multipleAsSeparateInputs: false,
|
||||
inputValue: filter.values,
|
||||
id: "#{filter.name}_value",
|
||||
hiddenFieldAction: "change->filter--filters-form#autocompleteSendForm"
|
||||
# Set on the template as there is a timing issue where the
|
||||
# angular component is not yet ready in the DOM and hence
|
||||
# the action can't be registered on the input field at the time
|
||||
# of the #connect lifecycle hook of the filter--filters-form
|
||||
# Stimulus controller.
|
||||
}.merge(autocomplete_options.except(:component)) %>
|
||||
<% end %>
|
||||
@@ -1,12 +0,0 @@
|
||||
<label class="advanced-filters--filter-value"
|
||||
data-filter--filters-form-target="filterValueContainer"
|
||||
data-filter-name="<%= filter.name %>">
|
||||
<%= angular_component_tag "opce-spot-switch",
|
||||
inputs: {
|
||||
name: "v-" + filter.class.key.to_s,
|
||||
checked: filter.values.first != "f",
|
||||
filterName: "boolean",
|
||||
id: "#{filter.name}_value"
|
||||
} %>
|
||||
</label>
|
||||
<div class="advanced-filters--filter-operator" style="visibility: hidden"></div>
|
||||
@@ -1,47 +0,0 @@
|
||||
<div class="advanced-filters--filter-value <%= value_visibility %>"
|
||||
data-filter--filters-form-target="filterValueContainer"
|
||||
data-filter-name="<%= filter.name %>">
|
||||
<% if filter.type == :string %>
|
||||
<div>
|
||||
<%= text_field_tag :value,
|
||||
filter.values.first,
|
||||
class: "advanced-filters--text-field -slim",
|
||||
data: {
|
||||
"filter--filters-form-target": "simpleValue",
|
||||
"filter-name": filter.name
|
||||
} %>
|
||||
</div>
|
||||
<% elsif filter.type == :text %>
|
||||
<div>
|
||||
<%= text_field_tag :value,
|
||||
filter.values.first,
|
||||
class: "advanced-filters--text-field -slim",
|
||||
data: {
|
||||
"filter--filters-form-target": "simpleValue",
|
||||
"filter-name": filter.name
|
||||
} %>
|
||||
</div>
|
||||
<% elsif filter.type == :integer %>
|
||||
<div>
|
||||
<%= number_field_tag :value,
|
||||
filter.values.first,
|
||||
class: "advanced-filters--text-field -slim",
|
||||
step: "any",
|
||||
data: {
|
||||
"filter--filters-form-target": "simpleValue",
|
||||
"filter-name": filter.name
|
||||
} %>
|
||||
</div>
|
||||
<% elsif filter.type == :float %>
|
||||
<div>
|
||||
<%= number_field_tag :value,
|
||||
filter.values.first,
|
||||
class: "advanced-filters--text-field -slim",
|
||||
step: "any",
|
||||
data: {
|
||||
"filter--filters-form-target": "simpleValue",
|
||||
"filter-name": filter.name
|
||||
} %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
@@ -1,16 +0,0 @@
|
||||
<div class="between-dates">
|
||||
<span><%= t(:label_date_from) %>:</span>
|
||||
<%= angular_component_tag "opce-basic-single-date-picker",
|
||||
inputs: {
|
||||
value: from_value,
|
||||
id: "between-dates-from-value-#{filter.name}",
|
||||
name: "from_value"
|
||||
} %>
|
||||
<span><%= t(:label_date_to) %>:</span>
|
||||
<%= angular_component_tag "opce-basic-single-date-picker",
|
||||
inputs: {
|
||||
value: to_value,
|
||||
id: "between-dates-to-value-#{filter.name}",
|
||||
name: "to_value"
|
||||
} %>
|
||||
</div>
|
||||
@@ -1,15 +0,0 @@
|
||||
<div class="days">
|
||||
<div class="form--field-container">
|
||||
<div class="form--text-field-container -xslim">
|
||||
<span class="inline-label">
|
||||
<%= number_field_tag :value,
|
||||
value,
|
||||
id: "#{filter.name}_value",
|
||||
class: "advanced-filters--text-field -slim",
|
||||
"data-filter--filters-form-target": "days",
|
||||
"data-filter-name": filter.name %>
|
||||
<label for="<%= "#{filter.name}_value" %>" class="form-label -transparent"><%= t("datetime.units.day.other") %></label>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,24 +0,0 @@
|
||||
<%
|
||||
value_format = ""
|
||||
if [">t-", "<t-", "t-", "<t+", ">t+", "t+"].include? selected_operator
|
||||
value_format = "days"
|
||||
elsif selected_operator == "=d"
|
||||
value_format = "on-date"
|
||||
elsif selected_operator == "<>d"
|
||||
value_format = "between-dates"
|
||||
end
|
||||
%>
|
||||
<div class="advanced-filters--filter-value <%= value_visibility %> <%= value_format %>"
|
||||
data-filter--filters-form-target="filterValueContainer"
|
||||
data-filter-name="<%= filter.name %>">
|
||||
<%= render partial: "filters/date/days",
|
||||
locals: { filter: filter,
|
||||
value: value_format == "days" ? filter.values.first : "" } %>
|
||||
<%= render partial: "filters/date/on_date",
|
||||
locals: { filter: filter,
|
||||
value: value_format == "on-date" ? filter.values.first : "" } %>
|
||||
<%= render partial: "filters/date/between_dates",
|
||||
locals: { filter: filter,
|
||||
from_value: value_format == "between-dates" ? filter.values.first : "",
|
||||
to_value: value_format == "between-dates" ? filter.values.second : "" } %>
|
||||
</div>
|
||||
@@ -1,10 +0,0 @@
|
||||
<div class="on-date">
|
||||
<div class="form--field-container -visible-overflow">
|
||||
<%= angular_component_tag "opce-basic-single-date-picker",
|
||||
inputs: {
|
||||
value: value,
|
||||
id: "on-date-value-#{filter.name}",
|
||||
name: "value"
|
||||
} %>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,8 +0,0 @@
|
||||
<% filter_values = filter.values || [] %>
|
||||
|
||||
<div class="advanced-filters--filter-value <%= value_visibility %> <%= filter_values.size > 1 ? "multi-value" : "" %>"
|
||||
data-filter--filters-form-target="filterValueContainer"
|
||||
data-filter-name="<%= filter.name %>">
|
||||
<%= render partial: "filters/list/select", locals: { filter: filter, selected_values: filter_values.first, multi_value: false } %>
|
||||
<%= render partial: "filters/list/select", locals: { filter: filter, selected_values: filter_values, multi_value: true } %>
|
||||
</div>
|
||||
@@ -1,30 +0,0 @@
|
||||
<div class="<%= multi_value ? "multi-select" : "single-select" %>">
|
||||
<span class="inline-label">
|
||||
<% select_options = [:value,
|
||||
options_from_collection_for_select(
|
||||
filter.allowed_values,
|
||||
:second,
|
||||
:first,
|
||||
selected_values
|
||||
),
|
||||
{
|
||||
class: "form--select -slim",
|
||||
"data-filter--filters-form-target": "filterValueSelect",
|
||||
"data-filter-name": filter.name,
|
||||
id: "#{filter.name}_value"
|
||||
}]
|
||||
if multi_value
|
||||
select_options.third[:multiple] = true
|
||||
end %>
|
||||
<%= select_tag *select_options %>
|
||||
<a href=""
|
||||
class="form-label no-decoration-on-hover -transparent multi-select-toggle"
|
||||
tabindex="0"
|
||||
data-action="click->filter--filters-form#toggleMultiSelect"
|
||||
data-filter--filters-form-filter-name-param="<%= filter.name %>">
|
||||
<span class="icon-context icon-button <%= multi_value ? "icon-minus2" : "icon-add" %> icon4">
|
||||
<span class="sr-only"><%= t(:label_enable_multi_select) %></span>
|
||||
</span>
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
@@ -102,7 +102,7 @@ See COPYRIGHT and LICENSE files for more details.
|
||||
<table class="header">
|
||||
<tr>
|
||||
<td>
|
||||
<%= OpenProject::TextFormatting::Renderer.format_text(Setting.localized_emails_header) %>
|
||||
<%= format_mail_html(Setting.localized_emails_header) %>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -118,7 +118,7 @@ See COPYRIGHT and LICENSE files for more details.
|
||||
<table class="footer">
|
||||
<tr>
|
||||
<td>
|
||||
<%= OpenProject::TextFormatting::Renderer.format_text(Setting.localized_emails_footer) %>
|
||||
<%= format_mail_html(Setting.localized_emails_footer) %>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
)
|
||||
%>
|
||||
|
||||
<%= format_text(@message) %>
|
||||
<%= format_mail_html(@message) %>
|
||||
|
||||
<%= I18n.t(:"mail_member_added_project.body.roles") %>
|
||||
<ul>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
)
|
||||
%>
|
||||
|
||||
<%= format_text(@message) %>
|
||||
<%= format_mail_html(@message) %>
|
||||
|
||||
<%= I18n.t(:"mail_member_updated_global.body.roles") %>
|
||||
<ul>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
)
|
||||
%>
|
||||
|
||||
<%= format_text(@message) %>
|
||||
<%= format_mail_html(@message) %>
|
||||
|
||||
<%= I18n.t(:"mail_member_updated_project.body.roles") %>
|
||||
<ul>
|
||||
|
||||
@@ -29,7 +29,7 @@ See COPYRIGHT and LICENSE files for more details.
|
||||
|
||||
<h1><%= link_to @project.name, project_url(@project) %></h1>
|
||||
|
||||
<%= format_text(@notification_text, project: @project) %>
|
||||
<%= format_mail_html(@notification_text, project: @project) %>
|
||||
|
||||
<% if @work_package&.visible? %>
|
||||
<p>
|
||||
|
||||
@@ -29,7 +29,7 @@ See COPYRIGHT and LICENSE files for more details.
|
||||
|
||||
<h1><%= link_to @project.name, project_url(@project) %></h1>
|
||||
|
||||
<%= format_text(@notification_text, project: @project) %>
|
||||
<%= format_mail_html(@notification_text, project: @project) %>
|
||||
|
||||
<% if @project.project_creation_wizard_enabled? %>
|
||||
<p>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user