API TypeScript Tanpa Server: Menyimpan data di MongoDB dengan Azure Functions
Buat AZURE Function API untuk menyimpan data dengan API Mongoose ke Azure Cosmos DB, lalu sebarkan aplikasi Fungsi ke cloud Azure untuk hosting dengan titik akhir HTTP publik.
Catatan
Artikel ini menggunakan model pemrograman Azure Functions Node.js v4 yang saat ini dalam pratinjau.
Mempersiapkan lingkungan pengembangan Anda
Instal perangkat lunak berikut:
- Buat langganan Azure gratis
- Instal Node.js LTS v18+
- TypeScript v4+
- Azurite diinstal secara global untuk penyimpanan pengembangan lokal
- Runtime Azure Functions v4.16+
- Azure Functions Core Tools v4.0.5095+ (jika berjalan secara lokal) diinstal secara global untuk pengembangan lokal
- Instal Visual Studio Code dan gunakan ekstensi berikut:
1. Masuk ke Azure di Visual Studio Code
Jika Anda telah menggunakan ekstensi layanan Azure, Anda seharusnya sudah masuk dan dapat melompati langkah ini.
Setelah menginstal ekstensi layanan Azure di Visual Studio Code, Anda perlu masuk ke akun Azure Anda.
Di Visual Studio Code, buka penjelajah Azure dengan memilih ikon Azure di bilah sisi utama atau gunakan pintasan keyboard (Shift + Alt + A).
Di bagian Sumber Daya , pilih Masuk ke Azure, dan ikuti perintah.
Setelah masuk, verifikasi bahwa alamat email akun Azure Anda muncul di Bilah Status dan langganan Anda muncul di Azure explorer:
2. Buat grup sumber daya Azure
Grup sumber daya adalah kumpulan sumber daya berbasis wilayah. Dengan membuat grup sumber daya, lalu membuat sumber daya dalam grup itu, di akhir tutorial, Anda dapat menghapus grup sumber daya tanpa harus menghapus setiap sumber daya satu per satu.
Buat folder baru di sistem lokal Anda untuk digunakan sebagai akar proyek fungsi Azure.
Buka folder ini di Visual Studio Code.
Di Visual Studio Code, buka penjelajah Azure dengan memilih ikon Azure di bilah sisi utama atau gunakan pintasan keyboard (Shift + Alt + A).
Temukan langganan Anda di bawah Sumber Daya dan pilih + ikon lalu pilih Buat Grup Sumber Daya.
Gunakan tabel berikut untuk menyelesaikan petunjuk:
Prompt Value Masukkan nama grup sumber daya baru. azure-tutorial
Pilih lokasi untuk sumber daya baru. Pilih lokasi geografis yang dekat dengan Anda.
3. Buat aplikasi Functions lokal
Buat aplikasi Azure Functions (tanpa server) lokal yang berisi fungsi pemicu HTTP.
Di Visual Studio Code, buka palet perintah (Ctrl + Shift + P).
Cari dan pilih Azure Functions: Buat Proyek Baru .
Gunakan tabel berikut untuk menyelesaikan pembuatan proyek Azure Functions lokal:
Prompt Value Catatan Pilih folder yang akan berisi proyek fungsi Anda Pilih folder (default) saat ini. Pilih bahasa TypeScript Pilih model pemrograman TypeScript Model V4 (Pratinjau) Pilih templat untuk fungsi pertama proyek Anda Pemicu HTTP API dipanggil dengan permintaan HTTP. Menyediakan nama fungsi blogposts
Rute API adalah /api/blogposts
Saat Visual Studio Code membuat proyek, lihat kode API Anda dalam
./src/functions/blogposts.ts
file.import { app, HttpRequest, HttpResponseInit, InvocationContext } from "@azure/functions"; export async function blogposts(request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> { context.log(`Http function processed request for url "${request.url}"`); const name = request.query.get('name') || await request.text() || 'world'; return { body: `Hello, ${name}!` }; }; app.http('blogposts', { methods: ['GET', 'POST'], authLevel: 'anonymous', handler: blogposts });
Kode ini adalah boilerplate standar dalam model pemrograman v4 baru. Ini tidak dimaksudkan untuk menunjukkan satu-satunya cara untuk menulis lapisan API dengan POST dan GET.
Ganti kode sebelumnya dengan kode berikut untuk hanya mengizinkan permintaan GET mengembalikan semua posting blog.
import { app, HttpRequest, HttpResponseInit, InvocationContext } from "@azure/functions"; // curl --location 'http://localhost:7071/api/blogposts' --verbose export async function getBlogPosts(request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> { context.log(`Http function getBlogPosts processed request for url "${request.url}"`); // Empty array for now ... will fix later const blogposts = []; return { status: 200, jsonBody: { blogposts } }; }; app.get('getBlogPosts', { route: "blogposts", authLevel: 'anonymous', handler: getBlogPosts });
Ada beberapa perubahan model pemrograman Azure Functions Node.js v4 pada kode ini yang harus Anda perhatikan:
- Nama
getBlobPosts
fungsi , yang menunjukkan bahwa itu adalah permintaan GET, akan membantu Anda mengisolasi fungsi dalam log. - Properti
route
diatur keblogposts
, yang merupakan bagian dari rute API default yang disediakan,/api/blogposts
. - Properti
methods
telah dihapus dan tidak perlu karenaapp
penggunaanget
objek menunjukkan ini adalah permintaan GET. Fungsi metode tercantum di bawah ini. Jika Anda memiliki metode yang berbeda, Anda dapat kembali menggunakanmethods
properti .deleteRequest()
get()
patch()
post()
put()
- Nama
4. Mulai emulator penyimpanan lokal Azurite
Mengembangkan fungsi di komputer lokal Anda memerlukan emulator Storage (gratis) atau akun Azure Storage (berbayar).
Di terminal terpisah, mulai emulator penyimpanan lokal Azurite .
azurite --silent --location ./azurite --debug ./azurite/debug.log
Ini diperlukan untuk menjalankan Azure Functions secara lokal menggunakan emulator Azure Storage lokal. Emulator penyimpanan lokal ditentukan dalam local.settings.json
file dengan properti AzureWebJobsStorage dengan nilai UseDevelopmentStorage=true
.
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "node",
"AzureWebJobsFeatureFlags": "EnableWorkerIndexing"
}
}
azurite
Subfolder telah ditambahkan ke file Anda.gitignore
.
5. Jalankan fungsi tanpa server lokal
Jalankan proyek Azure Functions secara lokal untuk mengujinya sebelum disebarkan ke Azure.
Di Visual Studio Code, atur titik henti pada
return
pernyataan, di akhir fungsi getBlogPosts .Di Visual Studio Code, tekan F5 untuk meluncurkan penelusur kesalahan dan lampirkan ke host Azure Functions.
Anda juga dapat menggunakan perintah menu DebugStart>Mulai Penelusuran Kesalahan.
Output muncul di panel Terminal .
Di Visual Studio Code, buka penjelajah Azure dengan memilih ikon Azure di bilah sisi utama atau gunakan pintasan keyboard (Shift + Alt + A).
Di bagian Ruang Kerja, temukan dan perluas Proyek Lokal ->Functions ->getBlogPosts.
Klik kanan nama fungsi, getBlogPosts, lalu pilih Salin Url Fungsi.
Di browser Anda, tempelkan URL dan pilih Masukkan atau gunakan perintah cURL berikut di terminal:
curl http://localhost:7071/api/blogposts --verbose
Respons array kosong posting blog dikembalikan sebagai:
* Trying 127.0.0.1:7071... * Connected to localhost (127.0.0.1) port 7071 (#0) > GET /api/blogposts HTTP/1.1 > Host: localhost:7071 > User-Agent: curl/7.88.1 > Accept: */* > < HTTP/1.1 200 OK < Content-Type: application/json < Date: Mon, 08 May 2023 17:35:24 GMT < Server: Kestrel < Transfer-Encoding: chunked < {"blogposts":[]}* Connection #0 to host localhost left intact
Di Visual Studio Code, hentikan debugger, Shift + F5.
6. Buat aplikasi Azure Function di Visual Studio Code
Di bagian ini, Anda membuat sumber daya cloud aplikasi fungsi dan sumber daya terkait di langganan Azure Anda.
Di Visual Studio Code, buka palet perintah (Ctrl + Shift + P).
Cari dan pilih Azure Functions: Buat Aplikasi Fungsi di Azure (Tingkat Lanjut) .
Berikan informasi berikut pada permintaan:
Prompt Pilihan Masukkan nama unik global untuk aplikasi fungsi Ketik nama yang valid di jalur URL, seperti first-function
. Pascapending 3 karakter untuk membuat URL unik secara global. Nama yang Anda ketik akan divalidasi untuk memastikan bahwa nama tersebut bersifat unik di Azure Functions.Pilih tumpukan runtime Pilih Node.js 18 LTS atau versi yang lebih baru. Pilih OS Pilih Linux. Pilih grup sumber daya untuk sumber daya baru Buat grup sumber daya baru bernama azure-tutorial-first-function. Grup sumber daya ini pada akhirnya akan memiliki beberapa sumber daya: Azure Function, Azure Storage, dan Cosmos DB untuk MongoDB API. Pilih paket hosting Pilih Konsumsi. Pilih akun penyimpanan Pilih Buat akun penyimpanan baru dan terima nama default. Pilih sumber daya Application Insights untuk aplikasi Anda. Pilih Buat sumber daya Application Insights baru dan terima nama default. Tunggu hingga pemberitahuan mengonfirmasi bahwa aplikasi telah dibuat.
7. Sebarkan aplikasi Azure Function ke Azure di Visual Studio Code
Penting
Penyebaran ke aplikasi fungsi yang ada selalu menimpa konten aplikasi tersebut di Azure.
- Pilih ikon Azure di bilah Aktivitas, lalu di area Sumber Daya , klik kanan sumber daya aplikasi fungsi Anda dan pilih Aplikasi Sebarkan ke Fungsi.
- Jika Anda ditanya apakah Anda yakin ingin menyebarkan, pilih Sebarkan.
- Setelah penyebaran selesai, pemberitahuan ditampilkan dengan beberapa opsi. Pilih Tampilkan Output untuk melihat hasilnya. Jika melewatkan pemberitahuan, pilih ikon bel di sudut kanan bawah untuk melihatnya lagi.
8. Tambahkan pengaturan aplikasi ke aplikasi cloud
Pilih ikon Azure di bilah Aktivitas, lalu di area Sumber Daya, perluas sumber daya aplikasi fungsi Anda dan klik kanan pilih Aplikasi Pengaturan.
Pilih Tambahkan Pengaturan Baru dan tambahkan pengaturan berikut untuk mengaktifkan model pemrograman Node.js v4 (Pratinjau).
Pengaturan Nilai AzureWebJobsFeatureFlags EnableWorkerIndexing
9. Jalankan fungsi tanpa server jarak jauh
Di Visual Studio Code, buka penjelajah Azure dengan memilih ikon Azure di bilah sisi utama atau gunakan pintasan keyboard (Shift + Alt + A).
Di bagian Sumber Daya , perluas sumber daya aplikasi Azure Function Anda. Klik kanan nama fungsi dan pilih Salin Url Fungsi.
Tempelkan URL ke browser web. Array kosong yang sama dikembalikan seperti saat Anda menjalankan fungsi secara lokal.
{"blogposts":[]}
10. Tambahkan integrasi Azure Cosmos DB untuk MongoDB API
Azure Cosmos DB menyediakan API MongoDB untuk menyediakan titik integrasi yang familier.
Di Visual Studio Code, buka penjelajah Azure dengan memilih ikon Azure di bilah sisi utama atau gunakan pintasan keyboard (Shift + Alt + A).
Di bagian Sumber Daya , pilih lalu + pilih Buat Server Database. Gunakan tabel berikut untuk menyelesaikan perintah untuk membuat sumber daya Azure Cosmos DB baru.
Prompt Value Catatan Pilih Server Azure Database Azure Cosmos DB untuk API MongoDB Berikan nama akun Azure Cosmos DB. cosmosdb-mongodb-database
Pascapendingan tiga karakter untuk membuat nama unik. Nama menjadi bagian dari URL API. Pilih model kapasitas. Tanpa server Pilih grup sumber daya untuk sumber daya baru. azure-tutorial-first-function Pilih grup sumber daya yang Anda buat di bagian sebelumnya. Pilih lokasi untuk sumber daya baru. Pilih wilayah yang direkomendasikan.
11. Pasang dependensi mongoose
Di terminal Visual Studio Code, Ctrl + Shift + `, lalu instal paket npm:
npm install mongoose
12. Tambahkan kode mongoose untuk posting blog
Di Visual Studio Code, buat subdirektori bernama lib di
./src/
, buat file bernama./database.ts
dan salin kode berikut ke dalamnya.import { Schema, Document, createConnection, ConnectOptions, model, set } from 'mongoose'; const connectionString = process.env.MONGODB_URI; console.log('connectionString', connectionString); const connection = createConnection(connectionString, { useNewUrlParser: true, useUnifiedTopology: true, autoIndex: true } as ConnectOptions); export interface IBlogPost { author: string title: string body: string } export interface IBlogPostDocument extends IBlogPost, Document { id: string created: Date } const BlogPostSchema = new Schema({ id: Schema.Types.ObjectId, author: String, title: String, body: String, created: { type: Date, default: Date.now } }); BlogPostSchema.set('toJSON', { transform: function (doc, ret, options) { ret.id = ret._id; delete ret._id; delete ret.__v; } }); export const BlogPost = model<IBlogPostDocument>('BlogPost', BlogPostSchema); connection.model('BlogPost', BlogPostSchema); export default connection;
Di Visual Studio Code, buka file
./src/functions/blogposts
dan ganti seluruh kode file dengan yang berikut:import { app, HttpRequest, HttpResponseInit, InvocationContext } from "@azure/functions"; import connection from '../lib/database'; // curl --location 'http://localhost:7071/api/blogposts' --verbose export async function getBlogPosts(request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> { context.log(`Http function getBlogPosts processed request for url "${request.url}"`); const blogposts = await connection.model('BlogPost').find({}); return { status: 200, jsonBody: { blogposts } }; }; app.get('getBlogPosts', { route: "blogposts", authLevel: 'anonymous', handler: getBlogPosts });
13. Tambahkan string koneksi ke aplikasi lokal
Di Penjelajah Azure Visual Studio Code, pilih bagian Azure Cosmos DB dan perluas untuk mengklik kanan pilih sumber daya baru Anda.
Pilih Salin string koneksi.
Di Visual Studio Code, gunakan File explorer untuk membuka
./local.settings.json
.Tambahkan properti baru yang disebut
MONGODB_URI
dan tempelkan nilai string koneksi Anda.{ "IsEncrypted": false, "Values": { "AzureWebJobsStorage": "", "FUNCTIONS_WORKER_RUNTIME": "node", "AzureWebJobsFeatureFlags": "EnableWorkerIndexing", "MONGODB_URI": "mongodb://...." } }
Rahasia dalam
./local.settings.json
file:- Tidak disebarkan ke Azure karena disertakan
./.funcignore
dalam file. - Tidak diperiksa ke kontrol sumber karena disertakan
./.gitignore
dalam file.
- Tidak disebarkan ke Azure karena disertakan
Jalankan aplikasi secara lokal dan uji API dengan url yang sama di bagian sebelumnya.
14. Tambahkan string koneksi ke aplikasi jarak jauh
- Di Visual Studio Code, buka penjelajah Azure dengan memilih ikon Azure di bilah sisi utama atau gunakan pintasan keyboard (Shift + Alt + A).
- Di bagian Sumber Daya , temukan instans Azure Cosmos DB Anda. Klik kanan sumber daya dan pilih Salin String Koneksi ion.
- Di bagian Sumber Daya yang sama, temukan Aplikasi Fungsi Anda dan perluas simpul.
- Klik kanan Pengaturan Aplikasi dan pilih Tambahkan Pengaturan Baru.
- Masukkan nama pengaturan aplikasi,
MONGODB_URI
dan pilih Enter. - Tempelkan nilai yang Anda salin dan tekan enter.
15. Tambahkan API untuk membuat, memperbarui, dan menghapus blogpost
Di Visual Studio Code, gunakan palet perintah untuk menemukan dan memilih Azure Functions: Buat fungsi.
Pilih pemicu HTTP dan beri nama
blogpost
(tunggal).Salin kode berikut ke dalam file .
import { app, HttpRequest, HttpResponseInit, InvocationContext } from "@azure/functions"; import connection, { IBlogPost, IBlogPostDocument } from '../lib/database'; // curl -X POST --location 'http://localhost:7071/api/blogpost' --header 'Content-Type: application/json' --data '{"author":"john","title":"my first post", "body":"learn serverless node.js"}' --verbose export async function addBlogPost(request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> { context.log(`Http function addBlogPost processed request for url "${request.url}"`); const body = await request.json() as IBlogPost; const blogPostResult = await connection.model('BlogPost').create({ author: body?.author, title: body?.title, body: body?.body }); return { status: 200, jsonBody: { blogPostResult } }; }; // curl -X PUT --location 'http://localhost:7071/api/blogpost/64568e727f7d11e09eab473c' --header 'Content-Type: application/json' --data '{"author":"john jones","title":"my first serverless post", "body":"Learn serverless Node.js with Azure Functions"}' --verbose export async function updateBlogPost(request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> { context.log(`Http function updateBlogPost processed request for url "${request.url}"`); const body = await request.json() as IBlogPost; const id = request.params.id; const blogPostResult = await connection.model('BlogPost').updateOne({ _id: id }, { author: body?.author, title: body?.title, body: body?.body }); if(blogPostResult.matchedCount === 0) { return { status: 404, jsonBody: { message: 'Blog post not found' } }; } return { status: 200, jsonBody: { blogPostResult } }; }; // curl --location 'http://localhost:7071/api/blogpost/6456597918547e37d515bda3' --verbose export async function getBlogPost(request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> { context.log(`Http function getBlogPosts processed request for url "${request.url}"`); console.log('request.params.id', request.params.id) const id = request.params.id; const blogPost = await connection.model('BlogPost').findOne({ _id: id }); if(!blogPost) { return { status: 404, jsonBody: { message: 'Blog post not found' } }; } return { status: 200, jsonBody: { blogPost } }; }; // curl --location 'http://localhost:7071/api/blogpost/6456597918547e37d515bda3' --request DELETE --header 'Content-Type: application/json' --verbose export async function deleteBlogPost(request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> { context.log(`Http function deleteBlogPost processed request for url "${request.url}"`); const id = request.params.id; const blogPostResult = await connection.model('BlogPost').deleteOne({ _id: id }); if(blogPostResult.deletedCount === 0) { return { status: 404, jsonBody: { message: 'Blog post not found' } }; } return { status: 200, jsonBody: { blogPostResult } }; }; app.get('getBlogPost', { route: "blogpost/{id}", authLevel: 'anonymous', handler: getBlogPost }); app.post('postBlogPost', { route: "blogpost", authLevel: 'anonymous', handler: addBlogPost }); app.put('putBlogPost', { route: "blogpost/{id}", authLevel: 'anonymous', handler: updateBlogPost }); app.deleteRequest('deleteBlogPost', { route: "blogpost/{id}", authLevel: 'anonymous', handler: deleteBlogPost });
Mulai fungsi lokal dengan debugger lagi. API berikut tersedia:
deleteBlogPost: [DELETE] http://localhost:7071/api/blogpost/{id} getBlogPost: [GET] http://localhost:7071/api/blogpost/{id} getBlogPosts: [GET] http://localhost:7071/api/blogposts postBlogPost: [POST] http://localhost:7071/api/blogpost putBlogPost: [PUT] http://localhost:7071/api/blogpost/{id}
blogpost
Gunakan API (tunggal) dari perintah cURL untuk menambahkan beberapa posting blog.curl -X POST --location 'http://localhost:7071/api/blogpost' --header 'Content-Type: application/json' --data '{"author":"john","title":"my first post", "body":"learn serverless node.js"}' --verbose
blogposts
Gunakan API (jamak) dari perintah cURL untuk mendapatkan posting blog.curl http://localhost:7071/api/blogposts --verbose
16. Lihat semua data dengan ekstensi Visual Studio Code untuk Azure Cosmos DB
Di Visual Studio Code, buka penjelajah Azure dengan memilih ikon Azure di bilah sisi utama atau gunakan pintasan keyboard (Shift + Alt + A).
Di bagian Sumber Daya , klik kanan database Azure Cosmos DB Anda dan pilih Refresh.
Perluas database pengujian dan node koleksi blogposts untuk melihat dokumen.
Pilih salah satu item yang tercantum untuk melihat data di instans Azure Cosmos DB.
17. Sebarkan ulang aplikasi fungsi untuk menyertakan kode database
- Di Visual Studio Code, buka penjelajah Azure dengan memilih ikon Azure di bilah sisi utama atau gunakan pintasan keyboard (Shift + Alt + A).
- Di bagian Sumber Daya , klik kanan aplikasi Azure Function Anda dan pilih Sebarkan ke Aplikasi Fungsi.
- Di pop-up yang menanyakan apakah Anda yakin ingin menyebarkan, pilih Sebarkan.
- Tunggu hingga penyebaran selesai sebelum melanjutkan.
18. Gunakan Azure Function berbasis cloud
- Masih di Azure Explorer, di area Functions, memilih dan memperluas fungsi Anda lalu simpul Functions , yang mencantumkan API
- Klik kanan pada salah satu API dan pilih Salin Url Fungsi.
- Edit perintah cURL sebelumnya untuk menggunakan URL jarak jauh alih-alih URL lokal. Jalankan perintah untuk menguji API jarak jauh.
19. Mengkueri log Azure Function Anda
Untuk mencari log, gunakan portal Azure.
Di Visual Studio Code, pilih Azure Explorer, lalu di bawah Fungsi, klik kanan aplikasi fungsi Anda, lalu pilih Buka di Portal.
Tindakan ini membuka portal Azure ke Azure Function Anda.
Pilih Pengaturan, pilih Application Insights, lalu pilih Lihat data Application Insights.
Tautan ini membawa Anda ke sumber daya metrik terpisah yang dibuat untuk Anda saat membuat Fungsi Azure dengan Visual Studio Code.
Dari bagian Pemantauan, pilih Log. Jika jendela pop-up Kueri muncul, pilih X di sudut kanan atas pop-up untuk menutupnya.
Di panel Kueri 1 Baru, pada tab Tabel, klik dua kali tabel jejak.
Ini memasukkan kueri Kusto,
traces
ke jendela kueri.Edit kueri untuk mencari log kustom:
traces | where message startswith "***"
Pilih Jalankan.
Jika log tidak menampilkan hasil apa pun, mungkin karena ada beberapa menit penundaan antara permintaan HTTP ke Azure Function dan ketersediaan log di Kusto. Tunggu beberapa menit dan jalankan kueri lagi.
Anda tidak perlu melakukan apa pun tambahan untuk mendapatkan informasi pencatatan ini:
- Kode menggunakan fungsi
context.log
yang disediakan oleh kerangka Kerja Fungsi. Dengan menggunakancontext
, bukanconsole
, pengelogan Anda dapat disaring ke fungsi individu tertentu. Ini berguna jika aplikasi Fungsi Anda memiliki banyak fungsi. - Aplikasi Fungsi menambahkan Application Insights untuk Anda.
- Alat Kueri Kusto disertakan dalam portal Azure.
- Anda dapat memilih
traces
alih-alih harus belajar menulis kueri Kusto untuk mendapatkan bahkan informasi minimum dari log Anda.
- Kode menggunakan fungsi
20. Bersihkan sumber daya
Karena Anda menggunakan satu grup sumber daya, Anda dapat menghapus semua sumber daya dengan menghapus grup sumber daya.
- Di Visual Studio Code, buka penjelajah Azure dengan memilih ikon Azure di bilah sisi utama atau gunakan pintasan keyboard (Shift + Alt + A).
- Cari dan pilih Azure: Kelompokkan menurut grup sumber daya.
- Klik kanan pilih grup sumber daya Anda dan pilih Hapus Grup Sumber Daya.
- Masukkan nama grup sumber daya untuk mengonfirmasi penghapusan.
Kode sumber tersedia
Kode sumber lengkap untuk aplikasi Azure Function ini:
Langkah berikutnya
Saran dan Komentar
https://aka.ms/ContentUserFeedback.
Segera hadir: Sepanjang tahun 2024 kami akan menghentikan penggunaan GitHub Issues sebagai mekanisme umpan balik untuk konten dan menggantinya dengan sistem umpan balik baru. Untuk mengetahui informasi selengkapnya, lihat:Kirim dan lihat umpan balik untuk