Port export changes to develop (#8329)

Signed-off-by: Anna Khismatullina <anna.khismatullina@gmail.com>
This commit is contained in:
Anna Khismatullina
2025-03-25 21:10:51 +05:00
committed by GitHub
parent 163f6785c9
commit 6beddd3b16
80 changed files with 1557 additions and 987 deletions
+220 -636
View File
File diff suppressed because it is too large Load Diff
+3
View File
@@ -221,6 +221,9 @@
"@hcengineering/card": "^0.6.0",
"@hcengineering/card-assets": "^0.6.0",
"@hcengineering/card-resources": "^0.6.0",
"@hcengineering/export": "^0.6.0",
"@hcengineering/export-assets": "^0.6.0",
"@hcengineering/export-resources": "^0.6.0",
"@hcengineering/my-space": "^0.6.0",
"@hcengineering/my-space-assets": "^0.6.0",
"@hcengineering/my-space-resources": "^0.6.0",
+5 -1
View File
@@ -27,6 +27,7 @@ import { desktopPreferencesId } from '@hcengineering/desktop-preferences'
import { diffviewId } from '@hcengineering/diffview'
import { documentId } from '@hcengineering/document'
import { driveId } from '@hcengineering/drive'
import exportPlugin, { exportId } from '@hcengineering/export'
import gmail, { gmailId } from '@hcengineering/gmail'
import guest, { guestId } from '@hcengineering/guest'
import { hrId } from '@hcengineering/hr'
@@ -76,6 +77,7 @@ import '@hcengineering/desktop-preferences-assets'
import '@hcengineering/diffview-assets'
import '@hcengineering/document-assets'
import '@hcengineering/drive-assets'
import '@hcengineering/export-assets'
import '@hcengineering/gmail-assets'
import '@hcengineering/guest-assets'
import '@hcengineering/hr-assets'
@@ -208,6 +210,7 @@ function configureI18n (): void {
addStringsLoader(guestId, async (lang: string) => await import(`@hcengineering/guest-assets/lang/${lang}.json`))
addStringsLoader(loveId, async (lang: string) => await import(`@hcengineering/love-assets/lang/${lang}.json`))
addStringsLoader(printId, async (lang: string) => await import(`@hcengineering/print-assets/lang/${lang}.json`))
addStringsLoader(exportId, async (lang: string) => await import(`@hcengineering/export-assets/lang/${lang}.json`))
addStringsLoader(analyticsCollectorId, async (lang: string) => await import(`@hcengineering/analytics-collector-assets/lang/${lang}.json`))
addStringsLoader(testManagementId, async (lang: string) => await import(`@hcengineering/test-management-assets/lang/${lang}.json`))
addStringsLoader(surveyId, async (lang: string) => await import(`@hcengineering/survey-assets/lang/${lang}.json`))
@@ -273,6 +276,7 @@ export async function configurePlatform (): Promise<void> {
setMetadata(analyticsCollector.metadata.EndpointURL, config.ANALYTICS_COLLECTOR_URL)
setMetadata(aiBot.metadata.EndpointURL, config.AI_URL)
setMetadata(presence.metadata.PresenceUrl, config.PRESENCE_URL ?? '')
setMetadata(exportPlugin.metadata.ExportUrl, config.EXPORT_URL ?? '')
const languages = myBranding.languages !== undefined && myBranding.languages !== '' ? myBranding.languages.split(',').map((l) => l.trim()) : ['en', 'ru', 'es', 'pt', 'zh', 'fr', 'cs', 'it', 'de']
@@ -341,6 +345,7 @@ export async function configurePlatform (): Promise<void> {
addLocation(guestId, () => import(/* webpackChunkName: "guest" */ '@hcengineering/guest-resources'))
addLocation(loveId, () => import(/* webpackChunkName: "love" */ '@hcengineering/love-resources'))
addLocation(printId, () => import(/* webpackChunkName: "print" */ '@hcengineering/print-resources'))
addLocation(exportId, () => import(/* webpackChunkName: "export" */ '@hcengineering/export-resources'))
addLocation(textEditorId, () => import(/* webpackChunkName: "text-editor" */ '@hcengineering/text-editor-resources'))
addLocation(testManagementId, () => import(/* webpackChunkName: "test-management" */ '@hcengineering/test-management-resources'))
addLocation(surveyId, () => import(/* webpackChunkName: "survey" */ '@hcengineering/survey-resources'))
@@ -376,7 +381,6 @@ export async function configurePlatform (): Promise<void> {
configureNotifications()
setMetadata(setting.metadata.BackupUrl, config.BACKUP_URL ?? '')
setMetadata(setting.metadata.ExportUrl, config.EXPORT_URL ?? '')
if (config.INITIAL_URL !== '') {
setLocationStorageKey('uberflow_child')
+3
View File
@@ -104,6 +104,9 @@
"@hcengineering/drive": "^0.6.0",
"@hcengineering/drive-assets": "^0.6.0",
"@hcengineering/drive-resources": "^0.6.0",
"@hcengineering/export": "^0.6.0",
"@hcengineering/export-assets": "^0.6.0",
"@hcengineering/export-resources": "^0.6.0",
"@hcengineering/github": "^0.6.0",
"@hcengineering/github-assets": "^0.6.0",
"@hcengineering/github-resources": "^0.6.0",
+1 -1
View File
@@ -21,5 +21,5 @@
"PASSWORD_STRICTNESS": "none",
"STREAM_URL": "http://huly.local:1080/recording",
"PUBLIC_SCHEDULE_URL": "http://huly.local:8060",
"EXPORT_URL": "http://huly.local:4009/export"
"EXPORT_URL": "http://huly.local:4009"
}
+5 -1
View File
@@ -30,6 +30,7 @@ import { desktopPreferencesId } from '@hcengineering/desktop-preferences'
import { diffviewId } from '@hcengineering/diffview'
import { documentId } from '@hcengineering/document'
import { driveId } from '@hcengineering/drive'
import exportPlugin, { exportId } from '@hcengineering/export'
import gmail, { gmailId } from '@hcengineering/gmail'
import guest, { guestId } from '@hcengineering/guest'
import { hrId } from '@hcengineering/hr'
@@ -85,6 +86,7 @@ import '@hcengineering/desktop-preferences-assets'
import '@hcengineering/diffview-assets'
import '@hcengineering/document-assets'
import '@hcengineering/drive-assets'
import '@hcengineering/export-assets'
import '@hcengineering/gmail-assets'
import '@hcengineering/guest-assets'
import '@hcengineering/hr-assets'
@@ -334,6 +336,7 @@ function configureI18n(): void {
addStringsLoader(guestId, async (lang: string) => await import(`@hcengineering/guest-assets/lang/${lang}.json`))
addStringsLoader(loveId, async (lang: string) => await import(`@hcengineering/love-assets/lang/${lang}.json`))
addStringsLoader(printId, async (lang: string) => await import(`@hcengineering/print-assets/lang/${lang}.json`))
addStringsLoader(exportId, async (lang: string) => await import(`@hcengineering/export-assets/lang/${lang}.json`))
addStringsLoader(
analyticsCollectorId,
async (lang: string) => await import(`@hcengineering/analytics-collector-assets/lang/${lang}.json`)
@@ -450,6 +453,7 @@ export async function configurePlatform() {
setMetadata(print.metadata.PrintURL, config.PRINT_URL)
setMetadata(sign.metadata.SignURL, config.SIGN_URL)
setMetadata(presence.metadata.PresenceUrl, config.PRESENCE_URL ?? '')
setMetadata(exportPlugin.metadata.ExportUrl, config.EXPORT_URL ?? '')
const languages = myBranding.languages
? (myBranding.languages as string).split(',').map((l) => l.trim())
@@ -528,6 +532,7 @@ export async function configurePlatform() {
addLocation(guestId, () => import(/* webpackChunkName: "guest" */ '@hcengineering/guest-resources'))
addLocation(loveId, () => import(/* webpackChunkName: "love" */ '@hcengineering/love-resources'))
addLocation(printId, () => import(/* webpackChunkName: "print" */ '@hcengineering/print-resources'))
addLocation(exportId, () => import(/* webpackChunkName: "export" */ '@hcengineering/export-resources'))
addLocation(textEditorId, () => import(/* webpackChunkName: "text-editor" */ '@hcengineering/text-editor-resources'))
addLocation(uploaderId, () => import(/* webpackChunkName: "uploader" */ '@hcengineering/uploader-resources'))
addLocation(recorderId, () => import(/* webpackChunkName: "recorder" */ '@hcengineering/recorder-resources'))
@@ -565,7 +570,6 @@ export async function configurePlatform() {
setMetadata(workbench.metadata.DefaultSpecial, myBranding.defaultSpecial ?? 'issues')
setMetadata(setting.metadata.BackupUrl, config.BACKUP_URL ?? '')
setMetadata(setting.metadata.ExportUrl, config.EXPORT_URL ?? '')
initThemeStore()
}
+1
View File
@@ -101,6 +101,7 @@
"@hcengineering/model-love": "^0.6.0",
"@hcengineering/model-server-love": "^0.6.0",
"@hcengineering/model-print": "^0.6.0",
"@hcengineering/model-export": "^0.6.0",
"@hcengineering/model-questions": "^0.1.0",
"@hcengineering/model-training": "^0.1.0",
"@hcengineering/model-controlled-documents": "^0.1.0",
+2
View File
@@ -89,6 +89,7 @@ import { serverGithubId, createModel as serverGithubModel } from '@hcengineering
import { serverTimeId, createModel as serverTimeModel } from '@hcengineering/model-server-time'
import love, { loveId, createModel as loveModel } from '@hcengineering/model-love'
import { printId, createModel as printModel } from '@hcengineering/model-print'
import { exportId, createModel as exportModel } from '@hcengineering/model-export'
import { analyticsCollectorId, createModel as analyticsCollectorModel } from '@hcengineering/model-analytics-collector'
import { serverLoveId, createModel as serverLoveModel } from '@hcengineering/model-server-love'
import { serverProcessId, createModel as serverProcessModel } from '@hcengineering/model-server-process'
@@ -366,6 +367,7 @@ export default function buildModel (enabled: string[] = ['*'], disabled: string[
}
],
[printModel, printId],
[exportModel, exportId],
[aiBotModel, aiBotId],
[
cardModel,
+7
View File
@@ -0,0 +1,7 @@
module.exports = {
extends: ['./node_modules/@hcengineering/platform-rig/profiles/model/eslint.config.json'],
parserOptions: {
tsconfigRootDir: __dirname,
project: './tsconfig.json'
}
}
+4
View File
@@ -0,0 +1,4 @@
*
!/lib/**
!CHANGELOG.md
/lib/**/__tests__/
+5
View File
@@ -0,0 +1,5 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
"rigPackageName": "@hcengineering/platform-rig",
"rigProfile": "model"
}
+42
View File
@@ -0,0 +1,42 @@
{
"name": "@hcengineering/model-export",
"version": "0.6.0",
"main": "lib/index.js",
"svelte": "src/index.ts",
"types": "types/index.d.ts",
"author": "Copyright © Hardcore Engineering Inc.",
"template": "@hcengineering/model-package",
"license": "EPL-2.0",
"scripts": {
"build": "compile",
"build:watch": "compile",
"format": "format src",
"_phase:build": "compile transpile src",
"_phase:format": "format src",
"_phase:validate": "compile validate"
},
"devDependencies": {
"@hcengineering/platform-rig": "^0.6.0",
"@typescript-eslint/eslint-plugin": "^6.11.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-n": "^15.4.0",
"eslint": "^8.54.0",
"@typescript-eslint/parser": "^6.11.0",
"eslint-config-standard-with-typescript": "^40.0.0",
"prettier": "^3.1.0",
"typescript": "^5.3.3"
},
"dependencies": {
"@hcengineering/core": "^0.6.32",
"@hcengineering/model-core": "^0.6.0",
"@hcengineering/model": "^0.6.11",
"@hcengineering/platform": "^0.6.11",
"@hcengineering/ui": "^0.6.15",
"@hcengineering/workbench": "^0.6.16",
"@hcengineering/presentation": "^0.6.3",
"@hcengineering/model-presentation": "^0.6.0",
"@hcengineering/export-resources": "^0.6.0",
"@hcengineering/export": "^0.6.0"
}
}
+36
View File
@@ -0,0 +1,36 @@
//
// Copyright © 2025 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import { type Builder } from '@hcengineering/model'
import core from '@hcengineering/model-core'
import presentation from '@hcengineering/model-presentation'
import workbench from '@hcengineering/workbench'
import exportPlugin from './plugin'
export { exportId } from '@hcengineering/export'
export * from './migration'
export default exportPlugin
export function createModel (builder: Builder): void {
builder.createDoc(
presentation.class.ComponentPointExtension,
core.space.Model,
{
extension: workbench.extensions.SpecialViewAction,
component: exportPlugin.component.ExportButton
},
exportPlugin.extensions.ExportButton
)
}
+21
View File
@@ -0,0 +1,21 @@
//
// Copyright © 2025 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import { type MigrateOperation, type MigrationClient, type MigrationUpgradeClient } from '@hcengineering/model'
export const loveOperation: MigrateOperation = {
async migrate (client: MigrationClient): Promise<void> {},
async upgrade (state: Map<string, Set<string>>, client: () => Promise<MigrationUpgradeClient>): Promise<void> {}
}
+26
View File
@@ -0,0 +1,26 @@
//
// Copyright © 2025 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import { mergeIds } from '@hcengineering/platform'
import { exportId } from '@hcengineering/export'
import { type ComponentPointExtension } from '@hcengineering/presentation'
import { type Ref } from '@hcengineering/core'
import exportPlugin from '@hcengineering/export-resources/src/plugin'
export default mergeIds(exportId, exportPlugin, {
extensions: {
ExportButton: '' as Ref<ComponentPointExtension>
}
})
+10
View File
@@ -0,0 +1,10 @@
{
"extends": "./node_modules/@hcengineering/platform-rig/profiles/model/tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib",
"declarationDir": "./types",
"tsBuildInfoFile": ".build/build.tsbuildinfo"
}
}
+25 -1
View File
@@ -185,7 +185,31 @@ export function createModel (builder: Builder): void {
createEvent: RecruitEvents.PlusTalentButtonClicked,
createLabel: recruit.string.TalentCreateLabel,
createComponent: recruit.component.CreateCandidate,
createComponentProps: { shouldSaveDraft: false }
createComponentProps: { shouldSaveDraft: false },
actionVisible: true,
actionConfig: {
skipAttributes: ['_class', 'avatarType', 'space'],
attributeKeyMap: { city: 'location' },
attributeTransforms: {
channels: {
operations: [
{
type: 'group_by',
config: {
keyField: 'data.provider',
valueField: 'data.value'
}
},
{
type: 'join',
config: {
delimiter: ', '
}
}
]
}
}
}
},
position: 'vacancy'
},
+2 -1
View File
@@ -42,6 +42,7 @@
"@hcengineering/task": "^0.6.20",
"@hcengineering/notification": "^0.6.23",
"@hcengineering/templates": "^0.6.11",
"@hcengineering/activity": "^0.6.0"
"@hcengineering/activity": "^0.6.0",
"@hcengineering/export": "^0.6.0"
}
}
+4 -3
View File
@@ -36,6 +36,7 @@ import {
type WorkspaceSetting
} from '@hcengineering/setting'
import templates from '@hcengineering/templates'
import exportPlugin from '@hcengineering/export'
import setting from './plugin'
import workbench, { WidgetType } from '@hcengineering/model-workbench'
@@ -341,9 +342,9 @@ export function createModel (builder: Builder): void {
core.space.Model,
{
name: 'export',
label: setting.string.Export,
icon: setting.icon.Export,
component: setting.component.Export,
label: exportPlugin.string.Export,
icon: exportPlugin.icon.Export,
component: exportPlugin.component.ExportSettings,
group: 'settings-editor',
role: AccountRole.User,
order: 4800
+7
View File
@@ -0,0 +1,7 @@
module.exports = {
extends: ['./node_modules/@hcengineering/platform-rig/profiles/assets/eslint.config.json'],
parserOptions: {
tsconfigRootDir: __dirname,
project: './tsconfig.json'
}
}
+6
View File
@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
<symbol id="export" viewBox="0 0 16 16">
<path d="M7.92231 14.2015L7.99302 14.6964L7.92231 14.2015ZM6.22318 14.311L6.33389 13.8234L6.22318 14.311ZM4.79789 13.0748L5.26492 12.8963L4.79789 13.0748ZM13.373 12.5661L12.9774 12.2603L13.373 12.5661ZM9.05136 14.0402L8.98065 13.5452L9.05136 14.0402ZM13.1512 12.8218L13.5099 13.1701L13.1512 12.8218ZM13.1512 3.17821L12.7926 3.5266L13.1512 3.17821ZM13.373 3.43391L13.7686 3.12813L13.373 3.43391ZM7.98221 2.31213L8.98065 2.45477L9.12207 1.46482L8.12363 1.32218L7.98221 2.31213ZM13.4993 7.66487V8.33509H14.4993V7.66487H13.4993ZM8.98065 13.5452L7.8516 13.7065L7.99302 14.6964L9.12207 14.5351L8.98065 13.5452ZM7.8516 13.7065C7.39911 13.7711 7.0821 13.8162 6.83278 13.8363C6.58629 13.8562 6.44248 13.8481 6.33389 13.8234L6.11247 14.7986C6.364 14.8557 6.62341 14.8565 6.91329 14.8331C7.20033 14.8099 7.55283 14.7593 7.99302 14.6964L7.8516 13.7065ZM4.16602 11.3773C4.16602 11.822 4.1658 12.1781 4.18344 12.4655C4.20126 12.7558 4.23875 13.0125 4.33085 13.2534L5.26492 12.8963C5.22515 12.7923 5.19671 12.6511 5.18156 12.4042C5.16624 12.1546 5.16602 11.8344 5.16602 11.3773H4.16602ZM6.33389 13.8234C5.84503 13.7124 5.44393 13.3645 5.26492 12.8963L4.33085 13.2534C4.62921 14.0338 5.29771 14.6136 6.11247 14.7986L6.33389 13.8234ZM13.4993 8.33509C13.4993 9.48721 13.4986 10.3082 13.4244 10.9439C13.3517 11.5676 13.2136 11.9547 12.9774 12.2603L13.7686 12.8718C14.1587 12.3671 14.3338 11.7788 14.4177 11.0597C14.5001 10.3527 14.4993 9.46335 14.4993 8.33509H13.4993ZM9.12207 14.5351C10.239 14.3756 11.1195 14.2506 11.8078 14.069C12.5078 13.8843 13.0654 13.6277 13.5099 13.1701L12.7926 12.4734C12.5234 12.7504 12.1598 12.9419 11.5526 13.1021C10.9338 13.2654 10.1212 13.3823 8.98065 13.5452L9.12207 14.5351ZM12.9774 12.2603C12.9198 12.3347 12.8581 12.4059 12.7926 12.4734L13.5099 13.1701C13.6017 13.0756 13.688 12.9761 13.7686 12.8718L12.9774 12.2603ZM8.98065 2.45477C10.1212 2.6177 10.9338 2.73458 11.5526 2.89788C12.1598 3.05809 12.5234 3.24952 12.7926 3.5266L13.5099 2.82982C13.0654 2.37222 12.5078 2.11569 11.8078 1.93098C11.1195 1.74936 10.239 1.62438 9.12207 1.46482L8.98065 2.45477ZM14.4993 7.66487C14.4993 6.53661 14.5001 5.64727 14.4177 4.94022C14.3338 4.22111 14.1587 3.63289 13.7686 3.12813L12.9774 3.73968C13.2136 4.04531 13.3517 4.43238 13.4244 5.05606C13.4986 5.69179 13.4993 6.51275 13.4993 7.66487H14.4993ZM12.7926 3.5266C12.8581 3.59409 12.9198 3.66523 12.9774 3.73968L13.7686 3.12813C13.688 3.02391 13.6017 2.92431 13.5099 2.82982L12.7926 3.5266ZM8.12363 1.32218C7.62395 1.2508 7.22383 1.19335 6.89852 1.16973C6.56979 1.14587 6.27615 1.15257 5.99374 1.23142L6.26265 2.19458C6.38494 2.16044 6.54699 2.14684 6.82611 2.16711C7.10865 2.18762 7.46854 2.23875 7.98221 2.31213L8.12363 1.32218ZM5.16602 4.7546C5.16602 4.23572 5.1663 3.87222 5.18595 3.58961C5.20536 3.31043 5.24174 3.15194 5.29283 3.0357L4.37738 2.63328C4.25938 2.90171 4.21122 3.19145 4.18836 3.52025C4.16573 3.84563 4.16602 4.24985 4.16602 4.7546H5.16602ZM5.99374 1.23142C5.27211 1.43289 4.67888 1.9474 4.37738 2.63328L5.29283 3.0357C5.47374 2.62417 5.82967 2.31547 6.26265 2.19458L5.99374 1.23142Z"/>
<path d="M10.666 8.00002L11.0565 7.68767L11.3063 8.00002L11.0565 8.31237L10.666 8.00002ZM2.66602 8.50002C2.38987 8.50002 2.16602 8.27616 2.16602 8.00002C2.16602 7.72388 2.38987 7.50002 2.66602 7.50002V8.50002ZM8.38978 4.35434L11.0565 7.68767L10.2756 8.31237L7.60891 4.97903L8.38978 4.35434ZM11.0565 8.31237L8.38978 11.6457L7.60891 11.021L10.2756 7.68767L11.0565 8.31237ZM10.666 8.50002H2.66602V7.50002H10.666V8.50002Z"/>
</symbol>
</svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

+5
View File
@@ -0,0 +1,5 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
"rigPackageName": "@hcengineering/platform-rig",
"rigProfile": "assets"
}
+7
View File
@@ -0,0 +1,7 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'],
roots: ["./src"],
coverageReporters: ["text-summary", "html"]
}
+26
View File
@@ -0,0 +1,26 @@
{
"string": {
"WorkspaceNamePattern": "Název musí být 40 znaků nebo méně, není prázdný a nesmí obsahovat speciální znaky (<, >, /)",
"Export": "Export",
"DataToExport": "Data k exportu",
"ExportDocuments": "Dokumenty",
"ExportMilestones": "Milník",
"ExportIssues": "Problémy",
"ExportTestCases": "Testovací případy",
"ExportTestRuns": "Běhy testů",
"ExportTestPlans": "Testovací plány",
"ExportFormat": "Formát exportu",
"ExportJSON": "JSON",
"ExportCSV": "CSV",
"ExportUnifiedFormat": "Jednotný formát Huly",
"ExportIncludeContent": "Zahrnout obsah",
"ExportEverything": "Vše",
"ExportAttributesOnly": "Pouze atributy",
"ExportRequestSuccess": "Export byl zahájen.",
"ExportRequestSuccessMessage": "Export byl zahájen. Až bude dokončen, obdržíte oznámení ve své schránce.",
"ExportRequestFailed": "Nepodařilo se odeslat export.",
"ExportRequestFailedMessage": "Při odesílání exportu došlo k chybě. Zkuste to prosím později.",
"ExportCompleted": "Export byl dokončen. Vaše soubory jsou připraveny. Můžete je stáhnout z disku.",
"ExportFailed": "Export dat se nezdařil. Zkuste to prosím později."
}
}
+26
View File
@@ -0,0 +1,26 @@
{
"string": {
"WorkspaceNamePattern": "Name muss 40 Zeichen oder weniger sein, nicht leer sein und keine Sonderzeichen (<, >, /) enthalten",
"Export": "Exportieren",
"DataToExport": "Zu exportierende Daten",
"ExportDocuments": "Dokumente",
"ExportMilestones": "Meilenstein",
"ExportIssues": "Probleme",
"ExportTestCases": "Testfälle",
"ExportTestRuns": "Testläufe",
"ExportTestPlans": "Testpläne",
"ExportFormat": "Exportformat",
"ExportJSON": "JSON",
"ExportCSV": "CSV",
"ExportUnifiedFormat": "Huly Einheitliches Format",
"ExportIncludeContent": "Inhalt einschließen",
"ExportEverything": "Alles",
"ExportAttributesOnly": "Nur Attribute",
"ExportRequestSuccess": "Export gestartet.",
"ExportRequestSuccessMessage": "Export gestartet. Sie erhalten eine Benachrichtigung in Ihrem Posteingang, wenn der Export abgeschlossen ist.",
"ExportRequestFailed": "Export konnte nicht gestartet werden.",
"ExportRequestFailedMessage": "Beim Starten des Exports ist ein Fehler aufgetreten. Bitte versuchen Sie es später erneut.",
"ExportCompleted": "Export abgeschlossen. Ihre Dateien sind bereit. Sie können sie vom Laufwerk herunterladen.",
"ExportFailed": "Datenexport fehlgeschlagen. Bitte versuchen Sie es später erneut."
}
}
+26
View File
@@ -0,0 +1,26 @@
{
"string": {
"WorkspaceNamePattern": "Name must be 40 characters or less, not empty, and cannot contain special characters (<, >, /)",
"Export": "Export",
"DataToExport": "Data to export",
"ExportDocuments": "Documents",
"ExportMilestones": "Milestone",
"ExportIssues": "Issues",
"ExportTestCases": "Test Cases",
"ExportTestRuns": "Test Runs",
"ExportTestPlans": "Test Plans",
"ExportFormat": "Export format",
"ExportJSON": "JSON",
"ExportCSV": "CSV",
"ExportUnifiedFormat": "Huly Unified Format",
"ExportIncludeContent": "Include content",
"ExportEverything": "Everything",
"ExportAttributesOnly": "Attributes only",
"ExportRequestSuccess": "Export started.",
"ExportRequestSuccessMessage": "Export started. You will receive a notification in your Inbox when it completes.",
"ExportRequestFailed": "Failed to submit export.",
"ExportRequestFailedMessage": "Error occurred while submitting export. Please try again later.",
"ExportCompleted": "Export completed. Your files are ready. You can download them from the drive.",
"ExportFailed": "Failed to export data. Please try again later."
}
}
+26
View File
@@ -0,0 +1,26 @@
{
"string": {
"WorkspaceNamePattern": "El nombre debe tener 40 caracteres o menos, no esté vacío y no puede contener caracteres especiales (<, >, /)",
"Export": "Exportar",
"DataToExport": "Datos a exportar",
"ExportDocuments": "Documentos",
"ExportMilestones": "Hito",
"ExportIssues": "Problemas",
"ExportTestCases": "Casos de prueba",
"ExportTestRuns": "Ejecuciones de prueba",
"ExportTestPlans": "Planes de prueba",
"ExportFormat": "Formato de exportación",
"ExportJSON": "JSON",
"ExportCSV": "CSV",
"ExportUnifiedFormat": "Formato Unificado Huly",
"ExportIncludeContent": "Incluir contenido",
"ExportEverything": "Todo",
"ExportAttributesOnly": "Solo atributos",
"ExportRequestSuccess": "Exportación iniciada.",
"ExportRequestSuccessMessage": "Exportación iniciada. Recibirá una notificación en su bandeja de entrada cuando se complete.",
"ExportRequestFailed": "Error al enviar la exportación.",
"ExportRequestFailedMessage": "Ocurrió un error al enviar la exportación. Por favor, inténtelo más tarde.",
"ExportCompleted": "Exportación completada. Sus archivos están listos. Puede descargarlos desde la unidad.",
"ExportFailed": "Error al exportar datos. Por favor, inténtelo más tarde."
}
}
+26
View File
@@ -0,0 +1,26 @@
{
"string": {
"WorkspaceNamePattern": "Le nom doit comporter 40 caractères ou moins, ne doit pas être vide et ne doit pas contenir de caractères spéciaux (<, >, /)" ,
"Export": "Exporter",
"DataToExport": "Données à exporter",
"ExportDocuments": "Documents",
"ExportMilestones": "Jalon",
"ExportIssues": "Problèmes",
"ExportTestCases": "Cas de test",
"ExportTestRuns": "Exécutions de test",
"ExportTestPlans": "Plans de test",
"ExportFormat": "Format d'exportation",
"ExportJSON": "JSON",
"ExportCSV": "CSV",
"ExportUnifiedFormat": "Format Unifié Huly",
"ExportIncludeContent": "Inclure le contenu",
"ExportEverything": "Tout",
"ExportAttributesOnly": "Attributs uniquement",
"ExportRequestSuccess": "Exportation démarrée.",
"ExportRequestSuccessMessage": "Exportation démarrée. Vous recevrez une notification dans votre boîte de réception une fois terminée.",
"ExportRequestFailed": "Échec de la soumission de l'exportation.",
"ExportRequestFailedMessage": "Une erreur s'est produite lors de la soumission de l'exportation. Veuillez réessayer plus tard.",
"ExportCompleted": "Exportation terminée. Vos fichiers sont prêts. Vous pouvez les télécharger depuis le lecteur.",
"ExportFailed": "Échec de l'exportation des données. Veuillez réessayer plus tard."
}
}
+26
View File
@@ -0,0 +1,26 @@
{
"string": {
"WorkspaceNamePattern": "Il nome deve essere di 40 caratteri o meno, non vuoto e non può contenere caratteri speciali (<, >, /)",
"Export": "Esporta",
"DataToExport": "Dati da esportare",
"ExportDocuments": "Documenti",
"ExportMilestones": "Pietra miliare",
"ExportIssues": "Problemi",
"ExportTestCases": "Casi di test",
"ExportTestRuns": "Esecuzioni di test",
"ExportTestPlans": "Piani di test",
"ExportFormat": "Formato di esportazione",
"ExportJSON": "JSON",
"ExportCSV": "CSV",
"ExportUnifiedFormat": "Formato Unificato Huly",
"ExportIncludeContent": "Includi contenuto",
"ExportEverything": "Tutto",
"ExportAttributesOnly": "Solo attributi",
"ExportRequestSuccess": "Esportazione avviata.",
"ExportRequestSuccessMessage": "Esportazione avviata. Riceverai una notifica nella tua casella quando sarà completata.",
"ExportRequestFailed": "Impossibile inviare l'esportazione.",
"ExportRequestFailedMessage": "Si è verificato un errore durante l'invio dell'esportazione. Riprova più tardi.",
"ExportCompleted": "Esportazione completata. I tuoi file sono pronti. Puoi scaricarli dal drive.",
"ExportFailed": "Impossibile esportare i dati. Riprova più tardi."
}
}
+26
View File
@@ -0,0 +1,26 @@
{
"string": {
"WorkspaceNamePattern": "O nome deve ter 40 caracteres ou menos, não está vazio e não pode conter caracteres especiais (<, >, /)",
"Export": "Exportar",
"DataToExport": "Dados para exportar",
"ExportDocuments": "Documentos",
"ExportMilestones": "Marco",
"ExportIssues": "Problemas",
"ExportTestCases": "Casos de teste",
"ExportTestRuns": "Execuções de teste",
"ExportTestPlans": "Planos de teste",
"ExportFormat": "Formato de exportação",
"ExportJSON": "JSON",
"ExportCSV": "CSV",
"ExportUnifiedFormat": "Formato Unificado Huly",
"ExportIncludeContent": "Incluir conteúdo",
"ExportEverything": "Tudo",
"ExportAttributesOnly": "Apenas atributos",
"ExportRequestSuccess": "Exportação iniciada.",
"ExportRequestSuccessMessage": "Exportação iniciada. Você receberá uma notificação em sua caixa de entrada quando for concluída.",
"ExportRequestFailed": "Falha ao enviar exportação.",
"ExportRequestFailedMessage": "Ocorreu um erro ao enviar a exportação. Por favor, tente novamente mais tarde.",
"ExportCompleted": "Exportação concluída. Seus arquivos estão prontos. Você pode baixá-los do drive.",
"ExportFailed": "Falha ao exportar dados. Por favor, tente novamente mais tarde."
}
}
+26
View File
@@ -0,0 +1,26 @@
{
"string": {
"WorkspaceNamePattern": "Имя должно быть длиной 40 символов или меньше, не должно быть пустым и не может содержать специальные символы (<, >, /)",
"Export": "Экспорт",
"DataToExport": "Данные для экспорта",
"ExportDocuments": "Документы",
"ExportMilestones": "Этапы",
"ExportIssues": "Задачи",
"ExportTestCases": "Тест-кейсы",
"ExportTestRuns": "Прогоны тестов",
"ExportTestPlans": "Планы тестирования",
"ExportFormat": "Формат экспорта",
"ExportJSON": "JSON",
"ExportCSV": "CSV",
"ExportUnifiedFormat": "Единый формат Huly",
"ExportIncludeContent": "Включить содержимое",
"ExportEverything": "Всё",
"ExportAttributesOnly": "Только атрибуты",
"ExportRequestSuccess": "Экспорт начат.",
"ExportRequestSuccessMessage": "Экспорт начат. Вы получите уведомление во входящих, когда он будет завершен.",
"ExportRequestFailed": "Не удалось начать экспорт.",
"ExportRequestFailedMessage": "Произошла ошибка при отправке экспорта. Пожалуйста, попробуйте позже.",
"ExportCompleted": "Экспорт завершен. Ваши файлы готовы. Вы можете скачать их с диска.",
"ExportFailed": "Не удалось экспортировать данные. Пожалуйста, попробуйте позже."
}
}
+26
View File
@@ -0,0 +1,26 @@
{
"string": {
"WorkspaceNamePattern": "名称必须为 40 个字符或更少,不能为空,不能包含特殊字符(<、>、/",
"Export": "导出",
"DataToExport": "要导出的数据",
"ExportDocuments": "文档",
"ExportMilestones": "里程碑",
"ExportIssues": "问题",
"ExportTestCases": "测试用例",
"ExportTestRuns": "测试运行",
"ExportTestPlans": "测试计划",
"ExportFormat": "导出格式",
"ExportJSON": "JSON",
"ExportCSV": "CSV",
"ExportUnifiedFormat": "Huly 统一格式",
"ExportIncludeContent": "包含内容",
"ExportEverything": "所有内容",
"ExportAttributesOnly": "仅属性",
"ExportRequestSuccess": "导出已开始。",
"ExportRequestSuccessMessage": "导出已开始。完成后您将在收件箱中收到通知。",
"ExportRequestFailed": "提交导出失败。",
"ExportRequestFailedMessage": "提交导出时发生错误。请稍后重试。",
"ExportCompleted": "导出完成。您的文件已准备就绪。您可以从驱动器下载它们。",
"ExportFailed": "导出数据失败。请稍后重试。"
}
}
+40
View File
@@ -0,0 +1,40 @@
{
"name": "@hcengineering/export-assets",
"version": "0.6.0",
"main": "src/index.ts",
"author": "Copyright © Hardcore Engineering Inc.",
"template": "@hcengineering/assets-package",
"license": "EPL-2.0",
"scripts": {
"build": "compile",
"test": "jest --passWithNoTests --silent",
"build:docs": "",
"format": "format src",
"build:watch": "compile",
"_phase:build": "compile transpile src",
"_phase:test": "jest --passWithNoTests --silent",
"_phase:format": "format src",
"_phase:validate": "compile validate"
},
"devDependencies": {
"@hcengineering/platform-rig": "^0.6.0",
"@typescript-eslint/eslint-plugin": "^6.11.0",
"@typescript-eslint/parser": "^6.11.0",
"eslint-config-standard-with-typescript": "^40.0.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-n": "^15.4.0",
"eslint-plugin-promise": "^6.1.1",
"eslint": "^8.54.0",
"prettier": "^3.1.0",
"@types/node": "~20.11.16",
"jest": "^29.7.0",
"ts-jest": "^29.1.1",
"@types/jest": "^29.5.5",
"prettier-plugin-svelte": "^3.2.2",
"typescript": "^5.3.3"
},
"dependencies": {
"@hcengineering/platform": "^0.6.11",
"@hcengineering/export": "^0.6.0"
}
}
@@ -0,0 +1,6 @@
import { makeLocalesTest } from '@hcengineering/platform'
it(
'Locales are equale',
makeLocalesTest((lang) => import(`../../lang/${lang}.json`))
)
+23
View File
@@ -0,0 +1,23 @@
//
// Copyright © 2025 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import { loadMetadata } from '@hcengineering/platform'
import exportPlugin from '@hcengineering/export'
const icons = require('../assets/icons.svg') as string // eslint-disable-line
loadMetadata(exportPlugin.icon, {
Export: `${icons}#export`
})
+11
View File
@@ -0,0 +1,11 @@
{
"extends": "./node_modules/@hcengineering/platform-rig/profiles/assets/tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib",
"declarationDir": "./types",
"types": ["node", "jest"],
"tsBuildInfoFile": ".build/build.tsbuildinfo"
}
}
+4
View File
@@ -0,0 +1,4 @@
module.exports = {
extends: ['./node_modules/@hcengineering/platform-rig/profiles/ui/eslint.config.json'],
parserOptions: { tsconfigRootDir: __dirname }
}
+22
View File
@@ -0,0 +1,22 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"trailingComma": "none",
"tabWidth": 2,
"semi": false,
"singleQuote": true,
"printWidth": 120,
"useTabs": false,
"bracketSpacing": true,
"proseWrap": "preserve",
"plugins": [
"prettier-plugin-svelte"
],
"overrides": [
{
"files": "*.svelte",
"options": {
"parser": "svelte"
}
}
]
}
+5
View File
@@ -0,0 +1,5 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
"rigPackageName": "@hcengineering/platform-rig",
"rigProfile": "ui"
}
+7
View File
@@ -0,0 +1,7 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'],
roots: ["./src"],
coverageReporters: ["text-summary", "html"]
}
+47
View File
@@ -0,0 +1,47 @@
{
"name": "@hcengineering/export-resources",
"version": "0.6.0",
"main": "src/index.ts",
"author": "Hardcore Engineering Inc.",
"scripts": {
"build": "compile ui",
"build:docs": "api-extractor run --local",
"format": "format src",
"svelte-check": "do-svelte-check",
"_phase:svelte-check": "do-svelte-check",
"build:watch": "compile ui",
"_phase:build": "compile ui",
"_phase:format": "format src",
"_phase:validate": "compile validate"
},
"devDependencies": {
"svelte-loader": "^3.2.0",
"sass": "^1.53.0",
"svelte-preprocess": "^5.1.3",
"@hcengineering/platform-rig": "^0.6.0",
"@typescript-eslint/eslint-plugin": "^6.11.0",
"@typescript-eslint/parser": "^6.11.0",
"eslint-config-standard-with-typescript": "^40.0.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-n": "^15.4.0",
"eslint-plugin-promise": "^6.1.1",
"prettier-plugin-svelte": "^3.2.2",
"eslint": "^8.54.0",
"prettier": "^3.1.0",
"svelte-check": "^3.6.9",
"typescript": "^5.3.3",
"jest": "^29.7.0",
"ts-jest": "^29.1.1",
"@types/jest": "^29.5.5",
"eslint-plugin-svelte": "^2.35.1",
"svelte-eslint-parser": "^0.33.1"
},
"dependencies": {
"svelte": "^4.2.19",
"@hcengineering/platform": "^0.6.11",
"@hcengineering/core": "^0.6.32",
"@hcengineering/presentation": "^0.6.3",
"@hcengineering/ui": "^0.6.15",
"@hcengineering/export": "^0.6.0"
}
}
@@ -0,0 +1,5 @@
module.exports = {
plugins: [
require('autoprefixer')
]
}
@@ -0,0 +1,91 @@
<!--
// Copyright © 2025 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { Class, Doc, Ref } from '@hcengineering/core'
import { Button, showPopup } from '@hcengineering/ui'
import { getMetadata } from '@hcengineering/platform'
import presentation, { MessageBox } from '@hcengineering/presentation'
import { type TransformConfig } from '@hcengineering/export'
import plugin from '../plugin'
export let _class: Ref<Class<Doc>>
export let query: string = ''
export let visible: boolean = false
export let config: TransformConfig = {}
async function handleExport (): Promise<void> {
try {
const baseUrl = getMetadata(plugin.metadata.ExportUrl)
const token = getMetadata(presentation.metadata.Token)
if (token == null) {
throw new Error('No token available')
}
const res = await fetch(`${baseUrl}/exportSync?format=csv`, {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
_class,
query,
attributesOnly: true,
config
})
})
if (!res.ok) {
showPopup(MessageBox, {
label: plugin.string.ExportRequestFailed,
kind: 'error',
message: plugin.string.ExportRequestFailedMessage
})
}
const filename = `Talants_${new Date().toLocaleDateString()}_${new Date().toLocaleTimeString()}.csv`
const blob = await res.blob()
const url = window.URL.createObjectURL(blob)
const a = document.createElement('a')
a.style.display = 'none'
a.href = url
a.download = filename
document.body.appendChild(a)
a.click()
window.URL.revokeObjectURL(url)
document.body.removeChild(a)
console.log('Export downloaded successfully')
} catch (err) {
showPopup(MessageBox, {
label: plugin.string.ExportRequestFailed,
kind: 'error',
message: plugin.string.ExportRequestFailedMessage
})
}
}
</script>
{#if visible}
<Button
icon={plugin.icon.Export}
label={plugin.string.Export}
kind={'regular'}
size={'medium'}
showTooltip={{ label: plugin.string.Export }}
on:click={handleExport}
/>
{/if}
@@ -17,24 +17,25 @@
import { getMetadata } from '@hcengineering/platform'
import presentation, { MessageBox } from '@hcengineering/presentation'
import { Breadcrumb, Button, DropdownLabelsIntl, Header, Label, Scroller, showPopup } from '@hcengineering/ui'
import setting from '../plugin'
import plugin from '../plugin'
const classItems = [
{ id: 'tracker:class:Issue', label: setting.string.ExportIssues },
{ id: 'document:class:Document', label: setting.string.ExportDocuments },
{ id: 'testManagement:class:TestCase', label: setting.string.ExportTestCases },
{ id: 'testManagement:class:TestRun', label: setting.string.ExportTestRuns },
{ id: 'testManagement:class:TestPlan', label: setting.string.ExportTestPlans }
{ id: 'tracker:class:Issue', label: plugin.string.ExportIssues },
{ id: 'tracker:class:Milestone', label: plugin.string.ExportMilestones },
{ id: 'document:class:Document', label: plugin.string.ExportDocuments },
{ id: 'testManagement:class:TestCase', label: plugin.string.ExportTestCases },
{ id: 'testManagement:class:TestRun', label: plugin.string.ExportTestRuns },
{ id: 'testManagement:class:TestPlan', label: plugin.string.ExportTestPlans }
]
const formatItems = [
{ id: 'json', label: setting.string.ExportJSON },
{ id: 'csv', label: setting.string.ExportCSV }
{ id: 'json', label: plugin.string.ExportJSON },
{ id: 'csv', label: plugin.string.ExportCSV }
]
const detailLevelItems = [
{ id: 'everything', label: setting.string.ExportEverything },
{ id: 'attributesOnly', label: setting.string.ExportAttributesOnly }
{ id: 'everything', label: plugin.string.ExportEverything },
{ id: 'attributesOnly', label: plugin.string.ExportAttributesOnly }
]
let selectedClass: Ref<Class<Doc>> = 'tracker:class:Issue' as Ref<Class<Doc>>
@@ -47,35 +48,37 @@
try {
isExporting = true
const baseUrl = getMetadata(setting.metadata.ExportUrl) ?? ''
const baseUrl = getMetadata(plugin.metadata.ExportUrl) ?? ''
const token = getMetadata(presentation.metadata.Token) ?? ''
const attributesOnly = selectedDetailLevel === 'attributesOnly'
const res = await fetch(
`${baseUrl}?class=${selectedClass}&type=${selectedFormat}&attributesOnly=${attributesOnly}`,
{
method: 'GET',
headers: {
Authorization: 'Bearer ' + token,
'Content-Type': 'application/json'
}
}
)
const res = await fetch(`${baseUrl}/exportAsync?format=${selectedFormat}`, {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
_class: selectedClass,
attributesOnly,
config: {}
})
})
if (!res.ok) {
throw new Error('Export failed to start')
}
showPopup(MessageBox, {
label: setting.string.ExportRequestSuccess,
label: plugin.string.ExportRequestSuccess,
kind: 'success',
message: setting.string.ExportRequestSuccessMessage
message: plugin.string.ExportRequestSuccessMessage
})
} catch (err) {
showPopup(MessageBox, {
label: setting.string.ExportRequestFailed,
label: plugin.string.ExportRequestFailed,
kind: 'error',
message: setting.string.ExportRequestFailedMessage
message: plugin.string.ExportRequestFailedMessage
})
} finally {
isExporting = false
@@ -85,7 +88,7 @@
<div class="hulyComponent">
<Header adaptive={'disabled'}>
<Breadcrumb icon={setting.icon.Export} label={setting.string.Export} size={'large'} isCurrent />
<Breadcrumb icon={plugin.icon.Export} label={plugin.string.Export} size={'large'} isCurrent />
</Header>
<div class="hulyComponent-content__column content">
<Scroller align={'center'} padding={'var(--spacing-3)'} bottomPadding={'var(--spacing-3)'}>
@@ -94,7 +97,7 @@
<div class="p-1 min-w-80">
<div class="antiGrid-row">
<div class="antiGrid-row__header">
<Label label={setting.string.DataToExport} />
<Label label={plugin.string.DataToExport} />
</div>
<div class="antiGrid-row__content">
<DropdownLabelsIntl items={classItems} bind:selected={selectedClass} />
@@ -102,7 +105,7 @@
</div>
<div class="antiGrid-row">
<div class="antiGrid-row__header">
<Label label={setting.string.ExportFormat} />
<Label label={plugin.string.ExportFormat} />
</div>
<div class="antiGrid-row__content">
<DropdownLabelsIntl items={formatItems} bind:selected={selectedFormat} />
@@ -110,7 +113,7 @@
</div>
<div class="antiGrid-row">
<div class="antiGrid-row__header">
<Label label={setting.string.ExportIncludeContent} />
<Label label={plugin.string.ExportIncludeContent} />
</div>
<div class="antiGrid-row__content">
<DropdownLabelsIntl items={detailLevelItems} bind:selected={selectedDetailLevel} />
@@ -120,7 +123,7 @@
</div>
<div class="flex-row-center p-2">
<Button
label={setting.string.Export}
label={plugin.string.Export}
kind={'primary'}
size={'medium'}
disabled={isExporting}
+25
View File
@@ -0,0 +1,25 @@
//
// Copyright © 2025 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import { type Resources } from '@hcengineering/platform'
import ExportButton from './components/ExportButton.svelte'
import ExportSettings from './components/ExportSettings.svelte'
export default async (): Promise<Resources> => ({
component: {
ExportButton,
ExportSettings
}
})
+41
View File
@@ -0,0 +1,41 @@
//
// Copyright © 2025 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import { type IntlString, mergeIds } from '@hcengineering/platform'
import exportPlugin, { exportId } from '@hcengineering/export'
export default mergeIds(exportId, exportPlugin, {
string: {
WorkspaceNamePattern: '' as IntlString,
DataToExport: '' as IntlString,
ExportDocuments: '' as IntlString,
ExportMilestones: '' as IntlString,
ExportIssues: '' as IntlString,
ExportTestCases: '' as IntlString,
ExportTestRuns: '' as IntlString,
ExportTestPlans: '' as IntlString,
ExportFormat: '' as IntlString,
ExportJSON: '' as IntlString,
ExportCSV: '' as IntlString,
ExportUnifiedFormat: '' as IntlString,
ExportIncludeContent: '' as IntlString,
ExportEverything: '' as IntlString,
ExportAttributesOnly: '' as IntlString,
ExportRequestSuccess: '' as IntlString,
ExportRequestSuccessMessage: '' as IntlString,
ExportRequestFailed: '' as IntlString,
ExportRequestFailedMessage: '' as IntlString
}
})
@@ -0,0 +1,5 @@
const sveltePreprocess = require('svelte-preprocess')
module.exports = {
preprocess: sveltePreprocess()
};
+9
View File
@@ -0,0 +1,9 @@
{
"extends": "./node_modules/@hcengineering/platform-rig/profiles/ui/tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib",
"declarationDir": "./types"
}
}
+7
View File
@@ -0,0 +1,7 @@
module.exports = {
extends: ['./node_modules/@hcengineering/platform-rig/profiles/default/eslint.config.json'],
parserOptions: {
tsconfigRootDir: __dirname,
project: './tsconfig.json'
}
}
+4
View File
@@ -0,0 +1,4 @@
*
!/lib/**
!CHANGELOG.md
/lib/**/__tests__/
+4
View File
@@ -0,0 +1,4 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
"rigPackageName": "@hcengineering/platform-rig"
}
+7
View File
@@ -0,0 +1,7 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'],
roots: ["./src"],
coverageReporters: ["text-summary", "html"]
}
+45
View File
@@ -0,0 +1,45 @@
{
"name": "@hcengineering/export",
"version": "0.6.0",
"main": "lib/index.js",
"svelte": "src/index.ts",
"types": "types/index.d.ts",
"files": [
"lib/**/*",
"types/**/*",
"tsconfig.json"
],
"author": "Copyright © Hardcore Engineering Inc.",
"license": "EPL-2.0",
"scripts": {
"build": "compile",
"build:watch": "compile",
"test": "jest --passWithNoTests --silent",
"format": "format src",
"_phase:build": "compile transpile src",
"_phase:test": "jest --passWithNoTests --silent",
"_phase:format": "format src",
"_phase:validate": "compile validate"
},
"devDependencies": {
"@hcengineering/platform-rig": "^0.6.0",
"@typescript-eslint/eslint-plugin": "^6.11.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-n": "^15.4.0",
"eslint": "^8.54.0",
"@typescript-eslint/parser": "^6.11.0",
"eslint-config-standard-with-typescript": "^40.0.0",
"prettier": "^3.1.0",
"typescript": "^5.3.3",
"jest": "^29.7.0",
"ts-jest": "^29.1.1",
"@types/jest": "^29.5.5",
"prettier-plugin-svelte": "^3.2.2"
},
"dependencies": {
"@hcengineering/platform": "^0.6.11",
"@hcengineering/core": "^0.6.32",
"@hcengineering/ui": "^0.6.15"
}
}
+22
View File
@@ -0,0 +1,22 @@
//
// Copyright © 2025 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import { exportPlugin, exportId } from './plugin'
export * from './types'
export { exportId }
export default exportPlugin
+39
View File
@@ -0,0 +1,39 @@
//
// Copyright © 2025 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import { type IntlString, type Metadata, type Plugin, plugin, type Asset } from '@hcengineering/platform'
import { type AnyComponent } from '@hcengineering/ui/src/types'
export const exportId = 'export' as Plugin
export const exportPlugin = plugin(exportId, {
string: {
Export: '' as IntlString,
ExportCompleted: '' as IntlString,
ExportFailed: '' as IntlString
},
component: {
ExportButton: '' as AnyComponent,
ExportSettings: '' as AnyComponent
},
icon: {
Export: '' as Asset
},
metadata: {
ExportUrl: '' as Metadata<string>
}
})
export default exportPlugin
+45
View File
@@ -0,0 +1,45 @@
//
// Copyright © 2025 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
export enum OperationType {
IDENTITY = 'identity',
GROUP_BY = 'group_by'
// JOIN = 'join',
// EXTRACT = 'extract',
// MAP = 'map'
}
export interface OperationConfig {
[OperationType.IDENTITY]: undefined
[OperationType.GROUP_BY]: {
keyField: string
valueField?: string
}
}
export interface TransformOperation {
type: OperationType
config: OperationConfig[OperationType]
}
export interface AttributeTransform {
operations: TransformOperation[]
}
export interface TransformConfig {
skipAttributes?: string[]
attributeKeyMap?: Record<string, string>
attributeTransforms?: Record<string, AttributeTransform>
}
+10
View File
@@ -0,0 +1,10 @@
{
"extends": "./node_modules/@hcengineering/platform-rig/profiles/default/tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib",
"declarationDir": "./types",
"tsBuildInfoFile": ".build/build.tsbuildinfo"
}
}
-4
View File
@@ -67,8 +67,4 @@
<symbol id="relation" viewBox="0 0 16 16">
<path d="M2 8L5 5V7H11V5L14 8L11 11V9H5V11L2 8Z" />
</symbol>
<symbol id="export" viewBox="0 0 16 16">
<path d="M7.92231 14.2015L7.99302 14.6964L7.92231 14.2015ZM6.22318 14.311L6.33389 13.8234L6.22318 14.311ZM4.79789 13.0748L5.26492 12.8963L4.79789 13.0748ZM13.373 12.5661L12.9774 12.2603L13.373 12.5661ZM9.05136 14.0402L8.98065 13.5452L9.05136 14.0402ZM13.1512 12.8218L13.5099 13.1701L13.1512 12.8218ZM13.1512 3.17821L12.7926 3.5266L13.1512 3.17821ZM13.373 3.43391L13.7686 3.12813L13.373 3.43391ZM7.98221 2.31213L8.98065 2.45477L9.12207 1.46482L8.12363 1.32218L7.98221 2.31213ZM13.4993 7.66487V8.33509H14.4993V7.66487H13.4993ZM8.98065 13.5452L7.8516 13.7065L7.99302 14.6964L9.12207 14.5351L8.98065 13.5452ZM7.8516 13.7065C7.39911 13.7711 7.0821 13.8162 6.83278 13.8363C6.58629 13.8562 6.44248 13.8481 6.33389 13.8234L6.11247 14.7986C6.364 14.8557 6.62341 14.8565 6.91329 14.8331C7.20033 14.8099 7.55283 14.7593 7.99302 14.6964L7.8516 13.7065ZM4.16602 11.3773C4.16602 11.822 4.1658 12.1781 4.18344 12.4655C4.20126 12.7558 4.23875 13.0125 4.33085 13.2534L5.26492 12.8963C5.22515 12.7923 5.19671 12.6511 5.18156 12.4042C5.16624 12.1546 5.16602 11.8344 5.16602 11.3773H4.16602ZM6.33389 13.8234C5.84503 13.7124 5.44393 13.3645 5.26492 12.8963L4.33085 13.2534C4.62921 14.0338 5.29771 14.6136 6.11247 14.7986L6.33389 13.8234ZM13.4993 8.33509C13.4993 9.48721 13.4986 10.3082 13.4244 10.9439C13.3517 11.5676 13.2136 11.9547 12.9774 12.2603L13.7686 12.8718C14.1587 12.3671 14.3338 11.7788 14.4177 11.0597C14.5001 10.3527 14.4993 9.46335 14.4993 8.33509H13.4993ZM9.12207 14.5351C10.239 14.3756 11.1195 14.2506 11.8078 14.069C12.5078 13.8843 13.0654 13.6277 13.5099 13.1701L12.7926 12.4734C12.5234 12.7504 12.1598 12.9419 11.5526 13.1021C10.9338 13.2654 10.1212 13.3823 8.98065 13.5452L9.12207 14.5351ZM12.9774 12.2603C12.9198 12.3347 12.8581 12.4059 12.7926 12.4734L13.5099 13.1701C13.6017 13.0756 13.688 12.9761 13.7686 12.8718L12.9774 12.2603ZM8.98065 2.45477C10.1212 2.6177 10.9338 2.73458 11.5526 2.89788C12.1598 3.05809 12.5234 3.24952 12.7926 3.5266L13.5099 2.82982C13.0654 2.37222 12.5078 2.11569 11.8078 1.93098C11.1195 1.74936 10.239 1.62438 9.12207 1.46482L8.98065 2.45477ZM14.4993 7.66487C14.4993 6.53661 14.5001 5.64727 14.4177 4.94022C14.3338 4.22111 14.1587 3.63289 13.7686 3.12813L12.9774 3.73968C13.2136 4.04531 13.3517 4.43238 13.4244 5.05606C13.4986 5.69179 13.4993 6.51275 13.4993 7.66487H14.4993ZM12.7926 3.5266C12.8581 3.59409 12.9198 3.66523 12.9774 3.73968L13.7686 3.12813C13.688 3.02391 13.6017 2.92431 13.5099 2.82982L12.7926 3.5266ZM8.12363 1.32218C7.62395 1.2508 7.22383 1.19335 6.89852 1.16973C6.56979 1.14587 6.27615 1.15257 5.99374 1.23142L6.26265 2.19458C6.38494 2.16044 6.54699 2.14684 6.82611 2.16711C7.10865 2.18762 7.46854 2.23875 7.98221 2.31213L8.12363 1.32218ZM5.16602 4.7546C5.16602 4.23572 5.1663 3.87222 5.18595 3.58961C5.20536 3.31043 5.24174 3.15194 5.29283 3.0357L4.37738 2.63328C4.25938 2.90171 4.21122 3.19145 4.18836 3.52025C4.16573 3.84563 4.16602 4.24985 4.16602 4.7546H5.16602ZM5.99374 1.23142C5.27211 1.43289 4.67888 1.9474 4.37738 2.63328L5.29283 3.0357C5.47374 2.62417 5.82967 2.31547 6.26265 2.19458L5.99374 1.23142Z"/>
<path d="M10.666 8.00002L11.0565 7.68767L11.3063 8.00002L11.0565 8.31237L10.666 8.00002ZM2.66602 8.50002C2.38987 8.50002 2.16602 8.27616 2.16602 8.00002C2.16602 7.72388 2.38987 7.50002 2.66602 7.50002V8.50002ZM8.38978 4.35434L11.0565 7.68767L10.2756 8.31237L7.60891 4.97903L8.38978 4.35434ZM11.0565 8.31237L8.38978 11.6457L7.60891 11.021L10.2756 7.68767L11.0565 8.31237ZM10.666 8.50002H2.66602V7.50002H10.666V8.50002Z"/>
</symbol>
</svg>

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 19 KiB

+1 -21
View File
@@ -136,26 +136,6 @@
"SystemSetupString": "Nastavení systému ({day})",
"DefaultString": "Výchozí ({day})",
"AddAttribute": "Přidat atribut",
"WorkspaceNamePattern": "Název musí být 40 znaků nebo méně, není prázdný a nesmí obsahovat speciální znaky (<, >, /)",
"Export": "Export",
"DataToExport": "Data k exportu",
"ExportDocuments": "Dokumenty",
"ExportIssues": "Problémy",
"ExportTestCases": "Testovací případy",
"ExportTestRuns": "Běhy testů",
"ExportTestPlans": "Testovací plány",
"ExportFormat": "Formát exportu",
"ExportJSON": "JSON",
"ExportCSV": "CSV",
"ExportUnifiedFormat": "Jednotný formát Huly",
"ExportIncludeContent": "Zahrnout obsah",
"ExportEverything": "Vše",
"ExportAttributesOnly": "Pouze atributy",
"ExportRequestSuccess": "Export byl zahájen.",
"ExportRequestSuccessMessage": "Export byl zahájen. Až bude dokončen, obdržíte oznámení ve své schránce.",
"ExportRequestFailed": "Nepodařilo se odeslat export.",
"ExportRequestFailedMessage": "Při odesílání exportu došlo k chybě. Zkuste to prosím později.",
"ExportCompleted": "Export byl dokončen. Vaše soubory jsou připraveny. Můžete je stáhnout z disku.",
"ExportFailed": "Export dat se nezdařil. Zkuste to prosím později."
"WorkspaceNamePattern": "Název musí být 40 znaků nebo méně, není prázdný a nesmí obsahovat speciální znaky (<, >, /)"
}
}
+1 -21
View File
@@ -136,26 +136,6 @@
"SystemSetupString": "Systemkonfiguration ({day})",
"DefaultString": "Stillschweigend ({day})",
"AddAttribute": "Attribut hinzufügen",
"WorkspaceNamePattern": "Name muss 40 Zeichen oder weniger sein, nicht leer sein und keine Sonderzeichen (<, >, /) enthalten",
"Export": "Exportieren",
"DataToExport": "Zu exportierende Daten",
"ExportDocuments": "Dokumente",
"ExportIssues": "Probleme",
"ExportTestCases": "Testfälle",
"ExportTestRuns": "Testläufe",
"ExportTestPlans": "Testpläne",
"ExportFormat": "Exportformat",
"ExportJSON": "JSON",
"ExportCSV": "CSV",
"ExportUnifiedFormat": "Huly Einheitliches Format",
"ExportIncludeContent": "Inhalt einschließen",
"ExportEverything": "Alles",
"ExportAttributesOnly": "Nur Attribute",
"ExportRequestSuccess": "Export gestartet.",
"ExportRequestSuccessMessage": "Export gestartet. Sie erhalten eine Benachrichtigung in Ihrem Posteingang, wenn der Export abgeschlossen ist.",
"ExportRequestFailed": "Export konnte nicht gestartet werden.",
"ExportRequestFailedMessage": "Beim Starten des Exports ist ein Fehler aufgetreten. Bitte versuchen Sie es später erneut.",
"ExportCompleted": "Export abgeschlossen. Ihre Dateien sind bereit. Sie können sie vom Laufwerk herunterladen.",
"ExportFailed": "Datenexport fehlgeschlagen. Bitte versuchen Sie es später erneut."
"WorkspaceNamePattern": "Name muss 40 Zeichen oder weniger sein, nicht leer sein und keine Sonderzeichen (<, >, /) enthalten"
}
}
+1 -21
View File
@@ -136,26 +136,6 @@
"SystemSetupString": "System Setup ({day})",
"DefaultString": "Default ({day})",
"AddAttribute": "Add attribute",
"WorkspaceNamePattern": "Name must be 40 characters or less, not empty, and cannot contain special characters (<, >, /)",
"Export": "Export",
"DataToExport": "Data to export",
"ExportDocuments": "Documents",
"ExportIssues": "Issues",
"ExportTestCases": "Test Cases",
"ExportTestRuns": "Test Runs",
"ExportTestPlans": "Test Plans",
"ExportFormat": "Export format",
"ExportJSON": "JSON",
"ExportCSV": "CSV",
"ExportUnifiedFormat": "Huly Unified Format",
"ExportIncludeContent": "Include content",
"ExportEverything": "Everything",
"ExportAttributesOnly": "Attributes only",
"ExportRequestSuccess": "Export started.",
"ExportRequestSuccessMessage": "Export started. You will receive a notification in your Inbox when it completes.",
"ExportRequestFailed": "Failed to submit export.",
"ExportRequestFailedMessage": "Error occurred while submitting export. Please try again later.",
"ExportCompleted": "Export completed. Your files are ready. You can download them from the drive.",
"ExportFailed": "Failed to export data. Please try again later."
"WorkspaceNamePattern": "Name must be 40 characters or less, not empty, and cannot contain special characters (<, >, /)"
}
}
+1 -21
View File
@@ -127,26 +127,6 @@
"SystemSetupString": "Configuración del sistema ({day})",
"DefaultString": "Predeterminado ({day})",
"AddAttribute": "Añadir atributo",
"WorkspaceNamePattern": "El nombre debe tener 40 caracteres o menos, no esté vacío y no puede contener caracteres especiales (<, >, /)",
"Export": "Exportar",
"DataToExport": "Datos a exportar",
"ExportDocuments": "Documentos",
"ExportIssues": "Problemas",
"ExportTestCases": "Casos de prueba",
"ExportTestRuns": "Ejecuciones de prueba",
"ExportTestPlans": "Planes de prueba",
"ExportFormat": "Formato de exportación",
"ExportJSON": "JSON",
"ExportCSV": "CSV",
"ExportUnifiedFormat": "Formato Unificado Huly",
"ExportIncludeContent": "Incluir contenido",
"ExportEverything": "Todo",
"ExportAttributesOnly": "Solo atributos",
"ExportRequestSuccess": "Exportación iniciada.",
"ExportRequestSuccessMessage": "Exportación iniciada. Recibirá una notificación en su bandeja de entrada cuando se complete.",
"ExportRequestFailed": "Error al enviar la exportación.",
"ExportRequestFailedMessage": "Ocurrió un error al enviar la exportación. Por favor, inténtelo más tarde.",
"ExportCompleted": "Exportación completada. Sus archivos están listos. Puede descargarlos desde la unidad.",
"ExportFailed": "Error al exportar datos. Por favor, inténtelo más tarde."
"WorkspaceNamePattern": "El nombre debe tener 40 caracteres o menos, no esté vacío y no puede contener caracteres especiales (<, >, /)"
}
}
+1 -21
View File
@@ -136,26 +136,6 @@
"SystemSetupString": "Configuration du système ({day})",
"DefaultString": "Par défaut ({day})",
"AddAttribute": "Ajouter un attribut",
"WorkspaceNamePattern": "Le nom doit comporter 40 caractères ou moins, ne doit pas être vide et ne doit pas contenir de caractères spéciaux (<, >, /)" ,
"Export": "Exporter",
"DataToExport": "Données à exporter",
"ExportDocuments": "Documents",
"ExportIssues": "Problèmes",
"ExportTestCases": "Cas de test",
"ExportTestRuns": "Exécutions de test",
"ExportTestPlans": "Plans de test",
"ExportFormat": "Format d'exportation",
"ExportJSON": "JSON",
"ExportCSV": "CSV",
"ExportUnifiedFormat": "Format Unifié Huly",
"ExportIncludeContent": "Inclure le contenu",
"ExportEverything": "Tout",
"ExportAttributesOnly": "Attributs uniquement",
"ExportRequestSuccess": "Exportation démarrée.",
"ExportRequestSuccessMessage": "Exportation démarrée. Vous recevrez une notification dans votre boîte de réception une fois terminée.",
"ExportRequestFailed": "Échec de la soumission de l'exportation.",
"ExportRequestFailedMessage": "Une erreur s'est produite lors de la soumission de l'exportation. Veuillez réessayer plus tard.",
"ExportCompleted": "Exportation terminée. Vos fichiers sont prêts. Vous pouvez les télécharger depuis le lecteur.",
"ExportFailed": "Échec de l'exportation des données. Veuillez réessayer plus tard."
"WorkspaceNamePattern": "Le nom doit comporter 40 caractères ou moins, ne doit pas être vide et ne doit pas contenir de caractères spéciaux (<, >, /)"
}
}
+1 -21
View File
@@ -136,26 +136,6 @@
"SystemSetupString": "Configurazione del sistema ({day})",
"DefaultString": "Predefinito ({day})",
"AddAttribute": "Aggiungi attributo",
"WorkspaceNamePattern": "Il nome deve essere di 40 caratteri o meno, non vuoto e non può contenere caratteri speciali (<, >, /)",
"Export": "Esporta",
"DataToExport": "Dati da esportare",
"ExportDocuments": "Documenti",
"ExportIssues": "Problemi",
"ExportTestCases": "Casi di test",
"ExportTestRuns": "Esecuzioni di test",
"ExportTestPlans": "Piani di test",
"ExportFormat": "Formato di esportazione",
"ExportJSON": "JSON",
"ExportCSV": "CSV",
"ExportUnifiedFormat": "Formato Unificato Huly",
"ExportIncludeContent": "Includi contenuto",
"ExportEverything": "Tutto",
"ExportAttributesOnly": "Solo attributi",
"ExportRequestSuccess": "Esportazione avviata.",
"ExportRequestSuccessMessage": "Esportazione avviata. Riceverai una notifica nella tua casella quando sarà completata.",
"ExportRequestFailed": "Impossibile inviare l'esportazione.",
"ExportRequestFailedMessage": "Si è verificato un errore durante l'invio dell'esportazione. Riprova più tardi.",
"ExportCompleted": "Esportazione completata. I tuoi file sono pronti. Puoi scaricarli dal drive.",
"ExportFailed": "Impossibile esportare i dati. Riprova più tardi."
"WorkspaceNamePattern": "Il nome deve essere di 40 caratteri o meno, non vuoto e non può contenere caratteri speciali (<, >, /)"
}
}
+1 -21
View File
@@ -127,26 +127,6 @@
"SystemSetupString": "Configuração do sistema ({day})",
"DefaultString": "Predefinição ({day})",
"AddAttribute": "Adicionar atributo",
"WorkspaceNamePattern": "O nome deve ter 40 caracteres ou menos, não está vazio e não pode conter caracteres especiais (<, >, /)",
"Export": "Exportar",
"DataToExport": "Dados para exportar",
"ExportDocuments": "Documentos",
"ExportIssues": "Problemas",
"ExportTestCases": "Casos de teste",
"ExportTestRuns": "Execuções de teste",
"ExportTestPlans": "Planos de teste",
"ExportFormat": "Formato de exportação",
"ExportJSON": "JSON",
"ExportCSV": "CSV",
"ExportUnifiedFormat": "Formato Unificado Huly",
"ExportIncludeContent": "Incluir conteúdo",
"ExportEverything": "Tudo",
"ExportAttributesOnly": "Apenas atributos",
"ExportRequestSuccess": "Exportação iniciada.",
"ExportRequestSuccessMessage": "Exportação iniciada. Você receberá uma notificação em sua caixa de entrada quando for concluída.",
"ExportRequestFailed": "Falha ao enviar exportação.",
"ExportRequestFailedMessage": "Ocorreu um erro ao enviar a exportação. Por favor, tente novamente mais tarde.",
"ExportCompleted": "Exportação concluída. Seus arquivos estão prontos. Você pode baixá-los do drive.",
"ExportFailed": "Falha ao exportar dados. Por favor, tente novamente mais tarde."
"WorkspaceNamePattern": "O nome deve ter 40 caracteres ou menos, não está vazio e não pode conter caracteres especiais (<, >, /)"
}
}
+1 -21
View File
@@ -137,26 +137,6 @@
"SystemSetupString": "Системная настройка ({day})",
"DefaultString": "По умолчанию ({day})",
"AddAttribute": "Добавить атрибут",
"WorkspaceNamePattern": "Имя должно быть длиной 40 символов или меньше, не должно быть пустым и не может содержать специальные символы (<, >, /)",
"Export": "Экспорт",
"DataToExport": "Данные для экспорта",
"ExportDocuments": "Документы",
"ExportIssues": "Задачи",
"ExportTestCases": "Тест-кейсы",
"ExportTestRuns": "Прогоны тестов",
"ExportTestPlans": "Планы тестирования",
"ExportFormat": "Формат экспорта",
"ExportJSON": "JSON",
"ExportCSV": "CSV",
"ExportUnifiedFormat": "Единый формат Huly",
"ExportIncludeContent": "Включить содержимое",
"ExportEverything": "Всё",
"ExportAttributesOnly": "Только атрибуты",
"ExportRequestSuccess": "Экспорт начат.",
"ExportRequestSuccessMessage": "Экспорт начат. Вы получите уведомление во входящих, когда он будет завершен.",
"ExportRequestFailed": "Не удалось начать экспорт.",
"ExportRequestFailedMessage": "Произошла ошибка при отправке экспорта. Пожалуйста, попробуйте позже.",
"ExportCompleted": "Экспорт завершен. Ваши файлы готовы. Вы можете скачать их с диска.",
"ExportFailed": "Не удалось экспортировать данные. Пожалуйста, попробуйте позже."
"WorkspaceNamePattern": "Имя должно быть длиной 40 символов или меньше, не должно быть пустым и не может содержать специальные символы (<, >, /)"
}
}
+1 -21
View File
@@ -136,26 +136,6 @@
"SystemSetupString": "系统设置({day})",
"DefaultString": "违约({day})",
"AddAttribute": "添加属性",
"WorkspaceNamePattern": "名称必须为 40 个字符或更少,不能为空,不能包含特殊字符(<、>、/",
"Export": "导出",
"DataToExport": "要导出的数据",
"ExportDocuments": "文档",
"ExportIssues": "问题",
"ExportTestCases": "测试用例",
"ExportTestRuns": "测试运行",
"ExportTestPlans": "测试计划",
"ExportFormat": "导出格式",
"ExportJSON": "JSON",
"ExportCSV": "CSV",
"ExportUnifiedFormat": "Huly 统一格式",
"ExportIncludeContent": "包含内容",
"ExportEverything": "所有内容",
"ExportAttributesOnly": "仅属性",
"ExportRequestSuccess": "导出已开始。",
"ExportRequestSuccessMessage": "导出已开始。完成后您将在收件箱中收到通知。",
"ExportRequestFailed": "提交导出失败。",
"ExportRequestFailedMessage": "提交导出时发生错误。请稍后重试。",
"ExportCompleted": "导出完成。您的文件已准备就绪。您可以从驱动器下载它们。",
"ExportFailed": "导出数据失败。请稍后重试。"
"WorkspaceNamePattern": "名称必须为 40 个字符或更少,不能为空,不能包含特殊字符(<、>、/"
}
}
+1 -2
View File
@@ -33,6 +33,5 @@ loadMetadata(setting.icon, {
InviteSettings: `${icons}#inviteSettings`,
InviteWorkspace: `${icons}#inviteWorkspace`,
Views: `${icons}#views`,
Relations: `${icons}#relation`,
Export: `${icons}#export`
Relations: `${icons}#relation`
})
+1 -3
View File
@@ -67,7 +67,6 @@ import CreateRelation from './components/CreateRelation.svelte'
import EditRelation from './components/EditRelation.svelte'
import setting from './plugin'
import { filterDescendants, getOwnerFirstName, getOwnerLastName, getOwnerPosition, getValue } from './utils'
import Export from './components/Export.svelte'
export * from './store'
export {
@@ -144,8 +143,7 @@ export default async (): Promise<Resources> => ({
SettingsWidget,
CreateAttributePopup,
CreateRelation,
EditRelation,
Export
EditRelation
},
actionImpl: {
DeleteMixin
+1 -21
View File
@@ -118,26 +118,6 @@ export default mergeIds(settingId, setting, {
StartOfTheWeek: '' as IntlString,
SystemSetupString: '' as IntlString,
DefaultString: '' as IntlString,
WorkspaceNamePattern: '' as IntlString,
Export: '' as IntlString,
DataToExport: '' as IntlString,
ExportDocuments: '' as IntlString,
ExportIssues: '' as IntlString,
ExportTestCases: '' as IntlString,
ExportTestRuns: '' as IntlString,
ExportTestPlans: '' as IntlString,
ExportFormat: '' as IntlString,
ExportJSON: '' as IntlString,
ExportCSV: '' as IntlString,
ExportUnifiedFormat: '' as IntlString,
ExportIncludeContent: '' as IntlString,
ExportEverything: '' as IntlString,
ExportAttributesOnly: '' as IntlString,
ExportRequestSuccess: '' as IntlString,
ExportRequestSuccessMessage: '' as IntlString,
ExportRequestFailed: '' as IntlString,
ExportRequestFailedMessage: '' as IntlString,
ExportCompleted: '' as IntlString,
ExportFailed: '' as IntlString
WorkspaceNamePattern: '' as IntlString
}
})
+2 -5
View File
@@ -172,8 +172,7 @@ export default plugin(settingId, {
Backup: '' as AnyComponent,
CreateAttributePopup: '' as AnyComponent,
CreateRelation: '' as AnyComponent,
EditRelation: '' as AnyComponent,
Export: '' as AnyComponent
EditRelation: '' as AnyComponent
},
string: {
Settings: '' as IntlString,
@@ -238,7 +237,6 @@ export default plugin(settingId, {
Terms: '' as Asset,
Signout: '' as Asset,
SelectWorkspace: '' as Asset,
Export: '' as Asset,
Clazz: '' as Asset,
Enums: '' as Asset,
InviteSettings: '' as Asset,
@@ -256,7 +254,6 @@ export default plugin(settingId, {
Value: '' as Ref<TemplateField>
},
metadata: {
BackupUrl: '' as Metadata<string>,
ExportUrl: '' as Metadata<string>
BackupUrl: '' as Metadata<string>
}
})
@@ -15,7 +15,7 @@
<script lang="ts">
import { Class, Doc, DocumentQuery, FindOptions, Ref, Space, WithLookup, mergeQueries } from '@hcengineering/core'
import { Asset, getResource, IntlString, Resource } from '@hcengineering/platform'
import { getClient } from '@hcengineering/presentation'
import { getClient, ComponentExtensions } from '@hcengineering/presentation'
import {
AnyComponent,
Breadcrumb,
@@ -38,8 +38,7 @@
ViewletSelector,
ViewletSettingButton
} from '@hcengineering/view-resources'
import { ParentsNavigationModel } from '@hcengineering/workbench'
import workbench, { ParentsNavigationModel } from '@hcengineering/workbench'
import ComponentNavigator from './ComponentNavigator.svelte'
export let _class: Ref<Class<Doc>>
@@ -57,6 +56,8 @@
export let modes: IModeSelector<any> | undefined = undefined
export let navigationModel: ParentsNavigationModel | undefined = undefined
export let queryBuilder: Resource<() => Promise<DocumentQuery<Doc>>> | undefined = undefined
export let actionConfig: Record<string, any> = {}
export let actionVisible: boolean = false
const client = getClient()
const hierarchy = client.getHierarchy()
@@ -64,7 +65,6 @@
let search = ''
let viewlet: WithLookup<Viewlet> | undefined
let filterVisible: boolean = false
let preference: ViewletPreference | undefined
let viewlets: Array<WithLookup<Viewlet>> = []
let viewOptions: ViewOptions | undefined
@@ -147,6 +147,10 @@
<FilterButton {_class} bind:visible={filterVisible} />
</svelte:fragment>
<svelte:fragment slot="actions">
<ComponentExtensions
extension={workbench.extensions.SpecialViewAction}
props={{ _class, visible: actionVisible, query: resultQuery, config: actionConfig }}
/>
{#if createLabel && createComponent}
<Button
icon={IconAdd}
+2 -1
View File
@@ -82,7 +82,8 @@ export const workbenchPlugin = plugin(workbenchId, {
},
extensions: {
WorkbenchExtensions: '' as ComponentExtensionId,
WorkbenchTabExtensions: '' as ComponentExtensionId
WorkbenchTabExtensions: '' as ComponentExtensionId,
SpecialViewAction: '' as ComponentExtensionId
},
function: {
CreateWidgetTab: '' as Resource<(widget: Widget, tab: WidgetTab, newTab: boolean) => Promise<void>>,
+20
View File
@@ -1908,6 +1908,26 @@
"projectFolder": "models/print",
"shouldPublish": false
},
{
"packageName": "@hcengineering/model-export",
"projectFolder": "models/export",
"shouldPublish": false
},
{
"packageName": "@hcengineering/export",
"projectFolder": "plugins/export",
"shouldPublish": false
},
{
"packageName": "@hcengineering/export-assets",
"projectFolder": "plugins/export-assets",
"shouldPublish": false
},
{
"packageName": "@hcengineering/export-resources",
"projectFolder": "plugins/export-resources",
"shouldPublish": false
},
{
"packageName": "@hcengineering/sign",
"projectFolder": "plugins/sign",
+1
View File
@@ -73,6 +73,7 @@
"express": "^4.21.2",
"ws": "^8.18.0",
"@hcengineering/drive": "^0.6.0",
"@hcengineering/export": "^0.6.0",
"uuid": "^8.3.2",
"tar": "^7.4.3",
"archiver": "^7.0.1",
+2 -7
View File
@@ -84,11 +84,6 @@ export class UnifiedConverter {
continue
}
// Skip mixins if attributesOnly
if (attributesOnly && hierarchy.isMixin(key as Ref<Mixin<Doc>>)) {
continue
}
// Process mixin or add raw value
if (hierarchy.isMixin(key as Ref<Mixin<Doc>>) && hierarchy.hasMixin(doc, key as Ref<Mixin<Doc>>)) {
const mixinAttrs = hierarchy.getAllAttributes(key as Ref<Mixin<Doc>>)
@@ -195,7 +190,7 @@ export class UnifiedConverter {
}
if (type._class === core.class.Collection) {
if (attributesOnly || value.length === 0) {
if (value.length === 0) {
return undefined
}
@@ -212,7 +207,7 @@ export class UnifiedConverter {
return await Promise.all(collectionDocs.map(async (doc) => await this.convert(doc)))
}
if (type._class === core.class.TypeTimestamp) {
if (type._class === core.class.TypeTimestamp || type._class === core.class.TypeDate) {
return this.formatTimestamp(value as number)
}
@@ -13,20 +13,27 @@
// limitations under the License.
//
import { stringify } from 'csv-stringify/sync'
import { mkdir, writeFile } from 'fs/promises'
import { join } from 'path'
import { Transformer } from '../transformer'
import { UnifiedAttachment, UnifiedDoc } from '../types'
import { stringify } from 'csv-stringify/sync'
import { type TransformConfig } from '@hcengineering/export'
export class UnifiedCsvSerializer {
private readonly transformer: Transformer
constructor (config?: TransformConfig) {
this.transformer = new Transformer(config)
}
async serializeSpace (docs: UnifiedDoc[], outputPath: string, spaceName: string): Promise<void> {
const jsonData = docs.map((doc) => this.createCsvData(doc))
const csvData = docs.map((doc) => this.createCsvData(doc))
const filePath = join(outputPath, `${spaceName}.csv`)
// Get all unique headers
const headers = Array.from(new Set(jsonData.flatMap((obj) => Object.keys(obj)))).sort()
const headers = Array.from(new Set(csvData.flatMap((obj) => Object.keys(obj)))).sort()
const csvContent = stringify(jsonData, {
const csvContent = stringify(csvData, {
header: true,
columns: headers
})
@@ -41,29 +48,14 @@ export class UnifiedCsvSerializer {
}
private createCsvData (doc: UnifiedDoc): object {
const { data, attachments, collectionFields = [] } = doc
const { data, attachments = [] } = doc
const csvData: Record<string, any> = {}
Object.entries(data).forEach(([key, value]) => {
if (Array.isArray(value) && collectionFields.includes(key)) {
// Convert arrays to JSON string
csvData[key] = value
.map((item) => {
if (this.isUnifiedDoc(item)) {
return JSON.stringify(this.createCsvData(item))
}
return String(item)
})
.join('; ')
return
}
if (this.isUnifiedDoc(value)) {
csvData[key] = JSON.stringify(this.createCsvData(value))
return
}
csvData[key] = value
const formatted = this.transformer.transformAttribute(key, value)
Object.entries(formatted).forEach(([formattedKey, formattedValue]) => {
csvData[formattedKey] = formattedValue
})
})
if (attachments !== undefined && attachments.length > 0) {
@@ -100,8 +92,4 @@ export class UnifiedCsvSerializer {
return paths
}
private isUnifiedDoc (value: any): value is UnifiedDoc {
return value !== null && typeof value === 'object' && '_class' in value && 'data' in value
}
}
+19 -14
View File
@@ -13,21 +13,28 @@
// limitations under the License.
//
import { Client, MeasureContext, WorkspaceIds } from '@hcengineering/core'
import { Client, DocumentQuery, MeasureContext, WorkspaceIds } from '@hcengineering/core'
import { Class, Doc, Ref, Space } from '@hcengineering/core/types/classes'
import core from '@hcengineering/model-core'
import { StorageAdapter } from '@hcengineering/server-core'
import path from 'path'
import { UnifiedConverter } from './converter'
import { UnifiedJsonSerializer } from './json/json-serializer'
import { UnifiedCsvSerializer } from './csv/csv-serializer'
import { UnifiedJsonSerializer } from './json/json-serializer'
import { type TransformConfig } from '@hcengineering/export'
export enum ExportType {
export enum ExportFormat {
UNIFIED = 'unified',
CSV = 'csv',
JSON = 'json'
}
export interface ExportOptions {
format: ExportFormat
attributesOnly: boolean
query?: DocumentQuery<Doc>
}
export class WorkspaceExporter {
private readonly jsonSerializer: UnifiedJsonSerializer
private readonly csvSerializer: UnifiedCsvSerializer
@@ -37,20 +44,18 @@ export class WorkspaceExporter {
context: MeasureContext,
private readonly client: Client,
storage: StorageAdapter,
wsIds: WorkspaceIds
wsIds: WorkspaceIds,
config?: TransformConfig
) {
this.jsonSerializer = new UnifiedJsonSerializer()
this.csvSerializer = new UnifiedCsvSerializer()
this.csvSerializer = new UnifiedCsvSerializer(config)
this.converter = new UnifiedConverter(context, client, storage, wsIds)
}
async export (
_class: Ref<Class<Doc>>,
outputDir: string,
format: ExportType = ExportType.UNIFIED,
attributesOnly: boolean = false
): Promise<void> {
const docs = await this.client.findAll(_class, {})
async export (_class: Ref<Class<Doc>>, outputDir: string, options: ExportOptions): Promise<void> {
const { format, attributesOnly, query } = options
const docs = await this.client.findAll(_class, query ?? {})
const docsBySpace = new Map<Ref<Space>, Doc[]>()
// Group documents by space
@@ -76,9 +81,9 @@ export class WorkspaceExporter {
// Convert all docs to UnifiedDoc format
const unifiedDoc = await Promise.all(spaceDocs.map((doc) => this.converter.convert(doc, attributesOnly)))
if (format === ExportType.JSON) {
if (format === ExportFormat.JSON) {
await this.jsonSerializer.serializeSpace(unifiedDoc, outputDir, spaceName)
} else if (format === ExportType.CSV) {
} else if (format === ExportFormat.CSV) {
await this.csvSerializer.serializeSpace(unifiedDoc, outputDir, spaceName)
} else {
throw new Error(`Unsupported format: ${format}`)
+78 -34
View File
@@ -21,6 +21,7 @@ import core, {
Class,
Client,
Doc,
DocumentQuery,
generateId,
MeasureContext,
type PersonId,
@@ -31,7 +32,7 @@ import core, {
} from '@hcengineering/core'
import drive, { createFile, Drive } from '@hcengineering/drive'
import notification from '@hcengineering/notification'
import { Asset, IntlString, setMetadata } from '@hcengineering/platform'
import { setMetadata } from '@hcengineering/platform'
import { createClient, getAccountClient, getTransactorEndpoint } from '@hcengineering/server-client'
import { initStatisticsContext, StorageAdapter, StorageConfiguration } from '@hcengineering/server-core'
import { buildStorageFromConfig } from '@hcengineering/server-storage'
@@ -43,11 +44,12 @@ import { createWriteStream } from 'fs'
import fs from 'fs/promises'
import { IncomingHttpHeaders, type Server } from 'http'
import { tmpdir } from 'os'
import { join } from 'path'
import { basename, join } from 'path'
import { v4 as uuid } from 'uuid'
import WebSocket from 'ws'
import { ApiError } from './error'
import { ExportType, WorkspaceExporter } from './exporter'
import { ExportFormat, WorkspaceExporter } from './exporter'
import exportPlugin, { type TransformConfig } from '@hcengineering/export'
const extractCookieToken = (cookie?: string): string | null => {
if (cookie === undefined || cookie === null) {
@@ -160,19 +162,27 @@ export function createServer (storageConfig: StorageConfiguration): { app: Expre
app.use(cors())
app.use(express.json())
app.get(
'/export',
app.post(
'/exportAsync',
wrapRequest(async (req, res, wsIds, token, socialId) => {
const classId = req.query.class as Ref<Class<Doc<Space>>>
const exportType = req.query.type as ExportType
const attributesOnly = req.query.attributesOnly === 'true'
const format = req.query.format as ExportFormat
if (classId == null || exportType == null) {
const {
_class,
query,
attributesOnly
}: {
_class: Ref<Class<Doc<Space>>>
query?: DocumentQuery<Doc>
attributesOnly: boolean
} = req.body
if (_class == null || format == null) {
throw new ApiError(400, 'Missing required parameters')
}
const platformClient = await createPlatformClient(token)
const { account, workspace } = decodeToken(token)
const { account } = decodeToken(token)
const txOperations = new TxOperations(platformClient, socialId)
@@ -183,30 +193,17 @@ export function createServer (storageConfig: StorageConfiguration): { app: Expre
try {
const exporter = new WorkspaceExporter(measureCtx, txOperations, storageAdapter, wsIds)
await exporter.export(classId, exportDir, exportType, attributesOnly)
await exporter.export(_class, exportDir, { format, attributesOnly, query })
const hierarchy = platformClient.getHierarchy()
const className = hierarchy.getClass(classId).label
const className = hierarchy.getClass(_class).label
const archiveDir = await fs.mkdtemp(join(tmpdir(), 'export-archive-'))
const archiveName = `export-${workspace}-${className}-${exportType}-${Date.now()}.zip`
const archiveName = `export-${wsIds.uuid}-${className}-${format}-${Date.now()}.zip`
const archivePath = join(archiveDir, archiveName)
const files = await fs.readdir(exportDir)
if (files.length === 0) {
throw new ApiError(400, 'No files were exported')
}
await saveToArchive(exportDir, archivePath)
const exportDrive = await saveToDrive(
measureCtx,
txOperations,
storageAdapter,
wsIds,
archivePath,
archiveName,
account
)
const exportDrive = await saveToDrive(measureCtx, txOperations, storageAdapter, wsIds, archivePath, account)
await sendSuccessNotification(txOperations, account, exportDrive, archiveName)
} catch (err: any) {
@@ -219,6 +216,54 @@ export function createServer (storageConfig: StorageConfiguration): { app: Expre
})
)
app.post(
'/exportSync',
wrapRequest(async (req, res, wsIds, token, socialId) => {
const format = req.query.format as ExportFormat
const {
_class,
query,
attributesOnly,
config
}: {
_class: Ref<Class<Doc<Space>>>
query?: DocumentQuery<Doc>
attributesOnly?: boolean
config?: TransformConfig
} = req.body
if (_class == null || format == null) {
throw new ApiError(400, 'Missing required parameters')
}
const platformClient = await createPlatformClient(token)
const txOperations = new TxOperations(platformClient, socialId)
const exportDir = await fs.mkdtemp(join(tmpdir(), 'export-'))
try {
const exporter = new WorkspaceExporter(measureCtx, txOperations, storageAdapter, wsIds, config)
await exporter.export(_class, exportDir, { format, attributesOnly: attributesOnly ?? false, query })
const files = await fs.readdir(exportDir)
if (files.length === 0) {
throw new ApiError(400, 'No data to export')
}
if (files.length !== 1) {
throw new ApiError(400, 'Unexpected number of files exported')
}
const exportedFile = join(exportDir, files[0])
res.download(exportedFile, basename(exportedFile), () => {})
} catch (err: any) {
console.error('Export failed:', err)
throw err
} finally {
void fs.rmdir(exportDir, { recursive: true })
}
})
)
app.use((err: any, _req: any, res: any, _next: any) => {
console.log(err)
if (err instanceof ApiError) {
@@ -237,7 +282,7 @@ export function createServer (storageConfig: StorageConfiguration): { app: Expre
}
}
export async function createPlatformClient (token: string): Promise<Client> {
async function createPlatformClient (token: string): Promise<Client> {
setMetadata(client.metadata.ClientSocketFactory, (url) => {
return new WebSocket(url, {
headers: {
@@ -287,7 +332,6 @@ async function saveToDrive (
storage: StorageAdapter,
wsIds: WorkspaceIds,
archivePath: string,
archiveName: string,
account: AccountUuid
): Promise<Ref<Drive>> {
const exportDrive = await ensureExportDrive(client, account)
@@ -297,7 +341,7 @@ async function saveToDrive (
await storage.put(ctx, wsIds, blobId, fileContent, 'application/gzip', fileContent.length)
await createFile(client, exportDrive, drive.ids.Root, {
title: archiveName,
title: basename(archivePath),
file: blobId,
size: fileContent.length,
type: 'application/gzip',
@@ -353,8 +397,8 @@ async function sendSuccessNotification (
user: account,
objectId: exportDrive,
objectClass: drive.class.Drive,
icon: 'setting:icon:Export' as Asset,
message: 'setting:string:ExportCompleted' as IntlString,
icon: exportPlugin.icon.Export,
message: exportPlugin.string.ExportCompleted,
props: {
fileName: archiveName
},
@@ -378,8 +422,8 @@ async function sendFailureNotification (client: TxOperations, account: AccountUu
user: account,
objectId: core.class.Doc,
objectClass: core.class.Doc,
icon: 'setting:icon:Export' as Asset,
message: 'setting:string:ExportFailed' as IntlString,
icon: exportPlugin.icon.Export,
message: exportPlugin.string.ExportFailed,
props: {
error
},
@@ -0,0 +1,158 @@
//
// Copyright © 2025 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import {
type TransformConfig,
type AttributeTransform,
type TransformOperation,
OperationType,
type OperationConfig
} from '@hcengineering/export'
export class Transformer {
private readonly skipAttributes: Set<string>
private readonly attributeKeyMap: Map<string, string>
private readonly attributeTransforms: Map<string, AttributeTransform>
constructor (config: TransformConfig = {}) {
// Initialize skip attributes
this.skipAttributes = new Set(config.skipAttributes ?? [])
// Initialize attribute key mapping
this.attributeKeyMap = new Map()
if (config.attributeKeyMap !== undefined) {
Object.entries(config.attributeKeyMap).forEach(([key, value]) => {
this.attributeKeyMap.set(key, value)
})
}
// Initialize attribute transforms
this.attributeTransforms = new Map()
if (config.attributeTransforms !== undefined) {
Object.entries(config.attributeTransforms).forEach(([key, transform]) => {
this.attributeTransforms.set(key, transform)
})
}
}
public isAttributeNeeded (key: string): boolean {
return !this.skipAttributes.has(key)
}
public transformAttribute (key: string, value: any): Record<string, any> {
// 1. if attribute is not needed, return empty object
if (!this.isAttributeNeeded(key)) {
return {}
}
// check if there is a special transformation for this key
const transform = this.attributeTransforms.get(key)
if (transform !== undefined) {
return this.applyTransform(value, transform)
}
const transformedKey = this.transformAttributeKey(key)
// 2. if value is an array, return {key, value.length}
if (Array.isArray(value)) {
return { [transformedKey]: value.length }
}
// 3. if value is an object, return object with prefix "key."
if (value !== null && typeof value === 'object') {
const result: Record<string, any> = {}
for (const [objKey, objValue] of Object.entries(value)) {
result[`${transformedKey}.${objKey}`] = objValue
}
return result
}
// 4. if value is a primitive, return {key, value}
return { [transformedKey]: value }
}
private transformAttributeKey (key: string): string {
return this.attributeKeyMap.get(key) ?? key
}
private applyTransform (value: any, transform: AttributeTransform): Record<string, any> {
let result = value
// apply operations sequentially
for (const operation of transform.operations) {
result = this.applyOperation(result, operation)
}
// if result is an object, return it
if (result !== null && typeof result === 'object' && !Array.isArray(result)) {
return result as Record<string, any>
}
// otherwise wrap in an object with key "value"
return { value: result }
}
private applyOperation (value: any, operation: TransformOperation): any {
switch (operation.type) {
case OperationType.IDENTITY:
return value
case OperationType.GROUP_BY: {
if (!Array.isArray(value)) return value
const { keyField, valueField } = operation.config as OperationConfig[OperationType.GROUP_BY]
const result: Record<string, any[]> = {}
for (const item of value) {
const key = this.getNestedValue(item, keyField)
if (key === undefined) continue
if (result[key] === undefined) {
result[key] = []
}
if (valueField !== undefined) {
const itemValue = this.getNestedValue(item, valueField)
if (itemValue !== undefined) {
result[key].push(itemValue)
}
} else {
result[key].push(item)
}
}
return result
}
default:
return value
}
}
private getNestedValue (obj: any, path: string): any {
const parts = path.split('.')
let current = obj
for (const part of parts) {
if (current === null || current === undefined) {
return undefined
}
current = current[part]
}
return current
}
}