feat: integrate Microsoft Power Apps SDK and enhance email handling
- Added dependency for @microsoft/power-apps to package.json. - Updated power.config.json to include appId, environmentId, and connection references. - Implemented sanitisation function for HTML content in App.tsx to prevent XSS. - Enhanced error handling in email loading and marking as read functionalities. - Updated email display logic to handle HTML content and previews. - Refactored OutlookService to use auto-generated service from @microsoft/power-apps. - Added new methods for sending, marking as read, and deleting emails in OutlookService. - Updated types for Email to include bodyPreview, isHtml, hasAttachments, and importance. - Configured Vite to exclude @microsoft/power-apps/data from the build. - Created .gitignore to exclude build artifacts and environment files. - Added local development shim for @microsoft/power-apps/data to support local testing. - Defined type stubs for @microsoft/power-apps/data to facilitate local development.
This commit is contained in:
68
src/App.tsx
68
src/App.tsx
@@ -48,6 +48,14 @@ import { OutlookService } from './services/OutlookService';
|
||||
|
||||
const outlookService = new OutlookService();
|
||||
|
||||
/** Sanitise HTML content - strip script tags for basic XSS prevention */
|
||||
function sanitiseHtml(html: string): string {
|
||||
return html
|
||||
.replace(/<script[\s\S]*?<\/script>/gi, '')
|
||||
.replace(/on\w+="[^"]*"/gi, '')
|
||||
.replace(/on\w+='[^']*'/gi, '');
|
||||
}
|
||||
|
||||
/* ─── styles ─── */
|
||||
const useStyles = makeStyles({
|
||||
root: {
|
||||
@@ -426,14 +434,20 @@ export default function App() {
|
||||
const [composeSubject, setComposeSubject] = useState('');
|
||||
const [composeBody, setComposeBody] = useState('');
|
||||
const [sending, setSending] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
/* data */
|
||||
const loadEmails = useCallback(async () => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const msgs = await outlookService.getMessages(activeFolder);
|
||||
setEmails(msgs);
|
||||
setSelectedEmail(null);
|
||||
} catch (err) {
|
||||
console.error('Error loading emails:', err);
|
||||
setError('No se pudieron cargar los correos. Comprueba la conexión.');
|
||||
setEmails([]);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -446,8 +460,22 @@ export default function App() {
|
||||
const selectEmail = async (email: Email) => {
|
||||
setSelectedEmail(email);
|
||||
if (!email.isRead) {
|
||||
await outlookService.markAsRead(email.id);
|
||||
setEmails(prev => prev.map(e => (e.id === email.id ? { ...e, isRead: true } : e)));
|
||||
try {
|
||||
await outlookService.markAsRead(email.id);
|
||||
setEmails(prev => prev.map(e => (e.id === email.id ? { ...e, isRead: true } : e)));
|
||||
} catch (err) {
|
||||
console.error('Error marking as read:', err);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = async (email: Email) => {
|
||||
try {
|
||||
await outlookService.deleteEmail(email.id);
|
||||
setEmails(prev => prev.filter(e => e.id !== email.id));
|
||||
if (selectedEmail?.id === email.id) setSelectedEmail(null);
|
||||
} catch (err) {
|
||||
console.error('Error deleting email:', err);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -524,12 +552,12 @@ export default function App() {
|
||||
<aside className={s.folderPane}>
|
||||
<div className={s.folderHeader}>
|
||||
<Avatar
|
||||
name="Lago"
|
||||
initials="LA"
|
||||
name="User"
|
||||
initials="US"
|
||||
size={28}
|
||||
color="brand"
|
||||
/>
|
||||
<Text weight="semibold" size={300}>lago@powerplatform.top</Text>
|
||||
<Text weight="semibold" size={300}>user@contoso.com</Text>
|
||||
</div>
|
||||
|
||||
<Divider style={{ margin: '6px 0' }} />
|
||||
@@ -601,6 +629,14 @@ export default function App() {
|
||||
<div className={s.emptyState} style={{ padding: '40px 0' }}>
|
||||
<Spinner size="small" label="Cargando…" />
|
||||
</div>
|
||||
) : error ? (
|
||||
<div className={s.emptyState} style={{ padding: '60px 16px' }}>
|
||||
<Mail24Regular fontSize={40} style={{ opacity: 0.3 }} />
|
||||
<Text size={300} style={{ textAlign: 'center' }}>{error}</Text>
|
||||
<Button appearance="subtle" size="small" icon={<ArrowSync24Regular />} onClick={loadEmails}>
|
||||
Reintentar
|
||||
</Button>
|
||||
</div>
|
||||
) : filtered.length === 0 ? (
|
||||
<div className={s.emptyState} style={{ padding: '60px 16px' }}>
|
||||
<Mail24Regular fontSize={40} style={{ opacity: 0.3 }} />
|
||||
@@ -632,7 +668,7 @@ export default function App() {
|
||||
<span className={s.messageTime}>{formatTime(email.receivedAt)}</span>
|
||||
</div>
|
||||
<div className={s.messageSubject}>{email.subject}</div>
|
||||
<div className={s.messagePreview}>{email.body.substring(0, 80)}…</div>
|
||||
<div className={s.messagePreview}>{email.bodyPreview || email.body.substring(0, 80)}…</div>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
@@ -672,7 +708,14 @@ export default function App() {
|
||||
|
||||
<Divider style={{ margin: '0 28px' }} />
|
||||
|
||||
<div className={s.readingBody}>{selectedEmail.body}</div>
|
||||
{selectedEmail.isHtml ? (
|
||||
<div
|
||||
className={s.readingBody}
|
||||
dangerouslySetInnerHTML={{ __html: sanitiseHtml(selectedEmail.body) }}
|
||||
/>
|
||||
) : (
|
||||
<div className={s.readingBody}>{selectedEmail.body}</div>
|
||||
)}
|
||||
|
||||
<Divider style={{ margin: '0 28px' }} />
|
||||
|
||||
@@ -697,6 +740,7 @@ export default function App() {
|
||||
appearance="subtle"
|
||||
icon={<Delete24Regular />}
|
||||
size="small"
|
||||
onClick={() => handleDelete(selectedEmail)}
|
||||
>
|
||||
Eliminar
|
||||
</Button>
|
||||
@@ -806,11 +850,9 @@ function ComposeDialog({
|
||||
function ConnectionsPanel() {
|
||||
const s = useStyles();
|
||||
|
||||
// Connections from power.config.json — replace with your own
|
||||
const connections = [
|
||||
{ id: '1', name: 'shared_outlook', displayName: 'Outlook', connector: 'Microsoft Outlook', status: 'connected' as const, color: '#0078D4' },
|
||||
{ id: '2', name: 'shared_office365', displayName: 'Office 365 Users', connector: 'Office 365 Users', status: 'connected' as const, color: '#D83B01' },
|
||||
{ id: '3', name: 'shared_dataverse', displayName: 'Dataverse', connector: 'Microsoft Dataverse', status: 'connected' as const, color: '#7719BA' },
|
||||
{ id: '4', name: 'shared_azuresql', displayName: 'Azure SQL', connector: 'Azure SQL Database', status: 'disconnected' as const, color: '#0078D4' },
|
||||
{ id: 'your-connection-ref-id', name: 'shared_outlook', displayName: 'Outlook.com', connector: 'Microsoft Outlook', status: 'connected' as const, color: '#0078D4' },
|
||||
];
|
||||
|
||||
const archBoxes = [
|
||||
@@ -818,7 +860,7 @@ function ConnectionsPanel() {
|
||||
{ label: 'PAC Client', bg: tokens.colorNeutralBackground4, fg: tokens.colorNeutralForeground1 },
|
||||
{ label: 'Connection Ref', bg: tokens.colorNeutralBackground4, fg: tokens.colorNeutralForeground1 },
|
||||
{ label: 'Connection', bg: tokens.colorNeutralBackground4, fg: tokens.colorNeutralForeground1 },
|
||||
{ label: 'Outlook / Dataverse', bg: '#7719BA', fg: '#fff' },
|
||||
{ label: 'Outlook', bg: '#0078D4', fg: '#fff' },
|
||||
];
|
||||
|
||||
return (
|
||||
@@ -887,7 +929,7 @@ function ConnectionsPanel() {
|
||||
<br />
|
||||
<div style={{ color: tokens.colorNeutralForeground3 }}># Añadir Outlook como data source</div>
|
||||
<div>pac code add-data-source -a shared_outlook \</div>
|
||||
<div style={{ paddingLeft: 24 }}>-c 4839c34829284206bf6a11d4ce577491</div>
|
||||
<div style={{ paddingLeft: 24 }}>-c <your-connection-id></div>
|
||||
<br />
|
||||
<div style={{ color: tokens.colorNeutralForeground3 }}># Añadir Dataverse (tabular)</div>
|
||||
<div>pac code add-data-source -a shared_commondataservice \</div>
|
||||
|
||||
@@ -1,148 +1,139 @@
|
||||
import type { Email, UserProfile } from '../types';
|
||||
import { Outlook_comService } from '../generated';
|
||||
import type { Outlook_comModel } from '../generated';
|
||||
import type { Email } from '../types';
|
||||
import { FOLDER_PATH_MAP } from '../types';
|
||||
|
||||
// NOTE: In a real code app, these would be injected via the Power Platform
|
||||
// client library (@microsoft/power-apps-code-solutions or similar).
|
||||
// The client exposes typed services like Office365UsersService and
|
||||
// OutlookService that wrap the connector calls.
|
||||
//
|
||||
// For this demo, we simulate the connector responses with mock data.
|
||||
// To wire up real connectors:
|
||||
// 1. Create connections in make.powerapps.com (e.g. "shared_office365", "shared_outlook")
|
||||
// 2. Run: pac code add-data-source -a <apiName> -c <connectionId>
|
||||
// 3. The generator creates typed Model and Service files automatically
|
||||
//
|
||||
// Real service usage would look like:
|
||||
//
|
||||
// import { OutlookService } from './services/OutlookService';
|
||||
// const outlook = new OutlookService();
|
||||
// const emails = await outlook.getMessages('inbox');
|
||||
/**
|
||||
* Adapter around the auto-generated Outlook_comService.
|
||||
* Maps the Power Platform connector responses to the UI's Email type.
|
||||
*/
|
||||
|
||||
const MOCK_USER: UserProfile = {
|
||||
displayName: 'Lago',
|
||||
email: 'jose@lago.dev',
|
||||
};
|
||||
const REVERSE_FOLDER_MAP: Record<string, Email['folder']> = Object.fromEntries(
|
||||
Object.entries(FOLDER_PATH_MAP).map(([k, v]) => [v, k as Email['folder']])
|
||||
) as Record<string, Email['folder']>;
|
||||
|
||||
const MOCK_EMAILS: Email[] = [
|
||||
{
|
||||
id: '1',
|
||||
from: 'jose@lago.dev',
|
||||
fromName: 'Lago',
|
||||
to: 'lago@example.com',
|
||||
subject: 'Sprint planning — Thursday 10:00',
|
||||
body: 'Hola,\n\nVamos a planificar el sprint del mes que viene el jueves a las 10:00. Necesito que reviseis los temas pendientes del backlog.\n\nUn saludo',
|
||||
receivedAt: new Date(Date.now() - 1000 * 60 * 30).toISOString(),
|
||||
isRead: false,
|
||||
folder: 'inbox',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
from: 'maria@partner.com',
|
||||
fromName: 'María García',
|
||||
to: 'lago@example.com',
|
||||
subject: 'RE: Presupuesto proyecto digital 2026',
|
||||
body: 'Hola Lago,\n\nGracias por la información. He revisado el presupuesto y parece correcto. Adjunto la propuesta final.\n\n¿Podemos hablar mañana por teléfono?\n\nSaludos',
|
||||
receivedAt: new Date(Date.now() - 1000 * 60 * 60 * 2).toISOString(),
|
||||
isRead: false,
|
||||
folder: 'inbox',
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
from: 'devops@company.com',
|
||||
fromName: 'DevOps Team',
|
||||
to: 'lago@example.com',
|
||||
subject: '[Alert] Production deployment failed — pipeline #4821',
|
||||
body: 'Pipeline #4821 failed at stage: build\n\nError: npm ERR! code ETARGET\nnpm ERR! notarget No valid target for react@19.0.1\n\nView logs: https://devops.company.com/pipelines/4821',
|
||||
receivedAt: new Date(Date.now() - 1000 * 60 * 60 * 5).toISOString(),
|
||||
isRead: true,
|
||||
folder: 'inbox',
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
from: 'lago@example.com',
|
||||
fromName: 'Lago',
|
||||
to: 'equipo@company.com',
|
||||
subject: 'Resumen semanal — Power Platform',
|
||||
body: 'Equipo,\n\nAquí va el resumen de lo hecho esta semana:\n\n• Demo de code apps publicada en el blog\n• Connector de Outlook integrado\n• Migración del backlog a Dataverse completada\n\nPara la semana que viene:\n• Terminar la integración con Teams\n• Revisar los permisos de producción\n\nSaludos',
|
||||
receivedAt: new Date(Date.now() - 1000 * 60 * 60 * 24).toISOString(),
|
||||
isRead: true,
|
||||
folder: 'sent',
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
from: 'newsletter@techweekly.com',
|
||||
fromName: 'Tech Weekly',
|
||||
to: 'lago@example.com',
|
||||
subject: 'This Week in AI & Power Platform — Issue #47',
|
||||
body: 'Top stories this week:\n\n1. Microsoft announces GPT-5 integration for Copilot Studio\n2. Power Apps code apps reach 1,400+ connectors milestone\n3. Dataverse 2026 Wave 1 features now generally available\n\nRead more inside...',
|
||||
receivedAt: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(),
|
||||
isRead: true,
|
||||
folder: 'inbox',
|
||||
},
|
||||
{
|
||||
id: '6',
|
||||
from: 'noreply@linkedin.com',
|
||||
fromName: 'LinkedIn',
|
||||
to: 'lago@example.com',
|
||||
subject: '5 people viewed your profile this week',
|
||||
body: 'Your profile got noticed!\n\n5 people in Austria viewed your profile in the past week, including recruiters and hiring managers.\n\nView who they are →',
|
||||
receivedAt: new Date(Date.now() - 1000 * 60 * 60 * 72).toISOString(),
|
||||
isRead: true,
|
||||
folder: 'inbox',
|
||||
},
|
||||
{
|
||||
id: '7',
|
||||
from: 'lago@example.com',
|
||||
fromName: 'Lago',
|
||||
to: 'draft',
|
||||
subject: 'Borrador: Propuesta reunión trimestral',
|
||||
body: 'Borrador de la propuesta para la reunión trimestral con dirección...',
|
||||
receivedAt: new Date(Date.now() - 1000 * 60 * 60 * 6).toISOString(),
|
||||
isRead: true,
|
||||
folder: 'drafts',
|
||||
},
|
||||
];
|
||||
/** Extract a display name from "Name <email>" or just "email" */
|
||||
function parseSender(raw?: string): { email: string; name: string } {
|
||||
if (!raw) return { email: '', name: '(desconocido)' };
|
||||
// Some connectors return "Display Name <user@example.com>"
|
||||
const match = raw.match(/^(.+?)\s*<(.+)>$/);
|
||||
if (match) return { name: match[1].trim(), email: match[2].trim() };
|
||||
// Otherwise it's just an email address
|
||||
const namePart = raw.split('@')[0].replace(/[._-]/g, ' ');
|
||||
return { email: raw, name: namePart.charAt(0).toUpperCase() + namePart.slice(1) };
|
||||
}
|
||||
|
||||
function mapMessage(
|
||||
msg: Outlook_comModel.ClientReceiveMessageStringEnums,
|
||||
folderHint: Email['folder'],
|
||||
): Email {
|
||||
const sender = parseSender(msg.From);
|
||||
return {
|
||||
id: msg.Id ?? '',
|
||||
from: sender.email,
|
||||
fromName: sender.name,
|
||||
to: msg.To ?? '',
|
||||
subject: msg.Subject ?? '(Sin asunto)',
|
||||
body: msg.Body ?? '',
|
||||
bodyPreview: msg.BodyPreview ?? msg.Body?.substring(0, 100) ?? '',
|
||||
receivedAt: msg.DateTimeReceived ?? new Date().toISOString(),
|
||||
isRead: msg.IsRead ?? true,
|
||||
isHtml: msg.IsHtml ?? false,
|
||||
hasAttachments: msg.HasAttachment ?? false,
|
||||
importance: msg.Importance ?? 'Normal',
|
||||
folder: folderHint,
|
||||
};
|
||||
}
|
||||
|
||||
export class OutlookService {
|
||||
private delay(ms: number) {
|
||||
return new Promise((r) => setTimeout(r, ms));
|
||||
}
|
||||
|
||||
async getProfile(): Promise<UserProfile> {
|
||||
await this.delay(300);
|
||||
return MOCK_USER;
|
||||
}
|
||||
|
||||
async getMessages(folder: string = 'inbox'): Promise<Email[]> {
|
||||
await this.delay(600);
|
||||
return MOCK_EMAILS.filter((e) => e.folder === folder);
|
||||
/**
|
||||
* Fetch emails from a given folder.
|
||||
* Uses GetEmails (v1) which returns ClientReceiveMessageStringEnums[].
|
||||
*/
|
||||
async getMessages(folder: string = 'inbox', searchQuery?: string): Promise<Email[]> {
|
||||
const folderPath = FOLDER_PATH_MAP[folder] ?? 'Inbox';
|
||||
const uiFolder = (REVERSE_FOLDER_MAP[folderPath] ?? 'inbox') as Email['folder'];
|
||||
|
||||
const result = await Outlook_comService.GetEmails(
|
||||
folderPath,
|
||||
/* fetchOnlyUnread */ undefined,
|
||||
/* includeAttachments */ false,
|
||||
searchQuery,
|
||||
/* top */ 50,
|
||||
);
|
||||
|
||||
const messages = result.data ?? [];
|
||||
return messages.map((m: Outlook_comModel.ClientReceiveMessageStringEnums) => mapMessage(m, uiFolder));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single email by id.
|
||||
*/
|
||||
async getMessage(id: string): Promise<Email | undefined> {
|
||||
await this.delay(200);
|
||||
return MOCK_EMAILS.find((e) => e.id === id);
|
||||
}
|
||||
|
||||
async sendMessage(email: Partial<Email>): Promise<Email> {
|
||||
await this.delay(800);
|
||||
const newEmail: Email = {
|
||||
id: Date.now().toString(),
|
||||
from: MOCK_USER.email,
|
||||
fromName: MOCK_USER.displayName,
|
||||
to: email.to || '',
|
||||
subject: email.subject || '(Sin asunto)',
|
||||
body: email.body || '',
|
||||
receivedAt: new Date().toISOString(),
|
||||
isRead: true,
|
||||
folder: 'sent',
|
||||
const result = await Outlook_comService.GetEmail(id, false);
|
||||
if (!result.data) return undefined;
|
||||
const msg = result.data;
|
||||
const sender = parseSender(msg.From);
|
||||
return {
|
||||
id: msg.Id ?? '',
|
||||
from: sender.email,
|
||||
fromName: sender.name,
|
||||
to: msg.To ?? '',
|
||||
subject: msg.Subject ?? '(Sin asunto)',
|
||||
body: msg.Body ?? '',
|
||||
bodyPreview: msg.BodyPreview ?? '',
|
||||
receivedAt: msg.DateTimeReceived ?? new Date().toISOString(),
|
||||
isRead: msg.IsRead ?? true,
|
||||
isHtml: msg.IsHtml ?? false,
|
||||
hasAttachments: msg.HasAttachment ?? false,
|
||||
importance: 'Normal',
|
||||
folder: 'inbox',
|
||||
};
|
||||
MOCK_EMAILS.push(newEmail);
|
||||
return newEmail;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an email via the V2 HTML endpoint.
|
||||
*/
|
||||
async sendMessage(params: { to: string; subject: string; body: string }): Promise<void> {
|
||||
await Outlook_comService.SendEmailV2({
|
||||
To: params.to,
|
||||
Subject: params.subject,
|
||||
Body: params.body,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark an email as read.
|
||||
*/
|
||||
async markAsRead(id: string): Promise<void> {
|
||||
await this.delay(100);
|
||||
const email = MOCK_EMAILS.find((e) => e.id === id);
|
||||
if (email) email.isRead = true;
|
||||
await Outlook_comService.MarkAsRead(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an email.
|
||||
*/
|
||||
async deleteEmail(id: string): Promise<void> {
|
||||
await Outlook_comService.DeleteEmail(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reply to an email (HTML).
|
||||
*/
|
||||
async replyTo(messageId: string, body: string, replyAll: boolean = false): Promise<void> {
|
||||
await Outlook_comService.ReplyToV3(messageId, {
|
||||
Body: body,
|
||||
ReplyAll: replyAll,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Forward an email.
|
||||
*/
|
||||
async forward(messageId: string, to: string, body: string): Promise<void> {
|
||||
await Outlook_comService.ForwardEmail(messageId, {
|
||||
ToRecipients: to,
|
||||
Comment: body,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
21
src/shims/power-apps-data.ts
Normal file
21
src/shims/power-apps-data.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Local development shim for @microsoft/power-apps/data.
|
||||
*
|
||||
* In production, the Power Platform runtime provides this module.
|
||||
* During local dev, this shim logs calls and returns empty results
|
||||
* so the app can render without the real connector backend.
|
||||
*/
|
||||
|
||||
export function getClient(_dataSourcesInfo: unknown) {
|
||||
return {
|
||||
async executeAsync<_TParams, TResult>(request: {
|
||||
connectorOperation: { operationName: string };
|
||||
}): Promise<{ data?: TResult; error?: { message: string } }> {
|
||||
console.warn(
|
||||
`[power-apps shim] ${request.connectorOperation.operationName} called — ` +
|
||||
`returning empty result (local dev mode, no Power Platform runtime).`
|
||||
);
|
||||
return { data: undefined };
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -5,8 +5,12 @@ export interface Email {
|
||||
to: string;
|
||||
subject: string;
|
||||
body: string;
|
||||
bodyPreview: string;
|
||||
receivedAt: string;
|
||||
isRead: boolean;
|
||||
isHtml: boolean;
|
||||
hasAttachments: boolean;
|
||||
importance: 'Low' | 'Normal' | 'High';
|
||||
folder: 'inbox' | 'sent' | 'drafts' | 'trash';
|
||||
}
|
||||
|
||||
@@ -17,8 +21,10 @@ export interface Folder {
|
||||
unreadCount: number;
|
||||
}
|
||||
|
||||
export interface UserProfile {
|
||||
displayName: string;
|
||||
email: string;
|
||||
avatar?: string;
|
||||
}
|
||||
/** Maps UI folder ids to Outlook folderPath values */
|
||||
export const FOLDER_PATH_MAP: Record<string, string> = {
|
||||
inbox: 'Inbox',
|
||||
sent: 'SentItems',
|
||||
drafts: 'Drafts',
|
||||
trash: 'DeletedItems',
|
||||
};
|
||||
|
||||
29
src/types/power-apps.d.ts
vendored
Normal file
29
src/types/power-apps.d.ts
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Type stub for @microsoft/power-apps/data.
|
||||
*
|
||||
* This module is provided by the Power Platform runtime at execution time.
|
||||
* During local development / tsc type-checking it is not installed,
|
||||
* so we declare just enough types for the generated service to compile.
|
||||
*/
|
||||
declare module '@microsoft/power-apps/data' {
|
||||
export interface IOperationResult<T> {
|
||||
data?: T;
|
||||
error?: { message: string; code?: string };
|
||||
}
|
||||
|
||||
export interface ConnectorOperation {
|
||||
tableName: string;
|
||||
operationName: string;
|
||||
parameters?: unknown;
|
||||
}
|
||||
|
||||
export interface ExecuteRequest {
|
||||
connectorOperation: ConnectorOperation;
|
||||
}
|
||||
|
||||
export interface IClient {
|
||||
executeAsync<TParams, TResult>(request: ExecuteRequest): Promise<IOperationResult<TResult>>;
|
||||
}
|
||||
|
||||
export function getClient(dataSourcesInfo: unknown): IClient;
|
||||
}
|
||||
Reference in New Issue
Block a user