Skip to content

Commit 6177dec

Browse files
heyitsaamirclaude
andauthored
docs: fix content gaps, CI detection, and logging (#2808)
## Summary - fill in missing SubmitData + sub-route handler sections for TS/Python executing-actions page. C# gets N/A with its own "Server Handlers" structure since it doesn't have sub-routing - fix deploy workflow — `NODE_ENV=production` was on a separate `generate:docs` step, then `npm run build` re-ran it via `prebuild` without the env var, overwriting prod output with `[Dev]` markers. moved env to the build step - fix generate script production codepath — it silently skipped missing sections without tracking them in `contentGapsManifest`, so the exit check never fired - clean up Python logging docs (no custom logger injection, just standard logging with optional `ConsoleFormatter`) - move child-logger heading into language includes so it only shows for TS - nuke blank observability index page - add devblogs links to blog posts ## Test plan - [ ] `NODE_ENV=production npm run generate:docs` passes with 0 content gaps - [ ] C# executing-actions page shows "Server Handlers" without SubmitData/routing sections - [ ] TS/Python executing-actions pages show full Routing & Handlers section - [ ] Python logging page doesn't mention custom logger injection - [ ] Child Loggers heading only appears on TS logging page --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 5aafff7 commit 6177dec

11 files changed

Lines changed: 176 additions & 69 deletions

File tree

.github/workflows/deploy-teams-docs.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ jobs:
5555

5656
- name: Build static site
5757
working-directory: teams.md
58+
env:
59+
NODE_ENV: production
5860
run: npm run build
5961

6062
- name: Setup GitHub Pages

teams.md/blog/2026-04-28-teams-cli-preview/index.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ tags: [teams-sdk, cli, preview, announcement, skills]
1919
description: Introducing the teams-dev agent skill and the Teams CLI v3 Preview for registering and building Teams agents.
2020
---
2121

22+
:::info
23+
This post is also available on the [Microsoft 365 Developer Blog](https://devblogs.microsoft.com/microsoft365dev/from-prompt-to-production-teams-agent-setup-simplified/).
24+
:::
25+
2226
You want to build a Teams agent. Maybe it answers customer questions from a knowledge base. Maybe it runs your team's standups. The interesting part is the logic, the thing the agent actually *does*.
2327

2428
But before you write a single line of that logic, you have to register it with Teams. That takes a number of steps.

teams.md/scripts/generate-language-docs.ts

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -175,16 +175,41 @@ function processLanguageIncludeTags(
175175
if (isProduction && targetLanguage) {
176176
const inclPath = getIncludeFilePath(templatePath, targetLanguage);
177177
if (!fs.existsSync(inclPath)) {
178-
// Skip missing content (prod)
178+
// Track missing include file as a content gap
179+
const gapKey = normalizePath(path.relative(TEMPLATES_DIR, templatePath));
180+
if (!contentGapsManifest[gapKey]) {
181+
contentGapsManifest[gapKey] = {};
182+
}
183+
if (!contentGapsManifest[gapKey][sectionName]) {
184+
contentGapsManifest[gapKey][sectionName] = [];
185+
}
186+
if (!contentGapsManifest[gapKey][sectionName].includes(targetLanguage)) {
187+
contentGapsManifest[gapKey][sectionName].push(targetLanguage);
188+
}
179189
return '';
180190
}
181191

182192
try {
183193
const fileContent = readFileUtf8Normalized(inclPath);
184194
const sectionContent = extractSection(fileContent, sectionName);
185195

186-
if (sectionContent === null || sectionContent === '' || sectionContent === 'EMPTY_SECTION') {
187-
// Skip missing sections (null), intentional N/A content (empty string), or empty sections
196+
if (sectionContent === '') {
197+
// Intentional N/A content - skip without tracking as a gap
198+
return '';
199+
}
200+
201+
if (sectionContent === null || sectionContent === 'EMPTY_SECTION') {
202+
// Track missing/empty sections as content gaps
203+
const gapKey = normalizePath(path.relative(TEMPLATES_DIR, templatePath));
204+
if (!contentGapsManifest[gapKey]) {
205+
contentGapsManifest[gapKey] = {};
206+
}
207+
if (!contentGapsManifest[gapKey][sectionName]) {
208+
contentGapsManifest[gapKey][sectionName] = [];
209+
}
210+
if (!contentGapsManifest[gapKey][sectionName].includes(targetLanguage)) {
211+
contentGapsManifest[gapKey][sectionName].push(targetLanguage);
212+
}
188213
return '';
189214
}
190215

teams.md/src/components/include/in-depth-guides/adaptive-cards/executing-actions/csharp.incl.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,13 @@ private static AdaptiveCard CreateProfileCardWithValidation()
192192
}
193193
```
194194

195-
<!-- server-handler-example -->
195+
<!-- handlers-section -->
196+
197+
## Server Handlers
198+
199+
### Basic Structure
200+
201+
Card actions arrive as `card.action` activities in your app. These give you access to the validated input values plus any `data` values you had configured to be sent back to you.
196202

197203
```csharp
198204
using System.Text.Json;
@@ -286,8 +292,6 @@ teams.OnAdaptiveCardAction(async (context, cancellationToken) =>
286292
});
287293
```
288294

289-
<!-- data-typing-note -->
290-
291295
:::note
292296
The `data` values come from JSON and need to be extracted using the helper method shown above to handle different JSON element types.
293297
:::

teams.md/src/components/include/in-depth-guides/adaptive-cards/executing-actions/python.incl.md

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,66 @@ def create_profile_card_input_validation():
103103
return card
104104
```
105105

106-
<!-- server-handler-example -->
106+
<!-- handlers-section -->
107+
108+
## Routing & Handlers
109+
110+
### Using SubmitData
111+
112+
The SDK provides a `SubmitData` helper that sets the routing key for your action. This is the recommended way to wire up actions to specific handlers:
113+
114+
```python
115+
from microsoft_teams.cards import ExecuteAction, SubmitData
116+
# ...
117+
118+
ExecuteAction(title="Submit Feedback") \
119+
.with_data(SubmitData("submit_feedback")) \
120+
.with_associated_inputs("auto")
121+
122+
# You can also pass extra static data alongside the action name
123+
ExecuteAction(title="Save") \
124+
.with_data(SubmitData("save_profile", {"entity_id": "12345"})) \
125+
.with_associated_inputs("auto")
126+
```
127+
128+
`SubmitData` sets a reserved `action` key in the card's data payload. When the user clicks the button, the SDK router reads this key to dispatch to the matching handler.
129+
130+
### Action-Specific Handlers
131+
132+
Register handlers for specific actions. When you use `SubmitData` to set the action name on the card, the SDK routes directly to the matching handler:
133+
134+
```python
135+
from microsoft_teams.apps import App, ActivityContext
136+
from microsoft_teams.api import AdaptiveCardInvokeActivity, AdaptiveCardActionMessageResponse, AdaptiveCardInvokeResponse
137+
# ...
138+
139+
# 'submit_feedback' matches the identifier passed to SubmitData('submit_feedback')
140+
@app.on_card_action_execute("submit_feedback")
141+
async def handle_submit_feedback(ctx: ActivityContext[AdaptiveCardInvokeActivity]) -> AdaptiveCardInvokeResponse:
142+
data = ctx.activity.value.action.data
143+
await ctx.send(f"Feedback received: {data.get('feedback')}")
144+
return AdaptiveCardActionMessageResponse(
145+
status_code=200,
146+
type="application/vnd.microsoft.activity.message",
147+
value="Action processed successfully",
148+
)
149+
150+
@app.on_card_action_execute("save_profile")
151+
async def handle_save_profile(ctx: ActivityContext[AdaptiveCardInvokeActivity]) -> AdaptiveCardInvokeResponse:
152+
data = ctx.activity.value.action.data
153+
await ctx.send(f"Profile saved!\nName: {data.get('name')}\nEmail: {data.get('email')}")
154+
return AdaptiveCardActionMessageResponse(
155+
status_code=200,
156+
type="application/vnd.microsoft.activity.message",
157+
value="Action processed successfully",
158+
)
159+
```
160+
161+
The decorator argument matches the value passed to `SubmitData`. This is cleaner than a catch-all with a switch statement, and scales better as you add more actions.
162+
163+
### Catch-All Handler
164+
165+
If you need to handle all card actions in one place, you can use the catch-all handler:
107166

108167
```python
109168
from microsoft_teams.api import AdaptiveCardInvokeActivity, AdaptiveCardActionErrorResponse, AdaptiveCardActionMessageResponse, HttpError, InnerHttpError, AdaptiveCardInvokeResponse
@@ -158,8 +217,6 @@ async def handle_card_action(ctx: ActivityContext[AdaptiveCardInvokeActivity]) -
158217
)
159218
```
160219

161-
<!-- data-typing-note -->
162-
163220
:::note
164221
The `data` values are accessible as a dictionary and can be accessed using `.get()` method for safe access.
165222
:::

teams.md/src/components/include/in-depth-guides/adaptive-cards/executing-actions/typescript.incl.md

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,65 @@ function createProfileCardInputValidation() {
122122
}
123123
```
124124

125-
<!-- server-handler-example -->
125+
<!-- handlers-section -->
126+
127+
## Routing & Handlers
128+
129+
### Using SubmitData
130+
131+
The SDK provides a `SubmitData` helper that sets the routing key for your action. This is the recommended way to wire up actions to specific handlers:
132+
133+
```typescript
134+
import { ExecuteAction, SubmitData } from '@microsoft/teams.cards';
135+
// ...
136+
137+
new ExecuteAction({ title: 'Submit Feedback' })
138+
.withData(new SubmitData('submit_feedback'))
139+
.withAssociatedInputs('auto')
140+
141+
// You can also pass extra static data alongside the action name
142+
new ExecuteAction({ title: 'Save' })
143+
.withData(new SubmitData('save_profile', { entityId: '12345' }))
144+
.withAssociatedInputs('auto')
145+
```
146+
147+
`SubmitData` sets a reserved `action` key in the card's data payload. When the user clicks the button, the SDK router reads this key to dispatch to the matching handler.
148+
149+
### Action-Specific Handlers
150+
151+
Register handlers for specific actions. When you use `SubmitData` to set the action name on the card, the SDK routes directly to the matching handler:
152+
153+
```typescript
154+
import { App } from '@microsoft/teams.apps';
155+
// ...
156+
157+
// 'submit_feedback' matches the identifier passed to SubmitData('submit_feedback')
158+
app.on('card.action.submit_feedback', async ({ activity, send }) => {
159+
const data = activity.value.action.data;
160+
await send(`Feedback received: ${data.feedback}`);
161+
return {
162+
statusCode: 200,
163+
type: 'application/vnd.microsoft.activity.message',
164+
value: 'Action processed successfully',
165+
};
166+
});
167+
168+
app.on('card.action.save_profile', async ({ activity, send }) => {
169+
const data = activity.value.action.data;
170+
await send(`Profile saved!\nName: ${data.name}\nEmail: ${data.email}`);
171+
return {
172+
statusCode: 200,
173+
type: 'application/vnd.microsoft.activity.message',
174+
value: 'Action processed successfully',
175+
};
176+
});
177+
```
178+
179+
The route name follows the pattern `card.action.<action-name>`, where `<action-name>` matches the value passed to `SubmitData`. This is cleaner than a catch-all with a switch statement, and scales better as you add more actions.
180+
181+
### Catch-All Handler
182+
183+
If you need to handle all card actions in one place, you can use the catch-all handler:
126184

127185
```typescript
128186
import {
@@ -189,8 +247,6 @@ app.on('card.action', async ({ activity, send }) => {
189247
});
190248
```
191249

192-
<!-- data-typing-note -->
193-
194250
:::note
195251
The `data` values are not typed and come as `any`, so you will need to cast them to the correct type in this case.
196252
:::

teams.md/src/components/include/in-depth-guides/observability/logging/python.incl.md

Lines changed: 13 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,26 @@
11
<!-- default-logger -->
22

3-
Python's standard `logging` module (configured with `ConsoleFormatter` from the SDK).
3+
Python's standard `logging` module, using `logging.getLogger(__name__)` per module.
44

55
<!-- package-name -->
66

77
`microsoft-teams-common`
88

99
<!-- custom-logger-example -->
1010

11-
The Python SDK writes to the standard `logging` module. Configure a handler and formatter at startup:
11+
The Python SDK uses standard `logging` — there's no custom logger to inject into `App`. To see SDK log output, attach a handler to the `microsoft_teams` logger hierarchy. The SDK ships a `ConsoleFormatter` with color-coded output if you want it:
1212

1313
```python
14-
import asyncio
1514
import logging
16-
17-
from microsoft_teams.api import MessageActivity
18-
from microsoft_teams.apps import ActivityContext, App
15+
import os
1916
from microsoft_teams.common import ConsoleFormatter
2017

21-
# Setup logging
22-
logging.getLogger().setLevel(logging.DEBUG)
23-
stream_handler = logging.StreamHandler()
24-
stream_handler.setFormatter(ConsoleFormatter())
25-
logging.getLogger().addHandler(stream_handler)
26-
logger = logging.getLogger(__name__)
27-
28-
app = App()
29-
30-
31-
@app.on_message
32-
async def handle_message(ctx: ActivityContext[MessageActivity]):
33-
logger.debug(ctx.activity)
34-
await ctx.send(f"You said '{ctx.activity.text}'")
35-
36-
37-
if __name__ == "__main__":
38-
asyncio.run(app.start())
18+
handler = logging.StreamHandler()
19+
handler.setFormatter(ConsoleFormatter())
20+
logging.getLogger("microsoft_teams").addHandler(handler)
21+
logging.getLogger("microsoft_teams").setLevel(
22+
os.getenv("LOG_LEVEL", "INFO").upper()
23+
)
3924
```
4025

4126
<!-- log-levels -->
@@ -55,6 +40,10 @@ handler.addFilter(ConsoleFilter("microsoft_teams*")) # only SDK loggers
5540
logging.getLogger().addHandler(handler)
5641
```
5742

43+
<!-- child-logger -->
44+
45+
N/A
46+
5847
<!-- env-vars -->
5948

6049
The Python SDK does not read logging environment variables on its own. If you want `LOG_LEVEL` to control verbosity, read it yourself at startup:

teams.md/src/components/include/in-depth-guides/observability/logging/typescript.incl.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ Env vars override options passed to the constructor. If you do not pass a logger
5757
5858
<!-- child-logger -->
5959

60+
## Child Loggers
61+
6062
Call `log.child('scope')` on an existing logger to get a scoped logger. Its name is `parent/scope`, and pattern/level are inherited.
6163

6264
```typescript

teams.md/src/pages/templates/in-depth-guides/adaptive-cards/executing-actions.mdx

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -60,26 +60,4 @@ Input Controls provide ways for you to validate. More details can be found on th
6060

6161
<LanguageInclude section="input-validation-example" />
6262

63-
## Routing & Handlers
64-
65-
### Using SubmitData
66-
67-
The SDK provides a `SubmitData` helper that sets the routing key for your action. This is the recommended way to wire up actions to specific handlers:
68-
69-
<LanguageInclude section="submit-data-example" />
70-
71-
### Action-Specific Handlers
72-
73-
Register handlers for specific actions. When you use `SubmitData` to set the action name on the card, the SDK routes directly to the matching handler:
74-
75-
<LanguageInclude section="sub-route-handler-example" />
76-
77-
This is cleaner than a catch-all with a switch statement, and scales better as you add more actions.
78-
79-
### Catch-All Handler
80-
81-
If you need to handle all card actions in one place, you can use the catch-all handler:
82-
83-
<LanguageInclude section="server-handler-example" />
84-
85-
<LanguageInclude section="data-typing-note" />
63+
<LanguageInclude section="handlers-section" />

teams.md/src/pages/templates/in-depth-guides/observability/README.mdx

Lines changed: 0 additions & 8 deletions
This file was deleted.

0 commit comments

Comments
 (0)