Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8aa04cc380 | ||
|
|
83fc434c5d | ||
|
|
3e9820fa8d | ||
|
|
adb38631b3 | ||
|
|
aa519e7d81 | ||
|
|
a18203934c | ||
|
|
608e5751a9 | ||
|
|
7af3da9393 | ||
|
|
bd49fd8d5f | ||
|
|
e758f78caf | ||
|
|
3ba7a070d9 | ||
|
|
e8d4f11d8e | ||
|
|
4996f3dd28 | ||
|
|
be91d870f9 | ||
|
|
2009112712 | ||
|
|
82ee097bdc | ||
|
|
e5fea85351 | ||
|
|
7e8151355d | ||
|
|
790a8d6f93 | ||
|
|
ec2b6ae691 | ||
|
|
ae2919e564 | ||
|
|
b578ea5951 | ||
|
|
95c69507aa | ||
|
|
0b8984116b | ||
|
|
77e592502c | ||
|
|
f80b2f2c38 | ||
|
|
07cfaa78ef | ||
|
|
950f452fef | ||
|
|
4fe9e57847 | ||
|
|
d7dbb87a8f | ||
|
|
9efeb180d6 | ||
|
|
1c14567bf1 | ||
|
|
ac89b5aff6 | ||
|
|
859004f4fc | ||
|
|
c2d3fbcd21 | ||
|
|
5fb7e76005 | ||
|
|
86df188e83 |
Vendored
+1
-1
@@ -67,7 +67,7 @@
|
||||
"--config",
|
||||
"./jest-integration.config.ts",
|
||||
"${relativeFile}",
|
||||
"--silent=false"
|
||||
"--silent=false",
|
||||
],
|
||||
"cwd": "${workspaceFolder}/packages/twenty-server",
|
||||
"console": "integratedTerminal",
|
||||
|
||||
@@ -17,7 +17,7 @@ FIREFLIES_API_KEY=your_fireflies_api_key_here
|
||||
# Fireflies plan level - affects which fields are available
|
||||
# Options: free, pro, business, enterprise
|
||||
# This controls which GraphQL fields are requested to avoid 403 errors
|
||||
FIREFLIES_PLAN_LEVEL=pro
|
||||
FIREFLIES_PLAN=free
|
||||
|
||||
# Twenty CRM API key for authentication
|
||||
# Generate this from your Twenty instance: Settings > Developers > API Keys
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
.yarn
|
||||
Binary file not shown.
@@ -1,5 +1,76 @@
|
||||
# Changelog
|
||||
|
||||
## [0.3.1] - 2025-12-08
|
||||
|
||||
Import all
|
||||
|
||||
### Added
|
||||
- Historical import CLI: `yarn meeting:all` to fetch and insert historical Fireflies meetings with filters (date range, organizers, participants, channel, mine) and dry-run support.
|
||||
- Fireflies transcripts listing with pagination and date filtering to support bulk imports.
|
||||
|
||||
### Changed
|
||||
- Deduplication now checks `firefliesMeetingId` before creating meetings (webhook + bulk).
|
||||
- Shared historical importer pipeline reusing existing note/meeting formatting.
|
||||
|
||||
## [0.3.0] - 2025-12-08
|
||||
|
||||
Subscription-based query / Full transcript and AI notes for Pro+ / More
|
||||
|
||||
### Added
|
||||
- **Full transcript capture**: Meeting object now stores the complete meeting transcript with speaker names and timestamps (`transcript` field)
|
||||
- **Rich AI meeting notes**: Captures detailed AI-generated meeting notes from Fireflies (`notes` field with 7,000+ char summaries)
|
||||
- **Expanded summary fields**: Now fetches all available Fireflies summary data:
|
||||
- `notes` - Detailed AI-generated meeting notes with timestamps and section headers
|
||||
- `bullet_gist` - Emoji-enhanced bullet point summaries
|
||||
- `outline` / `shorthand_bullet` - Timestamped meeting outline
|
||||
- `gist` - One-sentence meeting summary
|
||||
- `short_summary` - Single paragraph summary
|
||||
- `short_overview` - Brief overview
|
||||
- **New Meeting fields**:
|
||||
- `transcript` - Full meeting transcript with speaker attribution
|
||||
- `notes` - AI-generated detailed notes
|
||||
- `audioUrl` - Link to audio recording (Pro+)
|
||||
- `videoUrl` - Link to video recording (Business+)
|
||||
- `meetingLink` - Original meeting link
|
||||
- `neutralPercent` - Neutral sentiment percentage
|
||||
- **Meeting delete utility**: New `yarn meeting:delete <meetingId>` script for cleanup and re-import
|
||||
- **Debug meeting utility**: New `scripts/debug-meeting.ts` to inspect raw Fireflies API responses
|
||||
|
||||
### Changed
|
||||
- **Plan-based GraphQL queries**: Completely redesigned query system with three tiers:
|
||||
- **Free**: Basic fields only (title, date, duration, participants, transcript_url, meeting_link)
|
||||
- **Pro**: Adds full transcript (`sentences`), summary fields, speakers, audio_url
|
||||
- **Business+**: Adds analytics, video_url, speaker stats, meeting metrics
|
||||
- **Action items parsing**: Fixed parsing of `action_items` which Fireflies returns as newline-separated string, not array
|
||||
- **Note body format**: Enhanced with Meeting Notes, Outline, Key Points sections from rich Fireflies data
|
||||
- **Import status**: Added `PARTIAL` status for imports missing summary/analytics data
|
||||
|
||||
### Fixed
|
||||
- Missing `notes` and `bullet_gist` fields in data transform (were fetched but not passed through)
|
||||
- Proper fallback: Uses `shorthand_bullet` when `outline` is empty (Fireflies stores outline content there)
|
||||
- Summary readiness detection now checks `notes` field in addition to `overview` and `action_items`
|
||||
|
||||
### Documentation
|
||||
- Updated README with complete API access comparison table by subscription plan
|
||||
- Documented all available Fireflies summary fields and their plan requirements
|
||||
|
||||
## [0.2.3] - 2025-12-06
|
||||
|
||||
### Added
|
||||
- **Meeting ingest utility**: New `yarn meeting:ingest <meetingId>` script to manually fetch and import specific Fireflies meetings into Twenty
|
||||
- **Plan-based field selection**: Added `FIREFLIES_PLAN` configuration to control which GraphQL fields are requested based on subscription level (free, pro, business, enterprise)
|
||||
- **Main entry point**: New `src/index.ts` centralizing all exports for cleaner imports
|
||||
|
||||
### Changed
|
||||
- **Auth configuration**: Disabled authentication requirement for webhook route (`isAuthRequired: false`) to support serverless deployments
|
||||
- **Signature verification fallback**: Webhook handler now supports signature in payload body as fallback when HTTP headers aren't forwarded to serverless functions (production doesn't work for Fireflies webhook)
|
||||
- **Improved type safety**: Replaced `any` types with proper TypeScript types throughout codebase
|
||||
|
||||
### Enhanced
|
||||
- **Webhook debugging**: Added detailed debug output including param keys, header info, and signature comparison details
|
||||
- **Test webhook script**: Includes signature in both header and payload, with diagnostic output for header forwarding status
|
||||
- **Documentation**: Added README sections on current twenty headers forward limitations and utility scripts
|
||||
|
||||
## [0.2.2] - 2025-11-04
|
||||
|
||||
### Added
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
|
||||
Automatically captures meeting notes with AI-generated summaries and insights from Fireflies.ai into your Twenty CRM.
|
||||
|
||||
### Current Status
|
||||
- Doesn't work with Fireflies webhook yet due to missing headers forwarding in twenty serverless func
|
||||
- Meeting ingestion utility scripts are available for individual meeting insertion and historical meetings with filters with yarn meeting:all
|
||||
|
||||
## Integration Overview
|
||||
|
||||
**Fireflies webhook → Fireflies API → Twenty CRM with summary-focused insights**
|
||||
@@ -19,19 +23,54 @@ Automatically captures meeting notes with AI-generated summaries and insights fr
|
||||
|
||||
## API Access by Subscription Plan
|
||||
|
||||
Fireflies API access varies significantly by subscription tier:
|
||||
Fireflies API access varies by subscription tier. This integration automatically adapts queries based on your plan and falls back gracefully if restrictions are encountered.
|
||||
|
||||
### Plan Comparison
|
||||
|
||||
| Feature | Free | Pro | Business | Enterprise |
|
||||
|---------|------|-----|----------|------------|
|
||||
| **API Rate Limit** | 50 requests/day | 50 requests/day | 60 requests/minute | 60 requests/minute |
|
||||
| **Storage** | 800 mins/seat | 8,000 mins/seat | Unlimited | Unlimited |
|
||||
| **AI Summaries** | Limited (20 credits) | Unlimited | Unlimited | Unlimited |
|
||||
| **Video Upload** | 100MB max | 1.5GB max | 1.5GB max | 1.5GB max |
|
||||
| **Advanced Features** | Basic transcription | AI apps, analytics | Team analytics, CI | Full API, SSO, compliance |
|
||||
|---------|:----:|:---:|:--------:|:----------:|
|
||||
| **API Rate Limit** | 50/day | 50/day | 60/min | 60/min |
|
||||
| **Basic Data** (title, date, duration) | ✅ | ✅ | ✅ | ✅ |
|
||||
| **Participants List** | ✅ | ✅ | ✅ | ✅ |
|
||||
| **Transcript URL** | ✅ | ✅ | ✅ | ✅ |
|
||||
| **Speakers** | ❌ | ✅ | ✅ | ✅ |
|
||||
| **Summary** (overview, keywords) | ❌ | ✅ | ✅ | ✅ |
|
||||
| **Audio URL** | ❌ | ✅ | ✅ | ✅ |
|
||||
| **Action Items** | ❌ | ❌ | ✅ | ✅ |
|
||||
| **Topics Discussed** | ❌ | ❌ | ✅ | ✅ |
|
||||
| **Video URL** | ❌ | ❌ | ✅ | ✅ |
|
||||
| **Sentiment Analytics** | ❌ | ❌ | ✅ | ✅ |
|
||||
| **Meeting Attendees (detailed)** | ❌ | ❌ | ✅ | ✅ |
|
||||
|
||||
**Key Design Pattern:** Subscription-based API access uses **tiered rate limiting** rather than feature gating. Lower tiers get severely restricted throughput (50/day vs 60/minute = 1,700x difference), making production integrations effectively require Business+ plans.
|
||||
### What You'll Get Per Plan
|
||||
|
||||
**Pro Plan Limitation:** Despite "unlimited" AI summaries, the 50 requests/day limit severely constrains production usage for meeting-heavy organizations.
|
||||
**Free Plan:**
|
||||
- Meeting title, date, duration
|
||||
- Participant names/emails (basic)
|
||||
- Link to transcript
|
||||
|
||||
**Pro Plan:**
|
||||
- Everything in Free, plus:
|
||||
- Speaker identification
|
||||
- AI summary (overview + keywords)
|
||||
- Audio recording URL
|
||||
|
||||
**Business Plan:**
|
||||
- Everything in Pro, plus:
|
||||
- Action items extraction
|
||||
- Topics discussed
|
||||
- Sentiment analysis (positive/negative/neutral %)
|
||||
- Video recording URL
|
||||
- Detailed meeting attendee info
|
||||
|
||||
### Configuration
|
||||
|
||||
Set your plan in `.env`:
|
||||
```bash
|
||||
FIREFLIES_PLAN=free # Options: free, pro, business, enterprise
|
||||
```
|
||||
|
||||
**Rate Limiting:** Free/Pro plans are limited to 50 API calls/day. The integration uses conservative retry settings by default to stay within limits.
|
||||
|
||||
## What Gets Captured
|
||||
|
||||
@@ -114,11 +153,6 @@ The `setup:fields` script adds 13 custom fields to store rich Fireflies data:
|
||||
| `firefliesMeetingId` | TEXT | Fireflies Meeting ID | Unique identifier from Fireflies |
|
||||
| `organizerEmail` | TEXT | Organizer Email | Email address of the meeting organizer |
|
||||
|
||||
Then re-sync:
|
||||
```bash
|
||||
npx twenty-cli app sync
|
||||
```
|
||||
|
||||
**Note:** Without custom fields, meetings will be created with just the title. The rich summary data will only be stored in Notes for 1-on-1 meetings.
|
||||
|
||||
## Configuration
|
||||
@@ -160,6 +194,29 @@ The integration uses **HMAC SHA-256 signature verification**:
|
||||
- Twenty verifies signature using your webhook secret
|
||||
- Invalid signatures are rejected immediately
|
||||
|
||||
### Current Platform Limitation (Headers)
|
||||
|
||||
- Twenty serverless route triggers currently do **not forward HTTP headers** to functions. Fireflies signatures sent in headers are stripped, so header-based verification does not work in production.
|
||||
- Workaround: the provided test script also includes the signature inside the payload; the handler falls back to that payload signature. Use this only for testing until header forwarding is supported.
|
||||
|
||||
## Utilities for meeting insertion (workarounds)
|
||||
|
||||
- Ingest a specific Fireflies meeting into Twenty:
|
||||
`yarn meeting:ingest <meetingId>` or `MEETING_ID=... yarn meeting:ingest`
|
||||
|
||||
- Fetch all/historical Fireflies meetings into Twenty:
|
||||
`yarn meeting:all [--from 2024-01-01] [--to 2024-02-01] [--organizer a@x.com] [--participant b@x.com] [--channel <channelId>] [--mine] [--dry-run]`
|
||||
|
||||
- Filters (combine as needed):
|
||||
- `--from` / `--to`: ISO or date string range filter
|
||||
- `--organizer` / `--participant`: comma-separated emails
|
||||
- `--channel`: Fireflies channel id
|
||||
- `--mine`: only meetings for the current Fireflies user
|
||||
- Controls:
|
||||
- `--dry-run`: list and transform without writing to Twenty
|
||||
- `--page-size`: pagination size (default 50)
|
||||
- `--max-records`: stop after N transcripts (default 500)
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
@@ -247,10 +304,7 @@ Client expressed strong interest in the enterprise plan.
|
||||
|
||||
## Future Implementation Opportunities
|
||||
|
||||
### Past Meetings Retrieval
|
||||
- **New trigger to retrieve past meetings from a contact** - Enable users to fetch historical meeting data from Fireflies for specific contacts, allowing retrospective capture and analysis of past interactions.
|
||||
|
||||
Next iteration would enhance the **intelligence layer** to:
|
||||
Next iterations would enhance the **intelligence layer** to:
|
||||
|
||||
### AI-Powered Insights
|
||||
- **Extract pain points, objections & buying signals** automatically from transcripts
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { type ApplicationConfig } from 'twenty-sdk/application';
|
||||
import { type ApplicationConfig } from 'twenty-sdk';
|
||||
|
||||
const config: ApplicationConfig = {
|
||||
universalIdentifier: 'a4df0c0f-c65e-44e5-8436-24814182d4ac',
|
||||
@@ -9,17 +9,25 @@ const config: ApplicationConfig = {
|
||||
FIREFLIES_WEBHOOK_SECRET: {
|
||||
universalIdentifier: 'f51f7646-be9f-4ba9-9b75-160dd288cd0c',
|
||||
description: 'Secret key for verifying Fireflies webhook signatures',
|
||||
isSecret: true,
|
||||
//isSecret: true,
|
||||
value: '',
|
||||
},
|
||||
FIREFLIES_API_KEY: {
|
||||
universalIdentifier: 'faa41f07-b28e-4500-b1c0-ce4b3d27924c',
|
||||
description: 'Fireflies GraphQL API key used to fetch meeting summaries',
|
||||
isSecret: true,
|
||||
//isSecret: true,
|
||||
value: '',
|
||||
},
|
||||
FIREFLIES_PLAN: {
|
||||
universalIdentifier: '57dbb73c-aac5-4247-9fcc-a070bb669f16',
|
||||
description: 'Fireflies plan: free, pro, business, enterprise',
|
||||
value: 'free',
|
||||
},
|
||||
TWENTY_API_KEY: {
|
||||
universalIdentifier: '02756551-5bf7-4fb2-8e08-1f622008d305',
|
||||
description: 'Twenty API key used when running scripts locally',
|
||||
isSecret: true,
|
||||
//isSecret: true,
|
||||
value: '',
|
||||
},
|
||||
SERVER_URL: {
|
||||
universalIdentifier: '9b3a5e8e-5973-4e6b-a059-2966075652aa',
|
||||
@@ -36,6 +44,11 @@ const config: ApplicationConfig = {
|
||||
description: 'Log level: silent, error, warn, info, debug (default: error)',
|
||||
value: 'error',
|
||||
},
|
||||
CAPTURE_LOGS: {
|
||||
universalIdentifier: 'adbcc267-309d-49b2-af71-76f1299d863e',
|
||||
description: 'Capture logs in webhook response for debugging (true/false)',
|
||||
value: 'true',
|
||||
},
|
||||
FIREFLIES_SUMMARY_STRATEGY: {
|
||||
universalIdentifier: '562b43d9-cd47-4ec1-ae16-5cc7ebc9729b',
|
||||
description: 'Summary fetch strategy: immediate_only, immediate_with_retry, delayed_polling, or basic_only',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "fireflies",
|
||||
"version": "0.2.2",
|
||||
"version": "0.3.0",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^24.5.0",
|
||||
@@ -11,12 +11,15 @@
|
||||
"scripts": {
|
||||
"test": "jest",
|
||||
"setup:fields": "tsx scripts/add-meeting-fields.ts",
|
||||
"test:webhook": "tsx scripts/test-webhook.ts"
|
||||
"test:webhook": "tsx scripts/test-webhook.ts",
|
||||
"meeting:ingest": "tsx scripts/ingest-meeting.ts",
|
||||
"meeting:delete": "tsx scripts/delete-meeting.ts",
|
||||
"meeting:all": "tsx scripts/fetch-all-meetings.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.13.1",
|
||||
"dotenv": "^16.3.1",
|
||||
"twenty-sdk": "0.0.3"
|
||||
"dotenv": "^17.2.3",
|
||||
"twenty-sdk": "0.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.5.5",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "fireflies",
|
||||
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
|
||||
"sourceRoot": "packages/twenty-apps/fireflies/src",
|
||||
"$schema": "../../../../node_modules/nx/schemas/project-schema.json",
|
||||
"sourceRoot": "packages/twenty-apps/community/fireflies/src",
|
||||
"projectType": "application",
|
||||
"tags": [
|
||||
"scope:apps"
|
||||
@@ -13,7 +13,7 @@
|
||||
"{workspaceRoot}/coverage/{projectRoot}"
|
||||
],
|
||||
"options": {
|
||||
"jestConfig": "packages/twenty-apps/fireflies/jest.config.mjs",
|
||||
"jestConfig": "packages/twenty-apps/community/fireflies/jest.config.mjs",
|
||||
"passWithNoTests": true
|
||||
},
|
||||
"configurations": {
|
||||
@@ -36,7 +36,7 @@
|
||||
],
|
||||
"options": {
|
||||
"lintFilePatterns": [
|
||||
"packages/twenty-apps/fireflies/**/*.{ts,tsx,js,jsx}"
|
||||
"packages/twenty-apps/community/fireflies/**/*.{ts,tsx,js,jsx}"
|
||||
]
|
||||
},
|
||||
"configurations": {
|
||||
|
||||
@@ -50,7 +50,11 @@ interface FieldDefinition {
|
||||
options?: FieldOption[];
|
||||
}
|
||||
|
||||
// Meeting fields based on Fireflies GraphQL API transcript schema
|
||||
// See: https://docs.fireflies.ai/graphql-api/query/transcript
|
||||
// Note: Some fields require higher plans (Pro, Business, Enterprise)
|
||||
const MEETING_FIELDS: FieldDefinition[] = [
|
||||
// === Internal Twenty Relations ===
|
||||
{
|
||||
type: 'RELATION',
|
||||
name: 'note',
|
||||
@@ -59,11 +63,21 @@ const MEETING_FIELDS: FieldDefinition[] = [
|
||||
icon: 'IconNotes',
|
||||
isNullable: true,
|
||||
},
|
||||
|
||||
// === Basic Fields (All Plans) ===
|
||||
{
|
||||
type: 'TEXT',
|
||||
name: 'firefliesMeetingId',
|
||||
label: 'Fireflies ID',
|
||||
description: 'Unique transcript ID from Fireflies (maps to: id)',
|
||||
icon: 'IconKey',
|
||||
isNullable: true,
|
||||
},
|
||||
{
|
||||
type: 'DATE_TIME',
|
||||
name: 'meetingDate',
|
||||
label: 'Meeting Date',
|
||||
description: 'Date and time when the meeting occurred',
|
||||
description: 'When the meeting occurred (maps to: date)',
|
||||
icon: 'IconCalendar',
|
||||
isNullable: true,
|
||||
},
|
||||
@@ -71,39 +85,107 @@ const MEETING_FIELDS: FieldDefinition[] = [
|
||||
type: 'NUMBER',
|
||||
name: 'duration',
|
||||
label: 'Duration (minutes)',
|
||||
description: 'Meeting duration in minutes',
|
||||
description: 'Meeting duration in minutes (maps to: duration)',
|
||||
icon: 'IconClock',
|
||||
isNullable: true,
|
||||
},
|
||||
{
|
||||
type: 'TEXT',
|
||||
name: 'meetingType',
|
||||
label: 'Meeting Type',
|
||||
description: 'Type of meeting (e.g., Sales Call, Sprint Planning, 1:1)',
|
||||
icon: 'IconTag',
|
||||
name: 'organizerEmail',
|
||||
label: 'Organizer Email',
|
||||
description: 'Meeting organizer email (maps to: organizer_email)',
|
||||
icon: 'IconMail',
|
||||
isNullable: true,
|
||||
},
|
||||
{
|
||||
type: 'LINKS',
|
||||
name: 'transcriptUrl',
|
||||
label: 'Transcript URL',
|
||||
description: 'Link to full transcript (maps to: transcript_url)',
|
||||
icon: 'IconFileText',
|
||||
isNullable: true,
|
||||
},
|
||||
{
|
||||
type: 'LINKS',
|
||||
name: 'meetingLink',
|
||||
label: 'Meeting Link',
|
||||
description: 'Original meeting link (maps to: meeting_link)',
|
||||
icon: 'IconLink',
|
||||
isNullable: true,
|
||||
},
|
||||
|
||||
// === Pro+ Fields (summary, speakers, audio_url, transcript) ===
|
||||
{
|
||||
type: 'TEXT',
|
||||
name: 'transcript',
|
||||
label: 'Full Transcript',
|
||||
description: 'Full meeting transcript with speaker names and timestamps [Pro+]',
|
||||
icon: 'IconFileText',
|
||||
isNullable: true,
|
||||
},
|
||||
{
|
||||
type: 'TEXT',
|
||||
name: 'overview',
|
||||
label: 'Overview',
|
||||
description: 'AI-generated meeting summary (maps to: summary.overview) [Pro+]',
|
||||
icon: 'IconFileDescription',
|
||||
isNullable: true,
|
||||
},
|
||||
{
|
||||
type: 'TEXT',
|
||||
name: 'notes',
|
||||
label: 'AI Notes',
|
||||
description: 'Detailed AI-generated meeting notes (maps to: summary.notes) [Pro+]',
|
||||
icon: 'IconNotes',
|
||||
isNullable: true,
|
||||
},
|
||||
{
|
||||
type: 'TEXT',
|
||||
name: 'keywords',
|
||||
label: 'Keywords',
|
||||
description: 'Key topics and themes discussed (comma-separated)',
|
||||
description: 'Key topics extracted (maps to: summary.keywords) [Pro+]',
|
||||
icon: 'IconTags',
|
||||
isNullable: true,
|
||||
},
|
||||
{
|
||||
type: 'LINKS',
|
||||
name: 'audioUrl',
|
||||
label: 'Audio URL',
|
||||
description: 'Link to audio recording (maps to: audio_url) [Pro+]',
|
||||
icon: 'IconHeadphones',
|
||||
isNullable: true,
|
||||
},
|
||||
|
||||
// === Business+ Fields (analytics, video_url, full summary) ===
|
||||
{
|
||||
type: 'TEXT',
|
||||
name: 'meetingType',
|
||||
label: 'Meeting Type',
|
||||
description: 'AI-detected meeting type (maps to: summary.meeting_type) [Business+]',
|
||||
icon: 'IconTag',
|
||||
isNullable: true,
|
||||
},
|
||||
{
|
||||
type: 'TEXT',
|
||||
name: 'topics',
|
||||
label: 'Topics Discussed',
|
||||
description: 'Topics covered in meeting (maps to: summary.topics_discussed) [Business+]',
|
||||
icon: 'IconListDetails',
|
||||
isNullable: true,
|
||||
},
|
||||
{
|
||||
type: 'NUMBER',
|
||||
name: 'sentimentScore',
|
||||
label: 'Sentiment Score',
|
||||
description: 'Overall meeting sentiment (0-1 scale, where 1 is most positive)',
|
||||
icon: 'IconMoodSmile',
|
||||
name: 'actionItemsCount',
|
||||
label: 'Action Items',
|
||||
description: 'Number of action items (count of: summary.action_items) [Business+]',
|
||||
icon: 'IconCheckbox',
|
||||
isNullable: true,
|
||||
},
|
||||
{
|
||||
type: 'NUMBER',
|
||||
name: 'positivePercent',
|
||||
label: 'Positive %',
|
||||
description: 'Percentage of positive sentiment in conversation',
|
||||
description: 'Positive sentiment % (maps to: analytics.sentiments.positive_pct) [Business+]',
|
||||
icon: 'IconThumbUp',
|
||||
isNullable: true,
|
||||
},
|
||||
@@ -111,69 +193,48 @@ const MEETING_FIELDS: FieldDefinition[] = [
|
||||
type: 'NUMBER',
|
||||
name: 'negativePercent',
|
||||
label: 'Negative %',
|
||||
description: 'Percentage of negative sentiment in conversation',
|
||||
description: 'Negative sentiment % (maps to: analytics.sentiments.negative_pct) [Business+]',
|
||||
icon: 'IconThumbDown',
|
||||
isNullable: true,
|
||||
},
|
||||
{
|
||||
type: 'NUMBER',
|
||||
name: 'actionItemsCount',
|
||||
label: 'Action Items',
|
||||
description: 'Number of action items identified',
|
||||
icon: 'IconCheckbox',
|
||||
name: 'neutralPercent',
|
||||
label: 'Neutral %',
|
||||
description: 'Neutral sentiment % (maps to: analytics.sentiments.neutral_pct) [Business+]',
|
||||
icon: 'IconMoodNeutral',
|
||||
isNullable: true,
|
||||
},
|
||||
{
|
||||
type: 'LINKS',
|
||||
name: 'transcriptUrl',
|
||||
label: 'Transcript URL',
|
||||
description: 'Link to full transcript in Fireflies',
|
||||
icon: 'IconFileText',
|
||||
isNullable: true,
|
||||
},
|
||||
{
|
||||
type: 'LINKS',
|
||||
name: 'recordingUrl',
|
||||
label: 'Recording URL',
|
||||
description: 'Link to video/audio recording in Fireflies',
|
||||
name: 'videoUrl',
|
||||
label: 'Video URL',
|
||||
description: 'Link to video recording (maps to: video_url) [Business+]',
|
||||
icon: 'IconVideo',
|
||||
isNullable: true,
|
||||
},
|
||||
{
|
||||
type: 'TEXT',
|
||||
name: 'firefliesMeetingId',
|
||||
label: 'Fireflies Meeting ID',
|
||||
description: 'Unique identifier from Fireflies',
|
||||
icon: 'IconKey',
|
||||
isNullable: true,
|
||||
},
|
||||
{
|
||||
type: 'TEXT',
|
||||
name: 'organizerEmail',
|
||||
label: 'Organizer Email',
|
||||
description: 'Email address of the meeting organizer',
|
||||
icon: 'IconMail',
|
||||
isNullable: true,
|
||||
},
|
||||
|
||||
// === Import Tracking Fields (Internal) ===
|
||||
{
|
||||
type: 'SELECT',
|
||||
name: 'importStatus',
|
||||
label: 'Import Status',
|
||||
description: 'Status of the meeting import from Fireflies',
|
||||
description: 'Status of the Fireflies import',
|
||||
icon: 'IconCheck',
|
||||
isNullable: true,
|
||||
options: [
|
||||
{ value: 'SUCCESS', label: 'Success', position: 0, color: 'green' },
|
||||
{ value: 'FAILED', label: 'Failed', position: 1, color: 'red' },
|
||||
{ value: 'PENDING', label: 'Pending', position: 2, color: 'yellow' },
|
||||
{ value: 'RETRYING', label: 'Retrying', position: 3, color: 'orange' },
|
||||
{ value: 'PARTIAL', label: 'Partial', position: 1, color: 'blue' },
|
||||
{ value: 'FAILED', label: 'Failed', position: 2, color: 'red' },
|
||||
{ value: 'PENDING', label: 'Pending', position: 3, color: 'yellow' },
|
||||
{ value: 'RETRYING', label: 'Retrying', position: 4, color: 'orange' },
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'TEXT',
|
||||
name: 'importError',
|
||||
label: 'Import Error',
|
||||
description: 'Error message if meeting import failed',
|
||||
description: 'Error message if import failed',
|
||||
icon: 'IconAlertTriangle',
|
||||
isNullable: true,
|
||||
},
|
||||
@@ -181,7 +242,7 @@ const MEETING_FIELDS: FieldDefinition[] = [
|
||||
type: 'DATE_TIME',
|
||||
name: 'lastImportAttempt',
|
||||
label: 'Last Import Attempt',
|
||||
description: 'Date and time of the last import attempt',
|
||||
description: 'When import was last attempted',
|
||||
icon: 'IconClock',
|
||||
isNullable: true,
|
||||
},
|
||||
@@ -189,7 +250,7 @@ const MEETING_FIELDS: FieldDefinition[] = [
|
||||
type: 'NUMBER',
|
||||
name: 'importAttempts',
|
||||
label: 'Import Attempts',
|
||||
description: 'Number of times import has been attempted',
|
||||
description: 'Number of import attempts',
|
||||
icon: 'IconRepeat',
|
||||
isNullable: true,
|
||||
},
|
||||
@@ -414,13 +475,9 @@ const main = async () => {
|
||||
}
|
||||
|
||||
if (createdCount === 0 && skippedCount === MEETING_FIELDS.length) {
|
||||
console.log('\n✨ All fields already exist. Nothing to do!');
|
||||
console.log('\n✨ All fields already exist. Nothing to do!\n');
|
||||
} else if (createdCount > 0) {
|
||||
console.log('\n✨ Custom fields added successfully!');
|
||||
console.log('\n📝 Next steps:');
|
||||
console.log(' 1. Re-sync your app: npx twenty-cli app sync');
|
||||
console.log(' 2. Update the createMeetingRecord function to use these fields');
|
||||
console.log(' 3. Test the integration with a real meeting');
|
||||
console.log('\n✨ Custom fields added successfully!\n');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
/* eslint-disable no-console */
|
||||
import * as dotenv from 'dotenv';
|
||||
import * as path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
dotenv.config({ path: path.join(__dirname, '../.env') });
|
||||
|
||||
const FIREFLIES_API_KEY = process.env.FIREFLIES_API_KEY;
|
||||
const meetingId = process.argv[2] || '01KBMR1ZYQ34YP8D2KB4B16QPH';
|
||||
|
||||
const main = async (): Promise<void> => {
|
||||
if (!FIREFLIES_API_KEY) {
|
||||
console.error('❌ FIREFLIES_API_KEY is required');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const query = `
|
||||
query GetTranscript($transcriptId: String!) {
|
||||
transcript(id: $transcriptId) {
|
||||
id
|
||||
title
|
||||
summary {
|
||||
overview
|
||||
notes
|
||||
gist
|
||||
bullet_gist
|
||||
short_summary
|
||||
short_overview
|
||||
outline
|
||||
shorthand_bullet
|
||||
action_items
|
||||
keywords
|
||||
topics_discussed
|
||||
meeting_type
|
||||
transcript_chapters
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const response = await fetch('https://api.fireflies.ai/graphql', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${FIREFLIES_API_KEY}`,
|
||||
},
|
||||
body: JSON.stringify({ query, variables: { transcriptId: meetingId } }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
console.error(`❌ API request failed with status ${response.status}`);
|
||||
console.error(errorText);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const json = await response.json();
|
||||
console.log('=== Fireflies API Response ===\n');
|
||||
console.log(JSON.stringify(json, null, 2));
|
||||
|
||||
if (json.data?.transcript?.summary) {
|
||||
const s = json.data.transcript.summary;
|
||||
console.log('\n=== Summary Fields Status ===');
|
||||
console.log('overview:', s.overview ? `✓ (${s.overview.length} chars)` : '✗ empty');
|
||||
console.log('notes:', s.notes ? `✓ (${s.notes.length} chars)` : '✗ empty');
|
||||
console.log('gist:', s.gist ? `✓ (${s.gist.length} chars)` : '✗ empty');
|
||||
console.log('bullet_gist:', s.bullet_gist ? `✓ (${s.bullet_gist.length} chars)` : '✗ empty');
|
||||
console.log('outline:', s.outline ? `✓ (${s.outline.length} chars)` : '✗ empty');
|
||||
console.log('action_items:', s.action_items?.length || 0, 'items');
|
||||
console.log('topics_discussed:', s.topics_discussed?.length || 0, 'topics');
|
||||
console.log('keywords:', s.keywords?.length || 0, 'keywords');
|
||||
}
|
||||
};
|
||||
|
||||
main().catch((error) => {
|
||||
console.error('❌ Failed to fetch meeting');
|
||||
console.error(error instanceof Error ? error.message : String(error));
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -0,0 +1,59 @@
|
||||
/* eslint-disable no-console */
|
||||
import * as dotenv from 'dotenv';
|
||||
import * as path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
dotenv.config({ path: path.join(__dirname, '../.env') });
|
||||
|
||||
const SERVER_URL = process.env.SERVER_URL || 'http://localhost:3000';
|
||||
const API_KEY = process.env.TWENTY_API_KEY;
|
||||
const meetingId = process.argv[2];
|
||||
|
||||
const main = async (): Promise<void> => {
|
||||
if (!API_KEY) {
|
||||
console.error('❌ TWENTY_API_KEY is required');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!meetingId) {
|
||||
console.error('Usage: yarn delete:meeting <meetingId>');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const response = await fetch(`${SERVER_URL}/graphql`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${API_KEY}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
query: `mutation DeleteMeeting($id: UUID!) { deleteMeeting(id: $id) { id } }`,
|
||||
variables: { id: meetingId },
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
console.error(`❌ Delete failed (status ${response.status})`);
|
||||
console.error(errorText);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
const deletedId = result.data?.deleteMeeting?.id;
|
||||
if (result.errors || !deletedId) {
|
||||
const message = result.errors?.[0]?.message || 'deleteMeeting returned null';
|
||||
console.error('❌ Error:', message);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log('✅ Deleted meeting:', deletedId);
|
||||
};
|
||||
|
||||
main().catch((error) => {
|
||||
console.error('❌ Failed to delete meeting');
|
||||
console.error(error instanceof Error ? error.message : String(error));
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -0,0 +1,222 @@
|
||||
/* eslint-disable no-console */
|
||||
/**
|
||||
* Fetch historical Fireflies meetings and insert into Twenty.
|
||||
*
|
||||
* Usage:
|
||||
* yarn meeting:all [--from 2024-01-01] [--to 2024-02-01] [--organizer alice@x.com] [--participant bob@x.com] [--channel <channelId>] [--mine] [--dry-run] [--page-size 50] [--max-records 200]
|
||||
*
|
||||
* Required env:
|
||||
* FIREFLIES_API_KEY
|
||||
* TWENTY_API_KEY
|
||||
*
|
||||
* Optional env:
|
||||
* SERVER_URL (defaults to http://localhost:3000)
|
||||
* FIREFLIES_PLAN (free|pro|business|enterprise)
|
||||
* AUTO_CREATE_CONTACTS (true|false)
|
||||
* FIREFLIES_* retry settings (see README)
|
||||
*/
|
||||
|
||||
import * as dotenv from 'dotenv';
|
||||
import { existsSync } from 'fs';
|
||||
import { dirname, join } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
import { FirefliesApiClient } from '../src/fireflies-api-client';
|
||||
import { type HistoricalImportFilters, HistoricalImporter } from '../src/historical-importer';
|
||||
import { createLogger } from '../src/logger';
|
||||
import { TwentyCrmService } from '../src/twenty-crm-service';
|
||||
import {
|
||||
getApiUrl,
|
||||
getFirefliesPlan,
|
||||
getSummaryFetchConfig,
|
||||
shouldAutoCreateContacts,
|
||||
} from '../src/utils';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
const envPath = join(__dirname, '..', '.env');
|
||||
if (existsSync(envPath)) {
|
||||
dotenv.config({ path: envPath });
|
||||
}
|
||||
|
||||
const logger = createLogger('cli:meeting:all');
|
||||
|
||||
type CliArgs = {
|
||||
from?: string;
|
||||
to?: string;
|
||||
organizer?: string[];
|
||||
participant?: string[];
|
||||
channel?: string;
|
||||
host?: string;
|
||||
mine?: boolean;
|
||||
dryRun?: boolean;
|
||||
pageSize?: number;
|
||||
maxRecords?: number;
|
||||
limit?: number;
|
||||
};
|
||||
|
||||
const parseArgs = (argv: string[]): CliArgs => {
|
||||
const args: CliArgs = {};
|
||||
|
||||
const parseNumberArg = (value?: string): number | undefined => {
|
||||
if (!value) return undefined;
|
||||
const parsed = Number.parseInt(value, 10);
|
||||
return Number.isNaN(parsed) ? undefined : parsed;
|
||||
};
|
||||
|
||||
for (let i = 0; i < argv.length; i += 1) {
|
||||
const current = argv[i];
|
||||
const next = argv[i + 1];
|
||||
switch (current) {
|
||||
case '--from':
|
||||
args.from = next;
|
||||
i += 1;
|
||||
break;
|
||||
case '--to':
|
||||
args.to = next;
|
||||
i += 1;
|
||||
break;
|
||||
case '--organizer':
|
||||
args.organizer = next ? next.split(',') : [];
|
||||
i += 1;
|
||||
break;
|
||||
case '--participant':
|
||||
args.participant = next ? next.split(',') : [];
|
||||
i += 1;
|
||||
break;
|
||||
case '--channel':
|
||||
args.channel = next;
|
||||
i += 1;
|
||||
break;
|
||||
case '--host':
|
||||
args.host = next;
|
||||
i += 1;
|
||||
break;
|
||||
case '--mine':
|
||||
args.mine = true;
|
||||
break;
|
||||
case '--dry-run':
|
||||
args.dryRun = true;
|
||||
break;
|
||||
case '--page-size':
|
||||
args.pageSize = parseNumberArg(next);
|
||||
i += 1;
|
||||
break;
|
||||
case '--max-records':
|
||||
args.maxRecords = parseNumberArg(next);
|
||||
i += 1;
|
||||
break;
|
||||
case '--limit':
|
||||
args.limit = parseNumberArg(next);
|
||||
i += 1;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return args;
|
||||
};
|
||||
|
||||
const parseDate = (value?: string): number | undefined => {
|
||||
if (!value) {
|
||||
return undefined;
|
||||
}
|
||||
const parsed = Date.parse(value);
|
||||
return Number.isNaN(parsed) ? undefined : parsed;
|
||||
};
|
||||
|
||||
const main = async (): Promise<void> => {
|
||||
const args = parseArgs(process.argv.slice(2));
|
||||
|
||||
const firefliesApiKey = process.env.FIREFLIES_API_KEY || '';
|
||||
const twentyApiKey = process.env.TWENTY_API_KEY || '';
|
||||
|
||||
if (!firefliesApiKey) {
|
||||
console.error('❌ FIREFLIES_API_KEY is required');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!twentyApiKey) {
|
||||
console.error('❌ TWENTY_API_KEY is required');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const fromDate = parseDate(args.from);
|
||||
const toDate = parseDate(args.to);
|
||||
|
||||
const filters: HistoricalImportFilters = {
|
||||
fromDate,
|
||||
toDate,
|
||||
organizers: args.organizer,
|
||||
participants: args.participant,
|
||||
channelId: args.channel,
|
||||
hostEmail: args.host,
|
||||
mine: args.mine,
|
||||
limit: args.limit,
|
||||
pageSize: args.pageSize,
|
||||
maxRecords: args.maxRecords,
|
||||
};
|
||||
|
||||
const summaryConfig = getSummaryFetchConfig();
|
||||
const plan = getFirefliesPlan();
|
||||
const autoCreateContacts = shouldAutoCreateContacts();
|
||||
|
||||
logger.info(
|
||||
`Starting historical import (dryRun=${Boolean(args.dryRun)}, plan=${plan}, pageSize=${filters.pageSize ?? 50})`,
|
||||
);
|
||||
|
||||
const firefliesClient = new FirefliesApiClient(firefliesApiKey);
|
||||
const twentyService = new TwentyCrmService(twentyApiKey, getApiUrl());
|
||||
const importer = new HistoricalImporter(firefliesClient, twentyService);
|
||||
|
||||
const result = await importer.run(filters, {
|
||||
dryRun: args.dryRun,
|
||||
autoCreateContacts,
|
||||
summaryConfig,
|
||||
plan,
|
||||
});
|
||||
|
||||
console.log('✅ Historical import summary:');
|
||||
const summary = {
|
||||
dryRun: result.dryRun,
|
||||
totalListed: result.totalListed,
|
||||
imported: result.imported,
|
||||
skippedExisting: result.skippedExisting,
|
||||
summaryPending: result.summaryPending,
|
||||
failed: result.failed,
|
||||
};
|
||||
console.log(JSON.stringify(summary, null, 2));
|
||||
|
||||
if (result.statuses.length > 0) {
|
||||
console.log('Status by meeting:');
|
||||
console.table(
|
||||
result.statuses.map((s) => ({
|
||||
meetingId: s.meetingId,
|
||||
title: s.title ?? '',
|
||||
status: s.status,
|
||||
reason: s.reason ?? '',
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
if (result.failed.length > 0) {
|
||||
process.exitCode = 1;
|
||||
}
|
||||
};
|
||||
|
||||
main().catch((error) => {
|
||||
console.error('❌ Failed to import historical meetings');
|
||||
if (error instanceof Error) {
|
||||
console.error(error.message);
|
||||
if (error.stack) {
|
||||
console.error(error.stack);
|
||||
}
|
||||
} else {
|
||||
console.error(String(error));
|
||||
}
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
/* eslint-disable no-console */
|
||||
/**
|
||||
* Fetch a Fireflies meeting by ID and insert it into Twenty using the same path
|
||||
* as the webhook handler.
|
||||
*
|
||||
* Usage:
|
||||
* yarn meeting:ingest <meetingId>
|
||||
* Or
|
||||
* MEETING_ID=... yarn meeting:ingest
|
||||
*
|
||||
* Required env:
|
||||
* FIREFLIES_API_KEY
|
||||
* FIREFLIES_WEBHOOK_SECRET
|
||||
* TWENTY_API_KEY
|
||||
*
|
||||
* Optional env:
|
||||
* SERVER_URL (defaults to http://localhost:3000)
|
||||
* FIREFLIES_PLAN (free|pro|business|enterprise)
|
||||
*/
|
||||
|
||||
import { createHmac } from 'crypto';
|
||||
import * as dotenv from 'dotenv';
|
||||
import { existsSync } from 'fs';
|
||||
import { dirname, join } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
import { WebhookHandler } from '../src/webhook-handler';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
const envPath = join(__dirname, '..', '.env');
|
||||
if (existsSync(envPath)) {
|
||||
dotenv.config({ path: envPath });
|
||||
}
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
const meetingId = args[0] || process.env.MEETING_ID;
|
||||
|
||||
if (!meetingId) {
|
||||
console.error('❌ meetingId is required (arg or MEETING_ID env)');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const firefliesApiKey = process.env.FIREFLIES_API_KEY || '';
|
||||
const twentyApiKey = process.env.TWENTY_API_KEY || '';
|
||||
const webhookSecret = process.env.FIREFLIES_WEBHOOK_SECRET || '';
|
||||
|
||||
if (!firefliesApiKey) {
|
||||
console.error('❌ FIREFLIES_API_KEY is required');
|
||||
process.exit(1);
|
||||
}
|
||||
if (!twentyApiKey) {
|
||||
console.error('❌ TWENTY_API_KEY is required');
|
||||
process.exit(1);
|
||||
}
|
||||
if (!webhookSecret) {
|
||||
console.error('❌ FIREFLIES_WEBHOOK_SECRET is required to generate signature');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const payload = {
|
||||
meetingId,
|
||||
eventType: 'Transcription completed',
|
||||
};
|
||||
|
||||
const body = JSON.stringify(payload);
|
||||
const signature = `sha256=${createHmac('sha256', webhookSecret)
|
||||
.update(body, 'utf8')
|
||||
.digest('hex')}`;
|
||||
|
||||
const main = async (): Promise<void> => {
|
||||
console.log(`🚀 Ingesting meeting ${meetingId} via webhook handler`);
|
||||
const handler = new WebhookHandler();
|
||||
const result = await handler.handle(payload, {
|
||||
'x-hub-signature': signature,
|
||||
body,
|
||||
});
|
||||
|
||||
console.log('✅ Result:');
|
||||
console.log(JSON.stringify(result, null, 2));
|
||||
|
||||
if (result.errors && result.errors.length > 0) {
|
||||
process.exitCode = 1;
|
||||
}
|
||||
};
|
||||
|
||||
main().catch((error) => {
|
||||
console.error('❌ Failed to ingest meeting');
|
||||
console.error(error instanceof Error ? error.message : String(error));
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
@@ -40,11 +40,13 @@ const FIREFLIES_WEBHOOK_SECRET = process.env.FIREFLIES_WEBHOOK_SECRET || 'test_s
|
||||
const _FIREFLIES_API_KEY = process.env.FIREFLIES_API_KEY || 'test_api_key';
|
||||
|
||||
// Test meeting data (simulating Fireflies API response)
|
||||
const TEST_MEETING_ID = 'test-meeting-local-' + Date.now();
|
||||
const TEST_MEETING_ID = process.env.MEETING_ID || 'test-meeting-local-' + Date.now();
|
||||
const CLIENT_REFERENCE_ID = process.env.CLIENT_REFERENCE_ID;
|
||||
|
||||
const TEST_WEBHOOK_PAYLOAD = {
|
||||
meetingId: TEST_MEETING_ID,
|
||||
eventType: 'Transcription completed',
|
||||
clientReferenceId: 'test-client-ref',
|
||||
...(CLIENT_REFERENCE_ID ? { clientReferenceId: CLIENT_REFERENCE_ID } : {}),
|
||||
};
|
||||
|
||||
// Mock Fireflies GraphQL API response
|
||||
@@ -119,11 +121,17 @@ const main = async () => {
|
||||
}
|
||||
|
||||
// Prepare webhook payload
|
||||
const body = JSON.stringify(TEST_WEBHOOK_PAYLOAD);
|
||||
const signature = generateHMACSignature(body, FIREFLIES_WEBHOOK_SECRET);
|
||||
const unsignedBody = JSON.stringify(TEST_WEBHOOK_PAYLOAD);
|
||||
const signature = generateHMACSignature(unsignedBody, FIREFLIES_WEBHOOK_SECRET);
|
||||
const payloadWithSignature = {
|
||||
...TEST_WEBHOOK_PAYLOAD,
|
||||
'x-hub-signature': signature,
|
||||
};
|
||||
const body = JSON.stringify(payloadWithSignature);
|
||||
|
||||
console.log('📤 Sending webhook payload:');
|
||||
console.log(JSON.stringify(TEST_WEBHOOK_PAYLOAD, null, 2));
|
||||
console.log(JSON.stringify(payloadWithSignature, null, 2));
|
||||
console.log('\nℹ️ Signature is sent both as header (preferred) and in payload as fallback (headers are not passed to serverless functions)\n');
|
||||
console.log(`\n🔐 HMAC Signature: ${signature}\n`);
|
||||
|
||||
// Check if server is reachable
|
||||
@@ -178,6 +186,31 @@ const main = async () => {
|
||||
console.log('📥 Response Body:');
|
||||
console.log(JSON.stringify(responseData, null, 2));
|
||||
|
||||
// Report whether the server appears to have received the header signature
|
||||
const debugMessages = Array.isArray((responseData as any)?.debug)
|
||||
? ((responseData as any).debug as string[])
|
||||
: [];
|
||||
const headerMissing =
|
||||
debugMessages.some((msg) => msg.includes('headerKeys=none')) ||
|
||||
debugMessages.some((msg) => msg.includes('providedSignature=undefined'));
|
||||
const signatureErrors =
|
||||
Array.isArray((responseData as any)?.errors) &&
|
||||
((responseData as any).errors as unknown[]).some(
|
||||
(err) => typeof err === 'string' && err.toLowerCase().includes('signature'),
|
||||
);
|
||||
|
||||
if (headerMissing) {
|
||||
console.log(
|
||||
'\n⚠️ Server did not report any received headers; it may be using payload fallback for signature verification.',
|
||||
);
|
||||
} else {
|
||||
console.log('\n✅ Server reported headers present (header-based signature should be used).');
|
||||
}
|
||||
|
||||
if (signatureErrors) {
|
||||
console.log('⚠️ Signature was rejected by the server (check webhook secret / payload).');
|
||||
}
|
||||
|
||||
if (response.ok) {
|
||||
console.log('\n✅ Webhook test completed successfully!');
|
||||
console.log('\n📋 Next steps:');
|
||||
|
||||
-426
@@ -1,426 +0,0 @@
|
||||
import { createLogger } from './logger';
|
||||
import type { FirefliesMeetingData, FirefliesParticipant, SummaryFetchConfig } from './types';
|
||||
|
||||
const logger = createLogger('fireflies-api');
|
||||
|
||||
export class FirefliesApiClient {
|
||||
private apiKey: string;
|
||||
|
||||
constructor(apiKey: string) {
|
||||
if (!apiKey) {
|
||||
logger.critical('FIREFLIES_API_KEY is required but not provided - this is a critical configuration error');
|
||||
throw new Error('FIREFLIES_API_KEY is required');
|
||||
}
|
||||
this.apiKey = apiKey;
|
||||
}
|
||||
|
||||
async fetchMeetingData(
|
||||
meetingId: string,
|
||||
options?: { timeout?: number }
|
||||
): Promise<FirefliesMeetingData> {
|
||||
const query = `
|
||||
query GetTranscript($transcriptId: String!) {
|
||||
transcript(id: $transcriptId) {
|
||||
id
|
||||
title
|
||||
date
|
||||
duration
|
||||
participants
|
||||
organizer_email
|
||||
meeting_attendees {
|
||||
displayName
|
||||
email
|
||||
phoneNumber
|
||||
name
|
||||
location
|
||||
}
|
||||
meeting_attendance {
|
||||
name
|
||||
join_time
|
||||
leave_time
|
||||
}
|
||||
speakers {
|
||||
name
|
||||
}
|
||||
summary {
|
||||
action_items
|
||||
overview
|
||||
}
|
||||
transcript_url
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const controller = new AbortController();
|
||||
const timeoutId = options?.timeout
|
||||
? setTimeout(() => controller.abort(), options.timeout)
|
||||
: null;
|
||||
|
||||
try {
|
||||
const response = await fetch('https://api.fireflies.ai/graphql', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${this.apiKey}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
query,
|
||||
variables: { transcriptId: meetingId },
|
||||
}),
|
||||
signal: controller.signal,
|
||||
});
|
||||
|
||||
if (timeoutId) clearTimeout(timeoutId);
|
||||
|
||||
if (!response.ok) {
|
||||
let errorDetails = `Fireflies API request failed with status ${response.status}`;
|
||||
try {
|
||||
const errorBody = await response.text();
|
||||
if (errorBody) {
|
||||
errorDetails += `: ${errorBody}`;
|
||||
}
|
||||
} catch {
|
||||
// Ignore if we can't read the response body
|
||||
}
|
||||
throw new Error(errorDetails);
|
||||
}
|
||||
|
||||
const json = await response.json() as {
|
||||
data?: { transcript?: any };
|
||||
errors?: Array<{ message?: string }>;
|
||||
};
|
||||
|
||||
if (json.errors && json.errors.length > 0) {
|
||||
throw new Error(`Fireflies API error: ${json.errors[0]?.message || 'Unknown error'}`);
|
||||
}
|
||||
|
||||
const transcript = json.data?.transcript;
|
||||
if (!transcript) {
|
||||
throw new Error('Invalid response from Fireflies API: missing transcript data');
|
||||
}
|
||||
|
||||
return this.transformMeetingData(transcript, meetingId);
|
||||
} catch (error) {
|
||||
if (timeoutId) clearTimeout(timeoutId);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async fetchMeetingDataWithRetry(
|
||||
meetingId: string,
|
||||
config: SummaryFetchConfig
|
||||
): Promise<{ data: FirefliesMeetingData; summaryReady: boolean }> {
|
||||
// immediate_only: single attempt, no retries
|
||||
if (config.strategy === 'immediate_only') {
|
||||
logger.debug(`fetching meeting ${meetingId} (strategy: immediate_only)`);
|
||||
const meetingData = await this.fetchMeetingData(meetingId, { timeout: 10000 });
|
||||
const ready = this.isSummaryReady(meetingData);
|
||||
logger.debug(`summary ready: ${ready}`);
|
||||
return { data: meetingData, summaryReady: ready };
|
||||
}
|
||||
|
||||
// immediate_with_retry: retry with exponential backoff
|
||||
logger.debug(`fetching meeting ${meetingId} (strategy: immediate_with_retry, maxAttempts: ${config.retryAttempts})`);
|
||||
|
||||
for (let attempt = 1; attempt <= config.retryAttempts; attempt++) {
|
||||
try {
|
||||
const meetingData = await this.fetchMeetingData(meetingId, { timeout: 10000 });
|
||||
const ready = this.isSummaryReady(meetingData);
|
||||
|
||||
logger.debug(`attempt ${attempt}/${config.retryAttempts}: summary ready=${ready}`);
|
||||
|
||||
if (ready) {
|
||||
return { data: meetingData, summaryReady: true };
|
||||
}
|
||||
|
||||
if (attempt < config.retryAttempts) {
|
||||
const delayMs = config.retryDelay * attempt;
|
||||
logger.debug(`summary not ready, waiting ${delayMs}ms before retry ${attempt + 1}`);
|
||||
await new Promise(resolve => setTimeout(resolve, delayMs));
|
||||
} else {
|
||||
logger.debug(`max retries reached, returning partial data`);
|
||||
return { data: meetingData, summaryReady: false };
|
||||
}
|
||||
} catch (error) {
|
||||
const errorMsg = error instanceof Error ? error.message : String(error);
|
||||
logger.error(`attempt ${attempt}/${config.retryAttempts} failed: ${errorMsg}`);
|
||||
|
||||
if (attempt === config.retryAttempts) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
const delayMs = config.retryDelay * attempt;
|
||||
logger.debug(`retrying in ${delayMs}ms...`);
|
||||
await new Promise(resolve => setTimeout(resolve, delayMs));
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('Failed to fetch meeting data after retries');
|
||||
}
|
||||
|
||||
private isSummaryReady(meetingData: FirefliesMeetingData): boolean {
|
||||
return (
|
||||
(meetingData.summary?.action_items?.length > 0) ||
|
||||
(meetingData.summary?.overview?.length > 0) ||
|
||||
meetingData.summary_status === 'completed'
|
||||
);
|
||||
}
|
||||
|
||||
private extractAllParticipants(transcript: any): FirefliesParticipant[] {
|
||||
const participantsWithEmails: FirefliesParticipant[] = [];
|
||||
const participantsNameOnly: FirefliesParticipant[] = [];
|
||||
|
||||
logger.debug('=== PARTICIPANT EXTRACTION DEBUG ===');
|
||||
logger.debug('participants field:', JSON.stringify(transcript.participants));
|
||||
logger.debug('meeting_attendees field:', JSON.stringify(transcript.meeting_attendees));
|
||||
logger.debug('speakers field:', transcript.speakers?.map((s: any) => s.name));
|
||||
logger.debug('meeting_attendance field:', transcript.meeting_attendance?.map((a: any) => a.name));
|
||||
logger.debug('organizer_email:', transcript.organizer_email);
|
||||
|
||||
// Helper function to check if a string is an email
|
||||
const isEmail = (str: string): boolean => {
|
||||
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(str.trim());
|
||||
};
|
||||
|
||||
// Helper function to check if already exists
|
||||
const isDuplicate = (name: string, email: string): boolean => {
|
||||
const nameLower = name.toLowerCase().trim();
|
||||
const emailLower = email.toLowerCase().trim();
|
||||
|
||||
return participantsWithEmails.some(p =>
|
||||
p.name.toLowerCase().trim() === nameLower ||
|
||||
(email && p.email.toLowerCase() === emailLower)
|
||||
) || participantsNameOnly.some(p =>
|
||||
p.name.toLowerCase().trim() === nameLower
|
||||
);
|
||||
};
|
||||
|
||||
// 1. Extract from legacy participants field (with emails)
|
||||
if (transcript.participants && Array.isArray(transcript.participants)) {
|
||||
transcript.participants.forEach((participant: string) => {
|
||||
// Handle comma-separated emails or names
|
||||
const parts = participant.split(',').map(p => p.trim());
|
||||
|
||||
parts.forEach(part => {
|
||||
const emailMatch = part.match(/<([^>]+)>/);
|
||||
const email = emailMatch ? emailMatch[1] : '';
|
||||
// Extract name properly: if there's an email in angle brackets, get the part before it
|
||||
const name = emailMatch
|
||||
? part.substring(0, part.indexOf('<')).trim()
|
||||
: part.trim();
|
||||
|
||||
// Skip if the "name" is actually an email address
|
||||
if (isEmail(name)) {
|
||||
logger.debug(`Skipping participant with email as name: "${name}"`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip if empty name
|
||||
if (!name) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip duplicates
|
||||
if (isDuplicate(name, email)) {
|
||||
logger.debug(`Skipping duplicate participant: "${name}" <${email}>`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (name && email) {
|
||||
participantsWithEmails.push({ name, email });
|
||||
} else if (name) {
|
||||
participantsNameOnly.push({ name, email: '' });
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 2. Extract from meeting_attendees field (structured)
|
||||
if (transcript.meeting_attendees && Array.isArray(transcript.meeting_attendees)) {
|
||||
transcript.meeting_attendees.forEach((attendee: any) => {
|
||||
const name = attendee.displayName || attendee.name || '';
|
||||
const email = attendee.email || '';
|
||||
|
||||
// Skip if name is actually an email
|
||||
if (isEmail(name)) {
|
||||
logger.debug(`Skipping attendee with email as name: "${name}"`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (name && !isDuplicate(name, email)) {
|
||||
if (email) {
|
||||
participantsWithEmails.push({ name, email });
|
||||
} else {
|
||||
participantsNameOnly.push({ name, email: '' });
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 3. Extract from speakers field (name only)
|
||||
if (transcript.speakers && Array.isArray(transcript.speakers)) {
|
||||
transcript.speakers.forEach((speaker: any) => {
|
||||
const name = speaker.name || '';
|
||||
|
||||
// Skip if name is actually an email
|
||||
if (isEmail(name)) {
|
||||
logger.debug(`Skipping speaker with email as name: "${name}"`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (name && !isDuplicate(name, '')) {
|
||||
participantsNameOnly.push({ name, email: '' });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 4. Extract from meeting_attendance field (name only)
|
||||
if (transcript.meeting_attendance && Array.isArray(transcript.meeting_attendance)) {
|
||||
transcript.meeting_attendance.forEach((attendance: any) => {
|
||||
const name = attendance.name || '';
|
||||
|
||||
// Skip if name is actually an email or contains comma-separated emails
|
||||
if (isEmail(name) || name.includes(',')) {
|
||||
logger.debug(`Skipping attendance with email/list as name: "${name}"`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (name && !isDuplicate(name, '')) {
|
||||
participantsNameOnly.push({ name, email: '' });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 5. Add organizer email if available and not already included
|
||||
const organizerEmail = transcript.organizer_email;
|
||||
if (organizerEmail) {
|
||||
// Check if organizer email is already in the participants
|
||||
const existsWithEmail = participantsWithEmails.some(p =>
|
||||
p.email.toLowerCase() === organizerEmail.toLowerCase()
|
||||
);
|
||||
|
||||
if (!existsWithEmail) {
|
||||
// Try to find organizer name from speakers/attendance and match with email
|
||||
let organizerName = '';
|
||||
|
||||
// Extract username from organizer email for matching
|
||||
const emailUsername = organizerEmail.split('@')[0].toLowerCase();
|
||||
const emailNameVariations = [emailUsername];
|
||||
|
||||
// Add common name variations based on email username
|
||||
if (emailUsername === 'alex') {
|
||||
emailNameVariations.push('alexander', 'alexandre', 'alex');
|
||||
}
|
||||
|
||||
// Look for organizer in speakers by matching email username to speaker names
|
||||
if (transcript.speakers && Array.isArray(transcript.speakers)) {
|
||||
const potentialOrganizerSpeaker = transcript.speakers.find((speaker: any) => {
|
||||
const name = (speaker.name || '').toLowerCase();
|
||||
return emailNameVariations.some(variation =>
|
||||
name.includes(variation) || variation.includes(name)
|
||||
);
|
||||
});
|
||||
if (potentialOrganizerSpeaker) {
|
||||
organizerName = potentialOrganizerSpeaker.name;
|
||||
}
|
||||
}
|
||||
|
||||
// Look for organizer in attendance
|
||||
if (!organizerName && transcript.meeting_attendance && Array.isArray(transcript.meeting_attendance)) {
|
||||
const potentialOrganizerAttendance = transcript.meeting_attendance.find((attendance: any) => {
|
||||
const name = (attendance.name || '').toLowerCase();
|
||||
return emailNameVariations.some(variation =>
|
||||
name.includes(variation) || variation.includes(name)
|
||||
);
|
||||
});
|
||||
if (potentialOrganizerAttendance) {
|
||||
organizerName = potentialOrganizerAttendance.name;
|
||||
}
|
||||
}
|
||||
|
||||
// If we found a name match, add as participant with email
|
||||
if (organizerName) {
|
||||
participantsWithEmails.push({ name: organizerName, email: organizerEmail });
|
||||
|
||||
// Remove from name-only participants to avoid duplicates
|
||||
const nameIndex = participantsNameOnly.findIndex(p =>
|
||||
p.name.toLowerCase().includes(organizerName.toLowerCase()) ||
|
||||
organizerName.toLowerCase().includes(p.name.toLowerCase())
|
||||
);
|
||||
if (nameIndex !== -1) {
|
||||
participantsNameOnly.splice(nameIndex, 1);
|
||||
}
|
||||
} else {
|
||||
// If no name found, add with generic organizer name
|
||||
participantsWithEmails.push({ name: 'Meeting Organizer', email: organizerEmail });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return participants with emails first, then name-only participants
|
||||
const allParticipants = [...participantsWithEmails, ...participantsNameOnly];
|
||||
|
||||
logger.debug('=== EXTRACTED PARTICIPANTS ===');
|
||||
logger.debug('With emails:', participantsWithEmails.length, JSON.stringify(participantsWithEmails));
|
||||
logger.debug('Name only:', participantsNameOnly.length, JSON.stringify(participantsNameOnly));
|
||||
logger.debug('Total:', allParticipants.length);
|
||||
|
||||
return allParticipants;
|
||||
}
|
||||
|
||||
private transformMeetingData(transcript: any, meetingId: string): FirefliesMeetingData {
|
||||
// Convert date to ISO string - handle both timestamp and ISO string formats
|
||||
let dateString: string;
|
||||
if (transcript.date) {
|
||||
if (typeof transcript.date === 'number') {
|
||||
// Unix timestamp in milliseconds
|
||||
dateString = new Date(transcript.date).toISOString();
|
||||
} else if (typeof transcript.date === 'string') {
|
||||
// Could be ISO string or timestamp string
|
||||
const parsed = Number(transcript.date);
|
||||
if (!isNaN(parsed)) {
|
||||
// It's a numeric string (timestamp)
|
||||
dateString = new Date(parsed).toISOString();
|
||||
} else {
|
||||
// It's already an ISO string
|
||||
dateString = transcript.date;
|
||||
}
|
||||
} else {
|
||||
dateString = new Date().toISOString();
|
||||
}
|
||||
} else {
|
||||
dateString = new Date().toISOString();
|
||||
}
|
||||
|
||||
return {
|
||||
id: transcript.id || meetingId,
|
||||
title: transcript.title || 'Untitled Meeting',
|
||||
date: dateString,
|
||||
duration: transcript.duration || 0,
|
||||
participants: this.extractAllParticipants(transcript),
|
||||
organizer_email: transcript.organizer_email,
|
||||
summary: {
|
||||
action_items: Array.isArray(transcript.summary?.action_items)
|
||||
? transcript.summary.action_items
|
||||
: (typeof transcript.summary?.action_items === 'string'
|
||||
? [transcript.summary.action_items]
|
||||
: []),
|
||||
overview: transcript.summary?.overview || '',
|
||||
keywords: transcript.summary?.keywords,
|
||||
topics_discussed: transcript.summary?.topics_discussed,
|
||||
meeting_type: transcript.summary?.meeting_type,
|
||||
},
|
||||
analytics: transcript.sentiments ? {
|
||||
sentiments: {
|
||||
positive_pct: transcript.sentiments.positive_pct || 0,
|
||||
negative_pct: transcript.sentiments.negative_pct || 0,
|
||||
neutral_pct: transcript.sentiments.neutral_pct || 0,
|
||||
}
|
||||
} : undefined,
|
||||
transcript_url: transcript.transcript_url || `https://app.fireflies.ai/view/${meetingId}`,
|
||||
recording_url: transcript.video_url || undefined,
|
||||
summary_status: transcript.summary_status,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
-227
@@ -1,227 +0,0 @@
|
||||
import type { FirefliesMeetingData, MeetingCreateInput } from './types';
|
||||
|
||||
export class MeetingFormatter {
|
||||
static formatNoteBody(meetingData: FirefliesMeetingData): string {
|
||||
const meetingDate = new Date(meetingData.date);
|
||||
const formattedDate = meetingDate.toLocaleString('en-US', {
|
||||
weekday: 'long',
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
});
|
||||
const durationMinutes = Math.round(meetingData.duration);
|
||||
|
||||
let noteBody = `**Date:** ${formattedDate}\n`;
|
||||
noteBody += `**Duration:** ${durationMinutes} minutes\n`;
|
||||
|
||||
if (meetingData.participants.length > 0) {
|
||||
const participantNames = meetingData.participants.map(p => p.name).join(', ');
|
||||
noteBody += `**Participants:** ${participantNames}\n`;
|
||||
}
|
||||
|
||||
// Overview section
|
||||
if (meetingData.summary?.overview) {
|
||||
noteBody += `\n## Overview\n${meetingData.summary.overview}\n`;
|
||||
}
|
||||
|
||||
// Key topics
|
||||
if (meetingData.summary?.topics_discussed && Array.isArray(meetingData.summary.topics_discussed) && meetingData.summary.topics_discussed.length > 0) {
|
||||
noteBody += `\n## Key Topics\n`;
|
||||
meetingData.summary.topics_discussed.forEach(topic => {
|
||||
noteBody += `- ${topic}\n`;
|
||||
});
|
||||
}
|
||||
|
||||
// Action items
|
||||
if (meetingData.summary?.action_items && Array.isArray(meetingData.summary.action_items) && meetingData.summary.action_items.length > 0) {
|
||||
noteBody += `\n## Action Items\n`;
|
||||
meetingData.summary.action_items.forEach(item => {
|
||||
noteBody += `- ${item}\n`;
|
||||
});
|
||||
}
|
||||
|
||||
// Insights section
|
||||
noteBody += `\n## Insights\n`;
|
||||
|
||||
if (meetingData.summary?.keywords && Array.isArray(meetingData.summary.keywords) && meetingData.summary.keywords.length > 0) {
|
||||
noteBody += `**Keywords:** ${meetingData.summary.keywords.join(', ')}\n`;
|
||||
}
|
||||
|
||||
if (meetingData.analytics?.sentiments) {
|
||||
const sentiments = meetingData.analytics.sentiments;
|
||||
noteBody += `**Sentiment:** ${sentiments.positive_pct}% positive, ${sentiments.negative_pct}% negative, ${sentiments.neutral_pct}% neutral\n`;
|
||||
}
|
||||
|
||||
if (meetingData.summary?.meeting_type) {
|
||||
noteBody += `**Meeting Type:** ${meetingData.summary.meeting_type}\n`;
|
||||
}
|
||||
|
||||
// Resources section
|
||||
noteBody += `\n## Resources\n`;
|
||||
noteBody += `[View Full Transcript](${meetingData.transcript_url})\n`;
|
||||
|
||||
if (meetingData.recording_url) {
|
||||
noteBody += `[Watch Recording](${meetingData.recording_url})\n`;
|
||||
}
|
||||
|
||||
return noteBody;
|
||||
}
|
||||
|
||||
static formatMeetingNotes(meetingData: FirefliesMeetingData): string {
|
||||
const meetingDate = new Date(meetingData.date);
|
||||
const formattedDate = meetingDate.toLocaleString('en-US', {
|
||||
weekday: 'long',
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
});
|
||||
const durationMinutes = Math.round(meetingData.duration);
|
||||
|
||||
let meetingNotes = `**Date:** ${formattedDate}\n`;
|
||||
meetingNotes += `**Duration:** ${durationMinutes} minutes\n`;
|
||||
|
||||
if (meetingData.participants.length > 0) {
|
||||
const participantNames = meetingData.participants.map(p => p.name).join(', ');
|
||||
meetingNotes += `**Participants:** ${participantNames}\n`;
|
||||
}
|
||||
|
||||
// Overview section
|
||||
if (meetingData.summary?.overview) {
|
||||
meetingNotes += `\n## Overview\n${meetingData.summary.overview}\n`;
|
||||
}
|
||||
|
||||
// Key topics
|
||||
if (meetingData.summary?.topics_discussed && Array.isArray(meetingData.summary.topics_discussed) && meetingData.summary.topics_discussed.length > 0) {
|
||||
meetingNotes += `\n## Key Topics\n`;
|
||||
meetingData.summary.topics_discussed.forEach(topic => {
|
||||
meetingNotes += `- ${topic}\n`;
|
||||
});
|
||||
}
|
||||
|
||||
// Action items
|
||||
if (meetingData.summary?.action_items && Array.isArray(meetingData.summary.action_items) && meetingData.summary.action_items.length > 0) {
|
||||
meetingNotes += `\n## Action Items\n`;
|
||||
meetingData.summary.action_items.forEach(item => {
|
||||
meetingNotes += `- ${item}\n`;
|
||||
});
|
||||
}
|
||||
|
||||
// Insights section
|
||||
meetingNotes += `\n## Insights\n`;
|
||||
|
||||
if (meetingData.summary?.keywords && Array.isArray(meetingData.summary.keywords) && meetingData.summary.keywords.length > 0) {
|
||||
meetingNotes += `**Keywords:** ${meetingData.summary.keywords.join(', ')}\n`;
|
||||
}
|
||||
|
||||
if (meetingData.analytics?.sentiments) {
|
||||
const sentiments = meetingData.analytics.sentiments;
|
||||
meetingNotes += `**Sentiment:** ${sentiments.positive_pct}% positive, ${sentiments.negative_pct}% negative, ${sentiments.neutral_pct}% neutral\n`;
|
||||
}
|
||||
|
||||
if (meetingData.summary?.meeting_type) {
|
||||
meetingNotes += `**Meeting Type:** ${meetingData.summary.meeting_type}\n`;
|
||||
}
|
||||
|
||||
// Resources section
|
||||
meetingNotes += `\n## Resources\n`;
|
||||
meetingNotes += `[View Full Transcript](${meetingData.transcript_url})\n`;
|
||||
|
||||
if (meetingData.recording_url) {
|
||||
meetingNotes += `[Watch Recording](${meetingData.recording_url})\n`;
|
||||
}
|
||||
|
||||
return meetingNotes;
|
||||
}
|
||||
|
||||
|
||||
static toMeetingCreateInput(
|
||||
meetingData: FirefliesMeetingData,
|
||||
noteId?: string
|
||||
): MeetingCreateInput {
|
||||
const durationMinutes = Math.round(meetingData.duration);
|
||||
|
||||
// Build input object with only defined values (omit null fields)
|
||||
const input: MeetingCreateInput = {
|
||||
name: meetingData.title,
|
||||
meetingDate: meetingData.date,
|
||||
duration: durationMinutes,
|
||||
actionItemsCount: meetingData.summary?.action_items?.length || 0,
|
||||
firefliesMeetingId: meetingData.id,
|
||||
};
|
||||
|
||||
// Add direct relationship to note if noteId is provided
|
||||
if (noteId) {
|
||||
input.noteId = noteId;
|
||||
}
|
||||
|
||||
// Only add optional fields if they have values
|
||||
if (meetingData.summary?.meeting_type) {
|
||||
input.meetingType = meetingData.summary.meeting_type;
|
||||
}
|
||||
|
||||
if (meetingData.summary?.keywords && Array.isArray(meetingData.summary.keywords) && meetingData.summary.keywords.length > 0) {
|
||||
input.keywords = meetingData.summary.keywords.join(', ');
|
||||
}
|
||||
|
||||
if (meetingData.analytics?.sentiments?.positive_pct) {
|
||||
input.sentimentScore = meetingData.analytics.sentiments.positive_pct / 100;
|
||||
input.positivePercent = meetingData.analytics.sentiments.positive_pct;
|
||||
}
|
||||
|
||||
if (meetingData.analytics?.sentiments?.negative_pct) {
|
||||
input.negativePercent = meetingData.analytics.sentiments.negative_pct;
|
||||
}
|
||||
|
||||
// Only add URLs if they are valid (not empty strings)
|
||||
if (meetingData.transcript_url && meetingData.transcript_url.trim()) {
|
||||
input.transcriptUrl = {
|
||||
primaryLinkUrl: meetingData.transcript_url,
|
||||
primaryLinkLabel: 'View Transcript'
|
||||
};
|
||||
}
|
||||
|
||||
if (meetingData.recording_url && meetingData.recording_url.trim()) {
|
||||
input.recordingUrl = {
|
||||
primaryLinkUrl: meetingData.recording_url,
|
||||
primaryLinkLabel: 'Watch Recording'
|
||||
};
|
||||
}
|
||||
|
||||
if (meetingData.organizer_email) {
|
||||
input.organizerEmail = meetingData.organizer_email;
|
||||
}
|
||||
|
||||
// Set success status and timestamps
|
||||
input.importStatus = 'SUCCESS';
|
||||
input.lastImportAttempt = new Date().toISOString();
|
||||
input.importAttempts = 1;
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
static toFailedMeetingCreateInput(
|
||||
meetingId: string,
|
||||
title: string,
|
||||
error: string,
|
||||
attempts: number = 1
|
||||
): MeetingCreateInput {
|
||||
const currentDate = new Date().toISOString();
|
||||
|
||||
return {
|
||||
name: title || `Failed Meeting Import - ${meetingId}`,
|
||||
meetingDate: currentDate,
|
||||
duration: 0,
|
||||
actionItemsCount: 0,
|
||||
firefliesMeetingId: meetingId,
|
||||
importStatus: 'FAILED',
|
||||
importError: error,
|
||||
lastImportAttempt: currentDate,
|
||||
importAttempts: attempts,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
+3
-2
@@ -1,4 +1,5 @@
|
||||
export { config, main } from './receive-fireflies-notes';
|
||||
// Serverless function entry point - re-exports from src/lib
|
||||
export { config, main } from '../../../src';
|
||||
export type {
|
||||
FirefliesMeetingData,
|
||||
FirefliesParticipant,
|
||||
@@ -6,5 +7,5 @@ export type {
|
||||
ProcessResult,
|
||||
SummaryFetchConfig,
|
||||
SummaryStrategy
|
||||
} from './types';
|
||||
} from '../../../src';
|
||||
|
||||
|
||||
-95
@@ -1,95 +0,0 @@
|
||||
export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'silent';
|
||||
|
||||
export interface LoggerConfig {
|
||||
logLevel: LogLevel;
|
||||
isTestEnvironment: boolean;
|
||||
}
|
||||
|
||||
const LOG_LEVELS: Record<LogLevel, number> = {
|
||||
debug: 0,
|
||||
info: 1,
|
||||
warn: 2,
|
||||
error: 3,
|
||||
silent: 4,
|
||||
};
|
||||
|
||||
/**
|
||||
* App-level Fireflies application logger with configurable log levels.
|
||||
*/
|
||||
export class AppLogger {
|
||||
private config: LoggerConfig;
|
||||
private context: string;
|
||||
|
||||
constructor(context: string) {
|
||||
this.context = context;
|
||||
this.config = {
|
||||
logLevel: this.parseLogLevel(process.env.LOG_LEVEL || 'error'),
|
||||
isTestEnvironment: process.env.NODE_ENV === 'test' || process.env.JEST_WORKER_ID !== undefined,
|
||||
};
|
||||
}
|
||||
|
||||
private parseLogLevel(level: string): LogLevel {
|
||||
const normalizedLevel = level.toLowerCase() as LogLevel;
|
||||
return Object.keys(LOG_LEVELS).includes(normalizedLevel) ? normalizedLevel : 'error';
|
||||
}
|
||||
|
||||
private shouldLog(level: LogLevel): boolean {
|
||||
return LOG_LEVELS[level] >= LOG_LEVELS[this.config.logLevel];
|
||||
}
|
||||
|
||||
/**
|
||||
* Log debug information (LOG_LEVEL=debug)
|
||||
*/
|
||||
debug(message: string, ...args: any[]): void {
|
||||
if (this.shouldLog('debug')) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`[${this.context}] ${message}`, ...args);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log informational messages (LOG_LEVEL=info or lower)
|
||||
*/
|
||||
info(message: string, ...args: any[]): void {
|
||||
if (this.shouldLog('info')) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`[${this.context}] ${message}`, ...args);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log warnings (LOG_LEVEL=warn or lower)
|
||||
*/
|
||||
warn(message: string, ...args: any[]): void {
|
||||
if (this.shouldLog('warn')) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(`[${this.context}] ${message}`, ...args);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log errors (LOG_LEVEL=error or lower)
|
||||
*/
|
||||
error(message: string, ...args: any[]): void {
|
||||
if (this.shouldLog('error')) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(`[${this.context}] ${message}`, ...args);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log critical errors that should ALWAYS be visible regardless of log level
|
||||
* Use sparingly - only for fatal errors, security issues, or data corruption
|
||||
*/
|
||||
critical(message: string, ...args: any[]): void {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(`[${this.context}] CRITICAL: ${message}`, ...args);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory function to create loggers with automatic context detection
|
||||
*/
|
||||
export const createLogger = (context: string): AppLogger => {
|
||||
return new AppLogger(context);
|
||||
};
|
||||
-130
@@ -1,130 +0,0 @@
|
||||
// Fireflies API Types
|
||||
export type FirefliesParticipant = {
|
||||
email: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type FirefliesWebhookPayload = {
|
||||
meetingId: string;
|
||||
eventType: string;
|
||||
clientReferenceId?: string;
|
||||
};
|
||||
|
||||
export type FirefliesMeetingData = {
|
||||
id: string;
|
||||
title: string;
|
||||
date: string;
|
||||
duration: number;
|
||||
participants: FirefliesParticipant[];
|
||||
organizer_email?: string;
|
||||
summary: {
|
||||
action_items: string[];
|
||||
keywords?: string[];
|
||||
overview: string;
|
||||
gist?: string;
|
||||
topics_discussed?: string[];
|
||||
meeting_type?: string;
|
||||
bullet_gist?: string;
|
||||
};
|
||||
analytics?: {
|
||||
sentiments?: {
|
||||
positive_pct: number;
|
||||
negative_pct: number;
|
||||
neutral_pct: number;
|
||||
};
|
||||
};
|
||||
transcript_url: string;
|
||||
recording_url?: string;
|
||||
summary_status?: string;
|
||||
};
|
||||
|
||||
// Configuration Types
|
||||
export type SummaryStrategy = 'immediate_only' | 'immediate_with_retry' | 'delayed_polling' | 'basic_only';
|
||||
|
||||
export type SummaryFetchConfig = {
|
||||
strategy: SummaryStrategy;
|
||||
retryAttempts: number;
|
||||
retryDelay: number;
|
||||
pollInterval: number;
|
||||
maxPolls: number;
|
||||
};
|
||||
|
||||
export type WebhookConfig = {
|
||||
secret: string;
|
||||
apiUrl: string;
|
||||
};
|
||||
|
||||
// Processing Result Types
|
||||
export type ProcessResult = {
|
||||
success: boolean;
|
||||
meetingId?: string;
|
||||
noteIds?: string[];
|
||||
newContacts?: string[];
|
||||
errors?: string[];
|
||||
debug?: string[];
|
||||
summaryReady?: boolean;
|
||||
summaryPending?: boolean;
|
||||
enhancementScheduled?: boolean;
|
||||
actionItemsCount?: number;
|
||||
sentimentScore?: number;
|
||||
meetingType?: string;
|
||||
keyTopics?: string[];
|
||||
};
|
||||
|
||||
// Twenty CRM Types
|
||||
export type GraphQLResponse<T> = {
|
||||
data: T;
|
||||
errors?: Array<{
|
||||
message?: string;
|
||||
extensions?: { code?: string }
|
||||
}>;
|
||||
};
|
||||
|
||||
export type IdNode = { id: string };
|
||||
|
||||
export type FindMeetingResponse = {
|
||||
meetings: { edges: Array<{ node: IdNode }> };
|
||||
};
|
||||
|
||||
export type FindPeopleResponse = {
|
||||
people: { edges: Array<{ node: { id: string; emails: { primaryEmail: string } } }> };
|
||||
};
|
||||
|
||||
export type CreatePersonResponse = {
|
||||
createPerson: { id: string }
|
||||
};
|
||||
|
||||
export type CreateNoteResponse = {
|
||||
createNote: { id: string }
|
||||
};
|
||||
|
||||
export type CreateMeetingResponse = {
|
||||
createMeeting: { id: string }
|
||||
};
|
||||
|
||||
export type Contact = {
|
||||
id: string;
|
||||
email: string;
|
||||
};
|
||||
|
||||
export type MeetingCreateInput = {
|
||||
name: string;
|
||||
noteId?: string | null; // This is the relation field
|
||||
meetingDate: string;
|
||||
duration: number;
|
||||
meetingType?: string | null;
|
||||
keywords?: string | null;
|
||||
sentimentScore?: number | null;
|
||||
positivePercent?: number | null;
|
||||
negativePercent?: number | null;
|
||||
actionItemsCount: number;
|
||||
transcriptUrl?: { primaryLinkUrl: string; primaryLinkLabel: string } | null;
|
||||
recordingUrl?: { primaryLinkUrl: string; primaryLinkLabel: string } | null;
|
||||
firefliesMeetingId: string;
|
||||
organizerEmail?: string | null;
|
||||
importStatus?: 'SUCCESS' | 'FAILED' | 'PENDING' | 'RETRYING' | null;
|
||||
importError?: string | null;
|
||||
lastImportAttempt?: string | null;
|
||||
importAttempts?: number | null;
|
||||
};
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
main,
|
||||
type FirefliesMeetingData,
|
||||
type FirefliesWebhookPayload,
|
||||
} from '../actions/receive-fireflies-notes';
|
||||
} from '../';
|
||||
|
||||
// Helper to generate HMAC signature
|
||||
const generateHMACSignature = (body: string, secret: string): string => {
|
||||
@@ -39,10 +39,12 @@ const mockFirefliesApiResponseWithSummary = {
|
||||
meeting_type: 'Sales Call',
|
||||
bullet_gist: '• Demonstrated core product features\n• Discussed enterprise pricing\n• Addressed integration questions',
|
||||
},
|
||||
sentiments: { // Note: Raw API has sentiments at top level, not in analytics
|
||||
positive_pct: 75,
|
||||
negative_pct: 10,
|
||||
neutral_pct: 15,
|
||||
analytics: {
|
||||
sentiments: {
|
||||
positive_pct: 75,
|
||||
negative_pct: 10,
|
||||
neutral_pct: 15,
|
||||
},
|
||||
},
|
||||
transcript_url: 'https://app.fireflies.ai/transcript/test-001',
|
||||
video_url: 'https://app.fireflies.ai/recording/test-001',
|
||||
@@ -81,7 +83,7 @@ const mockMeetingWithFullSummary: FirefliesMeetingData = {
|
||||
},
|
||||
},
|
||||
transcript_url: 'https://app.fireflies.ai/transcript/test-001',
|
||||
recording_url: 'https://app.fireflies.ai/recording/test-001',
|
||||
video_url: 'https://app.fireflies.ai/recording/test-001',
|
||||
summary_status: 'completed',
|
||||
};
|
||||
|
||||
@@ -404,7 +406,11 @@ describe('Fireflies Webhook Integration v2', () => {
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.summaryReady).toBe(true);
|
||||
expect(result.actionItemsCount).toBe(3);
|
||||
expect(result.sentimentScore).toBeCloseTo(0.75, 2); // Use toBeCloseTo for floating point comparison
|
||||
expect(result.sentimentAnalysis).toEqual({
|
||||
positive_pct: 75,
|
||||
negative_pct: 10,
|
||||
neutral_pct: 15,
|
||||
});
|
||||
expect(result.meetingType).toBe('Sales Call');
|
||||
expect(result.keyTopics).toEqual(['product features', 'pricing discussion', 'integration capabilities', 'support options']);
|
||||
});
|
||||
@@ -565,7 +571,7 @@ describe('Fireflies Webhook Integration v2', () => {
|
||||
|
||||
const createNoteMock = jest.fn();
|
||||
|
||||
global.fetch = jest.fn().mockImplementation((url: string, options?: any) => {
|
||||
global.fetch = jest.fn().mockImplementation((url: string, options?: RequestInit) => {
|
||||
if (url === 'https://api.fireflies.ai/graphql') {
|
||||
return Promise.resolve({
|
||||
ok: true,
|
||||
@@ -576,9 +582,9 @@ describe('Fireflies Webhook Integration v2', () => {
|
||||
}
|
||||
|
||||
// Twenty API
|
||||
const body = options?.body ? JSON.parse(options.body) : {};
|
||||
if (body.query?.includes('createNote')) {
|
||||
createNoteMock(body.variables);
|
||||
const requestBody = options?.body ? JSON.parse(options.body as string) : {};
|
||||
if (requestBody.query?.includes('createNote')) {
|
||||
createNoteMock(requestBody.variables);
|
||||
return Promise.resolve({
|
||||
ok: true,
|
||||
json: () => Promise.resolve({
|
||||
@@ -610,7 +616,7 @@ describe('Fireflies Webhook Integration v2', () => {
|
||||
expect(noteData.data.bodyV2.markdown).toContain('## Overview'); // Markdown header, not bold
|
||||
expect(noteData.data.bodyV2.markdown).toContain('## Action Items'); // Markdown header, not bold
|
||||
expect(noteData.data.bodyV2.markdown).toContain('**Sentiment:**'); // This is bold
|
||||
expect(noteData.data.bodyV2.markdown).toContain('[View Full Transcript]');
|
||||
expect(noteData.data.bodyV2.markdown).toContain('View Full Transcript on Fireflies');
|
||||
});
|
||||
|
||||
it('should create meeting records for multi-party meetings', async () => {
|
||||
@@ -624,7 +630,7 @@ describe('Fireflies Webhook Integration v2', () => {
|
||||
|
||||
const createMeetingMock = jest.fn();
|
||||
|
||||
global.fetch = jest.fn().mockImplementation((url: string, options?: any) => {
|
||||
global.fetch = jest.fn().mockImplementation((url: string, options?: RequestInit) => {
|
||||
if (url === 'https://api.fireflies.ai/graphql') {
|
||||
return Promise.resolve({
|
||||
ok: true,
|
||||
@@ -635,9 +641,9 @@ describe('Fireflies Webhook Integration v2', () => {
|
||||
}
|
||||
|
||||
// Twenty API
|
||||
const body = options?.body ? JSON.parse(options.body) : {};
|
||||
if (body.query?.includes('createMeeting')) {
|
||||
createMeetingMock(body.variables);
|
||||
const requestBody = options?.body ? JSON.parse(options.body as string) : {};
|
||||
if (requestBody.query?.includes('createMeeting')) {
|
||||
createMeetingMock(requestBody.variables);
|
||||
return Promise.resolve({
|
||||
ok: true,
|
||||
json: () => Promise.resolve({
|
||||
@@ -645,7 +651,7 @@ describe('Fireflies Webhook Integration v2', () => {
|
||||
}),
|
||||
});
|
||||
}
|
||||
if (body.query?.includes('createNote')) {
|
||||
if (requestBody.query?.includes('createNote')) {
|
||||
return Promise.resolve({
|
||||
ok: true,
|
||||
json: () => Promise.resolve({
|
||||
@@ -694,7 +700,7 @@ describe('Fireflies Webhook Integration v2', () => {
|
||||
});
|
||||
|
||||
it('should handle missing payload gracefully', async () => {
|
||||
const result = await main(null as any, {});
|
||||
const result = await main(null as unknown, {});
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.errors).toBeDefined();
|
||||
@@ -703,10 +709,10 @@ describe('Fireflies Webhook Integration v2', () => {
|
||||
it('should handle invalid payload structure', async () => {
|
||||
const invalidPayload = { invalid: 'data' };
|
||||
|
||||
const result = await main(invalidPayload as any, {});
|
||||
const result = await main(invalidPayload as unknown, {});
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.errors).toBeDefined();
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.errors).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
import { HistoricalImporter } from '../historical-importer';
|
||||
import type { FirefliesMeetingData, SummaryFetchConfig } from '../types';
|
||||
|
||||
const summaryConfig: SummaryFetchConfig = {
|
||||
strategy: 'immediate_with_retry',
|
||||
retryAttempts: 1,
|
||||
retryDelay: 0,
|
||||
pollInterval: 0,
|
||||
maxPolls: 0,
|
||||
};
|
||||
|
||||
const sampleMeeting: FirefliesMeetingData = {
|
||||
id: 'm-1',
|
||||
title: 'Sample',
|
||||
date: new Date().toISOString(),
|
||||
duration: 30,
|
||||
participants: [],
|
||||
summary: { action_items: [], overview: '' },
|
||||
transcript_url: 'https://example.com',
|
||||
};
|
||||
|
||||
describe('HistoricalImporter', () => {
|
||||
const buildImporter = () => {
|
||||
const firefliesClient = {
|
||||
listTranscripts: jest.fn(),
|
||||
fetchMeetingDataWithRetry: jest.fn(),
|
||||
} as unknown as jest.Mocked<any>;
|
||||
|
||||
const twentyService = {
|
||||
findMeetingByFirefliesId: jest.fn(),
|
||||
matchParticipantsToContacts: jest.fn(),
|
||||
createContactsForUnmatched: jest.fn(),
|
||||
createNoteOnly: jest.fn(),
|
||||
createMeeting: jest.fn(),
|
||||
createNoteTarget: jest.fn(),
|
||||
} as unknown as jest.Mocked<any>;
|
||||
|
||||
return { firefliesClient, twentyService };
|
||||
};
|
||||
|
||||
it('skips meetings that already exist by firefliesMeetingId', async () => {
|
||||
const { firefliesClient, twentyService } = buildImporter();
|
||||
|
||||
firefliesClient.listTranscripts.mockResolvedValue([{ id: 'existing' }]);
|
||||
twentyService.findMeetingByFirefliesId.mockResolvedValue({ id: 'twenty-id' });
|
||||
|
||||
const importer = new HistoricalImporter(firefliesClient, twentyService);
|
||||
const result = await importer.run(
|
||||
{},
|
||||
{ dryRun: false, autoCreateContacts: true, summaryConfig, plan: 'free' },
|
||||
);
|
||||
|
||||
expect(result.skippedExisting).toBe(1);
|
||||
expect(result.imported).toBe(0);
|
||||
expect(twentyService.createMeeting).not.toHaveBeenCalled();
|
||||
expect(result.statuses[0].status).toBe('skipped_existing');
|
||||
});
|
||||
|
||||
it('supports dry-run without writing to Twenty', async () => {
|
||||
const { firefliesClient, twentyService } = buildImporter();
|
||||
|
||||
firefliesClient.listTranscripts.mockResolvedValue([{ id: 'm-2' }]);
|
||||
firefliesClient.fetchMeetingDataWithRetry.mockResolvedValue({
|
||||
data: sampleMeeting,
|
||||
summaryReady: false,
|
||||
});
|
||||
twentyService.findMeetingByFirefliesId.mockResolvedValue(undefined);
|
||||
|
||||
const importer = new HistoricalImporter(firefliesClient, twentyService);
|
||||
const result = await importer.run(
|
||||
{},
|
||||
{ dryRun: true, autoCreateContacts: false, summaryConfig, plan: 'free' },
|
||||
);
|
||||
|
||||
expect(result.imported).toBe(1);
|
||||
expect(result.summaryPending).toBe(1);
|
||||
expect(twentyService.createMeeting).not.toHaveBeenCalled();
|
||||
expect(twentyService.createNoteOnly).not.toHaveBeenCalled();
|
||||
expect(result.statuses).toHaveLength(1);
|
||||
expect(result.statuses[0].status).toBe('pending_summary');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ process.env.AUTO_CREATE_CONTACTS = 'true';
|
||||
process.env.SERVER_URL = 'http://localhost:3000';
|
||||
process.env.TWENTY_API_KEY = 'test-api-key';
|
||||
process.env.LOG_LEVEL = 'silent';
|
||||
process.env.CAPTURE_LOGS = 'false';
|
||||
|
||||
// Reset mocks before each test
|
||||
beforeEach(() => {
|
||||
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
import { WebhookHandler } from '../webhook-handler';
|
||||
|
||||
describe('WebhookHandler log capture', () => {
|
||||
const originalEnv = process.env;
|
||||
|
||||
beforeEach(() => {
|
||||
process.env = {
|
||||
...originalEnv,
|
||||
FIREFLIES_WEBHOOK_SECRET: 'testsecret',
|
||||
FIREFLIES_API_KEY: '',
|
||||
TWENTY_API_KEY: '',
|
||||
CAPTURE_LOGS: 'false',
|
||||
LOG_LEVEL: 'silent',
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
process.env = originalEnv;
|
||||
});
|
||||
|
||||
it('includes debug logs in response when CAPTURE_LOGS is true', async () => {
|
||||
process.env.CAPTURE_LOGS = 'true';
|
||||
|
||||
const handler = new WebhookHandler();
|
||||
const result = await handler.handle(null);
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(Array.isArray(result.debug)).toBe(true);
|
||||
});
|
||||
|
||||
it('omits debug logs when CAPTURE_LOGS is false', async () => {
|
||||
process.env.CAPTURE_LOGS = 'false';
|
||||
|
||||
const handler = new WebhookHandler();
|
||||
const result = await handler.handle(null);
|
||||
|
||||
expect(result.debug).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
export {
|
||||
config,
|
||||
main,
|
||||
type FirefliesMeetingData,
|
||||
type FirefliesParticipant,
|
||||
type FirefliesWebhookPayload,
|
||||
type ProcessResult,
|
||||
type SummaryFetchConfig,
|
||||
type SummaryStrategy
|
||||
} from '../../serverlessFunctions/receive-fireflies-notes/src/index';
|
||||
|
||||
@@ -0,0 +1,823 @@
|
||||
import { createLogger } from './logger';
|
||||
import {
|
||||
FIREFLIES_PLANS,
|
||||
type FirefliesMeetingData,
|
||||
type FirefliesParticipant,
|
||||
type FirefliesPlan,
|
||||
type FirefliesTranscriptListItem,
|
||||
type FirefliesTranscriptListOptions,
|
||||
type SummaryFetchConfig
|
||||
} from './types';
|
||||
|
||||
const logger = createLogger('fireflies-api');
|
||||
|
||||
export class FirefliesApiClient {
|
||||
private apiKey: string;
|
||||
|
||||
constructor(apiKey: string) {
|
||||
if (!apiKey) {
|
||||
logger.critical('FIREFLIES_API_KEY is required but not provided - this is a critical configuration error');
|
||||
throw new Error('FIREFLIES_API_KEY is required');
|
||||
}
|
||||
this.apiKey = apiKey;
|
||||
}
|
||||
|
||||
async listTranscripts(options: FirefliesTranscriptListOptions = {}): Promise<FirefliesTranscriptListItem[]> {
|
||||
const {
|
||||
organizers,
|
||||
participants,
|
||||
hostEmail,
|
||||
participantEmail,
|
||||
userId,
|
||||
channelId,
|
||||
mine,
|
||||
fromDate,
|
||||
toDate,
|
||||
pageSize = 50,
|
||||
maxRecords = 500,
|
||||
} = options;
|
||||
|
||||
const sanitizedOrganizers = organizers?.filter(Boolean);
|
||||
const sanitizedParticipants = participants?.filter(Boolean);
|
||||
|
||||
const transcripts: FirefliesTranscriptListItem[] = [];
|
||||
let skip = options.skip ?? 0;
|
||||
const limit = options.limit ?? pageSize;
|
||||
|
||||
const baseQuery = `
|
||||
query Transcripts(
|
||||
$limit: Int
|
||||
$skip: Int
|
||||
$hostEmail: String
|
||||
$participantEmail: String
|
||||
$organizers: [String!]
|
||||
$participants: [String!]
|
||||
$userId: String
|
||||
$channelId: String
|
||||
$mine: Boolean
|
||||
$date: Float
|
||||
) {
|
||||
transcripts(
|
||||
limit: $limit
|
||||
skip: $skip
|
||||
host_email: $hostEmail
|
||||
participant_email: $participantEmail
|
||||
organizers: $organizers
|
||||
participants: $participants
|
||||
user_id: $userId
|
||||
channel_id: $channelId
|
||||
mine: $mine
|
||||
date: $date
|
||||
) {
|
||||
id
|
||||
title
|
||||
date
|
||||
duration
|
||||
organizer_email
|
||||
participants
|
||||
transcript_url
|
||||
meeting_link
|
||||
meeting_info { summary_status }
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
while (transcripts.length < maxRecords) {
|
||||
const pageVariables = {
|
||||
limit,
|
||||
skip,
|
||||
hostEmail,
|
||||
participantEmail,
|
||||
organizers: sanitizedOrganizers,
|
||||
participants: sanitizedParticipants,
|
||||
userId,
|
||||
channelId,
|
||||
mine,
|
||||
date: fromDate,
|
||||
};
|
||||
|
||||
const page = await this.executeTranscriptListQuery(baseQuery, pageVariables);
|
||||
const normalized = page
|
||||
.map((item) => {
|
||||
const normalizedDate = this.normalizeDate(item.date);
|
||||
return {
|
||||
id: (item.id as string) || '',
|
||||
title: (item.title as string) || 'Untitled Meeting',
|
||||
date: normalizedDate,
|
||||
duration: (item.duration as number) || 0,
|
||||
organizer_email: item.organizer_email as string | undefined,
|
||||
participants: Array.isArray(item.participants)
|
||||
? (item.participants as string[])
|
||||
: undefined,
|
||||
transcript_url: item.transcript_url as string | undefined,
|
||||
meeting_link: item.meeting_link as string | undefined,
|
||||
summary_status: (item.meeting_info as { summary_status?: string } | undefined)?.summary_status,
|
||||
};
|
||||
})
|
||||
.filter((item) => {
|
||||
if (toDate && item.date) {
|
||||
const itemTime = Date.parse(item.date);
|
||||
if (!Number.isNaN(itemTime) && itemTime > toDate) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
transcripts.push(...normalized);
|
||||
|
||||
if (page.length < limit) {
|
||||
break;
|
||||
}
|
||||
|
||||
skip += limit;
|
||||
}
|
||||
|
||||
if (transcripts.length > maxRecords) {
|
||||
return transcripts.slice(0, maxRecords);
|
||||
}
|
||||
|
||||
return transcripts;
|
||||
}
|
||||
|
||||
async fetchMeetingData(
|
||||
meetingId: string,
|
||||
options?: { timeout?: number; plan?: FirefliesPlan }
|
||||
): Promise<FirefliesMeetingData> {
|
||||
const plan = options?.plan ?? FIREFLIES_PLANS.FREE;
|
||||
const isPremiumPlan =
|
||||
plan === FIREFLIES_PLANS.BUSINESS || plan === FIREFLIES_PLANS.ENTERPRISE;
|
||||
|
||||
// Minimal query for free plans - only basic fields available on all plans
|
||||
// Note: audio_url requires Pro+, video_url requires Business+
|
||||
const freeQuery = `
|
||||
query GetTranscriptMinimal($transcriptId: String!) {
|
||||
transcript(id: $transcriptId) {
|
||||
id
|
||||
title
|
||||
date
|
||||
duration
|
||||
participants
|
||||
organizer_email
|
||||
transcript_url
|
||||
meeting_link
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
// Standard query for pro plans - adds speakers, summary, sentences, and audio_url (Pro+)
|
||||
// Note: video_url requires Business+
|
||||
const proQuery = `
|
||||
query GetTranscriptBasic($transcriptId: String!) {
|
||||
transcript(id: $transcriptId) {
|
||||
id
|
||||
title
|
||||
date
|
||||
duration
|
||||
participants
|
||||
organizer_email
|
||||
speakers {
|
||||
name
|
||||
}
|
||||
sentences {
|
||||
index
|
||||
speaker_name
|
||||
text
|
||||
start_time
|
||||
end_time
|
||||
}
|
||||
summary {
|
||||
overview
|
||||
keywords
|
||||
action_items
|
||||
notes
|
||||
gist
|
||||
bullet_gist
|
||||
short_summary
|
||||
short_overview
|
||||
outline
|
||||
shorthand_bullet
|
||||
}
|
||||
meeting_info {
|
||||
summary_status
|
||||
}
|
||||
transcript_url
|
||||
audio_url
|
||||
meeting_link
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
// Full query for business/enterprise - includes all fields
|
||||
const businessQuery = `
|
||||
query GetTranscriptFull($transcriptId: String!) {
|
||||
transcript(id: $transcriptId) {
|
||||
id
|
||||
title
|
||||
date
|
||||
duration
|
||||
participants
|
||||
organizer_email
|
||||
analytics {
|
||||
sentiments {
|
||||
positive_pct
|
||||
negative_pct
|
||||
neutral_pct
|
||||
}
|
||||
categories {
|
||||
questions
|
||||
tasks
|
||||
metrics
|
||||
date_times
|
||||
}
|
||||
speakers {
|
||||
speaker_id
|
||||
name
|
||||
duration
|
||||
word_count
|
||||
longest_monologue
|
||||
filler_words
|
||||
questions
|
||||
words_per_minute
|
||||
}
|
||||
}
|
||||
meeting_attendees {
|
||||
displayName
|
||||
email
|
||||
phoneNumber
|
||||
name
|
||||
location
|
||||
}
|
||||
meeting_attendance {
|
||||
name
|
||||
join_time
|
||||
leave_time
|
||||
}
|
||||
speakers {
|
||||
name
|
||||
}
|
||||
sentences {
|
||||
index
|
||||
speaker_name
|
||||
text
|
||||
start_time
|
||||
end_time
|
||||
ai_filters {
|
||||
task
|
||||
question
|
||||
sentiment
|
||||
}
|
||||
}
|
||||
summary {
|
||||
action_items
|
||||
overview
|
||||
keywords
|
||||
notes
|
||||
gist
|
||||
bullet_gist
|
||||
short_summary
|
||||
short_overview
|
||||
outline
|
||||
shorthand_bullet
|
||||
topics_discussed
|
||||
meeting_type
|
||||
transcript_chapters
|
||||
}
|
||||
meeting_info {
|
||||
summary_status
|
||||
}
|
||||
transcript_url
|
||||
audio_url
|
||||
video_url
|
||||
meeting_link
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
// Select query based on plan
|
||||
const queryToUse = isPremiumPlan ? businessQuery :
|
||||
(plan === FIREFLIES_PLANS.PRO ? proQuery : freeQuery);
|
||||
|
||||
const planFeatures = {
|
||||
[FIREFLIES_PLANS.FREE]: 'basic fields only (no summary, no audio/video)',
|
||||
[FIREFLIES_PLANS.PRO]: 'summary, speakers, audio_url',
|
||||
[FIREFLIES_PLANS.BUSINESS]: 'full access including analytics, video_url',
|
||||
[FIREFLIES_PLANS.ENTERPRISE]: 'full access including analytics, video_url',
|
||||
};
|
||||
logger.debug(`using ${plan} plan query (${planFeatures[plan]})`);
|
||||
|
||||
try {
|
||||
return await this.executeTranscriptQuery({
|
||||
meetingId,
|
||||
query: queryToUse,
|
||||
timeout: options?.timeout,
|
||||
});
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
|
||||
// Detect plan-specific errors
|
||||
const requiresBusiness = message.toLowerCase().includes('business or higher');
|
||||
const requiresPro = message.toLowerCase().includes('pro or higher');
|
||||
const planError = requiresBusiness || requiresPro ||
|
||||
message.toLowerCase().includes('higher plan') ||
|
||||
message.includes('Cannot query field');
|
||||
|
||||
// Fallback cascade: business -> pro -> free
|
||||
if (planError) {
|
||||
if (isPremiumPlan) {
|
||||
logger.warn(`Plan limitation detected (configured: ${plan}), falling back to pro query`);
|
||||
try {
|
||||
return await this.executeTranscriptQuery({
|
||||
meetingId,
|
||||
query: proQuery,
|
||||
timeout: options?.timeout,
|
||||
});
|
||||
} catch (proError) {
|
||||
const proMessage = proError instanceof Error ? proError.message : String(proError);
|
||||
if (proMessage.toLowerCase().includes('plan') || proMessage.includes('Cannot query field')) {
|
||||
logger.warn('Pro query also failed, falling back to minimal free query');
|
||||
return this.executeTranscriptQuery({
|
||||
meetingId,
|
||||
query: freeQuery,
|
||||
timeout: options?.timeout,
|
||||
});
|
||||
}
|
||||
throw proError;
|
||||
}
|
||||
} else if (plan === FIREFLIES_PLANS.PRO) {
|
||||
logger.warn(`Pro plan query failed (${requiresBusiness ? 'requires Business+' : 'unknown restriction'}), falling back to free query`);
|
||||
return this.executeTranscriptQuery({
|
||||
meetingId,
|
||||
query: freeQuery,
|
||||
timeout: options?.timeout,
|
||||
});
|
||||
} else {
|
||||
// Already using free query - some field might still be restricted
|
||||
logger.error(
|
||||
'Fireflies API rejected the minimal free query. This may indicate: ' +
|
||||
'1) The transcript ID is invalid, or ' +
|
||||
'2) Your API key does not have access to this transcript, or ' +
|
||||
'3) An unexpected API restriction : open an issue'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async fetchMeetingDataWithRetry(
|
||||
meetingId: string,
|
||||
config: SummaryFetchConfig,
|
||||
plan: FirefliesPlan = FIREFLIES_PLANS.FREE
|
||||
): Promise<{ data: FirefliesMeetingData; summaryReady: boolean }> {
|
||||
// immediate_only: single attempt, no retries
|
||||
if (config.strategy === 'immediate_only') {
|
||||
logger.debug(`fetching meeting ${meetingId} (strategy: immediate_only)`);
|
||||
const meetingData = await this.fetchMeetingData(meetingId, { timeout: 10000, plan });
|
||||
const ready = this.isSummaryReady(meetingData);
|
||||
logger.debug(`summary ready: ${ready}`);
|
||||
return { data: meetingData, summaryReady: ready };
|
||||
}
|
||||
|
||||
// immediate_with_retry: retry with linear backoff
|
||||
logger.debug(`fetching meeting ${meetingId} (strategy: immediate_with_retry, maxAttempts: ${config.retryAttempts})`);
|
||||
|
||||
for (let attempt = 1; attempt <= config.retryAttempts; attempt++) {
|
||||
try {
|
||||
const meetingData = await this.fetchMeetingData(meetingId, { timeout: 10000, plan });
|
||||
const ready = this.isSummaryReady(meetingData);
|
||||
|
||||
logger.debug(`attempt ${attempt}/${config.retryAttempts}: summary ready=${ready}`);
|
||||
|
||||
if (ready) {
|
||||
return { data: meetingData, summaryReady: true };
|
||||
}
|
||||
|
||||
if (attempt < config.retryAttempts) {
|
||||
const delayMs = config.retryDelay * attempt;
|
||||
logger.debug(`summary not ready, waiting ${delayMs}ms before retry ${attempt + 1}`);
|
||||
await new Promise(resolve => setTimeout(resolve, delayMs));
|
||||
} else {
|
||||
logger.debug(`max retries reached, returning partial data`);
|
||||
return { data: meetingData, summaryReady: false };
|
||||
}
|
||||
} catch (error) {
|
||||
const errorMsg = error instanceof Error ? error.message : String(error);
|
||||
logger.error(`attempt ${attempt}/${config.retryAttempts} failed: ${errorMsg}`);
|
||||
|
||||
if (attempt === config.retryAttempts) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
const delayMs = config.retryDelay * attempt;
|
||||
logger.debug(`retrying in ${delayMs}ms...`);
|
||||
await new Promise(resolve => setTimeout(resolve, delayMs));
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('Failed to fetch meeting data after retries');
|
||||
}
|
||||
|
||||
private async executeTranscriptQuery({
|
||||
meetingId,
|
||||
query,
|
||||
timeout,
|
||||
}: {
|
||||
meetingId: string;
|
||||
query: string;
|
||||
timeout?: number;
|
||||
}): Promise<FirefliesMeetingData> {
|
||||
const controller = new AbortController();
|
||||
const timeoutId = timeout ? setTimeout(() => controller.abort(), timeout) : null;
|
||||
|
||||
try {
|
||||
const response = await fetch('https://api.fireflies.ai/graphql', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${this.apiKey}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
query,
|
||||
variables: { transcriptId: meetingId },
|
||||
}),
|
||||
signal: controller.signal,
|
||||
});
|
||||
|
||||
if (timeoutId) clearTimeout(timeoutId);
|
||||
|
||||
if (!response.ok) {
|
||||
let errorDetails = `Fireflies API request failed with status ${response.status}`;
|
||||
try {
|
||||
const errorBody = await response.text();
|
||||
if (errorBody) {
|
||||
errorDetails += `: ${errorBody}`;
|
||||
}
|
||||
} catch {
|
||||
// Ignore if we can't read the response body
|
||||
}
|
||||
throw new Error(errorDetails);
|
||||
}
|
||||
|
||||
const json = await response.json() as {
|
||||
data?: { transcript?: Record<string, unknown> };
|
||||
errors?: Array<{ message?: string }>;
|
||||
};
|
||||
|
||||
if (json.errors && json.errors.length > 0) {
|
||||
throw new Error(`Fireflies API error: ${json.errors[0]?.message || 'Unknown error'}`);
|
||||
}
|
||||
|
||||
const transcript = json.data?.transcript;
|
||||
if (!transcript) {
|
||||
throw new Error('Invalid response from Fireflies API: missing transcript data');
|
||||
}
|
||||
|
||||
return this.transformMeetingData(transcript, meetingId);
|
||||
} finally {
|
||||
if (timeoutId) clearTimeout(timeoutId);
|
||||
}
|
||||
}
|
||||
|
||||
private isSummaryReady(meetingData: FirefliesMeetingData): boolean {
|
||||
return (
|
||||
(meetingData.summary?.action_items?.length > 0) ||
|
||||
(meetingData.summary?.overview?.length > 0) ||
|
||||
meetingData.summary_status === 'completed'
|
||||
);
|
||||
}
|
||||
|
||||
private extractAllParticipants(transcript: Record<string, unknown>): FirefliesParticipant[] {
|
||||
const participantsWithEmails: FirefliesParticipant[] = [];
|
||||
const participantsNameOnly: FirefliesParticipant[] = [];
|
||||
|
||||
logger.debug('=== PARTICIPANT EXTRACTION DEBUG ===');
|
||||
logger.debug('participants field:', JSON.stringify(transcript.participants));
|
||||
logger.debug('meeting_attendees field:', JSON.stringify(transcript.meeting_attendees));
|
||||
logger.debug('speakers field:', (transcript.speakers as Array<{ name: string }>)?.map((s) => s.name));
|
||||
logger.debug('meeting_attendance field:', (transcript.meeting_attendance as Array<{ name: string }>)?.map((a) => a.name));
|
||||
logger.debug('organizer_email:', transcript.organizer_email);
|
||||
|
||||
const isEmail = (str: string): boolean => {
|
||||
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(str.trim());
|
||||
};
|
||||
|
||||
const isDuplicate = (name: string, email: string): boolean => {
|
||||
const nameLower = name.toLowerCase().trim();
|
||||
const emailLower = email.toLowerCase().trim();
|
||||
|
||||
return participantsWithEmails.some(p =>
|
||||
p.name.toLowerCase().trim() === nameLower ||
|
||||
(email && p.email.toLowerCase() === emailLower)
|
||||
) || participantsNameOnly.some(p =>
|
||||
p.name.toLowerCase().trim() === nameLower
|
||||
);
|
||||
};
|
||||
|
||||
// 1. Extract from legacy participants field (with emails)
|
||||
if (transcript.participants && Array.isArray(transcript.participants)) {
|
||||
transcript.participants.forEach((participant: string) => {
|
||||
const parts = participant.split(',').map(p => p.trim());
|
||||
|
||||
parts.forEach(part => {
|
||||
const emailMatch = part.match(/<([^>]+)>/);
|
||||
const email = emailMatch ? emailMatch[1] : '';
|
||||
const name = emailMatch
|
||||
? part.substring(0, part.indexOf('<')).trim()
|
||||
: part.trim();
|
||||
|
||||
if (isEmail(name)) {
|
||||
logger.debug(`Skipping participant with email as name: "${name}"`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!name) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isDuplicate(name, email)) {
|
||||
logger.debug(`Skipping duplicate participant: "${name}" <${email}>`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (name && email) {
|
||||
participantsWithEmails.push({ name, email });
|
||||
} else if (name) {
|
||||
participantsNameOnly.push({ name, email: '' });
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 2. Extract from meeting_attendees field (structured)
|
||||
if (transcript.meeting_attendees && Array.isArray(transcript.meeting_attendees)) {
|
||||
transcript.meeting_attendees.forEach((attendee: Record<string, unknown>) => {
|
||||
const name = (attendee.displayName || attendee.name || '') as string;
|
||||
const email = (attendee.email || '') as string;
|
||||
|
||||
if (isEmail(name)) {
|
||||
logger.debug(`Skipping attendee with email as name: "${name}"`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (name && !isDuplicate(name, email)) {
|
||||
if (email) {
|
||||
participantsWithEmails.push({ name, email });
|
||||
} else {
|
||||
participantsNameOnly.push({ name, email: '' });
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 3. Extract from speakers field (name only)
|
||||
if (transcript.speakers && Array.isArray(transcript.speakers)) {
|
||||
transcript.speakers.forEach((speaker: Record<string, unknown>) => {
|
||||
const name = (speaker.name || '') as string;
|
||||
|
||||
if (isEmail(name)) {
|
||||
logger.debug(`Skipping speaker with email as name: "${name}"`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (name && !isDuplicate(name, '')) {
|
||||
participantsNameOnly.push({ name, email: '' });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 4. Extract from meeting_attendance field (name only)
|
||||
if (transcript.meeting_attendance && Array.isArray(transcript.meeting_attendance)) {
|
||||
transcript.meeting_attendance.forEach((attendance: Record<string, unknown>) => {
|
||||
const name = (attendance.name || '') as string;
|
||||
|
||||
if (isEmail(name) || name.includes(',')) {
|
||||
logger.debug(`Skipping attendance with email/list as name: "${name}"`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (name && !isDuplicate(name, '')) {
|
||||
participantsNameOnly.push({ name, email: '' });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 5. Add organizer email if available and not already included
|
||||
const organizerEmail = transcript.organizer_email as string | undefined;
|
||||
if (organizerEmail) {
|
||||
const existsWithEmail = participantsWithEmails.some(p =>
|
||||
p.email.toLowerCase() === organizerEmail.toLowerCase()
|
||||
);
|
||||
|
||||
if (!existsWithEmail) {
|
||||
let organizerName = '';
|
||||
|
||||
const emailUsername = organizerEmail.split('@')[0].toLowerCase();
|
||||
const emailNameVariations = [emailUsername];
|
||||
|
||||
if (transcript.speakers && Array.isArray(transcript.speakers)) {
|
||||
const potentialOrganizerSpeaker = transcript.speakers.find((speaker: Record<string, unknown>) => {
|
||||
const name = ((speaker.name || '') as string).toLowerCase();
|
||||
return emailNameVariations.some(variation =>
|
||||
name.includes(variation) || variation.includes(name)
|
||||
);
|
||||
}) as Record<string, unknown> | undefined;
|
||||
if (potentialOrganizerSpeaker) {
|
||||
organizerName = potentialOrganizerSpeaker.name as string;
|
||||
}
|
||||
}
|
||||
|
||||
if (!organizerName && transcript.meeting_attendance && Array.isArray(transcript.meeting_attendance)) {
|
||||
const potentialOrganizerAttendance = transcript.meeting_attendance.find((attendance: Record<string, unknown>) => {
|
||||
const name = ((attendance.name || '') as string).toLowerCase();
|
||||
return emailNameVariations.some(variation =>
|
||||
name.includes(variation) || variation.includes(name)
|
||||
);
|
||||
}) as Record<string, unknown> | undefined;
|
||||
if (potentialOrganizerAttendance) {
|
||||
organizerName = potentialOrganizerAttendance.name as string;
|
||||
}
|
||||
}
|
||||
|
||||
if (organizerName) {
|
||||
participantsWithEmails.push({ name: organizerName, email: organizerEmail });
|
||||
|
||||
const nameIndex = participantsNameOnly.findIndex(p =>
|
||||
p.name.toLowerCase().includes(organizerName.toLowerCase()) ||
|
||||
organizerName.toLowerCase().includes(p.name.toLowerCase())
|
||||
);
|
||||
if (nameIndex !== -1) {
|
||||
participantsNameOnly.splice(nameIndex, 1);
|
||||
}
|
||||
} else {
|
||||
participantsWithEmails.push({ name: 'Meeting Organizer', email: organizerEmail });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const allParticipants = [...participantsWithEmails, ...participantsNameOnly];
|
||||
|
||||
logger.debug('=== EXTRACTED PARTICIPANTS ===');
|
||||
logger.debug('With emails:', participantsWithEmails.length, JSON.stringify(participantsWithEmails));
|
||||
logger.debug('Name only:', participantsNameOnly.length, JSON.stringify(participantsNameOnly));
|
||||
logger.debug('Total:', allParticipants.length);
|
||||
|
||||
return allParticipants;
|
||||
}
|
||||
|
||||
private transformMeetingData(transcript: Record<string, unknown>, meetingId: string): FirefliesMeetingData {
|
||||
let dateString: string;
|
||||
if (transcript.date) {
|
||||
if (typeof transcript.date === 'number') {
|
||||
dateString = new Date(transcript.date).toISOString();
|
||||
} else if (typeof transcript.date === 'string') {
|
||||
const parsed = Number(transcript.date);
|
||||
if (!isNaN(parsed)) {
|
||||
dateString = new Date(parsed).toISOString();
|
||||
} else {
|
||||
dateString = transcript.date;
|
||||
}
|
||||
} else {
|
||||
dateString = new Date().toISOString();
|
||||
}
|
||||
} else {
|
||||
dateString = new Date().toISOString();
|
||||
}
|
||||
|
||||
const summary = transcript.summary as Record<string, unknown> | undefined;
|
||||
const analytics = transcript.analytics as Record<string, unknown> | undefined;
|
||||
const sentiments = analytics?.sentiments as Record<string, number> | undefined;
|
||||
const categories = analytics?.categories as Record<string, number> | undefined;
|
||||
const speakersAnalytics = analytics?.speakers as Array<Record<string, unknown>> | undefined;
|
||||
const meetingInfo = transcript.meeting_info as Record<string, unknown> | undefined;
|
||||
|
||||
// Transform sentences array
|
||||
const rawSentences = transcript.sentences as Array<Record<string, unknown>> | undefined;
|
||||
const sentences = rawSentences?.map(s => ({
|
||||
index: (s.index as number) || 0,
|
||||
speaker_name: (s.speaker_name as string) || 'Unknown',
|
||||
text: (s.text as string) || '',
|
||||
start_time: (s.start_time as string) || '0',
|
||||
end_time: (s.end_time as string) || '0',
|
||||
ai_filters: s.ai_filters as { task?: boolean; question?: boolean; sentiment?: string } | undefined,
|
||||
}));
|
||||
|
||||
// Transform speaker analytics
|
||||
const speakers = speakersAnalytics?.map(sp => ({
|
||||
speaker_id: (sp.speaker_id as string) || '',
|
||||
name: (sp.name as string) || 'Unknown',
|
||||
duration: (sp.duration as number) || 0,
|
||||
word_count: (sp.word_count as number) || 0,
|
||||
longest_monologue: (sp.longest_monologue as number) || 0,
|
||||
filler_words: (sp.filler_words as number) || 0,
|
||||
questions: (sp.questions as number) || 0,
|
||||
words_per_minute: (sp.words_per_minute as number) || 0,
|
||||
}));
|
||||
|
||||
return {
|
||||
id: (transcript.id as string) || meetingId,
|
||||
title: (transcript.title as string) || 'Untitled Meeting',
|
||||
date: dateString,
|
||||
duration: (transcript.duration as number) || 0,
|
||||
participants: this.extractAllParticipants(transcript),
|
||||
organizer_email: transcript.organizer_email as string | undefined,
|
||||
sentences,
|
||||
summary: {
|
||||
// action_items can be string or array - normalize to array
|
||||
action_items: Array.isArray(summary?.action_items)
|
||||
? summary.action_items as string[]
|
||||
: (typeof summary?.action_items === 'string' && summary.action_items.trim()
|
||||
? summary.action_items.split('\n').filter((item: string) => item.trim())
|
||||
: []),
|
||||
overview: (summary?.overview as string) || '',
|
||||
notes: summary?.notes as string | undefined,
|
||||
gist: summary?.gist as string | undefined,
|
||||
bullet_gist: summary?.bullet_gist as string | undefined,
|
||||
short_summary: summary?.short_summary as string | undefined,
|
||||
short_overview: summary?.short_overview as string | undefined,
|
||||
outline: summary?.outline as string | undefined,
|
||||
shorthand_bullet: summary?.shorthand_bullet as string | undefined,
|
||||
keywords: summary?.keywords as string[] | undefined,
|
||||
topics_discussed: summary?.topics_discussed as string[] | undefined,
|
||||
meeting_type: summary?.meeting_type as string | undefined,
|
||||
transcript_chapters: summary?.transcript_chapters as string[] | undefined,
|
||||
},
|
||||
analytics: (sentiments || categories || speakers) ? {
|
||||
sentiments: sentiments ? {
|
||||
positive_pct: sentiments.positive_pct || 0,
|
||||
negative_pct: sentiments.negative_pct || 0,
|
||||
neutral_pct: sentiments.neutral_pct || 0,
|
||||
} : undefined,
|
||||
categories: categories ? {
|
||||
questions: categories.questions || 0,
|
||||
tasks: categories.tasks || 0,
|
||||
metrics: categories.metrics || 0,
|
||||
date_times: categories.date_times || 0,
|
||||
} : undefined,
|
||||
speakers,
|
||||
} : undefined,
|
||||
meeting_info: meetingInfo ? {
|
||||
summary_status: meetingInfo.summary_status as string | undefined,
|
||||
} : undefined,
|
||||
// URLs by plan availability:
|
||||
transcript_url: (transcript.transcript_url as string) || `https://app.fireflies.ai/view/${meetingId}`,
|
||||
audio_url: transcript.audio_url as string | undefined, // Pro+
|
||||
video_url: transcript.video_url as string | undefined, // Business+
|
||||
meeting_link: transcript.meeting_link as string | undefined, // All plans
|
||||
summary_status: (meetingInfo?.summary_status as string) || (transcript.summary_status as string) || undefined,
|
||||
};
|
||||
}
|
||||
|
||||
private async executeTranscriptListQuery(
|
||||
query: string,
|
||||
variables: Record<string, unknown>,
|
||||
): Promise<Array<Record<string, unknown>>> {
|
||||
const response = await fetch('https://api.fireflies.ai/graphql', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${this.apiKey}`,
|
||||
},
|
||||
body: JSON.stringify({ query, variables }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorBody = await response.text();
|
||||
throw new Error(`Fireflies transcripts request failed: ${response.status} ${errorBody}`);
|
||||
}
|
||||
|
||||
const json = await response.json() as {
|
||||
data?: { transcripts?: Array<Record<string, unknown>> };
|
||||
errors?: Array<{ message?: string }>;
|
||||
};
|
||||
|
||||
if (json.errors && json.errors.length > 0) {
|
||||
const message = json.errors[0]?.message || 'Unknown error';
|
||||
throw new Error(`Fireflies API error: ${message}`);
|
||||
}
|
||||
|
||||
return json.data?.transcripts ?? [];
|
||||
}
|
||||
|
||||
private normalizeDate(dateValue: unknown): string | undefined {
|
||||
if (!dateValue) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (typeof dateValue === 'number') {
|
||||
return new Date(dateValue).toISOString();
|
||||
}
|
||||
|
||||
if (typeof dateValue === 'string') {
|
||||
const parsed = Number(dateValue);
|
||||
if (!Number.isNaN(parsed)) {
|
||||
return new Date(parsed).toISOString();
|
||||
}
|
||||
return dateValue;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,259 @@
|
||||
import type { FirefliesMeetingData, FirefliesSentence, MeetingCreateInput } from './types';
|
||||
|
||||
export class MeetingFormatter {
|
||||
// Format timestamp from seconds to MM:SS
|
||||
private static formatTimestamp(timeStr: string): string {
|
||||
const seconds = parseFloat(timeStr);
|
||||
if (isNaN(seconds)) return '00:00';
|
||||
const mins = Math.floor(seconds / 60);
|
||||
const secs = Math.floor(seconds % 60);
|
||||
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
// Format full transcript from sentences
|
||||
private static formatTranscript(sentences: FirefliesSentence[]): string {
|
||||
if (!sentences || sentences.length === 0) return '';
|
||||
|
||||
let transcript = '';
|
||||
let currentSpeaker = '';
|
||||
|
||||
for (const sentence of sentences) {
|
||||
const timestamp = this.formatTimestamp(sentence.start_time);
|
||||
const speaker = sentence.speaker_name || 'Unknown';
|
||||
|
||||
// Add speaker header when speaker changes
|
||||
if (speaker !== currentSpeaker) {
|
||||
currentSpeaker = speaker;
|
||||
transcript += `\n**${speaker}** [${timestamp}]\n`;
|
||||
}
|
||||
|
||||
transcript += `${sentence.text} `;
|
||||
}
|
||||
|
||||
return transcript.trim();
|
||||
}
|
||||
|
||||
static formatNoteBody(meetingData: FirefliesMeetingData): string {
|
||||
const meetingDate = meetingData.date ? new Date(meetingData.date) : null;
|
||||
const hasValidDate = meetingDate instanceof Date && !Number.isNaN(meetingDate.getTime());
|
||||
const formattedDate = hasValidDate
|
||||
? meetingDate.toLocaleString('en-US', {
|
||||
weekday: 'long',
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
})
|
||||
: 'Unknown date';
|
||||
const durationMinutes = Math.round(meetingData.duration);
|
||||
|
||||
let noteBody = `**Date:** ${formattedDate}\n`;
|
||||
noteBody += `**Duration:** ${durationMinutes} minutes\n`;
|
||||
|
||||
if (meetingData.participants.length > 0) {
|
||||
const participantNames = meetingData.participants.map(p => p.name).join(', ');
|
||||
noteBody += `**Participants:** ${participantNames}\n`;
|
||||
}
|
||||
|
||||
// Overview section
|
||||
if (meetingData.summary?.overview) {
|
||||
noteBody += `\n## Overview\n${meetingData.summary.overview}\n`;
|
||||
}
|
||||
|
||||
// Detailed AI Notes (the rich content from Fireflies)
|
||||
if (meetingData.summary?.notes) {
|
||||
noteBody += `\n## Meeting Notes\n${meetingData.summary.notes}\n`;
|
||||
}
|
||||
|
||||
// Bullet gist with emojis (if available and different from notes)
|
||||
if (meetingData.summary?.bullet_gist && !meetingData.summary?.notes) {
|
||||
noteBody += `\n## Key Points\n${meetingData.summary.bullet_gist}\n`;
|
||||
}
|
||||
|
||||
// Meeting outline with timestamps (shorthand_bullet contains the timestamped outline)
|
||||
const outline = meetingData.summary?.outline || meetingData.summary?.shorthand_bullet;
|
||||
if (outline) {
|
||||
noteBody += `\n## Outline\n${outline}\n`;
|
||||
}
|
||||
|
||||
// Key topics
|
||||
if (meetingData.summary?.topics_discussed?.length) {
|
||||
noteBody += `\n## Key Topics\n`;
|
||||
meetingData.summary.topics_discussed.forEach(topic => {
|
||||
noteBody += `- ${topic}\n`;
|
||||
});
|
||||
}
|
||||
|
||||
// Action items
|
||||
if (meetingData.summary?.action_items?.length) {
|
||||
noteBody += `\n## Action Items\n`;
|
||||
meetingData.summary.action_items.forEach(item => {
|
||||
noteBody += `- [ ] ${item}\n`;
|
||||
});
|
||||
}
|
||||
|
||||
// Insights section
|
||||
noteBody += `\n## Insights\n`;
|
||||
|
||||
if (meetingData.summary?.keywords?.length) {
|
||||
noteBody += `**Keywords:** ${meetingData.summary.keywords.join(', ')}\n`;
|
||||
}
|
||||
|
||||
if (meetingData.analytics?.sentiments) {
|
||||
const sentiments = meetingData.analytics.sentiments;
|
||||
noteBody += `**Sentiment:** ${sentiments.positive_pct}% positive, ${sentiments.negative_pct}% negative, ${sentiments.neutral_pct}% neutral\n`;
|
||||
}
|
||||
|
||||
if (meetingData.summary?.meeting_type) {
|
||||
noteBody += `**Meeting Type:** ${meetingData.summary.meeting_type}\n`;
|
||||
}
|
||||
|
||||
// Speaker analytics (Business+)
|
||||
if (meetingData.analytics?.speakers?.length) {
|
||||
noteBody += `\n### Speaker Stats\n`;
|
||||
for (const speaker of meetingData.analytics.speakers) {
|
||||
const talkTime = Math.round(speaker.duration / 60);
|
||||
noteBody += `- **${speaker.name}**: ${talkTime} min talk time, ${speaker.word_count} words, ${speaker.questions} questions\n`;
|
||||
}
|
||||
}
|
||||
|
||||
// Meeting metrics (Business+)
|
||||
if (meetingData.analytics?.categories) {
|
||||
const cats = meetingData.analytics.categories;
|
||||
noteBody += `\n### Meeting Metrics\n`;
|
||||
noteBody += `- Questions asked: ${cats.questions}\n`;
|
||||
noteBody += `- Tasks identified: ${cats.tasks}\n`;
|
||||
if (cats.metrics > 0) noteBody += `- Metrics mentioned: ${cats.metrics}\n`;
|
||||
if (cats.date_times > 0) noteBody += `- Dates/times discussed: ${cats.date_times}\n`;
|
||||
}
|
||||
|
||||
// Resources section
|
||||
noteBody += `\n## Resources\n`;
|
||||
noteBody += `[View Full Transcript on Fireflies](${meetingData.transcript_url})\n`;
|
||||
|
||||
if (meetingData.video_url) {
|
||||
noteBody += `[Watch Video Recording](${meetingData.video_url})\n`;
|
||||
}
|
||||
|
||||
if (meetingData.audio_url) {
|
||||
noteBody += `[Listen to Audio](${meetingData.audio_url})\n`;
|
||||
}
|
||||
|
||||
if (meetingData.meeting_link) {
|
||||
noteBody += `[Original Meeting Link](${meetingData.meeting_link})\n`;
|
||||
}
|
||||
|
||||
return noteBody;
|
||||
}
|
||||
|
||||
static toMeetingCreateInput(
|
||||
meetingData: FirefliesMeetingData,
|
||||
noteId?: string
|
||||
): MeetingCreateInput {
|
||||
const durationMinutes = Math.round(meetingData.duration);
|
||||
const hasSummary = Boolean(meetingData.summary?.overview || meetingData.summary?.action_items?.length);
|
||||
const hasAnalytics = Boolean(meetingData.analytics?.sentiments);
|
||||
|
||||
// Build input object with only defined values (omit null fields)
|
||||
const input: MeetingCreateInput = {
|
||||
name: meetingData.title,
|
||||
meetingDate: meetingData.date,
|
||||
duration: durationMinutes,
|
||||
actionItemsCount: meetingData.summary?.action_items?.length || 0,
|
||||
firefliesMeetingId: meetingData.id,
|
||||
};
|
||||
|
||||
// Add direct relationship to note if noteId is provided
|
||||
if (noteId) {
|
||||
input.noteId = noteId;
|
||||
}
|
||||
|
||||
// Basic fields (All plans)
|
||||
if (meetingData.organizer_email) {
|
||||
input.organizerEmail = meetingData.organizer_email;
|
||||
}
|
||||
if (meetingData.transcript_url?.trim()) {
|
||||
input.transcriptUrl = {
|
||||
primaryLinkUrl: meetingData.transcript_url,
|
||||
primaryLinkLabel: 'View Transcript'
|
||||
};
|
||||
}
|
||||
if (meetingData.meeting_link?.trim()) {
|
||||
input.meetingLink = {
|
||||
primaryLinkUrl: meetingData.meeting_link,
|
||||
primaryLinkLabel: 'Join Meeting'
|
||||
};
|
||||
}
|
||||
|
||||
// Pro+ fields (transcript, summary, notes, keywords, audio)
|
||||
if (meetingData.sentences?.length) {
|
||||
input.transcript = this.formatTranscript(meetingData.sentences);
|
||||
}
|
||||
if (meetingData.summary?.overview) {
|
||||
input.overview = meetingData.summary.overview;
|
||||
}
|
||||
if (meetingData.summary?.notes) {
|
||||
input.notes = meetingData.summary.notes;
|
||||
}
|
||||
if (meetingData.summary?.keywords?.length) {
|
||||
input.keywords = meetingData.summary.keywords.join(', ');
|
||||
}
|
||||
if (meetingData.audio_url?.trim()) {
|
||||
input.audioUrl = {
|
||||
primaryLinkUrl: meetingData.audio_url,
|
||||
primaryLinkLabel: 'Listen to Audio'
|
||||
};
|
||||
}
|
||||
|
||||
// Business+ fields (analytics, video, detailed summary)
|
||||
if (meetingData.summary?.meeting_type) {
|
||||
input.meetingType = meetingData.summary.meeting_type;
|
||||
}
|
||||
if (meetingData.summary?.topics_discussed?.length) {
|
||||
input.topics = meetingData.summary.topics_discussed.join(', ');
|
||||
}
|
||||
if (meetingData.analytics?.sentiments) {
|
||||
const sentiments = meetingData.analytics.sentiments;
|
||||
input.positivePercent = sentiments.positive_pct;
|
||||
input.negativePercent = sentiments.negative_pct;
|
||||
input.neutralPercent = sentiments.neutral_pct;
|
||||
}
|
||||
if (meetingData.video_url?.trim()) {
|
||||
input.videoUrl = {
|
||||
primaryLinkUrl: meetingData.video_url,
|
||||
primaryLinkLabel: 'Watch Video'
|
||||
};
|
||||
}
|
||||
|
||||
// Import status based on data completeness
|
||||
const isPartial = !hasSummary && !hasAnalytics;
|
||||
input.importStatus = isPartial ? 'PARTIAL' : 'SUCCESS';
|
||||
input.lastImportAttempt = new Date().toISOString();
|
||||
input.importAttempts = 1;
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
static toFailedMeetingCreateInput(
|
||||
meetingId: string,
|
||||
title: string,
|
||||
error: string,
|
||||
attempts: number = 1
|
||||
): MeetingCreateInput {
|
||||
const currentDate = new Date().toISOString();
|
||||
|
||||
return {
|
||||
name: title || `Failed Meeting Import - ${meetingId}`,
|
||||
meetingDate: currentDate,
|
||||
duration: 0,
|
||||
actionItemsCount: 0,
|
||||
firefliesMeetingId: meetingId,
|
||||
importStatus: 'FAILED',
|
||||
importError: error,
|
||||
lastImportAttempt: currentDate,
|
||||
importAttempts: attempts,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
import { type FirefliesApiClient } from './fireflies-api-client';
|
||||
import { MeetingFormatter } from './formatters';
|
||||
import { createLogger } from './logger';
|
||||
import { type TwentyCrmService } from './twenty-crm-service';
|
||||
import type {
|
||||
FirefliesPlan,
|
||||
FirefliesTranscriptListOptions,
|
||||
SummaryFetchConfig,
|
||||
} from './types';
|
||||
|
||||
const logger = createLogger('historical-importer');
|
||||
|
||||
export type HistoricalImportFilters = FirefliesTranscriptListOptions;
|
||||
|
||||
export type HistoricalImportOptions = {
|
||||
dryRun?: boolean;
|
||||
autoCreateContacts: boolean;
|
||||
summaryConfig: SummaryFetchConfig;
|
||||
plan: FirefliesPlan;
|
||||
};
|
||||
|
||||
export type HistoricalImportResult = {
|
||||
dryRun: boolean;
|
||||
totalListed: number;
|
||||
imported: number;
|
||||
skippedExisting: number;
|
||||
failed: Array<{ meetingId: string; reason: string }>;
|
||||
summaryPending: number;
|
||||
statuses: Array<{
|
||||
meetingId: string;
|
||||
title?: string;
|
||||
status: 'imported' | 'skipped_existing' | 'failed' | 'dry_run' | 'pending_summary';
|
||||
reason?: string;
|
||||
}>;
|
||||
};
|
||||
|
||||
export class HistoricalImporter {
|
||||
private firefliesClient: FirefliesApiClient;
|
||||
private twentyService: TwentyCrmService;
|
||||
|
||||
constructor(firefliesClient: FirefliesApiClient, twentyService: TwentyCrmService) {
|
||||
this.firefliesClient = firefliesClient;
|
||||
this.twentyService = twentyService;
|
||||
}
|
||||
|
||||
async run(
|
||||
filters: HistoricalImportFilters,
|
||||
options: HistoricalImportOptions,
|
||||
): Promise<HistoricalImportResult> {
|
||||
const { dryRun = false, autoCreateContacts, summaryConfig, plan } = options;
|
||||
|
||||
logger.info('Listing Fireflies transcripts for historical import...');
|
||||
const transcripts = await this.firefliesClient.listTranscripts(filters);
|
||||
logger.info(`Found ${transcripts.length} transcript(s) to process`);
|
||||
|
||||
let imported = 0;
|
||||
let skippedExisting = 0;
|
||||
let summaryPending = 0;
|
||||
const failed: Array<{ meetingId: string; reason: string }> = [];
|
||||
const statuses: HistoricalImportResult['statuses'] = [];
|
||||
|
||||
for (const transcript of transcripts) {
|
||||
const meetingId = transcript.id;
|
||||
|
||||
try {
|
||||
const existing = await this.twentyService.findMeetingByFirefliesId(meetingId);
|
||||
if (existing) {
|
||||
logger.debug(`Skipping ${meetingId}: already exists in Twenty (${existing.id})`);
|
||||
skippedExisting += 1;
|
||||
statuses.push({
|
||||
meetingId,
|
||||
title: transcript.title,
|
||||
status: 'skipped_existing',
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
logger.info(`Fetching meeting ${meetingId} details`);
|
||||
const { data: meetingData, summaryReady } =
|
||||
await this.firefliesClient.fetchMeetingDataWithRetry(
|
||||
meetingId,
|
||||
summaryConfig,
|
||||
plan,
|
||||
);
|
||||
|
||||
const isPendingSummary = summaryReady === false;
|
||||
if (isPendingSummary) {
|
||||
summaryPending += 1;
|
||||
}
|
||||
|
||||
if (dryRun) {
|
||||
logger.info(`[dry-run] Would import meeting "${meetingData.title}" (${meetingId})`);
|
||||
imported += 1;
|
||||
statuses.push({
|
||||
meetingId,
|
||||
title: meetingData.title,
|
||||
status: isPendingSummary ? 'pending_summary' : 'dry_run',
|
||||
reason: isPendingSummary ? 'summary not ready' : undefined,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
const { matchedContacts, unmatchedParticipants } =
|
||||
await this.twentyService.matchParticipantsToContacts(
|
||||
meetingData.participants,
|
||||
);
|
||||
|
||||
const newContactIds = autoCreateContacts
|
||||
? await this.twentyService.createContactsForUnmatched(unmatchedParticipants)
|
||||
: [];
|
||||
const allContactIds = [...matchedContacts.map(({ id }) => id), ...newContactIds];
|
||||
|
||||
const noteBody = MeetingFormatter.formatNoteBody(meetingData);
|
||||
const noteId = await this.twentyService.createNoteOnly(
|
||||
`Meeting: ${meetingData.title}`,
|
||||
noteBody,
|
||||
);
|
||||
|
||||
const meetingInput = MeetingFormatter.toMeetingCreateInput(meetingData, noteId);
|
||||
const createdMeetingId = await this.twentyService.createMeeting(meetingInput);
|
||||
|
||||
for (const contactId of allContactIds) {
|
||||
await this.twentyService.createNoteTarget(noteId, contactId);
|
||||
}
|
||||
|
||||
logger.info(
|
||||
`Imported meeting "${meetingData.title}" (${meetingId}) as ${createdMeetingId}`,
|
||||
);
|
||||
imported += 1;
|
||||
statuses.push({
|
||||
meetingId,
|
||||
title: meetingData.title,
|
||||
status: isPendingSummary ? 'pending_summary' : 'imported',
|
||||
reason: isPendingSummary ? 'summary not ready' : undefined,
|
||||
});
|
||||
} catch (error) {
|
||||
const reason = error instanceof Error ? error.message : String(error);
|
||||
logger.error(`Failed to import meeting ${meetingId}: ${reason}`);
|
||||
failed.push({ meetingId, reason });
|
||||
statuses.push({
|
||||
meetingId,
|
||||
title: transcript.title,
|
||||
status: 'failed',
|
||||
reason,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
dryRun,
|
||||
totalListed: transcripts.length,
|
||||
imported,
|
||||
skippedExisting,
|
||||
failed,
|
||||
summaryPending,
|
||||
statuses,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
// Main exports for the Fireflies integration
|
||||
export { config, main } from './receive-fireflies-notes';
|
||||
|
||||
// Types
|
||||
export type {
|
||||
FirefliesMeetingData,
|
||||
FirefliesParticipant,
|
||||
FirefliesWebhookPayload,
|
||||
ProcessResult,
|
||||
SummaryFetchConfig,
|
||||
SummaryStrategy
|
||||
} from './types';
|
||||
|
||||
// Services (for advanced usage)
|
||||
export { FirefliesApiClient } from './fireflies-api-client';
|
||||
export { MeetingFormatter } from './formatters';
|
||||
export { TwentyCrmService } from './twenty-crm-service';
|
||||
export { WebhookHandler } from './webhook-handler';
|
||||
|
||||
// Objects
|
||||
export { Meeting } from './objects';
|
||||
|
||||
// Utilities
|
||||
export { createLogger } from './logger';
|
||||
export { getApiUrl, getSummaryFetchConfig, shouldAutoCreateContacts, toBoolean } from './utils';
|
||||
export {
|
||||
getWebhookSecretFingerprint,
|
||||
isValidFirefliesPayload,
|
||||
verifyWebhookSignature
|
||||
} from './webhook-validator';
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'silent';
|
||||
|
||||
export type LoggerConfig = {
|
||||
logLevel: LogLevel;
|
||||
isTestEnvironment: boolean;
|
||||
captureForResponse: boolean;
|
||||
};
|
||||
|
||||
const LOG_LEVELS: Record<LogLevel, number> = {
|
||||
debug: 0,
|
||||
info: 1,
|
||||
warn: 2,
|
||||
error: 3,
|
||||
silent: 4,
|
||||
};
|
||||
|
||||
const loggerRegistry = new Set<AppLogger>();
|
||||
|
||||
export class AppLogger {
|
||||
private config: LoggerConfig;
|
||||
private context: string;
|
||||
private capturedLogs: string[] = [];
|
||||
|
||||
constructor(context: string) {
|
||||
this.context = context;
|
||||
this.config = {
|
||||
logLevel: this.parseLogLevel(process.env.LOG_LEVEL || 'error'),
|
||||
isTestEnvironment: process.env.NODE_ENV === 'test' || process.env.JEST_WORKER_ID !== undefined,
|
||||
captureForResponse: process.env.CAPTURE_LOGS === 'true',
|
||||
};
|
||||
|
||||
// Silence logs in test environment unless explicitly overridden
|
||||
if (this.config.isTestEnvironment && process.env.LOG_LEVEL === undefined) {
|
||||
this.config.logLevel = 'silent';
|
||||
}
|
||||
}
|
||||
|
||||
private parseLogLevel(level: string): LogLevel {
|
||||
const normalizedLevel = level.toLowerCase() as LogLevel;
|
||||
return Object.keys(LOG_LEVELS).includes(normalizedLevel) ? normalizedLevel : 'error';
|
||||
}
|
||||
|
||||
private shouldLog(level: LogLevel): boolean {
|
||||
return LOG_LEVELS[level] >= LOG_LEVELS[this.config.logLevel];
|
||||
}
|
||||
|
||||
private safeStringify(value: unknown): string {
|
||||
try {
|
||||
if (typeof value === 'string') return value;
|
||||
return JSON.stringify(value);
|
||||
} catch {
|
||||
return '[unserializable]';
|
||||
}
|
||||
}
|
||||
|
||||
private captureLog(level: LogLevel, message: string, ...args: unknown[]): void {
|
||||
if (!this.config.captureForResponse) {
|
||||
return;
|
||||
}
|
||||
|
||||
const timestamp = new Date().toISOString();
|
||||
const formattedMessage =
|
||||
args.length > 0
|
||||
? `${message} ${args.map((arg) => this.safeStringify(arg)).join(' ')}`
|
||||
: message;
|
||||
|
||||
this.capturedLogs.push(
|
||||
`[${timestamp}] [${level.toUpperCase()}] [${this.context}] ${formattedMessage}`,
|
||||
);
|
||||
}
|
||||
|
||||
debug(message: string, ...args: unknown[]): void {
|
||||
this.captureLog('debug', message, ...args);
|
||||
if (this.shouldLog('debug')) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`[${this.context}] ${message}`, ...args);
|
||||
}
|
||||
}
|
||||
|
||||
info(message: string, ...args: unknown[]): void {
|
||||
this.captureLog('info', message, ...args);
|
||||
if (this.shouldLog('info')) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`[${this.context}] ${message}`, ...args);
|
||||
}
|
||||
}
|
||||
|
||||
warn(message: string, ...args: unknown[]): void {
|
||||
this.captureLog('warn', message, ...args);
|
||||
if (this.shouldLog('warn')) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(`[${this.context}] ${message}`, ...args);
|
||||
}
|
||||
}
|
||||
|
||||
error(message: string, ...args: unknown[]): void {
|
||||
this.captureLog('error', message, ...args);
|
||||
if (this.shouldLog('error')) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(`[${this.context}] ${message}`, ...args);
|
||||
}
|
||||
}
|
||||
|
||||
// For fatal errors, security issues, or data corruption - always visible
|
||||
critical(message: string, ...args: unknown[]): void {
|
||||
this.captureLog('error', `CRITICAL: ${message}`, ...args);
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(`[${this.context}] CRITICAL: ${message}`, ...args);
|
||||
}
|
||||
|
||||
getCapturedLogs(): string[] {
|
||||
return [...this.capturedLogs];
|
||||
}
|
||||
|
||||
clearCapturedLogs(): void {
|
||||
this.capturedLogs = [];
|
||||
}
|
||||
}
|
||||
|
||||
export const createLogger = (context: string): AppLogger => {
|
||||
const logger = new AppLogger(context);
|
||||
loggerRegistry.add(logger);
|
||||
return logger;
|
||||
};
|
||||
|
||||
export const removeLogger = (logger: AppLogger): void => {
|
||||
loggerRegistry.delete(logger);
|
||||
};
|
||||
|
||||
export const getAllCapturedLogs = (): string[] => {
|
||||
const allLogs: string[] = [];
|
||||
for (const logger of loggerRegistry) {
|
||||
allLogs.push(...logger.getCapturedLogs());
|
||||
}
|
||||
return allLogs.sort();
|
||||
};
|
||||
|
||||
export const clearAllCapturedLogs = (): void => {
|
||||
for (const logger of loggerRegistry) {
|
||||
logger.clearCapturedLogs();
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
export { Meeting } from './meeting';
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ObjectMetadata } from 'twenty-sdk/application';
|
||||
import { Object } from 'twenty-sdk';
|
||||
|
||||
@ObjectMetadata({
|
||||
@Object({
|
||||
universalIdentifier: 'd1831348-b4a4-4426-9c0b-0af19e7a9c27',
|
||||
nameSingular: 'meeting',
|
||||
namePlural: 'meetings',
|
||||
|
||||
+4
-3
@@ -1,4 +1,4 @@
|
||||
import { type ServerlessFunctionConfig } from 'twenty-sdk/application';
|
||||
import { type FunctionConfig } from 'twenty-sdk';
|
||||
import type { ProcessResult } from './types';
|
||||
import { WebhookHandler } from './webhook-handler';
|
||||
|
||||
@@ -10,7 +10,7 @@ export const main = async (
|
||||
return handler.handle(params, headers);
|
||||
};
|
||||
|
||||
export const config: ServerlessFunctionConfig = {
|
||||
export const config: FunctionConfig = {
|
||||
universalIdentifier: '2d3ea303-667c-4bbe-9e3d-db6ffb9d6c74',
|
||||
name: 'receive-fireflies-notes',
|
||||
description:
|
||||
@@ -22,7 +22,8 @@ export const config: ServerlessFunctionConfig = {
|
||||
type: 'route',
|
||||
path: '/webhook/fireflies',
|
||||
httpMethod: 'POST',
|
||||
isAuthRequired: true,
|
||||
isAuthRequired: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
+75
-68
@@ -43,6 +43,20 @@ export class TwentyCrmService {
|
||||
return response.data?.meetings?.edges?.[0]?.node;
|
||||
}
|
||||
|
||||
async findMeetingByFirefliesId(meetingId: string): Promise<IdNode | undefined> {
|
||||
const query = `
|
||||
query FindMeetingByFirefliesId($meetingId: String!) {
|
||||
meetings(filter: { firefliesMeetingId: { eq: $meetingId } }) {
|
||||
edges { node { id } }
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const variables = { meetingId };
|
||||
const response = await this.gqlRequest<FindMeetingResponse>(query, variables);
|
||||
return response.data?.meetings?.edges?.[0]?.node;
|
||||
}
|
||||
|
||||
async matchParticipantsToContacts(
|
||||
participants: FirefliesParticipant[],
|
||||
): Promise<{
|
||||
@@ -53,21 +67,18 @@ export class TwentyCrmService {
|
||||
return { matchedContacts: [], unmatchedParticipants: [] };
|
||||
}
|
||||
|
||||
// Split participants into those with emails and those with names only
|
||||
const participantsWithEmails = participants.filter(p => p.email && p.email.trim());
|
||||
const participantsNameOnly = participants.filter(p => !p.email || !p.email.trim());
|
||||
|
||||
let matchedContacts: Contact[] = [];
|
||||
let unmatchedParticipants: FirefliesParticipant[] = [];
|
||||
|
||||
// 1. Match by email first
|
||||
if (participantsWithEmails.length > 0) {
|
||||
const emailMatches = await this.matchByEmail(participantsWithEmails);
|
||||
matchedContacts.push(...emailMatches.matchedContacts);
|
||||
unmatchedParticipants.push(...emailMatches.unmatchedParticipants);
|
||||
}
|
||||
|
||||
// 2. For participants without emails, try name-based matching
|
||||
if (participantsNameOnly.length > 0) {
|
||||
const nameMatches = await this.matchByName(participantsNameOnly, matchedContacts);
|
||||
matchedContacts.push(...nameMatches.matchedContacts);
|
||||
@@ -126,7 +137,6 @@ export class TwentyCrmService {
|
||||
const matchedContacts: Contact[] = [];
|
||||
const unmatchedParticipants: FirefliesParticipant[] = [];
|
||||
|
||||
// Get set of already matched contact IDs to avoid duplicates
|
||||
const alreadyMatchedIds = new Set(alreadyMatchedContacts.map(c => c.id));
|
||||
|
||||
for (const participant of participants) {
|
||||
@@ -152,27 +162,37 @@ export class TwentyCrmService {
|
||||
const firstName = nameParts[0];
|
||||
const lastName = nameParts.slice(1).join(' ');
|
||||
|
||||
// Try exact name match first
|
||||
let query = `
|
||||
query FindPeopleByName($firstName: String!, $lastName: String) {
|
||||
people(filter: {
|
||||
and: [
|
||||
{ name: { firstName: { eq: $firstName } } }
|
||||
${lastName ? '{ name: { lastName: { eq: $lastName } } }' : ''}
|
||||
]
|
||||
}) {
|
||||
edges { node { id emails { primaryEmail } name { firstName lastName } } }
|
||||
}
|
||||
}
|
||||
`;
|
||||
const hasLastName = Boolean(lastName);
|
||||
|
||||
let variables: any = { firstName };
|
||||
if (lastName) {
|
||||
variables.lastName = lastName;
|
||||
}
|
||||
const query = hasLastName
|
||||
? `
|
||||
query FindPeopleByName($firstName: String!, $lastName: String!) {
|
||||
people(filter: {
|
||||
and: [
|
||||
{ name: { firstName: { eq: $firstName } } }
|
||||
{ name: { lastName: { eq: $lastName } } }
|
||||
]
|
||||
}) {
|
||||
edges { node { id emails { primaryEmail } name { firstName lastName } } }
|
||||
}
|
||||
}
|
||||
`
|
||||
: `
|
||||
query FindPeopleByName($firstName: String!) {
|
||||
people(filter: {
|
||||
name: { firstName: { eq: $firstName } }
|
||||
}) {
|
||||
edges { node { id emails { primaryEmail } name { firstName lastName } } }
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const variables: Record<string, unknown> = hasLastName
|
||||
? { firstName, lastName }
|
||||
: { firstName };
|
||||
|
||||
try {
|
||||
const response = await this.gqlRequest<any>(query, variables);
|
||||
const response = await this.gqlRequest<{ people: { edges: Array<{ node: { id: string; emails?: { primaryEmail?: string }; name?: { firstName?: string; lastName?: string } } }> } }>(query, variables);
|
||||
const people = response.data?.people?.edges;
|
||||
|
||||
if (people && people.length > 0) {
|
||||
@@ -183,9 +203,8 @@ export class TwentyCrmService {
|
||||
};
|
||||
}
|
||||
|
||||
// If no exact match and we have a last name, try fuzzy matching
|
||||
if (lastName) {
|
||||
query = `
|
||||
if (hasLastName) {
|
||||
const fuzzyQuery = `
|
||||
query FindPeopleByNameFuzzy($firstName: String!) {
|
||||
people(filter: { name: { firstName: { ilike: $firstName } } }) {
|
||||
edges { node { id emails { primaryEmail } name { firstName lastName } } }
|
||||
@@ -193,12 +212,11 @@ export class TwentyCrmService {
|
||||
}
|
||||
`;
|
||||
|
||||
const fuzzyResponse = await this.gqlRequest<any>(query, { firstName: `%${firstName}%` });
|
||||
const fuzzyResponse = await this.gqlRequest<{ people: { edges: Array<{ node: { id: string; emails?: { primaryEmail?: string }; name?: { firstName?: string; lastName?: string } } }> } }>(fuzzyQuery, { firstName: `%${firstName}%` });
|
||||
const fuzzyPeople = fuzzyResponse.data?.people?.edges;
|
||||
|
||||
if (fuzzyPeople && fuzzyPeople.length > 0) {
|
||||
// Find best match by checking if last name contains our target
|
||||
const bestMatch = fuzzyPeople.find((edge: any) => {
|
||||
const bestMatch = fuzzyPeople.find((edge) => {
|
||||
const personLastName = edge.node.name?.lastName || '';
|
||||
return personLastName.toLowerCase().includes(lastName.toLowerCase());
|
||||
});
|
||||
@@ -215,7 +233,6 @@ export class TwentyCrmService {
|
||||
|
||||
return null;
|
||||
} catch {
|
||||
// Silently fail - don't break the entire process for a single contact lookup
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -225,17 +242,14 @@ export class TwentyCrmService {
|
||||
): Promise<string[]> {
|
||||
const newContactIds: string[] = [];
|
||||
|
||||
// Split participants into those with emails and those with names only
|
||||
const participantsWithEmails = participants.filter(p => p.email && p.email.trim());
|
||||
const participantsNameOnly = participants.filter(p => !p.email || !p.email.trim());
|
||||
|
||||
// Process participants with emails
|
||||
if (participantsWithEmails.length > 0) {
|
||||
const emailContactIds = await this.createContactsWithEmails(participantsWithEmails);
|
||||
newContactIds.push(...emailContactIds);
|
||||
}
|
||||
|
||||
// Process participants with names only
|
||||
if (participantsNameOnly.length > 0) {
|
||||
const nameContactIds = await this.createContactsNameOnly(participantsNameOnly);
|
||||
newContactIds.push(...nameContactIds);
|
||||
@@ -247,7 +261,6 @@ export class TwentyCrmService {
|
||||
private async createContactsWithEmails(participants: FirefliesParticipant[]): Promise<string[]> {
|
||||
const newContactIds: string[] = [];
|
||||
|
||||
// Deduplicate participants by email to prevent duplicate contact creation
|
||||
const uniqueParticipants = participants.reduce<FirefliesParticipant[]>((unique, participant) => {
|
||||
const existing = unique.find(p => p.email === participant.email);
|
||||
if (!existing) {
|
||||
@@ -276,7 +289,10 @@ export class TwentyCrmService {
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await this.gqlRequest<CreatePersonResponse>(mutation, variables);
|
||||
const response = await this.gqlRequest<CreatePersonResponse>(mutation, variables, {
|
||||
suppressErrorCodes: ['BAD_USER_INPUT'],
|
||||
suppressErrorMessageIncludes: ['Duplicate Emails', 'duplicate entry'],
|
||||
});
|
||||
if (!response.data?.createPerson?.id) {
|
||||
throw new Error(`Failed to create contact for ${participant.email}`);
|
||||
}
|
||||
@@ -297,7 +313,6 @@ export class TwentyCrmService {
|
||||
private async createContactsNameOnly(participants: FirefliesParticipant[]): Promise<string[]> {
|
||||
const newContactIds: string[] = [];
|
||||
|
||||
// Deduplicate participants by name to prevent duplicate contact creation
|
||||
const uniqueParticipants = participants.reduce<FirefliesParticipant[]>((unique, participant) => {
|
||||
const existing = unique.find(p =>
|
||||
p.name.toLowerCase().trim() === participant.name.toLowerCase().trim()
|
||||
@@ -311,7 +326,6 @@ export class TwentyCrmService {
|
||||
}, []);
|
||||
|
||||
for (const participant of uniqueParticipants) {
|
||||
// Check if we already have a contact with this exact name to avoid duplicates
|
||||
const existingContact = await this.findContactByName(participant.name);
|
||||
if (existingContact) {
|
||||
logger.warn(`Contact with name "${participant.name}" already exists. Skipping creation.`);
|
||||
@@ -330,8 +344,6 @@ export class TwentyCrmService {
|
||||
const variables = {
|
||||
data: {
|
||||
name: { firstName, lastName },
|
||||
// Note: We don't set emails for name-only participants
|
||||
// This will create a contact without an email address
|
||||
},
|
||||
};
|
||||
|
||||
@@ -346,7 +358,6 @@ export class TwentyCrmService {
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||
logger.warn(`Failed to create contact for ${participant.name}: ${errorMessage}`);
|
||||
// Continue processing other participants instead of failing completely
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -409,28 +420,7 @@ export class TwentyCrmService {
|
||||
},
|
||||
};
|
||||
|
||||
await this.gqlRequest<any>(mutation, variables);
|
||||
}
|
||||
|
||||
async createMeetingTarget(meetingId: string, contactId: string): Promise<void> {
|
||||
const mutation = `
|
||||
mutation CreateMeetingTarget($data: NoteTargetCreateInput!) {
|
||||
createNoteTarget(data: $data) {
|
||||
id
|
||||
meetingId
|
||||
personId
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const variables = {
|
||||
data: {
|
||||
meetingId,
|
||||
personId: contactId,
|
||||
},
|
||||
};
|
||||
|
||||
await this.gqlRequest<any>(mutation, variables);
|
||||
await this.gqlRequest<{ createNoteTarget: { id: string; noteId: string; personId: string } }>(mutation, variables);
|
||||
}
|
||||
|
||||
async createMeeting(meetingData: MeetingCreateInput): Promise<string> {
|
||||
@@ -442,7 +432,6 @@ export class TwentyCrmService {
|
||||
|
||||
const variables = { data: meetingData };
|
||||
|
||||
// Debug: log the variables being sent
|
||||
if (!this.isTestEnvironment) {
|
||||
logger.debug('createMeeting variables:', JSON.stringify(variables, null, 2));
|
||||
}
|
||||
@@ -456,7 +445,8 @@ export class TwentyCrmService {
|
||||
|
||||
private async gqlRequest<T>(
|
||||
query: string,
|
||||
variables?: Record<string, unknown>
|
||||
variables?: Record<string, unknown>,
|
||||
options?: { suppressErrorCodes?: string[]; suppressErrorMessageIncludes?: string[] }
|
||||
): Promise<GraphQLResponse<T>> {
|
||||
const url = `${this.apiUrl}/graphql`;
|
||||
|
||||
@@ -500,7 +490,16 @@ export class TwentyCrmService {
|
||||
|
||||
return json;
|
||||
} catch (error) {
|
||||
logger.error('GraphQL request error:', error);
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
const suppressByCode = options?.suppressErrorCodes?.some((code) =>
|
||||
message.includes(code),
|
||||
);
|
||||
const suppressByMessage = options?.suppressErrorMessageIncludes?.some((substring) =>
|
||||
message.includes(substring),
|
||||
);
|
||||
if (!suppressByCode && !suppressByMessage) {
|
||||
logger.error('GraphQL request error:', error);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -525,7 +524,15 @@ export class TwentyCrmService {
|
||||
return response.data.createMeeting.id;
|
||||
}
|
||||
|
||||
async findFailedMeetings(): Promise<any[]> {
|
||||
async findFailedMeetings(): Promise<Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
firefliesMeetingId: string;
|
||||
importError: string;
|
||||
lastImportAttempt: string;
|
||||
importAttempts: number;
|
||||
createdAt: string;
|
||||
}>> {
|
||||
const query = `
|
||||
query FindFailedMeetings {
|
||||
meetings(filter: { importStatus: { eq: "FAILED" } }) {
|
||||
@@ -544,8 +551,8 @@ export class TwentyCrmService {
|
||||
}
|
||||
`;
|
||||
|
||||
const response = await this.gqlRequest<any>(query);
|
||||
return response.data?.meetings?.edges?.map((edge: any) => edge.node) || [];
|
||||
const response = await this.gqlRequest<{ meetings: { edges: Array<{ node: { id: string; name: string; firefliesMeetingId: string; importError: string; lastImportAttempt: string; importAttempts: number; createdAt: string } }> } }>(query);
|
||||
return response.data?.meetings?.edges?.map((edge) => edge.node) || [];
|
||||
}
|
||||
|
||||
async retryFailedMeeting(meetingId: string, updatedData: Partial<MeetingCreateInput>): Promise<void> {
|
||||
@@ -564,7 +571,7 @@ export class TwentyCrmService {
|
||||
}
|
||||
};
|
||||
|
||||
await this.gqlRequest<any>(mutation, variables);
|
||||
await this.gqlRequest<{ updateMeeting: { id: string } }>(mutation, variables);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,235 @@
|
||||
// Fireflies API Types
|
||||
export type FirefliesParticipant = {
|
||||
email: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type FirefliesWebhookPayload = {
|
||||
meetingId: string;
|
||||
eventType: string;
|
||||
clientReferenceId?: string;
|
||||
};
|
||||
|
||||
// Transcript sentence from Fireflies API
|
||||
export type FirefliesSentence = {
|
||||
index: number;
|
||||
speaker_name: string;
|
||||
text: string;
|
||||
start_time: string;
|
||||
end_time: string;
|
||||
ai_filters?: {
|
||||
task?: boolean;
|
||||
question?: boolean;
|
||||
sentiment?: string;
|
||||
};
|
||||
};
|
||||
|
||||
// Speaker analytics from Fireflies API (Business+)
|
||||
export type FirefliesSpeakerAnalytics = {
|
||||
speaker_id: string;
|
||||
name: string;
|
||||
duration: number;
|
||||
word_count: number;
|
||||
longest_monologue: number;
|
||||
filler_words: number;
|
||||
questions: number;
|
||||
words_per_minute: number;
|
||||
};
|
||||
|
||||
// Based on Fireflies GraphQL API transcript schema
|
||||
// See: https://docs.fireflies.ai/graphql-api/query/transcript
|
||||
export type FirefliesMeetingData = {
|
||||
id: string;
|
||||
title: string;
|
||||
date: string;
|
||||
duration: number;
|
||||
participants: FirefliesParticipant[];
|
||||
organizer_email?: string;
|
||||
// Full transcript (Pro+)
|
||||
sentences?: FirefliesSentence[];
|
||||
summary: {
|
||||
// Pro+ fields
|
||||
action_items: string[];
|
||||
keywords?: string[];
|
||||
overview: string;
|
||||
notes?: string; // Detailed AI-generated meeting notes
|
||||
gist?: string; // 1-sentence summary
|
||||
bullet_gist?: string; // Bullet point summary with emojis
|
||||
short_summary?: string; // Single paragraph summary
|
||||
short_overview?: string; // Brief overview
|
||||
outline?: string; // Meeting outline with timestamps
|
||||
shorthand_bullet?: string;
|
||||
// Business+ fields
|
||||
topics_discussed?: string[];
|
||||
meeting_type?: string;
|
||||
transcript_chapters?: string[];
|
||||
};
|
||||
// Business+ analytics
|
||||
analytics?: {
|
||||
sentiments?: {
|
||||
positive_pct: number;
|
||||
negative_pct: number;
|
||||
neutral_pct: number;
|
||||
};
|
||||
categories?: {
|
||||
questions: number;
|
||||
tasks: number;
|
||||
metrics: number;
|
||||
date_times: number;
|
||||
};
|
||||
speakers?: FirefliesSpeakerAnalytics[];
|
||||
};
|
||||
meeting_info?: {
|
||||
summary_status?: string;
|
||||
};
|
||||
// URLs
|
||||
transcript_url: string;
|
||||
audio_url?: string; // Pro+
|
||||
video_url?: string; // Business+
|
||||
meeting_link?: string; // All plans
|
||||
summary_status?: string;
|
||||
};
|
||||
|
||||
export type FirefliesTranscriptListItem = {
|
||||
id: string;
|
||||
title: string;
|
||||
date?: string;
|
||||
duration?: number;
|
||||
organizer_email?: string;
|
||||
participants?: string[];
|
||||
transcript_url?: string;
|
||||
meeting_link?: string;
|
||||
summary_status?: string;
|
||||
};
|
||||
|
||||
export type FirefliesTranscriptListOptions = {
|
||||
limit?: number;
|
||||
skip?: number;
|
||||
organizers?: string[];
|
||||
participants?: string[];
|
||||
hostEmail?: string;
|
||||
participantEmail?: string;
|
||||
userId?: string;
|
||||
channelId?: string;
|
||||
mine?: boolean;
|
||||
fromDate?: number;
|
||||
toDate?: number;
|
||||
pageSize?: number;
|
||||
maxRecords?: number;
|
||||
};
|
||||
|
||||
// Configuration Types
|
||||
export type SummaryStrategy = 'immediate_only' | 'immediate_with_retry' | 'delayed_polling' | 'basic_only';
|
||||
|
||||
export type SummaryFetchConfig = {
|
||||
strategy: SummaryStrategy;
|
||||
retryAttempts: number;
|
||||
retryDelay: number;
|
||||
pollInterval: number;
|
||||
maxPolls: number;
|
||||
};
|
||||
|
||||
export const FIREFLIES_PLANS = {
|
||||
FREE: 'free',
|
||||
PRO: 'pro',
|
||||
BUSINESS: 'business',
|
||||
ENTERPRISE: 'enterprise',
|
||||
} as const;
|
||||
|
||||
export type FirefliesPlan = typeof FIREFLIES_PLANS[keyof typeof FIREFLIES_PLANS];
|
||||
|
||||
export type WebhookConfig = {
|
||||
secret: string;
|
||||
apiUrl: string;
|
||||
};
|
||||
|
||||
// Processing Result Types
|
||||
export type ProcessResult = {
|
||||
success: boolean;
|
||||
meetingId?: string;
|
||||
noteIds?: string[];
|
||||
newContacts?: string[];
|
||||
errors?: string[];
|
||||
debug?: string[];
|
||||
summaryReady?: boolean;
|
||||
summaryPending?: boolean;
|
||||
enhancementScheduled?: boolean;
|
||||
actionItemsCount?: number;
|
||||
sentimentAnalysis?: {
|
||||
positive_pct: number;
|
||||
negative_pct: number;
|
||||
neutral_pct: number;
|
||||
};
|
||||
meetingType?: string;
|
||||
keyTopics?: string[];
|
||||
};
|
||||
|
||||
// Twenty CRM Types
|
||||
export type GraphQLResponse<T> = {
|
||||
data: T;
|
||||
errors?: Array<{
|
||||
message?: string;
|
||||
extensions?: { code?: string }
|
||||
}>;
|
||||
};
|
||||
|
||||
export type IdNode = { id: string };
|
||||
|
||||
export type FindMeetingResponse = {
|
||||
meetings: { edges: Array<{ node: IdNode }> };
|
||||
};
|
||||
|
||||
export type FindPeopleResponse = {
|
||||
people: { edges: Array<{ node: { id: string; emails: { primaryEmail: string } } }> };
|
||||
};
|
||||
|
||||
export type CreatePersonResponse = {
|
||||
createPerson: { id: string }
|
||||
};
|
||||
|
||||
export type CreateNoteResponse = {
|
||||
createNote: { id: string }
|
||||
};
|
||||
|
||||
export type CreateMeetingResponse = {
|
||||
createMeeting: { id: string }
|
||||
};
|
||||
|
||||
export type Contact = {
|
||||
id: string;
|
||||
email: string;
|
||||
};
|
||||
|
||||
// Twenty CRM Meeting custom field input
|
||||
// Maps to fields defined in add-meeting-fields.ts
|
||||
export type MeetingCreateInput = {
|
||||
name: string;
|
||||
noteId?: string | null;
|
||||
// Basic fields (All plans)
|
||||
meetingDate: string;
|
||||
duration: number;
|
||||
firefliesMeetingId: string;
|
||||
organizerEmail?: string | null;
|
||||
transcriptUrl?: { primaryLinkUrl: string; primaryLinkLabel: string } | null;
|
||||
meetingLink?: { primaryLinkUrl: string; primaryLinkLabel: string } | null;
|
||||
// Pro+ fields
|
||||
transcript?: string | null;
|
||||
overview?: string | null;
|
||||
notes?: string | null;
|
||||
keywords?: string | null;
|
||||
audioUrl?: { primaryLinkUrl: string; primaryLinkLabel: string } | null;
|
||||
// Business+ fields
|
||||
meetingType?: string | null;
|
||||
topics?: string | null;
|
||||
actionItemsCount: number;
|
||||
positivePercent?: number | null;
|
||||
negativePercent?: number | null;
|
||||
neutralPercent?: number | null;
|
||||
videoUrl?: { primaryLinkUrl: string; primaryLinkLabel: string } | null;
|
||||
// Import tracking
|
||||
importStatus?: 'SUCCESS' | 'PARTIAL' | 'FAILED' | 'PENDING' | 'RETRYING' | null;
|
||||
importError?: string | null;
|
||||
lastImportAttempt?: string | null;
|
||||
importAttempts?: number | null;
|
||||
};
|
||||
|
||||
+13
-2
@@ -1,11 +1,11 @@
|
||||
import { FIREFLIES_PLANS, type FirefliesPlan, type SummaryFetchConfig, type SummaryStrategy } from './types';
|
||||
|
||||
export const toBoolean = (value: string | undefined, defaultValue: boolean): boolean => {
|
||||
if (value === undefined) return defaultValue;
|
||||
const normalized = value.trim().toLowerCase();
|
||||
return normalized === 'true' || normalized === '1' || normalized === 'yes';
|
||||
};
|
||||
|
||||
import type { SummaryFetchConfig, SummaryStrategy } from './types';
|
||||
|
||||
export const getApiUrl = (): string => {
|
||||
return process.env.SERVER_URL || 'http://localhost:3000';
|
||||
};
|
||||
@@ -28,3 +28,14 @@ export const shouldAutoCreateContacts = (): boolean => {
|
||||
return toBoolean(process.env.AUTO_CREATE_CONTACTS, true);
|
||||
};
|
||||
|
||||
export const getFirefliesPlan = (): FirefliesPlan => {
|
||||
const plan = (process.env.FIREFLIES_PLAN || FIREFLIES_PLANS.FREE).toLowerCase();
|
||||
if (plan === FIREFLIES_PLANS.BUSINESS || plan === FIREFLIES_PLANS.ENTERPRISE) {
|
||||
return plan as FirefliesPlan;
|
||||
}
|
||||
if (plan === FIREFLIES_PLANS.PRO) {
|
||||
return FIREFLIES_PLANS.PRO;
|
||||
}
|
||||
return FIREFLIES_PLANS.FREE;
|
||||
};
|
||||
|
||||
+109
-71
@@ -1,9 +1,9 @@
|
||||
import { FirefliesApiClient } from './fireflies-api-client';
|
||||
import { MeetingFormatter } from './formatters';
|
||||
import { createLogger } from './logger';
|
||||
import { clearAllCapturedLogs, createLogger, getAllCapturedLogs } from './logger';
|
||||
import { TwentyCrmService } from './twenty-crm-service';
|
||||
import type { FirefliesWebhookPayload, ProcessResult } from './types';
|
||||
import { getApiUrl, getSummaryFetchConfig, shouldAutoCreateContacts } from './utils';
|
||||
import { getApiUrl, getFirefliesPlan, getSummaryFetchConfig, shouldAutoCreateContacts } from './utils';
|
||||
import {
|
||||
getWebhookSecretFingerprint,
|
||||
isValidFirefliesPayload,
|
||||
@@ -12,17 +12,28 @@ import {
|
||||
|
||||
declare const process: { env: Record<string, string | undefined> };
|
||||
|
||||
const logger = createLogger('fireflies');
|
||||
|
||||
export class WebhookHandler {
|
||||
private debug: string[] = [];
|
||||
private isTestEnvironment: boolean;
|
||||
private logger: ReturnType<typeof createLogger>;
|
||||
|
||||
constructor() {
|
||||
this.isTestEnvironment = process.env.NODE_ENV === 'test' || process.env.JEST_WORKER_ID !== undefined;
|
||||
this.logger = createLogger('fireflies');
|
||||
}
|
||||
|
||||
private addDebugLogs(result: ProcessResult): ProcessResult {
|
||||
if (process.env.CAPTURE_LOGS === 'true') {
|
||||
const captured = getAllCapturedLogs();
|
||||
result.debug = [...this.debug, ...captured];
|
||||
} else {
|
||||
delete result.debug;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async handle(params: unknown, headers?: Record<string, string>): Promise<ProcessResult> {
|
||||
clearAllCapturedLogs();
|
||||
this.debug = [];
|
||||
|
||||
const result: ProcessResult = {
|
||||
success: false,
|
||||
noteIds: [],
|
||||
@@ -31,19 +42,27 @@ export class WebhookHandler {
|
||||
};
|
||||
|
||||
try {
|
||||
logger.debug('invoked');
|
||||
logger.debug(`apiUrl=${getApiUrl()}`);
|
||||
this.logger.debug('invoked');
|
||||
this.logger.debug(`apiUrl=${getApiUrl()}`);
|
||||
const paramKeys =
|
||||
params && typeof params === 'object'
|
||||
? Object.keys(params as Record<string, unknown>)
|
||||
: [];
|
||||
this.debug.push(
|
||||
`paramsType=${typeof params}`,
|
||||
`paramKeys=${paramKeys.join(',') || 'none'}`
|
||||
);
|
||||
|
||||
// 0) Validate environment configuration
|
||||
const firefliesApiKey = process.env.FIREFLIES_API_KEY || '';
|
||||
const twentyApiKey = process.env.TWENTY_API_KEY || '';
|
||||
|
||||
if (!firefliesApiKey) {
|
||||
logger.critical('FIREFLIES_API_KEY not configured - this is a critical configuration error');
|
||||
this.logger.critical('FIREFLIES_API_KEY not configured - this is a critical configuration error');
|
||||
throw new Error('FIREFLIES_API_KEY environment variable is required');
|
||||
}
|
||||
if (!twentyApiKey) {
|
||||
logger.critical('TWENTY_API_KEY not configured - this is a critical configuration error');
|
||||
this.logger.critical('TWENTY_API_KEY not configured - this is a critical configuration error');
|
||||
throw new Error('TWENTY_API_KEY environment variable is required');
|
||||
}
|
||||
|
||||
@@ -51,28 +70,30 @@ export class WebhookHandler {
|
||||
const { payload, extractedHeaders } = this.parsePayload(params);
|
||||
const finalHeaders = extractedHeaders || headers;
|
||||
|
||||
logger.debug(`payload meetingId=${payload.meetingId} eventType="${payload.eventType}"`);
|
||||
this.logger.debug(`payload meetingId=${payload.meetingId} eventType="${payload.eventType}"`);
|
||||
|
||||
// 2) Verify webhook signature
|
||||
const webhookSecret = process.env.FIREFLIES_WEBHOOK_SECRET || '';
|
||||
const secretFingerprint = getWebhookSecretFingerprint(webhookSecret);
|
||||
logger.debug(`webhook secret fingerprint=${secretFingerprint}`);
|
||||
this.logger.debug(`webhook secret fingerprint=${secretFingerprint}`);
|
||||
|
||||
this.verifySignature(payload, finalHeaders, webhookSecret);
|
||||
logger.debug('signature verification: ok');
|
||||
this.logger.debug('signature verification: ok');
|
||||
|
||||
// 3) Fetch meeting data from Fireflies
|
||||
const summaryConfig = getSummaryFetchConfig();
|
||||
logger.debug(`summary strategy: ${summaryConfig.strategy} (retryAttempts=${summaryConfig.retryAttempts}, retryDelay=${summaryConfig.retryDelay}ms)`);
|
||||
logger.debug(`fetching meeting data from Fireflies API`);
|
||||
const firefliesPlan = getFirefliesPlan();
|
||||
this.logger.debug(`summary strategy: ${summaryConfig.strategy} (retryAttempts=${summaryConfig.retryAttempts}, retryDelay=${summaryConfig.retryDelay}ms)`);
|
||||
this.logger.debug(`fetching meeting data from Fireflies API`);
|
||||
|
||||
const firefliesClient = new FirefliesApiClient(firefliesApiKey);
|
||||
const { data: meetingData, summaryReady } = await firefliesClient.fetchMeetingDataWithRetry(
|
||||
payload.meetingId,
|
||||
summaryConfig
|
||||
summaryConfig,
|
||||
firefliesPlan
|
||||
);
|
||||
|
||||
logger.debug(`meeting data fetched: title="${meetingData.title}" summaryReady=${summaryReady}`);
|
||||
this.logger.debug(`meeting data fetched: title="${meetingData.title}" summaryReady=${summaryReady}`);
|
||||
|
||||
result.summaryReady = summaryReady;
|
||||
result.summaryPending = !summaryReady;
|
||||
@@ -84,8 +105,7 @@ export class WebhookHandler {
|
||||
result.meetingType = meetingData.summary.meeting_type;
|
||||
|
||||
if (meetingData.analytics?.sentiments) {
|
||||
const sentiments = meetingData.analytics.sentiments;
|
||||
result.sentimentScore = sentiments.positive_pct / 100;
|
||||
result.sentimentAnalysis = meetingData.analytics.sentiments;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,29 +115,36 @@ export class WebhookHandler {
|
||||
getApiUrl()
|
||||
);
|
||||
|
||||
const existingMeeting = await twentyService.findExistingMeeting(meetingData.title);
|
||||
if (existingMeeting) {
|
||||
logger.debug(`meeting already exists id=${existingMeeting.id}`);
|
||||
const existingMeetingById = await twentyService.findMeetingByFirefliesId(meetingData.id);
|
||||
if (existingMeetingById) {
|
||||
this.logger.debug(`meeting already exists by firefliesMeetingId id=${existingMeetingById.id}`);
|
||||
result.success = true;
|
||||
result.meetingId = existingMeeting.id;
|
||||
result.debug = this.debug;
|
||||
return result;
|
||||
result.meetingId = existingMeetingById.id;
|
||||
return this.addDebugLogs(result);
|
||||
}
|
||||
logger.debug('no existing meeting found, proceeding');
|
||||
|
||||
const existingMeetingByTitle = await twentyService.findExistingMeeting(meetingData.title);
|
||||
if (existingMeetingByTitle) {
|
||||
this.logger.debug(`meeting already exists by title id=${existingMeetingByTitle.id}`);
|
||||
result.success = true;
|
||||
result.meetingId = existingMeetingByTitle.id;
|
||||
return this.addDebugLogs(result);
|
||||
}
|
||||
this.logger.debug('no existing meeting found, proceeding');
|
||||
|
||||
// 5) Match participants to existing contacts
|
||||
logger.debug(`total participants from API: ${meetingData.participants.length}`);
|
||||
this.logger.debug(`total participants from API: ${meetingData.participants.length}`);
|
||||
meetingData.participants.forEach((p, idx) => {
|
||||
logger.debug(`participant ${idx + 1}: name="${p.name}" email="${p.email || 'none'}"`);
|
||||
this.logger.debug(`participant ${idx + 1}: name="${p.name}" email="${p.email || 'none'}"`);
|
||||
});
|
||||
|
||||
const { matchedContacts, unmatchedParticipants } = await twentyService.matchParticipantsToContacts(
|
||||
meetingData.participants
|
||||
);
|
||||
logger.debug(`matched=${matchedContacts.length} unmatched=${unmatchedParticipants.length}`);
|
||||
this.logger.debug(`matched=${matchedContacts.length} unmatched=${unmatchedParticipants.length}`);
|
||||
|
||||
unmatchedParticipants.forEach((p, idx) => {
|
||||
logger.debug(`unmatched ${idx + 1}: name="${p.name}" email="${p.email || 'none'}"`);
|
||||
this.logger.debug(`unmatched ${idx + 1}: name="${p.name}" email="${p.email || 'none'}"`);
|
||||
});
|
||||
|
||||
// 6) Optionally create contacts
|
||||
@@ -126,7 +153,7 @@ export class WebhookHandler {
|
||||
? await twentyService.createContactsForUnmatched(unmatchedParticipants)
|
||||
: [];
|
||||
result.newContacts = newContactIds;
|
||||
logger.debug(`autoCreate=${autoCreate} createdContacts=${newContactIds.length}`);
|
||||
this.logger.debug(`autoCreate=${autoCreate} createdContacts=${newContactIds.length}`);
|
||||
|
||||
// 7) Create note first (so we can link to it from the meeting)
|
||||
const allContactIds = [...matchedContacts.map(({ id }) => id), ...newContactIds];
|
||||
@@ -136,13 +163,13 @@ export class WebhookHandler {
|
||||
noteBody
|
||||
);
|
||||
result.noteIds = [noteId];
|
||||
logger.debug(`created note id=${noteId}`);
|
||||
this.logger.debug(`created note id=${noteId}`);
|
||||
|
||||
// 8) Create meeting with direct relationship to the note
|
||||
const meetingInput = MeetingFormatter.toMeetingCreateInput(meetingData, noteId);
|
||||
logger.debug(`meeting duration: ${meetingData.duration} min (raw from API) → ${meetingInput.duration} min (rounded)`);
|
||||
this.logger.debug(`meeting duration: ${meetingData.duration} min (raw from API) → ${meetingInput.duration} min (rounded)`);
|
||||
result.meetingId = await twentyService.createMeeting(meetingInput);
|
||||
logger.debug(`created meeting id=${result.meetingId} with noteId=${noteId}`);
|
||||
this.logger.debug(`created meeting id=${result.meetingId} with noteId=${noteId}`);
|
||||
|
||||
// 9) Link note to participants (Meeting link is handled via the relation field)
|
||||
await this.linkNoteToParticipants(
|
||||
@@ -150,20 +177,19 @@ export class WebhookHandler {
|
||||
noteId,
|
||||
allContactIds
|
||||
);
|
||||
logger.debug(`linked note to ${allContactIds.length} participants`);
|
||||
this.logger.debug(`linked note to ${allContactIds.length} participants`);
|
||||
|
||||
result.success = true;
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Unknown error';
|
||||
logger.error(`error: ${message}`);
|
||||
this.logger.error(`error: ${message}`);
|
||||
result.errors?.push(message);
|
||||
|
||||
// Try to create a failed meeting record for tracking
|
||||
await this.createFailedMeetingRecord(params, message);
|
||||
}
|
||||
|
||||
result.debug = this.debug;
|
||||
return result;
|
||||
return this.addDebugLogs(result);
|
||||
}
|
||||
|
||||
private parsePayload(params: unknown): { payload: FirefliesWebhookPayload; extractedHeaders?: Record<string, string> } {
|
||||
@@ -172,16 +198,16 @@ export class WebhookHandler {
|
||||
|
||||
// Handle string-encoded params
|
||||
if (typeof normalizedParams === 'string') {
|
||||
logger.debug(`received params as string length=${normalizedParams.length}`);
|
||||
this.logger.debug(`received params as string length=${normalizedParams.length}`);
|
||||
try {
|
||||
const parsed = JSON.parse(normalizedParams);
|
||||
normalizedParams = parsed;
|
||||
if (parsed && typeof parsed === 'object') {
|
||||
const parsedKeys = Object.keys(parsed as Record<string, unknown>);
|
||||
logger.debug(`parsed params keys: ${parsedKeys.join(',') || 'none'}`);
|
||||
this.logger.debug(`parsed params keys: ${parsedKeys.join(',') || 'none'}`);
|
||||
}
|
||||
} catch (parseError) {
|
||||
logger.error(`error parsing string params: ${String(parseError)}`);
|
||||
this.logger.error(`error parsing string params: ${String(parseError)}`);
|
||||
throw new Error('Invalid or missing webhook payload');
|
||||
}
|
||||
}
|
||||
@@ -197,14 +223,14 @@ export class WebhookHandler {
|
||||
if (wrapper.headers && typeof wrapper.headers === 'object' && !Array.isArray(wrapper.headers)) {
|
||||
extractedHeaders = wrapper.headers as Record<string, string>;
|
||||
const headerKeys = Object.keys(extractedHeaders);
|
||||
logger.debug(`extracted headers from wrapper: ${headerKeys.join(',')}`);
|
||||
this.logger.debug(`extracted headers from wrapper: ${headerKeys.join(',')}`);
|
||||
}
|
||||
|
||||
const wrapperKeys = ['params', 'payload', 'body', 'data', 'event'];
|
||||
for (const key of wrapperKeys) {
|
||||
const candidate = wrapper[key];
|
||||
if (isValidFirefliesPayload(candidate)) {
|
||||
logger.debug(`detected payload under wrapper key "${key}"`);
|
||||
this.logger.debug(`detected payload under wrapper key "${key}"`);
|
||||
payload = candidate as FirefliesWebhookPayload;
|
||||
break;
|
||||
}
|
||||
@@ -212,7 +238,7 @@ export class WebhookHandler {
|
||||
}
|
||||
|
||||
if (!payload) {
|
||||
logger.error('error: Invalid or missing webhook payload');
|
||||
this.logger.error('error: Invalid or missing webhook payload');
|
||||
throw new Error('Invalid or missing webhook payload');
|
||||
}
|
||||
|
||||
@@ -220,7 +246,7 @@ export class WebhookHandler {
|
||||
const payloadRecord = payload as Record<string, unknown>;
|
||||
const payloadKeys = Object.keys(payloadRecord);
|
||||
if (payloadKeys.length > 0) {
|
||||
logger.debug(`payload keys: ${payloadKeys.join(',')}`);
|
||||
this.logger.debug(`payload keys: ${payloadKeys.join(',')}`);
|
||||
}
|
||||
|
||||
return { payload, extractedHeaders };
|
||||
@@ -235,8 +261,9 @@ export class WebhookHandler {
|
||||
const normalizedHeaders = headers || {};
|
||||
const headerKeys = Object.keys(normalizedHeaders);
|
||||
if (headerKeys.length > 0) {
|
||||
logger.debug(`header keys: ${headerKeys.join(',')}`);
|
||||
this.logger.debug(`header keys: ${headerKeys.join(',')}`);
|
||||
}
|
||||
this.debug.push(`headerKeys=${headerKeys.join(',') || 'none'}`);
|
||||
|
||||
const headerSignature = Object.entries(normalizedHeaders).find(
|
||||
([key]) => key.toLowerCase() === 'x-hub-signature',
|
||||
@@ -249,32 +276,40 @@ export class WebhookHandler {
|
||||
: undefined;
|
||||
|
||||
if (payloadSignature) {
|
||||
logger.debug('found signature inside payload');
|
||||
this.logger.debug('found signature inside payload');
|
||||
}
|
||||
|
||||
const signature =
|
||||
(typeof headerSignature === 'string' ? headerSignature : undefined) || payloadSignature;
|
||||
|
||||
const body = typeof normalizedHeaders['body'] === 'string'
|
||||
? normalizedHeaders['body']
|
||||
: JSON.stringify(payloadRecord);
|
||||
const payloadForSignature =
|
||||
payloadSignature && 'x-hub-signature' in payloadRecord
|
||||
? Object.fromEntries(
|
||||
Object.entries(payloadRecord).filter(
|
||||
([key]) => key.toLowerCase() !== 'x-hub-signature',
|
||||
),
|
||||
)
|
||||
: payloadRecord;
|
||||
|
||||
const body =
|
||||
typeof normalizedHeaders['body'] === 'string'
|
||||
? normalizedHeaders['body']
|
||||
: JSON.stringify(payloadForSignature);
|
||||
|
||||
const signatureCheck = verifyWebhookSignature(body, signature, webhookSecret);
|
||||
if (!signatureCheck.isValid) {
|
||||
logger.debug(
|
||||
this.debug.push(
|
||||
`signatureProvided=${Boolean(signature)}`,
|
||||
`signatureMatched=${signatureCheck.isValid}`,
|
||||
`webhookSecretFingerprint=${getWebhookSecretFingerprint(webhookSecret)}`
|
||||
);
|
||||
this.logger.debug(
|
||||
`signature check failed. headerPresent=${Boolean(
|
||||
headerSignature,
|
||||
)} payloadSignaturePresent=${Boolean(payloadSignature)}`,
|
||||
);
|
||||
if (signature) {
|
||||
logger.debug(`provided signature=${signature}`);
|
||||
} else {
|
||||
logger.debug('provided signature=undefined');
|
||||
}
|
||||
logger.debug(
|
||||
`computed signature=${signatureCheck.computedSignature ?? 'unavailable'}`,
|
||||
);
|
||||
logger.critical('Invalid webhook signature - potential security threat detected in production');
|
||||
this.logger.debug(`provided signature present=${Boolean(signature)}`);
|
||||
this.logger.critical('Invalid webhook signature - potential security threat detected in production');
|
||||
throw new Error('Invalid webhook signature');
|
||||
}
|
||||
}
|
||||
@@ -284,15 +319,13 @@ export class WebhookHandler {
|
||||
noteId: string,
|
||||
contactIds: string[]
|
||||
): Promise<void> {
|
||||
// Create Note-Person links for each participant
|
||||
for (const contactId of contactIds) {
|
||||
try {
|
||||
await twentyService.createNoteTarget(noteId, contactId);
|
||||
logger.debug(`linked note ${noteId} to person ${contactId}`);
|
||||
this.logger.debug(`linked note ${noteId} to person ${contactId}`);
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Unknown error';
|
||||
logger.error(`failed to link note to person ${contactId}: ${message}`);
|
||||
// Continue with other participants
|
||||
this.logger.error(`failed to link note to person ${contactId}: ${message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -302,11 +335,10 @@ export class WebhookHandler {
|
||||
try {
|
||||
const twentyApiKey = process.env.TWENTY_API_KEY || '';
|
||||
if (!twentyApiKey) {
|
||||
logger.debug('Cannot create failed meeting record: TWENTY_API_KEY not configured');
|
||||
this.logger.debug('Cannot create failed meeting record: TWENTY_API_KEY not configured');
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to extract meeting ID and title from the params
|
||||
let meetingId = 'unknown';
|
||||
let meetingTitle = 'Unknown Meeting';
|
||||
|
||||
@@ -314,7 +346,6 @@ export class WebhookHandler {
|
||||
if (payload?.meetingId) {
|
||||
meetingId = payload.meetingId;
|
||||
|
||||
// Try to get meeting title from Fireflies API if possible
|
||||
const firefliesApiKey = process.env.FIREFLIES_API_KEY || '';
|
||||
if (firefliesApiKey) {
|
||||
try {
|
||||
@@ -322,7 +353,11 @@ export class WebhookHandler {
|
||||
const meetingData = await firefliesClient.fetchMeetingData(meetingId);
|
||||
meetingTitle = meetingData.title || meetingTitle;
|
||||
} catch (fetchError) {
|
||||
logger.debug(`Could not fetch meeting title: ${fetchError instanceof Error ? fetchError.message : 'Unknown error'}`);
|
||||
this.logger.debug(
|
||||
`Could not fetch meeting title: ${
|
||||
fetchError instanceof Error ? fetchError.message : 'Unknown error'
|
||||
}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -335,10 +370,13 @@ export class WebhookHandler {
|
||||
);
|
||||
|
||||
const failedMeetingId = await twentyService.createFailedMeeting(failedMeetingData);
|
||||
logger.debug(`Created failed meeting record: ${failedMeetingId}`);
|
||||
this.logger.debug(`Created failed meeting record: ${failedMeetingId}`);
|
||||
} catch (recordError) {
|
||||
// Don't throw here - we don't want to break the original error handling
|
||||
logger.error(`Failed to create failed meeting record: ${recordError instanceof Error ? recordError.message : 'Unknown error'}`);
|
||||
this.logger.error(
|
||||
`Failed to create failed meeting record: ${
|
||||
recordError instanceof Error ? recordError.message : 'Unknown error'
|
||||
}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -32,3 +32,4 @@ export const MyComponent = () => {
|
||||
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
|
||||
@@ -118,6 +118,11 @@ Figma プラットフォームの学習に関するより包括的な詳細と
|
||||
これは推奨拡張機能の一部です。
|
||||
これは推奨拡張機能の一部です。
|
||||
これは推奨拡張機能の一部です。
|
||||
これは推奨拡張機能の一部です。
|
||||
これは推奨拡張機能の一部です。
|
||||
これは推奨拡張機能の一部です。
|
||||
これは推奨拡張機能の一部です。
|
||||
これは推奨拡張機能の一部です。
|
||||
|
||||
## コラボレーション
|
||||
|
||||
|
||||
@@ -158,7 +158,7 @@ SSL(HTTPS)は、特定のブラウザ機能が正しく動作するために
|
||||
|
||||
2. **.env ファイルを更新**
|
||||
|
||||
.env`ファイルを開き、`SERVER_URL\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`を更新します:
|
||||
.env`ファイルを開き、`SERVER_URL\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`を更新します:
|
||||
|
||||
```ini
|
||||
SERVER_URL=http(s)://your-domain-or-ip:your-port
|
||||
|
||||
@@ -30,7 +30,7 @@ Twentyは、日常をサポートする最適なデータモデルを形成す
|
||||
既存のオブジェクトの特性に過ぎないもの(例:会社の「業種」や機会の「ステータス」)はフィールドにします。 カテゴリ、ラベル、属性にはフィールドが最適です。 カテゴリ、ラベル、属性にはフィールドが最適です。 カテゴリ、ラベル、属性にはフィールドが最適です。 カテゴリ、ラベル、属性にはフィールドが最適です。 カテゴリ、ラベル、属性にはフィールドが最適です。 カテゴリ、ラベル、属性にはフィールドが最適です。 カテゴリ、ラベル、属性にはフィールドが最適です。 カテゴリ、ラベル、属性にはフィールドが最適です。 カテゴリ、ラベル、属性にはフィールドが最適です。 カテゴリ、ラベル、属性にはフィールドが最適です。 カテゴリ、ラベル、属性にはフィールドが最適です。 カテゴリ、ラベル、属性にはフィールドが最適です。 カテゴリ、ラベル、属性にはフィールドが最適です。 カテゴリ、ラベル、属性にはフィールドが最適です。 カテゴリ、ラベル、属性にはフィールドが最適です。 カテゴリ、ラベル、属性にはフィールドが最適です。 カテゴリ、ラベル、属性にはフィールドが最適です。 カテゴリ、ラベル、属性にはフィールドが最適です。 カテゴリ、ラベル、属性にはフィールドが最適です。 カテゴリ、ラベル、属性にはフィールドが最適です。 カテゴリ、ラベル、属性にはフィールドが最適です。 カテゴリ、ラベル、属性にはフィールドが最適です。 カテゴリ、ラベル、属性にはフィールドが最適です。
|
||||
|
||||
**3. 単独で存在する場合には新しいオブジェクトを作成してください。**
|
||||
概念が独自のライフサイクル、プロパティ、または関係を持つ場合、それは通常オブジェクトに値します。 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば:
|
||||
概念が独自のライフサイクル、プロパティ、または関係を持つ場合、それは通常オブジェクトに値します。 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば: 例えば:
|
||||
|
||||
- **プロジェクト**:独自の期限、所有者、タスクを持つもの
|
||||
- **サブスクリプション**:会社、製品、請求書を結ぶもの
|
||||
|
||||
@@ -45,9 +45,8 @@ sectionInfo: 配置您的Twenty工作空间设置和偏好
|
||||
刪除您的帳號將永久移除您對所有工作空間的訪問。
|
||||
刪除您的帳號將永久移除您對所有工作空間的訪問。 此操作無法撤銷,您將失去所有工作空間權限,如您只想退出特定的團隊,應考慮退出個別的工作空間。 刪除您的帳號將永久移除您對所有工作空間的訪問。
|
||||
刪除您的帳號將永久移除您對所有工作空間的訪問。 此操作無法撤銷,您將失去所有工作空間權限,如您只想退出特定的團隊,應考慮退出個別的工作空間。
|
||||
</Warning> 刪除您的帳號將永久移除您對所有工作空間的訪問。
|
||||
</Warning> 刪除您的帳號將永久移除您對所有工作空間的訪問。
|
||||
刪除您的帳號將永久移除您對所有工作空間的訪問。 此操作無法撤銷,您將失去所有工作空間權限,如您只想退出特定的團隊,應考慮退出個別的工作空間。
|
||||
|
||||
</Warning>
|
||||
|
||||
要删除您的帐户:
|
||||
|
||||
@@ -152,6 +152,7 @@ export type AggregateChartConfiguration = {
|
||||
graphType: GraphType;
|
||||
label?: Maybe<Scalars['String']>;
|
||||
prefix?: Maybe<Scalars['String']>;
|
||||
ratioAggregateConfig?: Maybe<RatioAggregateConfig>;
|
||||
suffix?: Maybe<Scalars['String']>;
|
||||
timezone?: Maybe<Scalars['String']>;
|
||||
};
|
||||
@@ -3695,6 +3696,17 @@ export type QueueRetentionConfig = {
|
||||
failedMaxCount: Scalars['Float'];
|
||||
};
|
||||
|
||||
export type RatioAggregateConfig = {
|
||||
__typename?: 'RatioAggregateConfig';
|
||||
fieldMetadataId: Scalars['UUID'];
|
||||
optionValue: Scalars['String'];
|
||||
};
|
||||
|
||||
export type RatioAggregateConfigInput = {
|
||||
fieldMetadataId: Scalars['UUID'];
|
||||
optionValue: Scalars['String'];
|
||||
};
|
||||
|
||||
export type Relation = {
|
||||
__typename?: 'Relation';
|
||||
sourceFieldMetadata: Field;
|
||||
|
||||
@@ -152,6 +152,7 @@ export type AggregateChartConfiguration = {
|
||||
graphType: GraphType;
|
||||
label?: Maybe<Scalars['String']>;
|
||||
prefix?: Maybe<Scalars['String']>;
|
||||
ratioAggregateConfig?: Maybe<RatioAggregateConfig>;
|
||||
suffix?: Maybe<Scalars['String']>;
|
||||
timezone?: Maybe<Scalars['String']>;
|
||||
};
|
||||
@@ -3546,6 +3547,17 @@ export type QueueRetentionConfig = {
|
||||
failedMaxCount: Scalars['Float'];
|
||||
};
|
||||
|
||||
export type RatioAggregateConfig = {
|
||||
__typename?: 'RatioAggregateConfig';
|
||||
fieldMetadataId: Scalars['UUID'];
|
||||
optionValue: Scalars['String'];
|
||||
};
|
||||
|
||||
export type RatioAggregateConfigInput = {
|
||||
fieldMetadataId: Scalars['UUID'];
|
||||
optionValue: Scalars['String'];
|
||||
};
|
||||
|
||||
export type Relation = {
|
||||
__typename?: 'Relation';
|
||||
sourceFieldMetadata: Field;
|
||||
@@ -4825,7 +4837,7 @@ export type SearchQueryVariables = Exact<{
|
||||
|
||||
export type SearchQuery = { __typename?: 'Query', search: { __typename?: 'SearchResultConnection', edges: Array<{ __typename?: 'SearchResultEdge', cursor: string, node: { __typename?: 'SearchRecord', recordId: any, objectNameSingular: string, label: string, imageUrl?: string | null, tsRankCD: number, tsRank: number } }>, pageInfo: { __typename?: 'SearchResultPageInfo', hasNextPage: boolean, endCursor?: string | null } } };
|
||||
|
||||
export type PageLayoutWidgetFragmentFragment = { __typename?: 'PageLayoutWidget', id: any, title: string, type: WidgetType, objectMetadataId?: any | null, createdAt: string, updatedAt: string, deletedAt?: string | null, pageLayoutTabId: any, gridPosition: { __typename?: 'GridPosition', column: number, columnSpan: number, row: number, rowSpan: number }, configuration?: { __typename?: 'AggregateChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, label?: string | null, displayDataLabel?: boolean | null, format?: string | null, description?: string | null, filter?: any | null, prefix?: string | null, suffix?: string | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'BarChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, primaryAxisGroupByFieldMetadataId: any, primaryAxisGroupBySubFieldName?: string | null, primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity | null, primaryAxisOrderBy?: GraphOrderBy | null, secondaryAxisGroupByFieldMetadataId?: any | null, secondaryAxisGroupBySubFieldName?: string | null, secondaryAxisGroupByDateGranularity?: ObjectRecordGroupByDateGranularity | null, secondaryAxisOrderBy?: GraphOrderBy | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, displayLegend?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null, groupMode?: BarChartGroupMode | null, isCumulative?: boolean | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'GaugeChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, displayDataLabel?: boolean | null, color?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'IframeConfiguration', url?: string | null } | { __typename?: 'LineChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, primaryAxisGroupByFieldMetadataId: any, primaryAxisGroupBySubFieldName?: string | null, primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity | null, primaryAxisOrderBy?: GraphOrderBy | null, secondaryAxisGroupByFieldMetadataId?: any | null, secondaryAxisGroupBySubFieldName?: string | null, secondaryAxisGroupByDateGranularity?: ObjectRecordGroupByDateGranularity | null, secondaryAxisOrderBy?: GraphOrderBy | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, displayLegend?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null, isStacked?: boolean | null, isCumulative?: boolean | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'PieChartConfiguration', graphType: GraphType, groupByFieldMetadataId: any, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, groupBySubFieldName?: string | null, dateGranularity?: ObjectRecordGroupByDateGranularity | null, orderBy?: GraphOrderBy | null, displayDataLabel?: boolean | null, showCenterMetric?: boolean | null, displayLegend?: boolean | null, color?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'StandaloneRichTextConfiguration', body: { __typename?: 'RichTextV2Body', blocknote?: string | null, markdown?: string | null } } | null };
|
||||
export type PageLayoutWidgetFragmentFragment = { __typename?: 'PageLayoutWidget', id: any, title: string, type: WidgetType, objectMetadataId?: any | null, createdAt: string, updatedAt: string, deletedAt?: string | null, pageLayoutTabId: any, gridPosition: { __typename?: 'GridPosition', column: number, columnSpan: number, row: number, rowSpan: number }, configuration?: { __typename?: 'AggregateChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, label?: string | null, displayDataLabel?: boolean | null, format?: string | null, description?: string | null, filter?: any | null, prefix?: string | null, suffix?: string | null, timezone?: string | null, firstDayOfTheWeek?: number | null, ratioAggregateConfig?: { __typename?: 'RatioAggregateConfig', fieldMetadataId: any, optionValue: string } | null } | { __typename?: 'BarChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, primaryAxisGroupByFieldMetadataId: any, primaryAxisGroupBySubFieldName?: string | null, primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity | null, primaryAxisOrderBy?: GraphOrderBy | null, secondaryAxisGroupByFieldMetadataId?: any | null, secondaryAxisGroupBySubFieldName?: string | null, secondaryAxisGroupByDateGranularity?: ObjectRecordGroupByDateGranularity | null, secondaryAxisOrderBy?: GraphOrderBy | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, displayLegend?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null, groupMode?: BarChartGroupMode | null, isCumulative?: boolean | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'GaugeChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, displayDataLabel?: boolean | null, color?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'IframeConfiguration', url?: string | null } | { __typename?: 'LineChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, primaryAxisGroupByFieldMetadataId: any, primaryAxisGroupBySubFieldName?: string | null, primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity | null, primaryAxisOrderBy?: GraphOrderBy | null, secondaryAxisGroupByFieldMetadataId?: any | null, secondaryAxisGroupBySubFieldName?: string | null, secondaryAxisGroupByDateGranularity?: ObjectRecordGroupByDateGranularity | null, secondaryAxisOrderBy?: GraphOrderBy | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, displayLegend?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null, isStacked?: boolean | null, isCumulative?: boolean | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'PieChartConfiguration', graphType: GraphType, groupByFieldMetadataId: any, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, groupBySubFieldName?: string | null, dateGranularity?: ObjectRecordGroupByDateGranularity | null, orderBy?: GraphOrderBy | null, displayDataLabel?: boolean | null, showCenterMetric?: boolean | null, displayLegend?: boolean | null, color?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'StandaloneRichTextConfiguration', body: { __typename?: 'RichTextV2Body', blocknote?: string | null, markdown?: string | null } } | null };
|
||||
|
||||
export type UpdatePageLayoutWithTabsAndWidgetsMutationVariables = Exact<{
|
||||
id: Scalars['String'];
|
||||
@@ -4833,7 +4845,7 @@ export type UpdatePageLayoutWithTabsAndWidgetsMutationVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type UpdatePageLayoutWithTabsAndWidgetsMutation = { __typename?: 'Mutation', updatePageLayoutWithTabsAndWidgets: { __typename?: 'PageLayout', id: any, name: string, type: PageLayoutType, objectMetadataId?: any | null, createdAt: string, updatedAt: string, deletedAt?: string | null, tabs?: Array<{ __typename?: 'PageLayoutTab', id: any, title: string, position: number, pageLayoutId: any, createdAt: string, updatedAt: string, widgets?: Array<{ __typename?: 'PageLayoutWidget', id: any, title: string, type: WidgetType, objectMetadataId?: any | null, createdAt: string, updatedAt: string, deletedAt?: string | null, pageLayoutTabId: any, gridPosition: { __typename?: 'GridPosition', column: number, columnSpan: number, row: number, rowSpan: number }, configuration?: { __typename?: 'AggregateChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, label?: string | null, displayDataLabel?: boolean | null, format?: string | null, description?: string | null, filter?: any | null, prefix?: string | null, suffix?: string | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'BarChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, primaryAxisGroupByFieldMetadataId: any, primaryAxisGroupBySubFieldName?: string | null, primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity | null, primaryAxisOrderBy?: GraphOrderBy | null, secondaryAxisGroupByFieldMetadataId?: any | null, secondaryAxisGroupBySubFieldName?: string | null, secondaryAxisGroupByDateGranularity?: ObjectRecordGroupByDateGranularity | null, secondaryAxisOrderBy?: GraphOrderBy | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, displayLegend?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null, groupMode?: BarChartGroupMode | null, isCumulative?: boolean | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'GaugeChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, displayDataLabel?: boolean | null, color?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'IframeConfiguration', url?: string | null } | { __typename?: 'LineChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, primaryAxisGroupByFieldMetadataId: any, primaryAxisGroupBySubFieldName?: string | null, primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity | null, primaryAxisOrderBy?: GraphOrderBy | null, secondaryAxisGroupByFieldMetadataId?: any | null, secondaryAxisGroupBySubFieldName?: string | null, secondaryAxisGroupByDateGranularity?: ObjectRecordGroupByDateGranularity | null, secondaryAxisOrderBy?: GraphOrderBy | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, displayLegend?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null, isStacked?: boolean | null, isCumulative?: boolean | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'PieChartConfiguration', graphType: GraphType, groupByFieldMetadataId: any, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, groupBySubFieldName?: string | null, dateGranularity?: ObjectRecordGroupByDateGranularity | null, orderBy?: GraphOrderBy | null, displayDataLabel?: boolean | null, showCenterMetric?: boolean | null, displayLegend?: boolean | null, color?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'StandaloneRichTextConfiguration', body: { __typename?: 'RichTextV2Body', blocknote?: string | null, markdown?: string | null } } | null }> | null }> | null } };
|
||||
export type UpdatePageLayoutWithTabsAndWidgetsMutation = { __typename?: 'Mutation', updatePageLayoutWithTabsAndWidgets: { __typename?: 'PageLayout', id: any, name: string, type: PageLayoutType, objectMetadataId?: any | null, createdAt: string, updatedAt: string, deletedAt?: string | null, tabs?: Array<{ __typename?: 'PageLayoutTab', id: any, title: string, position: number, pageLayoutId: any, createdAt: string, updatedAt: string, widgets?: Array<{ __typename?: 'PageLayoutWidget', id: any, title: string, type: WidgetType, objectMetadataId?: any | null, createdAt: string, updatedAt: string, deletedAt?: string | null, pageLayoutTabId: any, gridPosition: { __typename?: 'GridPosition', column: number, columnSpan: number, row: number, rowSpan: number }, configuration?: { __typename?: 'AggregateChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, label?: string | null, displayDataLabel?: boolean | null, format?: string | null, description?: string | null, filter?: any | null, prefix?: string | null, suffix?: string | null, timezone?: string | null, firstDayOfTheWeek?: number | null, ratioAggregateConfig?: { __typename?: 'RatioAggregateConfig', fieldMetadataId: any, optionValue: string } | null } | { __typename?: 'BarChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, primaryAxisGroupByFieldMetadataId: any, primaryAxisGroupBySubFieldName?: string | null, primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity | null, primaryAxisOrderBy?: GraphOrderBy | null, secondaryAxisGroupByFieldMetadataId?: any | null, secondaryAxisGroupBySubFieldName?: string | null, secondaryAxisGroupByDateGranularity?: ObjectRecordGroupByDateGranularity | null, secondaryAxisOrderBy?: GraphOrderBy | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, displayLegend?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null, groupMode?: BarChartGroupMode | null, isCumulative?: boolean | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'GaugeChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, displayDataLabel?: boolean | null, color?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'IframeConfiguration', url?: string | null } | { __typename?: 'LineChartConfiguration', graphType: GraphType, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, primaryAxisGroupByFieldMetadataId: any, primaryAxisGroupBySubFieldName?: string | null, primaryAxisDateGranularity?: ObjectRecordGroupByDateGranularity | null, primaryAxisOrderBy?: GraphOrderBy | null, secondaryAxisGroupByFieldMetadataId?: any | null, secondaryAxisGroupBySubFieldName?: string | null, secondaryAxisGroupByDateGranularity?: ObjectRecordGroupByDateGranularity | null, secondaryAxisOrderBy?: GraphOrderBy | null, omitNullValues?: boolean | null, axisNameDisplay?: AxisNameDisplay | null, displayDataLabel?: boolean | null, displayLegend?: boolean | null, rangeMin?: number | null, rangeMax?: number | null, color?: string | null, description?: string | null, filter?: any | null, isStacked?: boolean | null, isCumulative?: boolean | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'PieChartConfiguration', graphType: GraphType, groupByFieldMetadataId: any, aggregateFieldMetadataId: any, aggregateOperation: AggregateOperations, groupBySubFieldName?: string | null, dateGranularity?: ObjectRecordGroupByDateGranularity | null, orderBy?: GraphOrderBy | null, displayDataLabel?: boolean | null, showCenterMetric?: boolean | null, displayLegend?: boolean | null, color?: string | null, description?: string | null, filter?: any | null, timezone?: string | null, firstDayOfTheWeek?: number | null } | { __typename?: 'StandaloneRichTextConfiguration', body: { __typename?: 'RichTextV2Body', blocknote?: string | null, markdown?: string | null } } | null }> | null }> | null } };
|
||||
|
||||
export type OnDbEventSubscriptionVariables = Exact<{
|
||||
input: OnDbEventInput;
|
||||
@@ -5226,6 +5238,10 @@ export const PageLayoutWidgetFragmentFragmentDoc = gql`
|
||||
suffix
|
||||
timezone
|
||||
firstDayOfTheWeek
|
||||
ratioAggregateConfig {
|
||||
fieldMetadataId
|
||||
optionValue
|
||||
}
|
||||
}
|
||||
... on GaugeChartConfiguration {
|
||||
graphType
|
||||
|
||||
@@ -1063,6 +1063,11 @@ msgstr "Laat oplaai van lêers en aanhegsels toe"
|
||||
msgid "Allow users to sign in with an email and password."
|
||||
msgstr "Laat toe dat gebruikers aanmeld met 'n e-pos en wagwoord."
|
||||
|
||||
#. js-lingui-id: Vw8l6h
|
||||
#: src/modules/workflow/workflow-steps/workflow-actions/http-request-action/components/HttpRequestExecutionResult.tsx
|
||||
msgid "An error occurred"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: VD5bNu
|
||||
#: src/modules/auth/sign-in-up/hooks/useSignInUp.ts
|
||||
msgid "An error occurred while checking user existence"
|
||||
@@ -1076,9 +1081,6 @@ msgstr "9n Fout het voorgekom terwyl jou boodskap verwerk is"
|
||||
#. js-lingui-id: XyOToQ
|
||||
#: src/utils/get-error-message-from-apollo-error.util.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
@@ -3811,8 +3813,12 @@ msgstr "Voer 'n getal tussen 0 en 23 in"
|
||||
msgid "Enter number between 0 and 59"
|
||||
msgstr "Voer 'n getal tussen 0 en 59 in"
|
||||
|
||||
#. js-lingui-id: ahwn7G
|
||||
#. js-lingui-id: uhDeRP
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Enter number between 1 and 60"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: ahwn7G
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Enter number greater than 1"
|
||||
@@ -4384,6 +4390,8 @@ msgstr "Kon nie beeld opgelaai word nie: "
|
||||
#. js-lingui-id: ocUvR+
|
||||
#: src/modules/settings/data-model/fields/forms/boolean/constants/BooleanDataModelSelectOptions.ts
|
||||
#: src/modules/object-record/record-board/record-board-column/utils/getAggregateOperationShortLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "False"
|
||||
msgstr "Vals"
|
||||
|
||||
@@ -6015,6 +6023,11 @@ msgstr "Min"
|
||||
msgid "Min range"
|
||||
msgstr "Minimum reeks"
|
||||
|
||||
#. js-lingui-id: G6wJK8
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Minute value cannot exceed 60. For intervals greater than 60 minutes, use the \"Hours\" trigger type or a custom cron expression"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: agRWc1
|
||||
#: src/modules/workflow/workflow-steps/workflow-actions/delay-actions/components/WorkflowEditActionDelay.tsx
|
||||
msgid "Minutes"
|
||||
@@ -6428,6 +6441,12 @@ msgstr "Geen konfig veranderlikes pas by jou huidige filters nie. Probeer jou fi
|
||||
msgid "No country"
|
||||
msgstr "Geen land nie"
|
||||
|
||||
#. js-lingui-id: plhHQt
|
||||
#: src/modules/page-layout/widgets/graph/graphWidgetPieChart/components/PieChartCenterMetricLayer.tsx
|
||||
#: src/modules/page-layout/widgets/graph/components/NoDataLayer.tsx
|
||||
msgid "No data"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: B6C0XJ
|
||||
#: src/modules/page-layout/widgets/components/PageLayoutWidgetNoDataDisplay.tsx
|
||||
msgid "No Data"
|
||||
@@ -7071,6 +7090,11 @@ msgstr ""
|
||||
msgid "Page"
|
||||
msgstr "Bladsy"
|
||||
|
||||
#. js-lingui-id: SZgFyi
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "page layout"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: HFlGEK
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "page layout tab"
|
||||
@@ -7517,6 +7541,14 @@ msgstr "Wagry: {queueName}"
|
||||
msgid "Quick model for routing decisions"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: iaocTt
|
||||
#: src/modules/page-layout/widgets/graph/hooks/useGraphWidgetAggregateQuery.ts
|
||||
#: src/modules/page-layout/widgets/graph/hooks/useGraphWidgetAggregateQuery.ts
|
||||
#: src/modules/page-layout/widgets/graph/graphWidgetAggregateChart/utils/getAggregateChartOperationLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
msgid "Ratio"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: BT7pY+
|
||||
#: src/modules/settings/profile/components/SetOrChangePassword.tsx
|
||||
msgid "Receive an email containing password set link"
|
||||
@@ -8103,6 +8135,11 @@ msgstr "Soek vir 'n objek"
|
||||
msgid "Search operations"
|
||||
msgstr "Soek werksaamhede"
|
||||
|
||||
#. js-lingui-id: 996xRf
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "Search options"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: IMeaSJ
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
@@ -8332,6 +8369,11 @@ msgstr "Kies die gebeure wat jy wil stuur na hierdie eindpunt"
|
||||
msgid "Select the sheet to use"
|
||||
msgstr "Kies die bladsy om te gebruik"
|
||||
|
||||
#. js-lingui-id: iWf0Zj
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "Select value"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: AXTJAW
|
||||
#: src/pages/settings/profile/appearance/components/SettingsExperience.tsx
|
||||
msgid "Select your preferred language"
|
||||
@@ -9493,6 +9535,8 @@ msgstr "Sneller die funksie met Http-versoek"
|
||||
#. js-lingui-id: c+xCSz
|
||||
#: src/modules/settings/data-model/fields/forms/boolean/constants/BooleanDataModelSelectOptions.ts
|
||||
#: src/modules/object-record/record-board/record-board-column/utils/getAggregateOperationShortLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "True"
|
||||
msgstr "Waar"
|
||||
|
||||
|
||||
@@ -1063,6 +1063,11 @@ msgstr "السماح برفع الملفات والمرفقات"
|
||||
msgid "Allow users to sign in with an email and password."
|
||||
msgstr "السماح للمستخدمين بتسجيل الدخول باستخدام البريد الإلكتروني وكلمة المرور."
|
||||
|
||||
#. js-lingui-id: Vw8l6h
|
||||
#: src/modules/workflow/workflow-steps/workflow-actions/http-request-action/components/HttpRequestExecutionResult.tsx
|
||||
msgid "An error occurred"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: VD5bNu
|
||||
#: src/modules/auth/sign-in-up/hooks/useSignInUp.ts
|
||||
msgid "An error occurred while checking user existence"
|
||||
@@ -1076,9 +1081,6 @@ msgstr "حدث خطأ أثناء معالجة رسالتك"
|
||||
#. js-lingui-id: XyOToQ
|
||||
#: src/utils/get-error-message-from-apollo-error.util.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
@@ -3811,8 +3813,12 @@ msgstr "أدخل رقمًا بين 0 و 23"
|
||||
msgid "Enter number between 0 and 59"
|
||||
msgstr "أدخل رقمًا بين 0 و 59"
|
||||
|
||||
#. js-lingui-id: ahwn7G
|
||||
#. js-lingui-id: uhDeRP
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Enter number between 1 and 60"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: ahwn7G
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Enter number greater than 1"
|
||||
@@ -4384,6 +4390,8 @@ msgstr "فشل في تحميل الصورة: "
|
||||
#. js-lingui-id: ocUvR+
|
||||
#: src/modules/settings/data-model/fields/forms/boolean/constants/BooleanDataModelSelectOptions.ts
|
||||
#: src/modules/object-record/record-board/record-board-column/utils/getAggregateOperationShortLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "False"
|
||||
msgstr "خاطئ"
|
||||
|
||||
@@ -6015,6 +6023,11 @@ msgstr "الأدنى"
|
||||
msgid "Min range"
|
||||
msgstr "الحد الأدنى للمدى"
|
||||
|
||||
#. js-lingui-id: G6wJK8
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Minute value cannot exceed 60. For intervals greater than 60 minutes, use the \"Hours\" trigger type or a custom cron expression"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: agRWc1
|
||||
#: src/modules/workflow/workflow-steps/workflow-actions/delay-actions/components/WorkflowEditActionDelay.tsx
|
||||
msgid "Minutes"
|
||||
@@ -6428,6 +6441,12 @@ msgstr "لا توجد متغيرات تكوين مطابقة للمرشحات ا
|
||||
msgid "No country"
|
||||
msgstr "لا دولة"
|
||||
|
||||
#. js-lingui-id: plhHQt
|
||||
#: src/modules/page-layout/widgets/graph/graphWidgetPieChart/components/PieChartCenterMetricLayer.tsx
|
||||
#: src/modules/page-layout/widgets/graph/components/NoDataLayer.tsx
|
||||
msgid "No data"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: B6C0XJ
|
||||
#: src/modules/page-layout/widgets/components/PageLayoutWidgetNoDataDisplay.tsx
|
||||
msgid "No Data"
|
||||
@@ -7071,6 +7090,11 @@ msgstr ""
|
||||
msgid "Page"
|
||||
msgstr "الصفحة"
|
||||
|
||||
#. js-lingui-id: SZgFyi
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "page layout"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: HFlGEK
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "page layout tab"
|
||||
@@ -7517,6 +7541,14 @@ msgstr "الطابور: {queueName}"
|
||||
msgid "Quick model for routing decisions"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: iaocTt
|
||||
#: src/modules/page-layout/widgets/graph/hooks/useGraphWidgetAggregateQuery.ts
|
||||
#: src/modules/page-layout/widgets/graph/hooks/useGraphWidgetAggregateQuery.ts
|
||||
#: src/modules/page-layout/widgets/graph/graphWidgetAggregateChart/utils/getAggregateChartOperationLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
msgid "Ratio"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: BT7pY+
|
||||
#: src/modules/settings/profile/components/SetOrChangePassword.tsx
|
||||
msgid "Receive an email containing password set link"
|
||||
@@ -8103,6 +8135,11 @@ msgstr "ابحث عن الكائنات"
|
||||
msgid "Search operations"
|
||||
msgstr "ابحث عن العمليات"
|
||||
|
||||
#. js-lingui-id: 996xRf
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "Search options"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: IMeaSJ
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
@@ -8332,6 +8369,11 @@ msgstr "\\\\"
|
||||
msgid "Select the sheet to use"
|
||||
msgstr "اختر الورقة لاستخدامها"
|
||||
|
||||
#. js-lingui-id: iWf0Zj
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "Select value"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: AXTJAW
|
||||
#: src/pages/settings/profile/appearance/components/SettingsExperience.tsx
|
||||
msgid "Select your preferred language"
|
||||
@@ -9493,6 +9535,8 @@ msgstr "تشغيل الوظيفة عند طلب http"
|
||||
#. js-lingui-id: c+xCSz
|
||||
#: src/modules/settings/data-model/fields/forms/boolean/constants/BooleanDataModelSelectOptions.ts
|
||||
#: src/modules/object-record/record-board/record-board-column/utils/getAggregateOperationShortLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "True"
|
||||
msgstr "صحيح"
|
||||
|
||||
|
||||
@@ -1063,6 +1063,11 @@ msgstr "Permetre carregar arxius i adjunts"
|
||||
msgid "Allow users to sign in with an email and password."
|
||||
msgstr "Permetre als usuaris iniciar sessió amb un correu electrònic i contrasenya."
|
||||
|
||||
#. js-lingui-id: Vw8l6h
|
||||
#: src/modules/workflow/workflow-steps/workflow-actions/http-request-action/components/HttpRequestExecutionResult.tsx
|
||||
msgid "An error occurred"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: VD5bNu
|
||||
#: src/modules/auth/sign-in-up/hooks/useSignInUp.ts
|
||||
msgid "An error occurred while checking user existence"
|
||||
@@ -1076,9 +1081,6 @@ msgstr "S'ha produït un error mentre es processava el teu missatge"
|
||||
#. js-lingui-id: XyOToQ
|
||||
#: src/utils/get-error-message-from-apollo-error.util.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
@@ -3811,8 +3813,12 @@ msgstr "Introdueix un número entre 0 i 23"
|
||||
msgid "Enter number between 0 and 59"
|
||||
msgstr "Introdueix un número entre 0 i 59"
|
||||
|
||||
#. js-lingui-id: ahwn7G
|
||||
#. js-lingui-id: uhDeRP
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Enter number between 1 and 60"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: ahwn7G
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Enter number greater than 1"
|
||||
@@ -4384,6 +4390,8 @@ msgstr "Error en pujar la imatge: "
|
||||
#. js-lingui-id: ocUvR+
|
||||
#: src/modules/settings/data-model/fields/forms/boolean/constants/BooleanDataModelSelectOptions.ts
|
||||
#: src/modules/object-record/record-board/record-board-column/utils/getAggregateOperationShortLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "False"
|
||||
msgstr "Fals"
|
||||
|
||||
@@ -6015,6 +6023,11 @@ msgstr "Mínim"
|
||||
msgid "Min range"
|
||||
msgstr "Rang mínim"
|
||||
|
||||
#. js-lingui-id: G6wJK8
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Minute value cannot exceed 60. For intervals greater than 60 minutes, use the \"Hours\" trigger type or a custom cron expression"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: agRWc1
|
||||
#: src/modules/workflow/workflow-steps/workflow-actions/delay-actions/components/WorkflowEditActionDelay.tsx
|
||||
msgid "Minutes"
|
||||
@@ -6428,6 +6441,12 @@ msgstr "No hi ha variables de configuració que coincideixen amb els teus filtre
|
||||
msgid "No country"
|
||||
msgstr "Sense país"
|
||||
|
||||
#. js-lingui-id: plhHQt
|
||||
#: src/modules/page-layout/widgets/graph/graphWidgetPieChart/components/PieChartCenterMetricLayer.tsx
|
||||
#: src/modules/page-layout/widgets/graph/components/NoDataLayer.tsx
|
||||
msgid "No data"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: B6C0XJ
|
||||
#: src/modules/page-layout/widgets/components/PageLayoutWidgetNoDataDisplay.tsx
|
||||
msgid "No Data"
|
||||
@@ -7071,6 +7090,11 @@ msgstr ""
|
||||
msgid "Page"
|
||||
msgstr "Pàgina"
|
||||
|
||||
#. js-lingui-id: SZgFyi
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "page layout"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: HFlGEK
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "page layout tab"
|
||||
@@ -7517,6 +7541,14 @@ msgstr "Cua: {queueName}"
|
||||
msgid "Quick model for routing decisions"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: iaocTt
|
||||
#: src/modules/page-layout/widgets/graph/hooks/useGraphWidgetAggregateQuery.ts
|
||||
#: src/modules/page-layout/widgets/graph/hooks/useGraphWidgetAggregateQuery.ts
|
||||
#: src/modules/page-layout/widgets/graph/graphWidgetAggregateChart/utils/getAggregateChartOperationLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
msgid "Ratio"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: BT7pY+
|
||||
#: src/modules/settings/profile/components/SetOrChangePassword.tsx
|
||||
msgid "Receive an email containing password set link"
|
||||
@@ -8103,6 +8135,11 @@ msgstr "Cerca objectes"
|
||||
msgid "Search operations"
|
||||
msgstr "Cerca operacions"
|
||||
|
||||
#. js-lingui-id: 996xRf
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "Search options"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: IMeaSJ
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
@@ -8332,6 +8369,11 @@ msgstr "Selecciona els esdeveniments que vols enviar a aquest endpoint"
|
||||
msgid "Select the sheet to use"
|
||||
msgstr "Selecciona el full a utilitzar"
|
||||
|
||||
#. js-lingui-id: iWf0Zj
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "Select value"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: AXTJAW
|
||||
#: src/pages/settings/profile/appearance/components/SettingsExperience.tsx
|
||||
msgid "Select your preferred language"
|
||||
@@ -9493,6 +9535,8 @@ msgstr "Desencadena la funció amb una sol·licitud Http"
|
||||
#. js-lingui-id: c+xCSz
|
||||
#: src/modules/settings/data-model/fields/forms/boolean/constants/BooleanDataModelSelectOptions.ts
|
||||
#: src/modules/object-record/record-board/record-board-column/utils/getAggregateOperationShortLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "True"
|
||||
msgstr "Cert"
|
||||
|
||||
|
||||
@@ -1063,6 +1063,11 @@ msgstr "Povolit nahrávání souborů a příloh"
|
||||
msgid "Allow users to sign in with an email and password."
|
||||
msgstr "Povolit uživatelům přihlášení pomocí e-mailu a hesla."
|
||||
|
||||
#. js-lingui-id: Vw8l6h
|
||||
#: src/modules/workflow/workflow-steps/workflow-actions/http-request-action/components/HttpRequestExecutionResult.tsx
|
||||
msgid "An error occurred"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: VD5bNu
|
||||
#: src/modules/auth/sign-in-up/hooks/useSignInUp.ts
|
||||
msgid "An error occurred while checking user existence"
|
||||
@@ -1076,9 +1081,6 @@ msgstr "Při zpracování vaší zprávy došlo k chybě"
|
||||
#. js-lingui-id: XyOToQ
|
||||
#: src/utils/get-error-message-from-apollo-error.util.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
@@ -3811,8 +3813,12 @@ msgstr "Zadejte číslo mezi 0 a 23"
|
||||
msgid "Enter number between 0 and 59"
|
||||
msgstr "Zadejte číslo mezi 0 a 59"
|
||||
|
||||
#. js-lingui-id: ahwn7G
|
||||
#. js-lingui-id: uhDeRP
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Enter number between 1 and 60"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: ahwn7G
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Enter number greater than 1"
|
||||
@@ -4384,6 +4390,8 @@ msgstr "Nelze nahrát obrázek: "
|
||||
#. js-lingui-id: ocUvR+
|
||||
#: src/modules/settings/data-model/fields/forms/boolean/constants/BooleanDataModelSelectOptions.ts
|
||||
#: src/modules/object-record/record-board/record-board-column/utils/getAggregateOperationShortLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "False"
|
||||
msgstr "Nepravdivé"
|
||||
|
||||
@@ -6015,6 +6023,11 @@ msgstr "Minimální"
|
||||
msgid "Min range"
|
||||
msgstr "Minimální rozsah"
|
||||
|
||||
#. js-lingui-id: G6wJK8
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Minute value cannot exceed 60. For intervals greater than 60 minutes, use the \"Hours\" trigger type or a custom cron expression"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: agRWc1
|
||||
#: src/modules/workflow/workflow-steps/workflow-actions/delay-actions/components/WorkflowEditActionDelay.tsx
|
||||
msgid "Minutes"
|
||||
@@ -6428,6 +6441,12 @@ msgstr "Žádné konfigurační proměnné neodpovídají vašim současným fil
|
||||
msgid "No country"
|
||||
msgstr "Žádná země"
|
||||
|
||||
#. js-lingui-id: plhHQt
|
||||
#: src/modules/page-layout/widgets/graph/graphWidgetPieChart/components/PieChartCenterMetricLayer.tsx
|
||||
#: src/modules/page-layout/widgets/graph/components/NoDataLayer.tsx
|
||||
msgid "No data"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: B6C0XJ
|
||||
#: src/modules/page-layout/widgets/components/PageLayoutWidgetNoDataDisplay.tsx
|
||||
msgid "No Data"
|
||||
@@ -7071,6 +7090,11 @@ msgstr ""
|
||||
msgid "Page"
|
||||
msgstr "Stránka"
|
||||
|
||||
#. js-lingui-id: SZgFyi
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "page layout"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: HFlGEK
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "page layout tab"
|
||||
@@ -7517,6 +7541,14 @@ msgstr "Fronta: {queueName}"
|
||||
msgid "Quick model for routing decisions"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: iaocTt
|
||||
#: src/modules/page-layout/widgets/graph/hooks/useGraphWidgetAggregateQuery.ts
|
||||
#: src/modules/page-layout/widgets/graph/hooks/useGraphWidgetAggregateQuery.ts
|
||||
#: src/modules/page-layout/widgets/graph/graphWidgetAggregateChart/utils/getAggregateChartOperationLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
msgid "Ratio"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: BT7pY+
|
||||
#: src/modules/settings/profile/components/SetOrChangePassword.tsx
|
||||
msgid "Receive an email containing password set link"
|
||||
@@ -8103,6 +8135,11 @@ msgstr "Hledat objekty"
|
||||
msgid "Search operations"
|
||||
msgstr "Hledat operace"
|
||||
|
||||
#. js-lingui-id: 996xRf
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "Search options"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: IMeaSJ
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
@@ -8332,6 +8369,11 @@ msgstr "Vyberte události, které chcete poslat na tento koncový bod"
|
||||
msgid "Select the sheet to use"
|
||||
msgstr "Vyberte list k použití"
|
||||
|
||||
#. js-lingui-id: iWf0Zj
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "Select value"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: AXTJAW
|
||||
#: src/pages/settings/profile/appearance/components/SettingsExperience.tsx
|
||||
msgid "Select your preferred language"
|
||||
@@ -9493,6 +9535,8 @@ msgstr "Spouští funkci pomocí Http požadavku"
|
||||
#. js-lingui-id: c+xCSz
|
||||
#: src/modules/settings/data-model/fields/forms/boolean/constants/BooleanDataModelSelectOptions.ts
|
||||
#: src/modules/object-record/record-board/record-board-column/utils/getAggregateOperationShortLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "True"
|
||||
msgstr "Pravda"
|
||||
|
||||
|
||||
@@ -1063,6 +1063,11 @@ msgstr "Tillad upload af filer og vedhæftninger"
|
||||
msgid "Allow users to sign in with an email and password."
|
||||
msgstr "Tillad brugere at logge ind med en e-mail og password."
|
||||
|
||||
#. js-lingui-id: Vw8l6h
|
||||
#: src/modules/workflow/workflow-steps/workflow-actions/http-request-action/components/HttpRequestExecutionResult.tsx
|
||||
msgid "An error occurred"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: VD5bNu
|
||||
#: src/modules/auth/sign-in-up/hooks/useSignInUp.ts
|
||||
msgid "An error occurred while checking user existence"
|
||||
@@ -1076,9 +1081,6 @@ msgstr "Der opstod en fejl under behandlingen af din meddelelse"
|
||||
#. js-lingui-id: XyOToQ
|
||||
#: src/utils/get-error-message-from-apollo-error.util.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
@@ -3811,8 +3813,12 @@ msgstr "Indtast et tal mellem 0 og 23"
|
||||
msgid "Enter number between 0 and 59"
|
||||
msgstr "Indtast et tal mellem 0 og 59"
|
||||
|
||||
#. js-lingui-id: ahwn7G
|
||||
#. js-lingui-id: uhDeRP
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Enter number between 1 and 60"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: ahwn7G
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Enter number greater than 1"
|
||||
@@ -4384,6 +4390,8 @@ msgstr "Kunne ikke uploade billede: "
|
||||
#. js-lingui-id: ocUvR+
|
||||
#: src/modules/settings/data-model/fields/forms/boolean/constants/BooleanDataModelSelectOptions.ts
|
||||
#: src/modules/object-record/record-board/record-board-column/utils/getAggregateOperationShortLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "False"
|
||||
msgstr "Falsk"
|
||||
|
||||
@@ -6015,6 +6023,11 @@ msgstr "Min."
|
||||
msgid "Min range"
|
||||
msgstr "Minimumsgrænse"
|
||||
|
||||
#. js-lingui-id: G6wJK8
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Minute value cannot exceed 60. For intervals greater than 60 minutes, use the \"Hours\" trigger type or a custom cron expression"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: agRWc1
|
||||
#: src/modules/workflow/workflow-steps/workflow-actions/delay-actions/components/WorkflowEditActionDelay.tsx
|
||||
msgid "Minutes"
|
||||
@@ -6428,6 +6441,12 @@ msgstr "Ingen konfigurationsvariabler matcher dine nuværende filtre. Prøv at j
|
||||
msgid "No country"
|
||||
msgstr "Intet land"
|
||||
|
||||
#. js-lingui-id: plhHQt
|
||||
#: src/modules/page-layout/widgets/graph/graphWidgetPieChart/components/PieChartCenterMetricLayer.tsx
|
||||
#: src/modules/page-layout/widgets/graph/components/NoDataLayer.tsx
|
||||
msgid "No data"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: B6C0XJ
|
||||
#: src/modules/page-layout/widgets/components/PageLayoutWidgetNoDataDisplay.tsx
|
||||
msgid "No Data"
|
||||
@@ -7071,6 +7090,11 @@ msgstr ""
|
||||
msgid "Page"
|
||||
msgstr "Side"
|
||||
|
||||
#. js-lingui-id: SZgFyi
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "page layout"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: HFlGEK
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "page layout tab"
|
||||
@@ -7517,6 +7541,14 @@ msgstr "Kø: {queueName}"
|
||||
msgid "Quick model for routing decisions"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: iaocTt
|
||||
#: src/modules/page-layout/widgets/graph/hooks/useGraphWidgetAggregateQuery.ts
|
||||
#: src/modules/page-layout/widgets/graph/hooks/useGraphWidgetAggregateQuery.ts
|
||||
#: src/modules/page-layout/widgets/graph/graphWidgetAggregateChart/utils/getAggregateChartOperationLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
msgid "Ratio"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: BT7pY+
|
||||
#: src/modules/settings/profile/components/SetOrChangePassword.tsx
|
||||
msgid "Receive an email containing password set link"
|
||||
@@ -8103,6 +8135,11 @@ msgstr "Søg objekter"
|
||||
msgid "Search operations"
|
||||
msgstr "Søg operationer"
|
||||
|
||||
#. js-lingui-id: 996xRf
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "Search options"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: IMeaSJ
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
@@ -8332,6 +8369,11 @@ msgstr "Vælg de begivenheder du ønsker at sende til dette endepunkt"
|
||||
msgid "Select the sheet to use"
|
||||
msgstr "Vælg arket til brug"
|
||||
|
||||
#. js-lingui-id: iWf0Zj
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "Select value"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: AXTJAW
|
||||
#: src/pages/settings/profile/appearance/components/SettingsExperience.tsx
|
||||
msgid "Select your preferred language"
|
||||
@@ -9495,6 +9537,8 @@ msgstr "Udløser funktionen med Http-anmodning"
|
||||
#. js-lingui-id: c+xCSz
|
||||
#: src/modules/settings/data-model/fields/forms/boolean/constants/BooleanDataModelSelectOptions.ts
|
||||
#: src/modules/object-record/record-board/record-board-column/utils/getAggregateOperationShortLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "True"
|
||||
msgstr "Sand"
|
||||
|
||||
|
||||
@@ -1063,6 +1063,11 @@ msgstr "Hochladen von Dateien und Anhängen erlauben"
|
||||
msgid "Allow users to sign in with an email and password."
|
||||
msgstr "Benutzern erlauben, sich mit E-Mail und Passwort anzumelden."
|
||||
|
||||
#. js-lingui-id: Vw8l6h
|
||||
#: src/modules/workflow/workflow-steps/workflow-actions/http-request-action/components/HttpRequestExecutionResult.tsx
|
||||
msgid "An error occurred"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: VD5bNu
|
||||
#: src/modules/auth/sign-in-up/hooks/useSignInUp.ts
|
||||
msgid "An error occurred while checking user existence"
|
||||
@@ -1076,9 +1081,6 @@ msgstr "Ein Fehler ist bei der Verarbeitung Ihrer Nachricht aufgetreten"
|
||||
#. js-lingui-id: XyOToQ
|
||||
#: src/utils/get-error-message-from-apollo-error.util.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
@@ -3811,8 +3813,12 @@ msgstr "Geben Sie eine Zahl zwischen 0 und 23 ein"
|
||||
msgid "Enter number between 0 and 59"
|
||||
msgstr "Geben Sie eine Zahl zwischen 0 und 59 ein"
|
||||
|
||||
#. js-lingui-id: ahwn7G
|
||||
#. js-lingui-id: uhDeRP
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Enter number between 1 and 60"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: ahwn7G
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Enter number greater than 1"
|
||||
@@ -4384,6 +4390,8 @@ msgstr "Fehler beim Hochladen des Bildes: "
|
||||
#. js-lingui-id: ocUvR+
|
||||
#: src/modules/settings/data-model/fields/forms/boolean/constants/BooleanDataModelSelectOptions.ts
|
||||
#: src/modules/object-record/record-board/record-board-column/utils/getAggregateOperationShortLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "False"
|
||||
msgstr "Falsch"
|
||||
|
||||
@@ -6015,6 +6023,11 @@ msgstr "Min"
|
||||
msgid "Min range"
|
||||
msgstr "Minimaler Bereich"
|
||||
|
||||
#. js-lingui-id: G6wJK8
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Minute value cannot exceed 60. For intervals greater than 60 minutes, use the \"Hours\" trigger type or a custom cron expression"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: agRWc1
|
||||
#: src/modules/workflow/workflow-steps/workflow-actions/delay-actions/components/WorkflowEditActionDelay.tsx
|
||||
msgid "Minutes"
|
||||
@@ -6428,6 +6441,12 @@ msgstr "Keine Konfigurationsvariablen entsprechen Ihren aktuellen Filtern. Versu
|
||||
msgid "No country"
|
||||
msgstr "Kein Land"
|
||||
|
||||
#. js-lingui-id: plhHQt
|
||||
#: src/modules/page-layout/widgets/graph/graphWidgetPieChart/components/PieChartCenterMetricLayer.tsx
|
||||
#: src/modules/page-layout/widgets/graph/components/NoDataLayer.tsx
|
||||
msgid "No data"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: B6C0XJ
|
||||
#: src/modules/page-layout/widgets/components/PageLayoutWidgetNoDataDisplay.tsx
|
||||
msgid "No Data"
|
||||
@@ -7071,6 +7090,11 @@ msgstr ""
|
||||
msgid "Page"
|
||||
msgstr "Seite"
|
||||
|
||||
#. js-lingui-id: SZgFyi
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "page layout"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: HFlGEK
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "page layout tab"
|
||||
@@ -7517,6 +7541,14 @@ msgstr "Warteschlange: {queueName}"
|
||||
msgid "Quick model for routing decisions"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: iaocTt
|
||||
#: src/modules/page-layout/widgets/graph/hooks/useGraphWidgetAggregateQuery.ts
|
||||
#: src/modules/page-layout/widgets/graph/hooks/useGraphWidgetAggregateQuery.ts
|
||||
#: src/modules/page-layout/widgets/graph/graphWidgetAggregateChart/utils/getAggregateChartOperationLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
msgid "Ratio"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: BT7pY+
|
||||
#: src/modules/settings/profile/components/SetOrChangePassword.tsx
|
||||
msgid "Receive an email containing password set link"
|
||||
@@ -8103,6 +8135,11 @@ msgstr "Objekte suchen"
|
||||
msgid "Search operations"
|
||||
msgstr "Operationen suchen"
|
||||
|
||||
#. js-lingui-id: 996xRf
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "Search options"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: IMeaSJ
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
@@ -8332,6 +8369,11 @@ msgstr "Wählen Sie die Ereignisse aus, die Sie an diesen Endpunkt senden möcht
|
||||
msgid "Select the sheet to use"
|
||||
msgstr "Tabelle zur Verwendung auswählen"
|
||||
|
||||
#. js-lingui-id: iWf0Zj
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "Select value"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: AXTJAW
|
||||
#: src/pages/settings/profile/appearance/components/SettingsExperience.tsx
|
||||
msgid "Select your preferred language"
|
||||
@@ -9493,6 +9535,8 @@ msgstr "Löst die Funktion mit HTTP-Anfrage aus"
|
||||
#. js-lingui-id: c+xCSz
|
||||
#: src/modules/settings/data-model/fields/forms/boolean/constants/BooleanDataModelSelectOptions.ts
|
||||
#: src/modules/object-record/record-board/record-board-column/utils/getAggregateOperationShortLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "True"
|
||||
msgstr "Wahr"
|
||||
|
||||
|
||||
@@ -1063,6 +1063,11 @@ msgstr "Επιτρέπεται η ανάρτηση αρχείων και συν
|
||||
msgid "Allow users to sign in with an email and password."
|
||||
msgstr "Επιτρέψτε στους χρήστες να συνδέονται με email και κωδικό πρόσβασης."
|
||||
|
||||
#. js-lingui-id: Vw8l6h
|
||||
#: src/modules/workflow/workflow-steps/workflow-actions/http-request-action/components/HttpRequestExecutionResult.tsx
|
||||
msgid "An error occurred"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: VD5bNu
|
||||
#: src/modules/auth/sign-in-up/hooks/useSignInUp.ts
|
||||
msgid "An error occurred while checking user existence"
|
||||
@@ -1076,9 +1081,6 @@ msgstr "Παρουσιάστηκε σφάλμα κατά την επεξεργα
|
||||
#. js-lingui-id: XyOToQ
|
||||
#: src/utils/get-error-message-from-apollo-error.util.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
@@ -3811,8 +3813,12 @@ msgstr "Εισάγετε αριθμό μεταξύ 0 και 23"
|
||||
msgid "Enter number between 0 and 59"
|
||||
msgstr "Εισάγετε αριθμό μεταξύ 0 και 59"
|
||||
|
||||
#. js-lingui-id: ahwn7G
|
||||
#. js-lingui-id: uhDeRP
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Enter number between 1 and 60"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: ahwn7G
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Enter number greater than 1"
|
||||
@@ -4384,6 +4390,8 @@ msgstr "Αποτυχία μεταφόρτωσης εικόνας: "
|
||||
#. js-lingui-id: ocUvR+
|
||||
#: src/modules/settings/data-model/fields/forms/boolean/constants/BooleanDataModelSelectOptions.ts
|
||||
#: src/modules/object-record/record-board/record-board-column/utils/getAggregateOperationShortLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "False"
|
||||
msgstr "Ψευδής"
|
||||
|
||||
@@ -6015,6 +6023,11 @@ msgstr "Ελάχιστο"
|
||||
msgid "Min range"
|
||||
msgstr "Ελάχιστο εύρος"
|
||||
|
||||
#. js-lingui-id: G6wJK8
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Minute value cannot exceed 60. For intervals greater than 60 minutes, use the \"Hours\" trigger type or a custom cron expression"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: agRWc1
|
||||
#: src/modules/workflow/workflow-steps/workflow-actions/delay-actions/components/WorkflowEditActionDelay.tsx
|
||||
msgid "Minutes"
|
||||
@@ -6428,6 +6441,12 @@ msgstr "Καμία μεταβλητή διαμόρφωσης δεν ταιριά
|
||||
msgid "No country"
|
||||
msgstr "Χωρίς χώρα"
|
||||
|
||||
#. js-lingui-id: plhHQt
|
||||
#: src/modules/page-layout/widgets/graph/graphWidgetPieChart/components/PieChartCenterMetricLayer.tsx
|
||||
#: src/modules/page-layout/widgets/graph/components/NoDataLayer.tsx
|
||||
msgid "No data"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: B6C0XJ
|
||||
#: src/modules/page-layout/widgets/components/PageLayoutWidgetNoDataDisplay.tsx
|
||||
msgid "No Data"
|
||||
@@ -7071,6 +7090,11 @@ msgstr ""
|
||||
msgid "Page"
|
||||
msgstr "Σελίδα"
|
||||
|
||||
#. js-lingui-id: SZgFyi
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "page layout"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: HFlGEK
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "page layout tab"
|
||||
@@ -7517,6 +7541,14 @@ msgstr "Ουρά: {queueName}"
|
||||
msgid "Quick model for routing decisions"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: iaocTt
|
||||
#: src/modules/page-layout/widgets/graph/hooks/useGraphWidgetAggregateQuery.ts
|
||||
#: src/modules/page-layout/widgets/graph/hooks/useGraphWidgetAggregateQuery.ts
|
||||
#: src/modules/page-layout/widgets/graph/graphWidgetAggregateChart/utils/getAggregateChartOperationLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
msgid "Ratio"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: BT7pY+
|
||||
#: src/modules/settings/profile/components/SetOrChangePassword.tsx
|
||||
msgid "Receive an email containing password set link"
|
||||
@@ -8103,6 +8135,11 @@ msgstr "Αναζήτηση αντικειμένων"
|
||||
msgid "Search operations"
|
||||
msgstr "Αναζήτηση ενεργειών"
|
||||
|
||||
#. js-lingui-id: 996xRf
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "Search options"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: IMeaSJ
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
@@ -8332,6 +8369,11 @@ msgstr "Επιλέξτε τα γεγονότα που επιθυμείτε να
|
||||
msgid "Select the sheet to use"
|
||||
msgstr "Επιλέξτε το φύλλο που θα χρησιμοποιηθεί"
|
||||
|
||||
#. js-lingui-id: iWf0Zj
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "Select value"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: AXTJAW
|
||||
#: src/pages/settings/profile/appearance/components/SettingsExperience.tsx
|
||||
msgid "Select your preferred language"
|
||||
@@ -9497,6 +9539,8 @@ msgstr "Ενεργοποιεί τη λειτουργία με αίτημα Http"
|
||||
#. js-lingui-id: c+xCSz
|
||||
#: src/modules/settings/data-model/fields/forms/boolean/constants/BooleanDataModelSelectOptions.ts
|
||||
#: src/modules/object-record/record-board/record-board-column/utils/getAggregateOperationShortLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "True"
|
||||
msgstr "Αληθής"
|
||||
|
||||
|
||||
@@ -1058,6 +1058,11 @@ msgstr "Allow uploading files and attachments"
|
||||
msgid "Allow users to sign in with an email and password."
|
||||
msgstr "Allow users to sign in with an email and password."
|
||||
|
||||
#. js-lingui-id: Vw8l6h
|
||||
#: src/modules/workflow/workflow-steps/workflow-actions/http-request-action/components/HttpRequestExecutionResult.tsx
|
||||
msgid "An error occurred"
|
||||
msgstr "An error occurred"
|
||||
|
||||
#. js-lingui-id: VD5bNu
|
||||
#: src/modules/auth/sign-in-up/hooks/useSignInUp.ts
|
||||
msgid "An error occurred while checking user existence"
|
||||
@@ -1071,9 +1076,6 @@ msgstr "An error occurred while processing your message"
|
||||
#. js-lingui-id: XyOToQ
|
||||
#: src/utils/get-error-message-from-apollo-error.util.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
@@ -3806,8 +3808,12 @@ msgstr "Enter number between 0 and 23"
|
||||
msgid "Enter number between 0 and 59"
|
||||
msgstr "Enter number between 0 and 59"
|
||||
|
||||
#. js-lingui-id: ahwn7G
|
||||
#. js-lingui-id: uhDeRP
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Enter number between 1 and 60"
|
||||
msgstr "Enter number between 1 and 60"
|
||||
|
||||
#. js-lingui-id: ahwn7G
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Enter number greater than 1"
|
||||
@@ -4379,6 +4385,8 @@ msgstr "Failed to upload image: "
|
||||
#. js-lingui-id: ocUvR+
|
||||
#: src/modules/settings/data-model/fields/forms/boolean/constants/BooleanDataModelSelectOptions.ts
|
||||
#: src/modules/object-record/record-board/record-board-column/utils/getAggregateOperationShortLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "False"
|
||||
msgstr "False"
|
||||
|
||||
@@ -6010,6 +6018,11 @@ msgstr "Min"
|
||||
msgid "Min range"
|
||||
msgstr "Min range"
|
||||
|
||||
#. js-lingui-id: G6wJK8
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Minute value cannot exceed 60. For intervals greater than 60 minutes, use the \"Hours\" trigger type or a custom cron expression"
|
||||
msgstr "Minute value cannot exceed 60. For intervals greater than 60 minutes, use the \"Hours\" trigger type or a custom cron expression"
|
||||
|
||||
#. js-lingui-id: agRWc1
|
||||
#: src/modules/workflow/workflow-steps/workflow-actions/delay-actions/components/WorkflowEditActionDelay.tsx
|
||||
msgid "Minutes"
|
||||
@@ -6423,6 +6436,12 @@ msgstr "No config variables match your current filters. Try adjusting your filte
|
||||
msgid "No country"
|
||||
msgstr "No country"
|
||||
|
||||
#. js-lingui-id: plhHQt
|
||||
#: src/modules/page-layout/widgets/graph/graphWidgetPieChart/components/PieChartCenterMetricLayer.tsx
|
||||
#: src/modules/page-layout/widgets/graph/components/NoDataLayer.tsx
|
||||
msgid "No data"
|
||||
msgstr "No data"
|
||||
|
||||
#. js-lingui-id: B6C0XJ
|
||||
#: src/modules/page-layout/widgets/components/PageLayoutWidgetNoDataDisplay.tsx
|
||||
msgid "No Data"
|
||||
@@ -7066,6 +7085,11 @@ msgstr "p"
|
||||
msgid "Page"
|
||||
msgstr "Page"
|
||||
|
||||
#. js-lingui-id: SZgFyi
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "page layout"
|
||||
msgstr "page layout"
|
||||
|
||||
#. js-lingui-id: HFlGEK
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "page layout tab"
|
||||
@@ -7512,6 +7536,14 @@ msgstr "Queue: {queueName}"
|
||||
msgid "Quick model for routing decisions"
|
||||
msgstr "Quick model for routing decisions"
|
||||
|
||||
#. js-lingui-id: iaocTt
|
||||
#: src/modules/page-layout/widgets/graph/hooks/useGraphWidgetAggregateQuery.ts
|
||||
#: src/modules/page-layout/widgets/graph/hooks/useGraphWidgetAggregateQuery.ts
|
||||
#: src/modules/page-layout/widgets/graph/graphWidgetAggregateChart/utils/getAggregateChartOperationLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
msgid "Ratio"
|
||||
msgstr "Ratio"
|
||||
|
||||
#. js-lingui-id: BT7pY+
|
||||
#: src/modules/settings/profile/components/SetOrChangePassword.tsx
|
||||
msgid "Receive an email containing password set link"
|
||||
@@ -8098,6 +8130,11 @@ msgstr "Search objects"
|
||||
msgid "Search operations"
|
||||
msgstr "Search operations"
|
||||
|
||||
#. js-lingui-id: 996xRf
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "Search options"
|
||||
msgstr "Search options"
|
||||
|
||||
#. js-lingui-id: IMeaSJ
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
@@ -8327,6 +8364,11 @@ msgstr "Select the events you wish to send to this endpoint"
|
||||
msgid "Select the sheet to use"
|
||||
msgstr "Select the sheet to use"
|
||||
|
||||
#. js-lingui-id: iWf0Zj
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "Select value"
|
||||
msgstr "Select value"
|
||||
|
||||
#. js-lingui-id: AXTJAW
|
||||
#: src/pages/settings/profile/appearance/components/SettingsExperience.tsx
|
||||
msgid "Select your preferred language"
|
||||
@@ -9490,6 +9532,8 @@ msgstr "Triggers the function with Http request"
|
||||
#. js-lingui-id: c+xCSz
|
||||
#: src/modules/settings/data-model/fields/forms/boolean/constants/BooleanDataModelSelectOptions.ts
|
||||
#: src/modules/object-record/record-board/record-board-column/utils/getAggregateOperationShortLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "True"
|
||||
msgstr "True"
|
||||
|
||||
|
||||
@@ -1063,6 +1063,11 @@ msgstr "Permitir la carga de archivos y archivos adjuntos"
|
||||
msgid "Allow users to sign in with an email and password."
|
||||
msgstr "Permitir que los usuarios inicien sesión con un correo electrónico y una contraseña."
|
||||
|
||||
#. js-lingui-id: Vw8l6h
|
||||
#: src/modules/workflow/workflow-steps/workflow-actions/http-request-action/components/HttpRequestExecutionResult.tsx
|
||||
msgid "An error occurred"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: VD5bNu
|
||||
#: src/modules/auth/sign-in-up/hooks/useSignInUp.ts
|
||||
msgid "An error occurred while checking user existence"
|
||||
@@ -1076,9 +1081,6 @@ msgstr "Ocurrió un error al procesar su mensaje"
|
||||
#. js-lingui-id: XyOToQ
|
||||
#: src/utils/get-error-message-from-apollo-error.util.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
@@ -3811,8 +3813,12 @@ msgstr "Introduce un número entre 0 y 23"
|
||||
msgid "Enter number between 0 and 59"
|
||||
msgstr "Introduce un número entre 0 y 59"
|
||||
|
||||
#. js-lingui-id: ahwn7G
|
||||
#. js-lingui-id: uhDeRP
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Enter number between 1 and 60"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: ahwn7G
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Enter number greater than 1"
|
||||
@@ -4384,6 +4390,8 @@ msgstr "Error al subir la imagen: "
|
||||
#. js-lingui-id: ocUvR+
|
||||
#: src/modules/settings/data-model/fields/forms/boolean/constants/BooleanDataModelSelectOptions.ts
|
||||
#: src/modules/object-record/record-board/record-board-column/utils/getAggregateOperationShortLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "False"
|
||||
msgstr "Falso"
|
||||
|
||||
@@ -6015,6 +6023,11 @@ msgstr "Mín"
|
||||
msgid "Min range"
|
||||
msgstr "Rango mínimo"
|
||||
|
||||
#. js-lingui-id: G6wJK8
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Minute value cannot exceed 60. For intervals greater than 60 minutes, use the \"Hours\" trigger type or a custom cron expression"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: agRWc1
|
||||
#: src/modules/workflow/workflow-steps/workflow-actions/delay-actions/components/WorkflowEditActionDelay.tsx
|
||||
msgid "Minutes"
|
||||
@@ -6428,6 +6441,12 @@ msgstr "Ninguna variable de configuración coincide con sus filtros actuales. In
|
||||
msgid "No country"
|
||||
msgstr "Sin país"
|
||||
|
||||
#. js-lingui-id: plhHQt
|
||||
#: src/modules/page-layout/widgets/graph/graphWidgetPieChart/components/PieChartCenterMetricLayer.tsx
|
||||
#: src/modules/page-layout/widgets/graph/components/NoDataLayer.tsx
|
||||
msgid "No data"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: B6C0XJ
|
||||
#: src/modules/page-layout/widgets/components/PageLayoutWidgetNoDataDisplay.tsx
|
||||
msgid "No Data"
|
||||
@@ -7071,6 +7090,11 @@ msgstr "p"
|
||||
msgid "Page"
|
||||
msgstr "Página"
|
||||
|
||||
#. js-lingui-id: SZgFyi
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "page layout"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: HFlGEK
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "page layout tab"
|
||||
@@ -7517,6 +7541,14 @@ msgstr "Cola: {queueName}"
|
||||
msgid "Quick model for routing decisions"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: iaocTt
|
||||
#: src/modules/page-layout/widgets/graph/hooks/useGraphWidgetAggregateQuery.ts
|
||||
#: src/modules/page-layout/widgets/graph/hooks/useGraphWidgetAggregateQuery.ts
|
||||
#: src/modules/page-layout/widgets/graph/graphWidgetAggregateChart/utils/getAggregateChartOperationLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
msgid "Ratio"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: BT7pY+
|
||||
#: src/modules/settings/profile/components/SetOrChangePassword.tsx
|
||||
msgid "Receive an email containing password set link"
|
||||
@@ -8103,6 +8135,11 @@ msgstr "Buscar objetos"
|
||||
msgid "Search operations"
|
||||
msgstr "Buscar operaciones"
|
||||
|
||||
#. js-lingui-id: 996xRf
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "Search options"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: IMeaSJ
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
@@ -8332,6 +8369,11 @@ msgstr "Selecciona los eventos que deseas enviar a este endpoint"
|
||||
msgid "Select the sheet to use"
|
||||
msgstr "Selecciona la hoja a utilizar"
|
||||
|
||||
#. js-lingui-id: iWf0Zj
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "Select value"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: AXTJAW
|
||||
#: src/pages/settings/profile/appearance/components/SettingsExperience.tsx
|
||||
msgid "Select your preferred language"
|
||||
@@ -9495,6 +9537,8 @@ msgstr "Activa la función con solicitud Http"
|
||||
#. js-lingui-id: c+xCSz
|
||||
#: src/modules/settings/data-model/fields/forms/boolean/constants/BooleanDataModelSelectOptions.ts
|
||||
#: src/modules/object-record/record-board/record-board-column/utils/getAggregateOperationShortLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "True"
|
||||
msgstr "Verdadero"
|
||||
|
||||
|
||||
@@ -1063,6 +1063,11 @@ msgstr "Salli tiedostojen ja liitteiden lataaminen"
|
||||
msgid "Allow users to sign in with an email and password."
|
||||
msgstr "Salli käyttäjien kirjautua sisään sähköpostilla ja salasanalla."
|
||||
|
||||
#. js-lingui-id: Vw8l6h
|
||||
#: src/modules/workflow/workflow-steps/workflow-actions/http-request-action/components/HttpRequestExecutionResult.tsx
|
||||
msgid "An error occurred"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: VD5bNu
|
||||
#: src/modules/auth/sign-in-up/hooks/useSignInUp.ts
|
||||
msgid "An error occurred while checking user existence"
|
||||
@@ -1076,9 +1081,6 @@ msgstr "Virhe viestin käsittelyssä"
|
||||
#. js-lingui-id: XyOToQ
|
||||
#: src/utils/get-error-message-from-apollo-error.util.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
@@ -3811,8 +3813,12 @@ msgstr "Syötä luku 0:n ja 23:n väliltä"
|
||||
msgid "Enter number between 0 and 59"
|
||||
msgstr "Syötä luku 0:n ja 59:n väliltä"
|
||||
|
||||
#. js-lingui-id: ahwn7G
|
||||
#. js-lingui-id: uhDeRP
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Enter number between 1 and 60"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: ahwn7G
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Enter number greater than 1"
|
||||
@@ -4384,6 +4390,8 @@ msgstr "Kuvan lataus epäonnistui: "
|
||||
#. js-lingui-id: ocUvR+
|
||||
#: src/modules/settings/data-model/fields/forms/boolean/constants/BooleanDataModelSelectOptions.ts
|
||||
#: src/modules/object-record/record-board/record-board-column/utils/getAggregateOperationShortLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "False"
|
||||
msgstr "Väärä"
|
||||
|
||||
@@ -6015,6 +6023,11 @@ msgstr "Pienin"
|
||||
msgid "Min range"
|
||||
msgstr "Minimirajoitus"
|
||||
|
||||
#. js-lingui-id: G6wJK8
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Minute value cannot exceed 60. For intervals greater than 60 minutes, use the \"Hours\" trigger type or a custom cron expression"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: agRWc1
|
||||
#: src/modules/workflow/workflow-steps/workflow-actions/delay-actions/components/WorkflowEditActionDelay.tsx
|
||||
msgid "Minutes"
|
||||
@@ -6428,6 +6441,12 @@ msgstr "Mikään määritysmuuttuja ei vastaa nykyisiä suodattimiasi. Yritä mu
|
||||
msgid "No country"
|
||||
msgstr "Ei maata"
|
||||
|
||||
#. js-lingui-id: plhHQt
|
||||
#: src/modules/page-layout/widgets/graph/graphWidgetPieChart/components/PieChartCenterMetricLayer.tsx
|
||||
#: src/modules/page-layout/widgets/graph/components/NoDataLayer.tsx
|
||||
msgid "No data"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: B6C0XJ
|
||||
#: src/modules/page-layout/widgets/components/PageLayoutWidgetNoDataDisplay.tsx
|
||||
msgid "No Data"
|
||||
@@ -7071,6 +7090,11 @@ msgstr ""
|
||||
msgid "Page"
|
||||
msgstr "Sivu"
|
||||
|
||||
#. js-lingui-id: SZgFyi
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "page layout"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: HFlGEK
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "page layout tab"
|
||||
@@ -7517,6 +7541,14 @@ msgstr "Jono: {queueName}"
|
||||
msgid "Quick model for routing decisions"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: iaocTt
|
||||
#: src/modules/page-layout/widgets/graph/hooks/useGraphWidgetAggregateQuery.ts
|
||||
#: src/modules/page-layout/widgets/graph/hooks/useGraphWidgetAggregateQuery.ts
|
||||
#: src/modules/page-layout/widgets/graph/graphWidgetAggregateChart/utils/getAggregateChartOperationLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
msgid "Ratio"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: BT7pY+
|
||||
#: src/modules/settings/profile/components/SetOrChangePassword.tsx
|
||||
msgid "Receive an email containing password set link"
|
||||
@@ -8103,6 +8135,11 @@ msgstr "Etsi objekteja"
|
||||
msgid "Search operations"
|
||||
msgstr "Etsi toimintoja"
|
||||
|
||||
#. js-lingui-id: 996xRf
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "Search options"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: IMeaSJ
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
@@ -8332,6 +8369,11 @@ msgstr "Valitse tapahtumat, jotka haluat lähettää tähän päätepisteeseen"
|
||||
msgid "Select the sheet to use"
|
||||
msgstr "Valitse käytettävä arkki"
|
||||
|
||||
#. js-lingui-id: iWf0Zj
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "Select value"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: AXTJAW
|
||||
#: src/pages/settings/profile/appearance/components/SettingsExperience.tsx
|
||||
msgid "Select your preferred language"
|
||||
@@ -9493,6 +9535,8 @@ msgstr "Aktivoi toiminnon Http-pyynnöllä"
|
||||
#. js-lingui-id: c+xCSz
|
||||
#: src/modules/settings/data-model/fields/forms/boolean/constants/BooleanDataModelSelectOptions.ts
|
||||
#: src/modules/object-record/record-board/record-board-column/utils/getAggregateOperationShortLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "True"
|
||||
msgstr "Tosi"
|
||||
|
||||
|
||||
@@ -1063,6 +1063,11 @@ msgstr "Permettre le téléversement des fichiers et des pièces jointes"
|
||||
msgid "Allow users to sign in with an email and password."
|
||||
msgstr "Permettre aux utilisateurs de se connecter avec un courriel et un mot de passe."
|
||||
|
||||
#. js-lingui-id: Vw8l6h
|
||||
#: src/modules/workflow/workflow-steps/workflow-actions/http-request-action/components/HttpRequestExecutionResult.tsx
|
||||
msgid "An error occurred"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: VD5bNu
|
||||
#: src/modules/auth/sign-in-up/hooks/useSignInUp.ts
|
||||
msgid "An error occurred while checking user existence"
|
||||
@@ -1076,9 +1081,6 @@ msgstr "Une erreur s'est produite lors du traitement de votre message"
|
||||
#. js-lingui-id: XyOToQ
|
||||
#: src/utils/get-error-message-from-apollo-error.util.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
@@ -3811,8 +3813,12 @@ msgstr "Entrez un nombre entre 0 et 23"
|
||||
msgid "Enter number between 0 and 59"
|
||||
msgstr "Entrez un nombre entre 0 et 59"
|
||||
|
||||
#. js-lingui-id: ahwn7G
|
||||
#. js-lingui-id: uhDeRP
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Enter number between 1 and 60"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: ahwn7G
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Enter number greater than 1"
|
||||
@@ -4384,6 +4390,8 @@ msgstr "Échec du téléchargement de l'image : "
|
||||
#. js-lingui-id: ocUvR+
|
||||
#: src/modules/settings/data-model/fields/forms/boolean/constants/BooleanDataModelSelectOptions.ts
|
||||
#: src/modules/object-record/record-board/record-board-column/utils/getAggregateOperationShortLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "False"
|
||||
msgstr "Faux"
|
||||
|
||||
@@ -6015,6 +6023,11 @@ msgstr "Min"
|
||||
msgid "Min range"
|
||||
msgstr "Portée min"
|
||||
|
||||
#. js-lingui-id: G6wJK8
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Minute value cannot exceed 60. For intervals greater than 60 minutes, use the \"Hours\" trigger type or a custom cron expression"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: agRWc1
|
||||
#: src/modules/workflow/workflow-steps/workflow-actions/delay-actions/components/WorkflowEditActionDelay.tsx
|
||||
msgid "Minutes"
|
||||
@@ -6428,6 +6441,12 @@ msgstr "Aucune variable de configuration ne correspond à vos filtres actuels. E
|
||||
msgid "No country"
|
||||
msgstr "Aucun pays"
|
||||
|
||||
#. js-lingui-id: plhHQt
|
||||
#: src/modules/page-layout/widgets/graph/graphWidgetPieChart/components/PieChartCenterMetricLayer.tsx
|
||||
#: src/modules/page-layout/widgets/graph/components/NoDataLayer.tsx
|
||||
msgid "No data"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: B6C0XJ
|
||||
#: src/modules/page-layout/widgets/components/PageLayoutWidgetNoDataDisplay.tsx
|
||||
msgid "No Data"
|
||||
@@ -7071,6 +7090,11 @@ msgstr ""
|
||||
msgid "Page"
|
||||
msgstr "Page"
|
||||
|
||||
#. js-lingui-id: SZgFyi
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "page layout"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: HFlGEK
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "page layout tab"
|
||||
@@ -7517,6 +7541,14 @@ msgstr "File d'attente : {queueName}"
|
||||
msgid "Quick model for routing decisions"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: iaocTt
|
||||
#: src/modules/page-layout/widgets/graph/hooks/useGraphWidgetAggregateQuery.ts
|
||||
#: src/modules/page-layout/widgets/graph/hooks/useGraphWidgetAggregateQuery.ts
|
||||
#: src/modules/page-layout/widgets/graph/graphWidgetAggregateChart/utils/getAggregateChartOperationLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
msgid "Ratio"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: BT7pY+
|
||||
#: src/modules/settings/profile/components/SetOrChangePassword.tsx
|
||||
msgid "Receive an email containing password set link"
|
||||
@@ -8103,6 +8135,11 @@ msgstr "Rechercher des objets"
|
||||
msgid "Search operations"
|
||||
msgstr "Rechercher des opérations"
|
||||
|
||||
#. js-lingui-id: 996xRf
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "Search options"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: IMeaSJ
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
@@ -8332,6 +8369,11 @@ msgstr "Sélectionnez les événements à envoyer à ce point de terminaison"
|
||||
msgid "Select the sheet to use"
|
||||
msgstr "Sélectionnez la feuille à utiliser"
|
||||
|
||||
#. js-lingui-id: iWf0Zj
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "Select value"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: AXTJAW
|
||||
#: src/pages/settings/profile/appearance/components/SettingsExperience.tsx
|
||||
msgid "Select your preferred language"
|
||||
@@ -9495,6 +9537,8 @@ msgstr "Déclenche la fonction avec une requête HTTP"
|
||||
#. js-lingui-id: c+xCSz
|
||||
#: src/modules/settings/data-model/fields/forms/boolean/constants/BooleanDataModelSelectOptions.ts
|
||||
#: src/modules/object-record/record-board/record-board-column/utils/getAggregateOperationShortLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "True"
|
||||
msgstr "Vrai"
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1063,6 +1063,11 @@ msgstr "אפשר העלאה של קבצים וצרופות"
|
||||
msgid "Allow users to sign in with an email and password."
|
||||
msgstr "אפשר למשתמשים להיכנס עם דוא\"ל וסיסמה."
|
||||
|
||||
#. js-lingui-id: Vw8l6h
|
||||
#: src/modules/workflow/workflow-steps/workflow-actions/http-request-action/components/HttpRequestExecutionResult.tsx
|
||||
msgid "An error occurred"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: VD5bNu
|
||||
#: src/modules/auth/sign-in-up/hooks/useSignInUp.ts
|
||||
msgid "An error occurred while checking user existence"
|
||||
@@ -1076,9 +1081,6 @@ msgstr "אירעה שגיאה בעת עיבוד ההודעה שלך"
|
||||
#. js-lingui-id: XyOToQ
|
||||
#: src/utils/get-error-message-from-apollo-error.util.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
@@ -3811,8 +3813,12 @@ msgstr "הזן מספר בין 0 ל-23"
|
||||
msgid "Enter number between 0 and 59"
|
||||
msgstr "הזן מספר בין 0 ל-59"
|
||||
|
||||
#. js-lingui-id: ahwn7G
|
||||
#. js-lingui-id: uhDeRP
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Enter number between 1 and 60"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: ahwn7G
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Enter number greater than 1"
|
||||
@@ -4384,6 +4390,8 @@ msgstr "העלאת תמונה נכשלה: "
|
||||
#. js-lingui-id: ocUvR+
|
||||
#: src/modules/settings/data-model/fields/forms/boolean/constants/BooleanDataModelSelectOptions.ts
|
||||
#: src/modules/object-record/record-board/record-board-column/utils/getAggregateOperationShortLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "False"
|
||||
msgstr "שקר"
|
||||
|
||||
@@ -6015,6 +6023,11 @@ msgstr "מינ'"
|
||||
msgid "Min range"
|
||||
msgstr "טווח מינימלי"
|
||||
|
||||
#. js-lingui-id: G6wJK8
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Minute value cannot exceed 60. For intervals greater than 60 minutes, use the \"Hours\" trigger type or a custom cron expression"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: agRWc1
|
||||
#: src/modules/workflow/workflow-steps/workflow-actions/delay-actions/components/WorkflowEditActionDelay.tsx
|
||||
msgid "Minutes"
|
||||
@@ -6428,6 +6441,12 @@ msgstr "אין משתני הגדרות שתואמים לסינון הנוכחי
|
||||
msgid "No country"
|
||||
msgstr "אין מדינה"
|
||||
|
||||
#. js-lingui-id: plhHQt
|
||||
#: src/modules/page-layout/widgets/graph/graphWidgetPieChart/components/PieChartCenterMetricLayer.tsx
|
||||
#: src/modules/page-layout/widgets/graph/components/NoDataLayer.tsx
|
||||
msgid "No data"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: B6C0XJ
|
||||
#: src/modules/page-layout/widgets/components/PageLayoutWidgetNoDataDisplay.tsx
|
||||
msgid "No Data"
|
||||
@@ -7071,6 +7090,11 @@ msgstr ""
|
||||
msgid "Page"
|
||||
msgstr "דף"
|
||||
|
||||
#. js-lingui-id: SZgFyi
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "page layout"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: HFlGEK
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "page layout tab"
|
||||
@@ -7517,6 +7541,14 @@ msgstr "תור: {queueName}"
|
||||
msgid "Quick model for routing decisions"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: iaocTt
|
||||
#: src/modules/page-layout/widgets/graph/hooks/useGraphWidgetAggregateQuery.ts
|
||||
#: src/modules/page-layout/widgets/graph/hooks/useGraphWidgetAggregateQuery.ts
|
||||
#: src/modules/page-layout/widgets/graph/graphWidgetAggregateChart/utils/getAggregateChartOperationLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
msgid "Ratio"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: BT7pY+
|
||||
#: src/modules/settings/profile/components/SetOrChangePassword.tsx
|
||||
msgid "Receive an email containing password set link"
|
||||
@@ -8103,6 +8135,11 @@ msgstr "חפש אובייקטים"
|
||||
msgid "Search operations"
|
||||
msgstr "חפש פעולות"
|
||||
|
||||
#. js-lingui-id: 996xRf
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "Search options"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: IMeaSJ
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
@@ -8332,6 +8369,11 @@ msgstr "\\"
|
||||
msgid "Select the sheet to use"
|
||||
msgstr "בחר את הגיליון לשימוש"
|
||||
|
||||
#. js-lingui-id: iWf0Zj
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "Select value"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: AXTJAW
|
||||
#: src/pages/settings/profile/appearance/components/SettingsExperience.tsx
|
||||
msgid "Select your preferred language"
|
||||
@@ -9493,6 +9535,8 @@ msgstr "מפעיל את הפונקציה עם בקשת Http"
|
||||
#. js-lingui-id: c+xCSz
|
||||
#: src/modules/settings/data-model/fields/forms/boolean/constants/BooleanDataModelSelectOptions.ts
|
||||
#: src/modules/object-record/record-board/record-board-column/utils/getAggregateOperationShortLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "True"
|
||||
msgstr "נכון"
|
||||
|
||||
|
||||
@@ -1063,6 +1063,11 @@ msgstr "Fájlok és mellékletek feltöltésének engedélyezése"
|
||||
msgid "Allow users to sign in with an email and password."
|
||||
msgstr "Engedélyezze a felhasználóknak a bejelentkezést e-mail és jelszó segítségével."
|
||||
|
||||
#. js-lingui-id: Vw8l6h
|
||||
#: src/modules/workflow/workflow-steps/workflow-actions/http-request-action/components/HttpRequestExecutionResult.tsx
|
||||
msgid "An error occurred"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: VD5bNu
|
||||
#: src/modules/auth/sign-in-up/hooks/useSignInUp.ts
|
||||
msgid "An error occurred while checking user existence"
|
||||
@@ -1076,9 +1081,6 @@ msgstr "Hiba történt az üzenet feldolgozása során"
|
||||
#. js-lingui-id: XyOToQ
|
||||
#: src/utils/get-error-message-from-apollo-error.util.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
@@ -3811,8 +3813,12 @@ msgstr "Adjon meg egy számot 0 és 23 között"
|
||||
msgid "Enter number between 0 and 59"
|
||||
msgstr "Adjon meg egy számot 0 és 59 között"
|
||||
|
||||
#. js-lingui-id: ahwn7G
|
||||
#. js-lingui-id: uhDeRP
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Enter number between 1 and 60"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: ahwn7G
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Enter number greater than 1"
|
||||
@@ -4384,6 +4390,8 @@ msgstr "Nem sikerült feltölteni a képet: "
|
||||
#. js-lingui-id: ocUvR+
|
||||
#: src/modules/settings/data-model/fields/forms/boolean/constants/BooleanDataModelSelectOptions.ts
|
||||
#: src/modules/object-record/record-board/record-board-column/utils/getAggregateOperationShortLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "False"
|
||||
msgstr "Hamis"
|
||||
|
||||
@@ -6015,6 +6023,11 @@ msgstr "Min"
|
||||
msgid "Min range"
|
||||
msgstr "Minimális tartomány"
|
||||
|
||||
#. js-lingui-id: G6wJK8
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Minute value cannot exceed 60. For intervals greater than 60 minutes, use the \"Hours\" trigger type or a custom cron expression"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: agRWc1
|
||||
#: src/modules/workflow/workflow-steps/workflow-actions/delay-actions/components/WorkflowEditActionDelay.tsx
|
||||
msgid "Minutes"
|
||||
@@ -6428,6 +6441,12 @@ msgstr "Nincsenek konfigurációs változók, amelyek megfelelnének a jelenlegi
|
||||
msgid "No country"
|
||||
msgstr "Nincs ország"
|
||||
|
||||
#. js-lingui-id: plhHQt
|
||||
#: src/modules/page-layout/widgets/graph/graphWidgetPieChart/components/PieChartCenterMetricLayer.tsx
|
||||
#: src/modules/page-layout/widgets/graph/components/NoDataLayer.tsx
|
||||
msgid "No data"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: B6C0XJ
|
||||
#: src/modules/page-layout/widgets/components/PageLayoutWidgetNoDataDisplay.tsx
|
||||
msgid "No Data"
|
||||
@@ -7071,6 +7090,11 @@ msgstr ""
|
||||
msgid "Page"
|
||||
msgstr "Oldal"
|
||||
|
||||
#. js-lingui-id: SZgFyi
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "page layout"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: HFlGEK
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "page layout tab"
|
||||
@@ -7517,6 +7541,14 @@ msgstr "Sor: {queueName}"
|
||||
msgid "Quick model for routing decisions"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: iaocTt
|
||||
#: src/modules/page-layout/widgets/graph/hooks/useGraphWidgetAggregateQuery.ts
|
||||
#: src/modules/page-layout/widgets/graph/hooks/useGraphWidgetAggregateQuery.ts
|
||||
#: src/modules/page-layout/widgets/graph/graphWidgetAggregateChart/utils/getAggregateChartOperationLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
msgid "Ratio"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: BT7pY+
|
||||
#: src/modules/settings/profile/components/SetOrChangePassword.tsx
|
||||
msgid "Receive an email containing password set link"
|
||||
@@ -8103,6 +8135,11 @@ msgstr "Objektumok keresése"
|
||||
msgid "Search operations"
|
||||
msgstr "Műveletek keresése"
|
||||
|
||||
#. js-lingui-id: 996xRf
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "Search options"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: IMeaSJ
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
@@ -8332,6 +8369,11 @@ msgstr "Válassza ki, mely események szeretné elküldeni ebbe az végpontba"
|
||||
msgid "Select the sheet to use"
|
||||
msgstr "Válassza ki a használni kívánt lapot"
|
||||
|
||||
#. js-lingui-id: iWf0Zj
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "Select value"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: AXTJAW
|
||||
#: src/pages/settings/profile/appearance/components/SettingsExperience.tsx
|
||||
msgid "Select your preferred language"
|
||||
@@ -9493,6 +9535,8 @@ msgstr "A funkciót Http kéréssel indítja el"
|
||||
#. js-lingui-id: c+xCSz
|
||||
#: src/modules/settings/data-model/fields/forms/boolean/constants/BooleanDataModelSelectOptions.ts
|
||||
#: src/modules/object-record/record-board/record-board-column/utils/getAggregateOperationShortLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "True"
|
||||
msgstr "Igaz"
|
||||
|
||||
|
||||
@@ -1063,6 +1063,11 @@ msgstr "Consenti il caricamento di file e allegati"
|
||||
msgid "Allow users to sign in with an email and password."
|
||||
msgstr "Consenti agli utenti di accedere con e-mail e password."
|
||||
|
||||
#. js-lingui-id: Vw8l6h
|
||||
#: src/modules/workflow/workflow-steps/workflow-actions/http-request-action/components/HttpRequestExecutionResult.tsx
|
||||
msgid "An error occurred"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: VD5bNu
|
||||
#: src/modules/auth/sign-in-up/hooks/useSignInUp.ts
|
||||
msgid "An error occurred while checking user existence"
|
||||
@@ -1076,9 +1081,6 @@ msgstr "Si è verificato un errore durante l'elaborazione del messaggio"
|
||||
#. js-lingui-id: XyOToQ
|
||||
#: src/utils/get-error-message-from-apollo-error.util.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
@@ -3811,8 +3813,12 @@ msgstr "Immettere un numero tra 0 e 23"
|
||||
msgid "Enter number between 0 and 59"
|
||||
msgstr "Immettere un numero tra 0 e 59"
|
||||
|
||||
#. js-lingui-id: ahwn7G
|
||||
#. js-lingui-id: uhDeRP
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Enter number between 1 and 60"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: ahwn7G
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Enter number greater than 1"
|
||||
@@ -4384,6 +4390,8 @@ msgstr "Caricamento dell'immagine fallito: "
|
||||
#. js-lingui-id: ocUvR+
|
||||
#: src/modules/settings/data-model/fields/forms/boolean/constants/BooleanDataModelSelectOptions.ts
|
||||
#: src/modules/object-record/record-board/record-board-column/utils/getAggregateOperationShortLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "False"
|
||||
msgstr "Falso"
|
||||
|
||||
@@ -6015,6 +6023,11 @@ msgstr "Minimo"
|
||||
msgid "Min range"
|
||||
msgstr "Intervallo minimo"
|
||||
|
||||
#. js-lingui-id: G6wJK8
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Minute value cannot exceed 60. For intervals greater than 60 minutes, use the \"Hours\" trigger type or a custom cron expression"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: agRWc1
|
||||
#: src/modules/workflow/workflow-steps/workflow-actions/delay-actions/components/WorkflowEditActionDelay.tsx
|
||||
msgid "Minutes"
|
||||
@@ -6428,6 +6441,12 @@ msgstr "Nessuna variabile di configurazione corrisponde ai filtri attuali. Prova
|
||||
msgid "No country"
|
||||
msgstr "Nessun paese"
|
||||
|
||||
#. js-lingui-id: plhHQt
|
||||
#: src/modules/page-layout/widgets/graph/graphWidgetPieChart/components/PieChartCenterMetricLayer.tsx
|
||||
#: src/modules/page-layout/widgets/graph/components/NoDataLayer.tsx
|
||||
msgid "No data"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: B6C0XJ
|
||||
#: src/modules/page-layout/widgets/components/PageLayoutWidgetNoDataDisplay.tsx
|
||||
msgid "No Data"
|
||||
@@ -7071,6 +7090,11 @@ msgstr ""
|
||||
msgid "Page"
|
||||
msgstr "Pagina"
|
||||
|
||||
#. js-lingui-id: SZgFyi
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "page layout"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: HFlGEK
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "page layout tab"
|
||||
@@ -7517,6 +7541,14 @@ msgstr "Coda: {queueName}"
|
||||
msgid "Quick model for routing decisions"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: iaocTt
|
||||
#: src/modules/page-layout/widgets/graph/hooks/useGraphWidgetAggregateQuery.ts
|
||||
#: src/modules/page-layout/widgets/graph/hooks/useGraphWidgetAggregateQuery.ts
|
||||
#: src/modules/page-layout/widgets/graph/graphWidgetAggregateChart/utils/getAggregateChartOperationLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
msgid "Ratio"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: BT7pY+
|
||||
#: src/modules/settings/profile/components/SetOrChangePassword.tsx
|
||||
msgid "Receive an email containing password set link"
|
||||
@@ -8103,6 +8135,11 @@ msgstr "Cerca oggetti"
|
||||
msgid "Search operations"
|
||||
msgstr "Cerca operazioni"
|
||||
|
||||
#. js-lingui-id: 996xRf
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "Search options"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: IMeaSJ
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
@@ -8332,6 +8369,11 @@ msgstr "Seleziona gli eventi da inviare a questo endpoint"
|
||||
msgid "Select the sheet to use"
|
||||
msgstr "Seleziona il foglio da utilizzare"
|
||||
|
||||
#. js-lingui-id: iWf0Zj
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "Select value"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: AXTJAW
|
||||
#: src/pages/settings/profile/appearance/components/SettingsExperience.tsx
|
||||
msgid "Select your preferred language"
|
||||
@@ -9495,6 +9537,8 @@ msgstr "Attiva la funzione con richiesta Http"
|
||||
#. js-lingui-id: c+xCSz
|
||||
#: src/modules/settings/data-model/fields/forms/boolean/constants/BooleanDataModelSelectOptions.ts
|
||||
#: src/modules/object-record/record-board/record-board-column/utils/getAggregateOperationShortLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "True"
|
||||
msgstr "Vero"
|
||||
|
||||
|
||||
@@ -1063,6 +1063,11 @@ msgstr "ファイルと添付ファイルのアップロードを許可する"
|
||||
msgid "Allow users to sign in with an email and password."
|
||||
msgstr "ユーザーがメールとパスワードでサインインできるようにする。"
|
||||
|
||||
#. js-lingui-id: Vw8l6h
|
||||
#: src/modules/workflow/workflow-steps/workflow-actions/http-request-action/components/HttpRequestExecutionResult.tsx
|
||||
msgid "An error occurred"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: VD5bNu
|
||||
#: src/modules/auth/sign-in-up/hooks/useSignInUp.ts
|
||||
msgid "An error occurred while checking user existence"
|
||||
@@ -1076,9 +1081,6 @@ msgstr "メッセージの処理中にエラーが発生しました"
|
||||
#. js-lingui-id: XyOToQ
|
||||
#: src/utils/get-error-message-from-apollo-error.util.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
@@ -3811,8 +3813,12 @@ msgstr "0 から 23 の間の数字を入力してください"
|
||||
msgid "Enter number between 0 and 59"
|
||||
msgstr "0 から 59 の間の数字を入力してください"
|
||||
|
||||
#. js-lingui-id: ahwn7G
|
||||
#. js-lingui-id: uhDeRP
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Enter number between 1 and 60"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: ahwn7G
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Enter number greater than 1"
|
||||
@@ -4384,6 +4390,8 @@ msgstr "画像のアップロードに失敗しました: "
|
||||
#. js-lingui-id: ocUvR+
|
||||
#: src/modules/settings/data-model/fields/forms/boolean/constants/BooleanDataModelSelectOptions.ts
|
||||
#: src/modules/object-record/record-board/record-board-column/utils/getAggregateOperationShortLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "False"
|
||||
msgstr "偽"
|
||||
|
||||
@@ -6015,6 +6023,11 @@ msgstr "最小"
|
||||
msgid "Min range"
|
||||
msgstr "最小範囲"
|
||||
|
||||
#. js-lingui-id: G6wJK8
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Minute value cannot exceed 60. For intervals greater than 60 minutes, use the \"Hours\" trigger type or a custom cron expression"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: agRWc1
|
||||
#: src/modules/workflow/workflow-steps/workflow-actions/delay-actions/components/WorkflowEditActionDelay.tsx
|
||||
msgid "Minutes"
|
||||
@@ -6428,6 +6441,12 @@ msgstr "現在のフィルターでは設定変数と一致しません。フィ
|
||||
msgid "No country"
|
||||
msgstr "国なし"
|
||||
|
||||
#. js-lingui-id: plhHQt
|
||||
#: src/modules/page-layout/widgets/graph/graphWidgetPieChart/components/PieChartCenterMetricLayer.tsx
|
||||
#: src/modules/page-layout/widgets/graph/components/NoDataLayer.tsx
|
||||
msgid "No data"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: B6C0XJ
|
||||
#: src/modules/page-layout/widgets/components/PageLayoutWidgetNoDataDisplay.tsx
|
||||
msgid "No Data"
|
||||
@@ -7071,6 +7090,11 @@ msgstr ""
|
||||
msgid "Page"
|
||||
msgstr "ページ"
|
||||
|
||||
#. js-lingui-id: SZgFyi
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "page layout"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: HFlGEK
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "page layout tab"
|
||||
@@ -7517,6 +7541,14 @@ msgstr "キュー: {queueName}"
|
||||
msgid "Quick model for routing decisions"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: iaocTt
|
||||
#: src/modules/page-layout/widgets/graph/hooks/useGraphWidgetAggregateQuery.ts
|
||||
#: src/modules/page-layout/widgets/graph/hooks/useGraphWidgetAggregateQuery.ts
|
||||
#: src/modules/page-layout/widgets/graph/graphWidgetAggregateChart/utils/getAggregateChartOperationLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
msgid "Ratio"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: BT7pY+
|
||||
#: src/modules/settings/profile/components/SetOrChangePassword.tsx
|
||||
msgid "Receive an email containing password set link"
|
||||
@@ -8103,6 +8135,11 @@ msgstr "オブジェクトを検索"
|
||||
msgid "Search operations"
|
||||
msgstr "操作を検索"
|
||||
|
||||
#. js-lingui-id: 996xRf
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "Search options"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: IMeaSJ
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
@@ -8332,6 +8369,11 @@ msgstr "このエンドポイントに送信したいイベントを選択して
|
||||
msgid "Select the sheet to use"
|
||||
msgstr "使用するシートを選択"
|
||||
|
||||
#. js-lingui-id: iWf0Zj
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "Select value"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: AXTJAW
|
||||
#: src/pages/settings/profile/appearance/components/SettingsExperience.tsx
|
||||
msgid "Select your preferred language"
|
||||
@@ -9493,6 +9535,8 @@ msgstr "Httpリクエストで関数をトリガーします"
|
||||
#. js-lingui-id: c+xCSz
|
||||
#: src/modules/settings/data-model/fields/forms/boolean/constants/BooleanDataModelSelectOptions.ts
|
||||
#: src/modules/object-record/record-board/record-board-column/utils/getAggregateOperationShortLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "True"
|
||||
msgstr "真"
|
||||
|
||||
|
||||
@@ -1063,6 +1063,11 @@ msgstr "파일 및 첨부파일 업로드 허용"
|
||||
msgid "Allow users to sign in with an email and password."
|
||||
msgstr "이메일과 비밀번호로 로그인 허용."
|
||||
|
||||
#. js-lingui-id: Vw8l6h
|
||||
#: src/modules/workflow/workflow-steps/workflow-actions/http-request-action/components/HttpRequestExecutionResult.tsx
|
||||
msgid "An error occurred"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: VD5bNu
|
||||
#: src/modules/auth/sign-in-up/hooks/useSignInUp.ts
|
||||
msgid "An error occurred while checking user existence"
|
||||
@@ -1076,9 +1081,6 @@ msgstr "메시지를 처리하는 중 오류가 발생했습니다"
|
||||
#. js-lingui-id: XyOToQ
|
||||
#: src/utils/get-error-message-from-apollo-error.util.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
@@ -3811,8 +3813,12 @@ msgstr "0에서 23 사이의 숫자를 입력하세요"
|
||||
msgid "Enter number between 0 and 59"
|
||||
msgstr "0에서 59 사이의 숫자를 입력하세요"
|
||||
|
||||
#. js-lingui-id: ahwn7G
|
||||
#. js-lingui-id: uhDeRP
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Enter number between 1 and 60"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: ahwn7G
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Enter number greater than 1"
|
||||
@@ -4384,6 +4390,8 @@ msgstr "이미지 업로드 실패: "
|
||||
#. js-lingui-id: ocUvR+
|
||||
#: src/modules/settings/data-model/fields/forms/boolean/constants/BooleanDataModelSelectOptions.ts
|
||||
#: src/modules/object-record/record-board/record-board-column/utils/getAggregateOperationShortLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "False"
|
||||
msgstr "거짓"
|
||||
|
||||
@@ -6015,6 +6023,11 @@ msgstr "최소"
|
||||
msgid "Min range"
|
||||
msgstr "최소 범위"
|
||||
|
||||
#. js-lingui-id: G6wJK8
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Minute value cannot exceed 60. For intervals greater than 60 minutes, use the \"Hours\" trigger type or a custom cron expression"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: agRWc1
|
||||
#: src/modules/workflow/workflow-steps/workflow-actions/delay-actions/components/WorkflowEditActionDelay.tsx
|
||||
msgid "Minutes"
|
||||
@@ -6428,6 +6441,12 @@ msgstr "현재 필터와 일치하는 설정 변수가 없습니다. 필터 또
|
||||
msgid "No country"
|
||||
msgstr "국가 없음"
|
||||
|
||||
#. js-lingui-id: plhHQt
|
||||
#: src/modules/page-layout/widgets/graph/graphWidgetPieChart/components/PieChartCenterMetricLayer.tsx
|
||||
#: src/modules/page-layout/widgets/graph/components/NoDataLayer.tsx
|
||||
msgid "No data"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: B6C0XJ
|
||||
#: src/modules/page-layout/widgets/components/PageLayoutWidgetNoDataDisplay.tsx
|
||||
msgid "No Data"
|
||||
@@ -7071,6 +7090,11 @@ msgstr ""
|
||||
msgid "Page"
|
||||
msgstr "페이지"
|
||||
|
||||
#. js-lingui-id: SZgFyi
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "page layout"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: HFlGEK
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "page layout tab"
|
||||
@@ -7517,6 +7541,14 @@ msgstr "큐: {queueName}"
|
||||
msgid "Quick model for routing decisions"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: iaocTt
|
||||
#: src/modules/page-layout/widgets/graph/hooks/useGraphWidgetAggregateQuery.ts
|
||||
#: src/modules/page-layout/widgets/graph/hooks/useGraphWidgetAggregateQuery.ts
|
||||
#: src/modules/page-layout/widgets/graph/graphWidgetAggregateChart/utils/getAggregateChartOperationLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
msgid "Ratio"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: BT7pY+
|
||||
#: src/modules/settings/profile/components/SetOrChangePassword.tsx
|
||||
msgid "Receive an email containing password set link"
|
||||
@@ -8103,6 +8135,11 @@ msgstr "개체 검색"
|
||||
msgid "Search operations"
|
||||
msgstr "작업 검색"
|
||||
|
||||
#. js-lingui-id: 996xRf
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "Search options"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: IMeaSJ
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
@@ -8332,6 +8369,11 @@ msgstr "이 엔드포인트로 보내려는 이벤트를 선택하세요"
|
||||
msgid "Select the sheet to use"
|
||||
msgstr "사용할 시트 선택"
|
||||
|
||||
#. js-lingui-id: iWf0Zj
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "Select value"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: AXTJAW
|
||||
#: src/pages/settings/profile/appearance/components/SettingsExperience.tsx
|
||||
msgid "Select your preferred language"
|
||||
@@ -9493,6 +9535,8 @@ msgstr "Http 요청으로 함수를 실행"
|
||||
#. js-lingui-id: c+xCSz
|
||||
#: src/modules/settings/data-model/fields/forms/boolean/constants/BooleanDataModelSelectOptions.ts
|
||||
#: src/modules/object-record/record-board/record-board-column/utils/getAggregateOperationShortLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "True"
|
||||
msgstr "참"
|
||||
|
||||
|
||||
@@ -1063,6 +1063,11 @@ msgstr "Toestaan dat bestanden en bijlagen worden geüpload"
|
||||
msgid "Allow users to sign in with an email and password."
|
||||
msgstr "Sta gebruikers toe om in te loggen met een e-mailadres en wachtwoord."
|
||||
|
||||
#. js-lingui-id: Vw8l6h
|
||||
#: src/modules/workflow/workflow-steps/workflow-actions/http-request-action/components/HttpRequestExecutionResult.tsx
|
||||
msgid "An error occurred"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: VD5bNu
|
||||
#: src/modules/auth/sign-in-up/hooks/useSignInUp.ts
|
||||
msgid "An error occurred while checking user existence"
|
||||
@@ -1076,9 +1081,6 @@ msgstr "Er is een fout opgetreden tijdens het verwerken van uw bericht"
|
||||
#. js-lingui-id: XyOToQ
|
||||
#: src/utils/get-error-message-from-apollo-error.util.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
@@ -3811,8 +3813,12 @@ msgstr "Voer een getal in tussen 0 en 23"
|
||||
msgid "Enter number between 0 and 59"
|
||||
msgstr "Voer een getal in tussen 0 en 59"
|
||||
|
||||
#. js-lingui-id: ahwn7G
|
||||
#. js-lingui-id: uhDeRP
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Enter number between 1 and 60"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: ahwn7G
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Enter number greater than 1"
|
||||
@@ -4384,6 +4390,8 @@ msgstr "Afbeelding uploaden mislukt: "
|
||||
#. js-lingui-id: ocUvR+
|
||||
#: src/modules/settings/data-model/fields/forms/boolean/constants/BooleanDataModelSelectOptions.ts
|
||||
#: src/modules/object-record/record-board/record-board-column/utils/getAggregateOperationShortLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "False"
|
||||
msgstr "Vals"
|
||||
|
||||
@@ -6015,6 +6023,11 @@ msgstr "Min"
|
||||
msgid "Min range"
|
||||
msgstr "Min bereik"
|
||||
|
||||
#. js-lingui-id: G6wJK8
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Minute value cannot exceed 60. For intervals greater than 60 minutes, use the \"Hours\" trigger type or a custom cron expression"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: agRWc1
|
||||
#: src/modules/workflow/workflow-steps/workflow-actions/delay-actions/components/WorkflowEditActionDelay.tsx
|
||||
msgid "Minutes"
|
||||
@@ -6428,6 +6441,12 @@ msgstr "Geen configuratievariabelen die overeenkomen met je huidige filters. Pro
|
||||
msgid "No country"
|
||||
msgstr "Geen land"
|
||||
|
||||
#. js-lingui-id: plhHQt
|
||||
#: src/modules/page-layout/widgets/graph/graphWidgetPieChart/components/PieChartCenterMetricLayer.tsx
|
||||
#: src/modules/page-layout/widgets/graph/components/NoDataLayer.tsx
|
||||
msgid "No data"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: B6C0XJ
|
||||
#: src/modules/page-layout/widgets/components/PageLayoutWidgetNoDataDisplay.tsx
|
||||
msgid "No Data"
|
||||
@@ -7071,6 +7090,11 @@ msgstr ""
|
||||
msgid "Page"
|
||||
msgstr "Pagina"
|
||||
|
||||
#. js-lingui-id: SZgFyi
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "page layout"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: HFlGEK
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "page layout tab"
|
||||
@@ -7517,6 +7541,14 @@ msgstr "Wachtrij: {queueName}"
|
||||
msgid "Quick model for routing decisions"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: iaocTt
|
||||
#: src/modules/page-layout/widgets/graph/hooks/useGraphWidgetAggregateQuery.ts
|
||||
#: src/modules/page-layout/widgets/graph/hooks/useGraphWidgetAggregateQuery.ts
|
||||
#: src/modules/page-layout/widgets/graph/graphWidgetAggregateChart/utils/getAggregateChartOperationLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
msgid "Ratio"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: BT7pY+
|
||||
#: src/modules/settings/profile/components/SetOrChangePassword.tsx
|
||||
msgid "Receive an email containing password set link"
|
||||
@@ -8103,6 +8135,11 @@ msgstr "Zoek objecten"
|
||||
msgid "Search operations"
|
||||
msgstr "Zoek bewerkingen"
|
||||
|
||||
#. js-lingui-id: 996xRf
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "Search options"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: IMeaSJ
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
@@ -8332,6 +8369,11 @@ msgstr "Selecteer de gebeurtenissen die je naar dit eindpunt wilt sturen"
|
||||
msgid "Select the sheet to use"
|
||||
msgstr "Selecteer het te gebruiken blad"
|
||||
|
||||
#. js-lingui-id: iWf0Zj
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "Select value"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: AXTJAW
|
||||
#: src/pages/settings/profile/appearance/components/SettingsExperience.tsx
|
||||
msgid "Select your preferred language"
|
||||
@@ -9495,6 +9537,8 @@ msgstr "Activeert de functie met een Http-verzoek"
|
||||
#. js-lingui-id: c+xCSz
|
||||
#: src/modules/settings/data-model/fields/forms/boolean/constants/BooleanDataModelSelectOptions.ts
|
||||
#: src/modules/object-record/record-board/record-board-column/utils/getAggregateOperationShortLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "True"
|
||||
msgstr "Waar"
|
||||
|
||||
|
||||
@@ -1063,6 +1063,11 @@ msgstr "Tillat opplasting av filer og vedlegg"
|
||||
msgid "Allow users to sign in with an email and password."
|
||||
msgstr "Tillat brukere å logge inn med en e-post og passord."
|
||||
|
||||
#. js-lingui-id: Vw8l6h
|
||||
#: src/modules/workflow/workflow-steps/workflow-actions/http-request-action/components/HttpRequestExecutionResult.tsx
|
||||
msgid "An error occurred"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: VD5bNu
|
||||
#: src/modules/auth/sign-in-up/hooks/useSignInUp.ts
|
||||
msgid "An error occurred while checking user existence"
|
||||
@@ -1076,9 +1081,6 @@ msgstr "En feil oppstod ved behandling av meldingen din"
|
||||
#. js-lingui-id: XyOToQ
|
||||
#: src/utils/get-error-message-from-apollo-error.util.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
@@ -3811,8 +3813,12 @@ msgstr "Skriv inn et tall mellom 0 og 23"
|
||||
msgid "Enter number between 0 and 59"
|
||||
msgstr "Skriv inn et tall mellom 0 og 59"
|
||||
|
||||
#. js-lingui-id: ahwn7G
|
||||
#. js-lingui-id: uhDeRP
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Enter number between 1 and 60"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: ahwn7G
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Enter number greater than 1"
|
||||
@@ -4384,6 +4390,8 @@ msgstr "Feil ved opplasting av bilde: "
|
||||
#. js-lingui-id: ocUvR+
|
||||
#: src/modules/settings/data-model/fields/forms/boolean/constants/BooleanDataModelSelectOptions.ts
|
||||
#: src/modules/object-record/record-board/record-board-column/utils/getAggregateOperationShortLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "False"
|
||||
msgstr "Usann"
|
||||
|
||||
@@ -6015,6 +6023,11 @@ msgstr "Min"
|
||||
msgid "Min range"
|
||||
msgstr "Min rekkevidde"
|
||||
|
||||
#. js-lingui-id: G6wJK8
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Minute value cannot exceed 60. For intervals greater than 60 minutes, use the \"Hours\" trigger type or a custom cron expression"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: agRWc1
|
||||
#: src/modules/workflow/workflow-steps/workflow-actions/delay-actions/components/WorkflowEditActionDelay.tsx
|
||||
msgid "Minutes"
|
||||
@@ -6428,6 +6441,12 @@ msgstr "Ingen konfigurasjonsvariabler samsvarer med dine gjeldende filtre. Prøv
|
||||
msgid "No country"
|
||||
msgstr "Intet land"
|
||||
|
||||
#. js-lingui-id: plhHQt
|
||||
#: src/modules/page-layout/widgets/graph/graphWidgetPieChart/components/PieChartCenterMetricLayer.tsx
|
||||
#: src/modules/page-layout/widgets/graph/components/NoDataLayer.tsx
|
||||
msgid "No data"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: B6C0XJ
|
||||
#: src/modules/page-layout/widgets/components/PageLayoutWidgetNoDataDisplay.tsx
|
||||
msgid "No Data"
|
||||
@@ -7071,6 +7090,11 @@ msgstr ""
|
||||
msgid "Page"
|
||||
msgstr "Side"
|
||||
|
||||
#. js-lingui-id: SZgFyi
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "page layout"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: HFlGEK
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "page layout tab"
|
||||
@@ -7517,6 +7541,14 @@ msgstr "Kø: {queueName}"
|
||||
msgid "Quick model for routing decisions"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: iaocTt
|
||||
#: src/modules/page-layout/widgets/graph/hooks/useGraphWidgetAggregateQuery.ts
|
||||
#: src/modules/page-layout/widgets/graph/hooks/useGraphWidgetAggregateQuery.ts
|
||||
#: src/modules/page-layout/widgets/graph/graphWidgetAggregateChart/utils/getAggregateChartOperationLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
msgid "Ratio"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: BT7pY+
|
||||
#: src/modules/settings/profile/components/SetOrChangePassword.tsx
|
||||
msgid "Receive an email containing password set link"
|
||||
@@ -8103,6 +8135,11 @@ msgstr "Søk objekter"
|
||||
msgid "Search operations"
|
||||
msgstr "Søk operasjoner"
|
||||
|
||||
#. js-lingui-id: 996xRf
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "Search options"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: IMeaSJ
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
@@ -8332,6 +8369,11 @@ msgstr "Velg hendelsene du ønsker å sende til dette sluttpunktet"
|
||||
msgid "Select the sheet to use"
|
||||
msgstr "Velg arket som skal brukes"
|
||||
|
||||
#. js-lingui-id: iWf0Zj
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "Select value"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: AXTJAW
|
||||
#: src/pages/settings/profile/appearance/components/SettingsExperience.tsx
|
||||
msgid "Select your preferred language"
|
||||
@@ -9493,6 +9535,8 @@ msgstr "Utløser funksjonen med Http-forespørsel"
|
||||
#. js-lingui-id: c+xCSz
|
||||
#: src/modules/settings/data-model/fields/forms/boolean/constants/BooleanDataModelSelectOptions.ts
|
||||
#: src/modules/object-record/record-board/record-board-column/utils/getAggregateOperationShortLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "True"
|
||||
msgstr "Sant"
|
||||
|
||||
|
||||
@@ -1063,6 +1063,11 @@ msgstr "Zezwól na przesyłanie plików i załączników"
|
||||
msgid "Allow users to sign in with an email and password."
|
||||
msgstr "Zezwalaj użytkownikom na logowanie się za pomocą adresu e-mail i hasła."
|
||||
|
||||
#. js-lingui-id: Vw8l6h
|
||||
#: src/modules/workflow/workflow-steps/workflow-actions/http-request-action/components/HttpRequestExecutionResult.tsx
|
||||
msgid "An error occurred"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: VD5bNu
|
||||
#: src/modules/auth/sign-in-up/hooks/useSignInUp.ts
|
||||
msgid "An error occurred while checking user existence"
|
||||
@@ -1076,9 +1081,6 @@ msgstr "Wystąpił błąd podczas przetwarzania twojej wiadomości"
|
||||
#. js-lingui-id: XyOToQ
|
||||
#: src/utils/get-error-message-from-apollo-error.util.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
@@ -3811,8 +3813,12 @@ msgstr "Wprowadź liczbę między 0 a 23"
|
||||
msgid "Enter number between 0 and 59"
|
||||
msgstr "Wprowadź liczbę między 0 a 59"
|
||||
|
||||
#. js-lingui-id: ahwn7G
|
||||
#. js-lingui-id: uhDeRP
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Enter number between 1 and 60"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: ahwn7G
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Enter number greater than 1"
|
||||
@@ -4384,6 +4390,8 @@ msgstr "Nie udało się przesłać obrazu: "
|
||||
#. js-lingui-id: ocUvR+
|
||||
#: src/modules/settings/data-model/fields/forms/boolean/constants/BooleanDataModelSelectOptions.ts
|
||||
#: src/modules/object-record/record-board/record-board-column/utils/getAggregateOperationShortLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "False"
|
||||
msgstr "Fałsz"
|
||||
|
||||
@@ -6015,6 +6023,11 @@ msgstr "Min"
|
||||
msgid "Min range"
|
||||
msgstr "Minimalny zakres"
|
||||
|
||||
#. js-lingui-id: G6wJK8
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Minute value cannot exceed 60. For intervals greater than 60 minutes, use the \"Hours\" trigger type or a custom cron expression"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: agRWc1
|
||||
#: src/modules/workflow/workflow-steps/workflow-actions/delay-actions/components/WorkflowEditActionDelay.tsx
|
||||
msgid "Minutes"
|
||||
@@ -6428,6 +6441,12 @@ msgstr "Żadne zmienne konfiguracyjne nie odpowiadają bieżącym filtrom. Spró
|
||||
msgid "No country"
|
||||
msgstr "Brak kraju"
|
||||
|
||||
#. js-lingui-id: plhHQt
|
||||
#: src/modules/page-layout/widgets/graph/graphWidgetPieChart/components/PieChartCenterMetricLayer.tsx
|
||||
#: src/modules/page-layout/widgets/graph/components/NoDataLayer.tsx
|
||||
msgid "No data"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: B6C0XJ
|
||||
#: src/modules/page-layout/widgets/components/PageLayoutWidgetNoDataDisplay.tsx
|
||||
msgid "No Data"
|
||||
@@ -7071,6 +7090,11 @@ msgstr ""
|
||||
msgid "Page"
|
||||
msgstr "Strona"
|
||||
|
||||
#. js-lingui-id: SZgFyi
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "page layout"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: HFlGEK
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "page layout tab"
|
||||
@@ -7517,6 +7541,14 @@ msgstr "Kolejka: {queueName}"
|
||||
msgid "Quick model for routing decisions"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: iaocTt
|
||||
#: src/modules/page-layout/widgets/graph/hooks/useGraphWidgetAggregateQuery.ts
|
||||
#: src/modules/page-layout/widgets/graph/hooks/useGraphWidgetAggregateQuery.ts
|
||||
#: src/modules/page-layout/widgets/graph/graphWidgetAggregateChart/utils/getAggregateChartOperationLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
msgid "Ratio"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: BT7pY+
|
||||
#: src/modules/settings/profile/components/SetOrChangePassword.tsx
|
||||
msgid "Receive an email containing password set link"
|
||||
@@ -8103,6 +8135,11 @@ msgstr "Szukaj obiektów"
|
||||
msgid "Search operations"
|
||||
msgstr "Szukaj operacji"
|
||||
|
||||
#. js-lingui-id: 996xRf
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "Search options"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: IMeaSJ
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
@@ -8332,6 +8369,11 @@ msgstr "Wybierz zdarzenia, które chcesz wysłać do tego punktu końcowego"
|
||||
msgid "Select the sheet to use"
|
||||
msgstr "Wybierz arkusz do użycia"
|
||||
|
||||
#. js-lingui-id: iWf0Zj
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "Select value"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: AXTJAW
|
||||
#: src/pages/settings/profile/appearance/components/SettingsExperience.tsx
|
||||
msgid "Select your preferred language"
|
||||
@@ -9493,6 +9535,8 @@ msgstr "Wywołuje funkcję żądaniem Http"
|
||||
#. js-lingui-id: c+xCSz
|
||||
#: src/modules/settings/data-model/fields/forms/boolean/constants/BooleanDataModelSelectOptions.ts
|
||||
#: src/modules/object-record/record-board/record-board-column/utils/getAggregateOperationShortLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "True"
|
||||
msgstr "Prawda"
|
||||
|
||||
|
||||
@@ -1058,6 +1058,11 @@ msgstr ""
|
||||
msgid "Allow users to sign in with an email and password."
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: Vw8l6h
|
||||
#: src/modules/workflow/workflow-steps/workflow-actions/http-request-action/components/HttpRequestExecutionResult.tsx
|
||||
msgid "An error occurred"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: VD5bNu
|
||||
#: src/modules/auth/sign-in-up/hooks/useSignInUp.ts
|
||||
msgid "An error occurred while checking user existence"
|
||||
@@ -1071,9 +1076,6 @@ msgstr ""
|
||||
#. js-lingui-id: XyOToQ
|
||||
#: src/utils/get-error-message-from-apollo-error.util.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
@@ -3806,8 +3808,12 @@ msgstr ""
|
||||
msgid "Enter number between 0 and 59"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: ahwn7G
|
||||
#. js-lingui-id: uhDeRP
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Enter number between 1 and 60"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: ahwn7G
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Enter number greater than 1"
|
||||
@@ -4379,6 +4385,8 @@ msgstr ""
|
||||
#. js-lingui-id: ocUvR+
|
||||
#: src/modules/settings/data-model/fields/forms/boolean/constants/BooleanDataModelSelectOptions.ts
|
||||
#: src/modules/object-record/record-board/record-board-column/utils/getAggregateOperationShortLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "False"
|
||||
msgstr ""
|
||||
|
||||
@@ -6010,6 +6018,11 @@ msgstr ""
|
||||
msgid "Min range"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: G6wJK8
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Minute value cannot exceed 60. For intervals greater than 60 minutes, use the \"Hours\" trigger type or a custom cron expression"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: agRWc1
|
||||
#: src/modules/workflow/workflow-steps/workflow-actions/delay-actions/components/WorkflowEditActionDelay.tsx
|
||||
msgid "Minutes"
|
||||
@@ -6423,6 +6436,12 @@ msgstr ""
|
||||
msgid "No country"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: plhHQt
|
||||
#: src/modules/page-layout/widgets/graph/graphWidgetPieChart/components/PieChartCenterMetricLayer.tsx
|
||||
#: src/modules/page-layout/widgets/graph/components/NoDataLayer.tsx
|
||||
msgid "No data"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: B6C0XJ
|
||||
#: src/modules/page-layout/widgets/components/PageLayoutWidgetNoDataDisplay.tsx
|
||||
msgid "No Data"
|
||||
@@ -7066,6 +7085,11 @@ msgstr ""
|
||||
msgid "Page"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: SZgFyi
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "page layout"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: HFlGEK
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "page layout tab"
|
||||
@@ -7512,6 +7536,14 @@ msgstr ""
|
||||
msgid "Quick model for routing decisions"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: iaocTt
|
||||
#: src/modules/page-layout/widgets/graph/hooks/useGraphWidgetAggregateQuery.ts
|
||||
#: src/modules/page-layout/widgets/graph/hooks/useGraphWidgetAggregateQuery.ts
|
||||
#: src/modules/page-layout/widgets/graph/graphWidgetAggregateChart/utils/getAggregateChartOperationLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
msgid "Ratio"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: BT7pY+
|
||||
#: src/modules/settings/profile/components/SetOrChangePassword.tsx
|
||||
msgid "Receive an email containing password set link"
|
||||
@@ -8098,6 +8130,11 @@ msgstr ""
|
||||
msgid "Search operations"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: 996xRf
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "Search options"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: IMeaSJ
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
@@ -8327,6 +8364,11 @@ msgstr ""
|
||||
msgid "Select the sheet to use"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: iWf0Zj
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "Select value"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: AXTJAW
|
||||
#: src/pages/settings/profile/appearance/components/SettingsExperience.tsx
|
||||
msgid "Select your preferred language"
|
||||
@@ -9488,6 +9530,8 @@ msgstr ""
|
||||
#. js-lingui-id: c+xCSz
|
||||
#: src/modules/settings/data-model/fields/forms/boolean/constants/BooleanDataModelSelectOptions.ts
|
||||
#: src/modules/object-record/record-board/record-board-column/utils/getAggregateOperationShortLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "True"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -1063,6 +1063,11 @@ msgstr "Permitir upload de arquivos e anexos"
|
||||
msgid "Allow users to sign in with an email and password."
|
||||
msgstr "Permitir que os usuários façam login com e-mail e senha."
|
||||
|
||||
#. js-lingui-id: Vw8l6h
|
||||
#: src/modules/workflow/workflow-steps/workflow-actions/http-request-action/components/HttpRequestExecutionResult.tsx
|
||||
msgid "An error occurred"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: VD5bNu
|
||||
#: src/modules/auth/sign-in-up/hooks/useSignInUp.ts
|
||||
msgid "An error occurred while checking user existence"
|
||||
@@ -1076,9 +1081,6 @@ msgstr "Ocorreu um erro ao processar sua mensagem"
|
||||
#. js-lingui-id: XyOToQ
|
||||
#: src/utils/get-error-message-from-apollo-error.util.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
@@ -3811,8 +3813,12 @@ msgstr "Insira um número entre 0 e 23"
|
||||
msgid "Enter number between 0 and 59"
|
||||
msgstr "Insira um número entre 0 e 59"
|
||||
|
||||
#. js-lingui-id: ahwn7G
|
||||
#. js-lingui-id: uhDeRP
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Enter number between 1 and 60"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: ahwn7G
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Enter number greater than 1"
|
||||
@@ -4384,6 +4390,8 @@ msgstr "Falha ao enviar imagem: "
|
||||
#. js-lingui-id: ocUvR+
|
||||
#: src/modules/settings/data-model/fields/forms/boolean/constants/BooleanDataModelSelectOptions.ts
|
||||
#: src/modules/object-record/record-board/record-board-column/utils/getAggregateOperationShortLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "False"
|
||||
msgstr "Falso"
|
||||
|
||||
@@ -6015,6 +6023,11 @@ msgstr "Mínimo"
|
||||
msgid "Min range"
|
||||
msgstr "Intervalo mínimo"
|
||||
|
||||
#. js-lingui-id: G6wJK8
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Minute value cannot exceed 60. For intervals greater than 60 minutes, use the \"Hours\" trigger type or a custom cron expression"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: agRWc1
|
||||
#: src/modules/workflow/workflow-steps/workflow-actions/delay-actions/components/WorkflowEditActionDelay.tsx
|
||||
msgid "Minutes"
|
||||
@@ -6428,6 +6441,12 @@ msgstr "Nenhuma variável de configuração corresponde aos seus filtros atuais.
|
||||
msgid "No country"
|
||||
msgstr "Sem país"
|
||||
|
||||
#. js-lingui-id: plhHQt
|
||||
#: src/modules/page-layout/widgets/graph/graphWidgetPieChart/components/PieChartCenterMetricLayer.tsx
|
||||
#: src/modules/page-layout/widgets/graph/components/NoDataLayer.tsx
|
||||
msgid "No data"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: B6C0XJ
|
||||
#: src/modules/page-layout/widgets/components/PageLayoutWidgetNoDataDisplay.tsx
|
||||
msgid "No Data"
|
||||
@@ -7071,6 +7090,11 @@ msgstr ""
|
||||
msgid "Page"
|
||||
msgstr "Página"
|
||||
|
||||
#. js-lingui-id: SZgFyi
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "page layout"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: HFlGEK
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "page layout tab"
|
||||
@@ -7517,6 +7541,14 @@ msgstr "Fila: {queueName}"
|
||||
msgid "Quick model for routing decisions"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: iaocTt
|
||||
#: src/modules/page-layout/widgets/graph/hooks/useGraphWidgetAggregateQuery.ts
|
||||
#: src/modules/page-layout/widgets/graph/hooks/useGraphWidgetAggregateQuery.ts
|
||||
#: src/modules/page-layout/widgets/graph/graphWidgetAggregateChart/utils/getAggregateChartOperationLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
msgid "Ratio"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: BT7pY+
|
||||
#: src/modules/settings/profile/components/SetOrChangePassword.tsx
|
||||
msgid "Receive an email containing password set link"
|
||||
@@ -8103,6 +8135,11 @@ msgstr "Pesquisar objetos"
|
||||
msgid "Search operations"
|
||||
msgstr "Pesquisar operações"
|
||||
|
||||
#. js-lingui-id: 996xRf
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "Search options"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: IMeaSJ
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
@@ -8332,6 +8369,11 @@ msgstr "Selecione os eventos que deseja enviar para este endpoint"
|
||||
msgid "Select the sheet to use"
|
||||
msgstr "Selecione a planilha a ser usada"
|
||||
|
||||
#. js-lingui-id: iWf0Zj
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "Select value"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: AXTJAW
|
||||
#: src/pages/settings/profile/appearance/components/SettingsExperience.tsx
|
||||
msgid "Select your preferred language"
|
||||
@@ -9493,6 +9535,8 @@ msgstr "Aciona a função com solicitação Http"
|
||||
#. js-lingui-id: c+xCSz
|
||||
#: src/modules/settings/data-model/fields/forms/boolean/constants/BooleanDataModelSelectOptions.ts
|
||||
#: src/modules/object-record/record-board/record-board-column/utils/getAggregateOperationShortLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "True"
|
||||
msgstr "Verdadeiro"
|
||||
|
||||
|
||||
@@ -1063,6 +1063,11 @@ msgstr "Permitir upload de arquivos e anexos"
|
||||
msgid "Allow users to sign in with an email and password."
|
||||
msgstr "Permitir que os utilizadores iniciem sessão com um e-mail e uma palavra-passe."
|
||||
|
||||
#. js-lingui-id: Vw8l6h
|
||||
#: src/modules/workflow/workflow-steps/workflow-actions/http-request-action/components/HttpRequestExecutionResult.tsx
|
||||
msgid "An error occurred"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: VD5bNu
|
||||
#: src/modules/auth/sign-in-up/hooks/useSignInUp.ts
|
||||
msgid "An error occurred while checking user existence"
|
||||
@@ -1076,9 +1081,6 @@ msgstr "Ocorreu um erro ao processar sua mensagem"
|
||||
#. js-lingui-id: XyOToQ
|
||||
#: src/utils/get-error-message-from-apollo-error.util.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
@@ -3811,8 +3813,12 @@ msgstr "Insira um número entre 0 e 23"
|
||||
msgid "Enter number between 0 and 59"
|
||||
msgstr "Insira um número entre 0 e 59"
|
||||
|
||||
#. js-lingui-id: ahwn7G
|
||||
#. js-lingui-id: uhDeRP
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Enter number between 1 and 60"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: ahwn7G
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Enter number greater than 1"
|
||||
@@ -4384,6 +4390,8 @@ msgstr "Falha ao carregar a imagem: "
|
||||
#. js-lingui-id: ocUvR+
|
||||
#: src/modules/settings/data-model/fields/forms/boolean/constants/BooleanDataModelSelectOptions.ts
|
||||
#: src/modules/object-record/record-board/record-board-column/utils/getAggregateOperationShortLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "False"
|
||||
msgstr "Falso"
|
||||
|
||||
@@ -6015,6 +6023,11 @@ msgstr "Mínimo"
|
||||
msgid "Min range"
|
||||
msgstr "Intervalo Mínimo"
|
||||
|
||||
#. js-lingui-id: G6wJK8
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Minute value cannot exceed 60. For intervals greater than 60 minutes, use the \"Hours\" trigger type or a custom cron expression"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: agRWc1
|
||||
#: src/modules/workflow/workflow-steps/workflow-actions/delay-actions/components/WorkflowEditActionDelay.tsx
|
||||
msgid "Minutes"
|
||||
@@ -6428,6 +6441,12 @@ msgstr "Nenhuma variável de configuração corresponde aos seus filtros atuais.
|
||||
msgid "No country"
|
||||
msgstr "Sem país"
|
||||
|
||||
#. js-lingui-id: plhHQt
|
||||
#: src/modules/page-layout/widgets/graph/graphWidgetPieChart/components/PieChartCenterMetricLayer.tsx
|
||||
#: src/modules/page-layout/widgets/graph/components/NoDataLayer.tsx
|
||||
msgid "No data"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: B6C0XJ
|
||||
#: src/modules/page-layout/widgets/components/PageLayoutWidgetNoDataDisplay.tsx
|
||||
msgid "No Data"
|
||||
@@ -7071,6 +7090,11 @@ msgstr ""
|
||||
msgid "Page"
|
||||
msgstr "Página"
|
||||
|
||||
#. js-lingui-id: SZgFyi
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "page layout"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: HFlGEK
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "page layout tab"
|
||||
@@ -7517,6 +7541,14 @@ msgstr "Fila: {queueName}"
|
||||
msgid "Quick model for routing decisions"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: iaocTt
|
||||
#: src/modules/page-layout/widgets/graph/hooks/useGraphWidgetAggregateQuery.ts
|
||||
#: src/modules/page-layout/widgets/graph/hooks/useGraphWidgetAggregateQuery.ts
|
||||
#: src/modules/page-layout/widgets/graph/graphWidgetAggregateChart/utils/getAggregateChartOperationLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
msgid "Ratio"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: BT7pY+
|
||||
#: src/modules/settings/profile/components/SetOrChangePassword.tsx
|
||||
msgid "Receive an email containing password set link"
|
||||
@@ -8103,6 +8135,11 @@ msgstr "Pesquisar objetos"
|
||||
msgid "Search operations"
|
||||
msgstr "Pesquisar operações"
|
||||
|
||||
#. js-lingui-id: 996xRf
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "Search options"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: IMeaSJ
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
@@ -8332,6 +8369,11 @@ msgstr "Selecione os eventos que deseja enviar para este endpoint"
|
||||
msgid "Select the sheet to use"
|
||||
msgstr "Selecione a planilha a ser usada"
|
||||
|
||||
#. js-lingui-id: iWf0Zj
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "Select value"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: AXTJAW
|
||||
#: src/pages/settings/profile/appearance/components/SettingsExperience.tsx
|
||||
msgid "Select your preferred language"
|
||||
@@ -9493,6 +9535,8 @@ msgstr "Aciona a função com solicitação Http"
|
||||
#. js-lingui-id: c+xCSz
|
||||
#: src/modules/settings/data-model/fields/forms/boolean/constants/BooleanDataModelSelectOptions.ts
|
||||
#: src/modules/object-record/record-board/record-board-column/utils/getAggregateOperationShortLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "True"
|
||||
msgstr "Verdadeiro"
|
||||
|
||||
|
||||
@@ -1063,6 +1063,11 @@ msgstr "Permite încărcarea fișierelor și atașamentelor"
|
||||
msgid "Allow users to sign in with an email and password."
|
||||
msgstr "Permite utilizatorilor să se autentifice cu email și parolă."
|
||||
|
||||
#. js-lingui-id: Vw8l6h
|
||||
#: src/modules/workflow/workflow-steps/workflow-actions/http-request-action/components/HttpRequestExecutionResult.tsx
|
||||
msgid "An error occurred"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: VD5bNu
|
||||
#: src/modules/auth/sign-in-up/hooks/useSignInUp.ts
|
||||
msgid "An error occurred while checking user existence"
|
||||
@@ -1076,9 +1081,6 @@ msgstr "A apărut o eroare la procesarea mesajului dumneavoastră"
|
||||
#. js-lingui-id: XyOToQ
|
||||
#: src/utils/get-error-message-from-apollo-error.util.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewGroup.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
#: src/modules/views/hooks/internal/usePersistViewFilter.ts
|
||||
@@ -3811,8 +3813,12 @@ msgstr "Introduceți un număr între 0 și 23"
|
||||
msgid "Enter number between 0 and 59"
|
||||
msgstr "Introduceți un număr între 0 și 59"
|
||||
|
||||
#. js-lingui-id: ahwn7G
|
||||
#. js-lingui-id: uhDeRP
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Enter number between 1 and 60"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: ahwn7G
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Enter number greater than 1"
|
||||
@@ -4384,6 +4390,8 @@ msgstr "Eșec la încărcarea imaginii:"
|
||||
#. js-lingui-id: ocUvR+
|
||||
#: src/modules/settings/data-model/fields/forms/boolean/constants/BooleanDataModelSelectOptions.ts
|
||||
#: src/modules/object-record/record-board/record-board-column/utils/getAggregateOperationShortLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "False"
|
||||
msgstr "Fals"
|
||||
|
||||
@@ -6015,6 +6023,11 @@ msgstr "Minim"
|
||||
msgid "Min range"
|
||||
msgstr "Interval minim"
|
||||
|
||||
#. js-lingui-id: G6wJK8
|
||||
#: src/modules/workflow/workflow-trigger/components/WorkflowEditTriggerCronForm.tsx
|
||||
msgid "Minute value cannot exceed 60. For intervals greater than 60 minutes, use the \"Hours\" trigger type or a custom cron expression"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: agRWc1
|
||||
#: src/modules/workflow/workflow-steps/workflow-actions/delay-actions/components/WorkflowEditActionDelay.tsx
|
||||
msgid "Minutes"
|
||||
@@ -6428,6 +6441,12 @@ msgstr "Nu există variabile de configurare care să corespundă filtrelor dvs.
|
||||
msgid "No country"
|
||||
msgstr "Fără țară"
|
||||
|
||||
#. js-lingui-id: plhHQt
|
||||
#: src/modules/page-layout/widgets/graph/graphWidgetPieChart/components/PieChartCenterMetricLayer.tsx
|
||||
#: src/modules/page-layout/widgets/graph/components/NoDataLayer.tsx
|
||||
msgid "No data"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: B6C0XJ
|
||||
#: src/modules/page-layout/widgets/components/PageLayoutWidgetNoDataDisplay.tsx
|
||||
msgid "No Data"
|
||||
@@ -7071,6 +7090,11 @@ msgstr ""
|
||||
msgid "Page"
|
||||
msgstr "Pagină"
|
||||
|
||||
#. js-lingui-id: SZgFyi
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "page layout"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: HFlGEK
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "page layout tab"
|
||||
@@ -7517,6 +7541,14 @@ msgstr "Coada: {queueName}"
|
||||
msgid "Quick model for routing decisions"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: iaocTt
|
||||
#: src/modules/page-layout/widgets/graph/hooks/useGraphWidgetAggregateQuery.ts
|
||||
#: src/modules/page-layout/widgets/graph/hooks/useGraphWidgetAggregateQuery.ts
|
||||
#: src/modules/page-layout/widgets/graph/graphWidgetAggregateChart/utils/getAggregateChartOperationLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
msgid "Ratio"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: BT7pY+
|
||||
#: src/modules/settings/profile/components/SetOrChangePassword.tsx
|
||||
msgid "Receive an email containing password set link"
|
||||
@@ -8103,6 +8135,11 @@ msgstr "Caută obiecte"
|
||||
msgid "Search operations"
|
||||
msgstr "Caută operațiuni"
|
||||
|
||||
#. js-lingui-id: 996xRf
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "Search options"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: IMeaSJ
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
@@ -8332,6 +8369,11 @@ msgstr "Selectează evenimentele pe care dorești să le trimiți la acest endpo
|
||||
msgid "Select the sheet to use"
|
||||
msgstr "Selectează foaia de utilizat"
|
||||
|
||||
#. js-lingui-id: iWf0Zj
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "Select value"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: AXTJAW
|
||||
#: src/pages/settings/profile/appearance/components/SettingsExperience.tsx
|
||||
msgid "Select your preferred language"
|
||||
@@ -9493,6 +9535,8 @@ msgstr "Declanșează funcția cu cerere Http"
|
||||
#. js-lingui-id: c+xCSz
|
||||
#: src/modules/settings/data-model/fields/forms/boolean/constants/BooleanDataModelSelectOptions.ts
|
||||
#: src/modules/object-record/record-board/record-board-column/utils/getAggregateOperationShortLabel.ts
|
||||
#: src/modules/command-menu/pages/page-layout/hooks/useChartSettingsValues.ts
|
||||
#: src/modules/command-menu/pages/page-layout/components/dropdown-content/ChartRatioOptionValueSelectionDropdownContent.tsx
|
||||
msgid "True"
|
||||
msgstr "Adevărat"
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user