Menghubungkan dan memvisualisasikan semua data Anda di Looker Studio

1. Pengantar

Looker Studio memungkinkan Anda membuat dasbor interaktif yang aktif dengan visualisasi data yang menarik, tanpa biaya. Ambil data Anda dari berbagai sumber dan buat laporan tanpa batas di Looker Studio, dengan kemampuan pengeditan dan berbagi penuh. Screenshot berikut adalah contoh dasbor Looker Studio:

2f296fddf6af7393.png

( Klik di sini untuk melihat contoh laporan ini di Looker Studio)

Konektor Komunitas adalah fitur untuk Looker Studio yang memungkinkan Anda menggunakan Apps Script untuk membuat konektor ke sumber data yang dapat diakses melalui internet. Konektor Komunitas dibuat oleh komunitas Looker Studio. Artinya, siapa saja dapat membuat Konektor Komunitas. Anda juga dapat membagikan Konektor Komunitas kepada orang lain sehingga mereka dapat mengakses data mereka sendiri dari dalam Looker Studio.

Anda dapat menggunakan Konektor Komunitas dalam berbagai kasus penggunaan:

  • Anda memvisualisasikan data dari platform komersial (misalnya, media sosial, pemasaran, analisis, dll.)
  • Anda memvisualisasikan data perusahaan lokal (misalnya, data penjualan dari database MySQL lokal)
  • Anda menyediakan cara bagi pelanggan untuk memvisualisasikan data mereka dari layanan Anda
  • Anda membuat platform pelaporan tombol tekan
  • Anda memvisualisasikan data Anda sendiri dari sumber web (misalnya, membuat dasbor Google Fit)

Yang akan Anda pelajari

  • Cara kerja Konektor Komunitas Looker Studio
  • Cara menggunakan Google Apps Script untuk membuat Konektor Komunitas
  • Cara menggunakan Konektor Komunitas di Looker Studio

Yang Anda butuhkan

  • Akses ke internet dan browser web
  • Akun Google
  • Memahami dasar-dasar Javascript dan Web API

2. Survei Cepat

Mengapa Anda memilih codelab ini?

Saya tertarik dengan visualisasi data secara umum. Saya ingin mempelajari Looker Studio lebih lanjut Saya ingin membuat Konektor Komunitas saya sendiri. Saya mencoba mengintegrasikan Looker Studio dengan platform lain. Saya tertarik dengan solusi Google Cloud.

Bagaimana rencana Anda menggunakan codelab/tutorial ini?

Hanya membaca sekilas Membacanya dan menyelesaikan latihan

Seberapa pahamkah Anda dengan Looker Studio?

Belum pernah mendengarnya Saya tahu apa itu, tetapi saya tidak menggunakannya. Saya menggunakannya secara rutin. Saya adalah pengguna ahli.

Apa yang paling tepat menggambarkan latar belakang Anda?

Developer Analis Bisnis / Keuangan / Data Data Scientist / Data Engineer Pakar Pemasaran / Media Sosial / Analisis Digital Desainer Lainnya

Anda dapat membuka halaman berikutnya untuk mengirimkan informasi survei.

3. Ringkasan Konektor Komunitas

Konektor Komunitas Looker Studio memungkinkan koneksi langsung dari Looker Studio ke sumber data yang dapat diakses melalui internet. Anda dapat terhubung ke platform komersial, set data publik, atau data pribadi lokal Anda sendiri. Konektor Komunitas dapat mengambil data melalui Web API, JDBC API, file datar (CSV, JSON, XML), dan Layanan Apps Script.

b25b8d6bea6da54b.png

Pertimbangkan skenario saat Anda telah memublikasikan paket di npm dan ingin melacak jumlah download paket dari waktu ke waktu menurut hari. Dalam codelab ini, Anda akan membuat Konektor Komunitas yang mengambil data menggunakan npm package download counts API. Konektor Komunitas kemudian dapat digunakan di Looker Studio untuk membuat dasbor guna memvisualisasikan jumlah download.

4. Alur Kerja Konektor Komunitas

Di Konektor Komunitas dasar, Anda akan menentukan empat fungsi:

  • getAuthType()
  • getConfig()
  • getSchema()
  • getData()

Bergantung pada langkah alur kerja saat ini, Looker Studio akan menjalankan fungsi konektor ini dan menggunakan respons pada langkah berikutnya. Video di bawah memberikan ringkasan tentang:

  • Cara kerja Konektor Komunitas
  • Berbagai langkah dalam alur kerja
  • Saat fungsi yang berbeda dipanggil
  • Saat Looker Studio menampilkan antarmuka pengguna yang berbeda
  • Tindakan pengguna yang diharapkan pada berbagai langkah

Anda dapat melanjutkan codelab setelah menonton video.

Anda tidak perlu menghafal alur kerja ini, cukup lihat untuk memahami apa yang terjadi di konektor. Anda dapat kembali ke diagram ini kapan saja.

cc6688bf38749e5.png

Pada langkah berikutnya, Anda akan mulai membuat konektor di Google Apps Script. Anda harus beralih antara UI Apps Script dan codelab ini.

5. Menyiapkan project Apps Script

Langkah 1: Buka Google Apps Script.

Langkah 2: Buat project Apps Script baru dengan mengklik "+ New project" di bagian kiri atas.

fb12d7318d0946cf.png

Anda akan melihat project shell dengan fungsi myFunction kosong dalam file Code.gs.

b245ce5eb3dd2ee2.png

Langkah 3: Hapus fungsi myFunction.

Langkah 4: Beri nama project:

  1. Klik Untitled project di kiri atas halaman
  2. Masukkan judul project.

7172aebc181c91d4.png

Mulai tulis kode konektor Anda di file Code.gs.

6. Menentukan getAuthType()

Looker Studio akan memanggil fungsi getAuthType() saat perlu mengetahui metode autentikasi yang digunakan oleh konektor. Fungsi ini harus menampilkan metode autentikasi yang diperlukan oleh konektor untuk mengizinkan layanan pihak ketiga.

Untuk konektor download npm yang Anda buat, Anda tidak perlu melakukan autentikasi dengan layanan pihak ketiga mana pun karena API yang Anda gunakan tidak memerlukan autentikasi apa pun. Salin kode berikut dan tambahkan ke file Code.gs Anda:

Code.gs

var cc = DataStudioApp.createCommunityConnector();

function getAuthType() {
  var AuthTypes = cc.AuthType;
  return cc
    .newAuthTypeResponse()
    .setAuthType(AuthTypes.NONE)
    .build();
}

Di sini, Anda menunjukkan bahwa konektor Anda tidak memerlukan autentikasi pihak ketiga (AuthTypes.NONE). Untuk melihat semua metode autentikasi yang didukung, lihat referensi AuthType().

7. Menentukan getConfig()

Pengguna konektor Anda harus mengonfigurasi konektor sebelum dapat mulai menggunakannya. Respons fungsi getConfig() menentukan opsi konfigurasi yang akan dilihat pengguna. Looker Studio memanggil fungsi getConfig() untuk mendapatkan detail konfigurasi konektor. Berdasarkan respons yang diberikan oleh getConfig(), Looker Studio akan merender layar konfigurasi konektor dan mengubah perilaku konektor tertentu.

Di layar konfigurasi, Anda dapat memberikan informasi atau mendapatkan input pengguna menggunakan elemen formulir berikut:

TEXTINPUT

Elemen input

Kotak teks satu baris.

TEXTAREA

Elemen input

Kotak area teks multi-baris.

SELECT_SINGLE

Elemen input

Dropdown untuk opsi pilihan tunggal.

SELECT_MULTIPLE

Elemen input

Dropdown untuk opsi multi-pilihan.

CHECKBOX

Elemen input

Satu kotak centang yang dapat digunakan untuk mengambil nilai boolean.

INFO

Elemen tampilan

Kotak teks biasa statis yang dapat digunakan untuk memberikan petunjuk atau informasi kepada pengguna.

Gunakan elemen INFO untuk memberikan petunjuk kepada pengguna dan elemen TEXTINPUT untuk mendapatkan nama paket input dari pengguna. Dalam respons getConfig(), Anda akan mengelompokkan elemen formulir ini di bawah kunci configParams.

Karena API yang Anda hubungkan memerlukan tanggal sebagai parameter, tetapkan dateRangeRequired ke true dalam respons getConfig(). Hal ini memberi tahu Looker Studio untuk memberikan rentang tanggal dengan semua permintaan data. Jika sumber data Anda tidak memerlukan tanggal sebagai parameter, Anda dapat menghapusnya.

Tambahkan kode getConfig() berikut ke file Code.gs Anda, di bawah kode yang ada untuk getAuthType():

Code.gs

function getConfig(request) {
  var config = cc.getConfig();
  
  config.newInfo()
    .setId('instructions')
    .setText('Enter npm package names to fetch their download count.');
  
  config.newTextInput()
    .setId('package')
    .setName('Enter a single package name')
    .setHelpText('e.g. googleapis or lighthouse')
    .setPlaceholder('googleapis');
  
  config.setDateRangeRequired(true);
  
  return config.build();
}

Berdasarkan configParams ini, saat Anda menggunakan konektor di Looker Studio, Anda dapat melihat layar konfigurasi seperti berikut. Namun, kita akan membahasnya lebih lanjut nanti.

7de872f17e59e92.png

Mari kita beralih ke fungsi berikutnya - getSchema().

8. Tentukan getSchema()

Looker Studio memanggil fungsi getSchema() untuk mendapatkan skema yang terkait dengan konfigurasi yang dipilih pengguna untuk konektor. Berdasarkan respons yang diberikan oleh getSchema(), Looker Studio akan menampilkan layar kolom kepada pengguna yang mencantumkan semua kolom di konektor.

Untuk konfigurasi spesifik konektor Anda, skema adalah daftar semua kolom yang datanya dapat disediakan oleh konektor. Konektor dapat menampilkan skema yang berbeda dengan kolom yang berbeda berdasarkan berbagai konfigurasi. Skema dapat berisi kolom yang Anda ambil dari sumber API, kolom yang Anda hitung di Apps Script, dan kolom yang dihitung di Looker Studio menggunakan formula kolom kalkulasi. Konektor Anda menyediakan metadata tentang setiap kolom dalam skema, termasuk:

  • Nama kolom
  • Jenis data untuk kolom
  • Informasi semantik

Tinjau referensi getSchema() dan Field nanti untuk mempelajari lebih lanjut.

Bergantung pada cara pengambilan konektor, skema dapat diperbaiki atau dihitung secara dinamis saat getSchema() dipanggil. Parameter konfigurasi dari getConfig() yang ditentukan oleh pengguna akan diberikan dalam argumen request untuk fungsi getSchema().

Untuk codelab ini, Anda tidak perlu mengakses argumen request. Anda akan mempelajari argumen request lebih lanjut saat menulis kode untuk fungsi getData() di segmen berikutnya.

Untuk konektor Anda, skemanya tetap dan berisi 3 kolom berikut:

packageName

Nama paket npm yang disediakan pengguna

downloads

Jumlah download paket npm

day

Tanggal penghitungan download

Di bawah ini adalah kode getSchema() untuk konektor Anda. Fungsi bantuan getFields() mengabstraksi pembuatan kolom karena fungsi ini diperlukan oleh getSchema() dan getData(). Tambahkan kode berikut ke file Code.gs Anda:

Code.gs

function getFields(request) {
  var cc = DataStudioApp.createCommunityConnector();
  var fields = cc.getFields();
  var types = cc.FieldType;
  var aggregations = cc.AggregationType;
  
  fields.newDimension()
    .setId('packageName')
    .setType(types.TEXT);
  
  fields.newMetric()
    .setId('downloads')
    .setType(types.NUMBER)
    .setAggregation(aggregations.SUM);
  
  fields.newDimension()
    .setId('day')
    .setType(types.YEAR_MONTH_DAY);
  
  return fields;
}

function getSchema(request) {
  var fields = getFields(request).build();
  return { schema: fields };
}

Berdasarkan skema ini, Anda dapat melihat kolom berikut di layar kolom Looker Studio saat menggunakan konektor di Looker Studio. Namun, hal ini akan dibahas lebih lanjut saat Anda menguji konektor.

c7cd7057b202be59.png

Mari kita lanjutkan ke fungsi terakhir - getData().

9. Menentukan getData() : Bagian 1

Looker Studio memanggil fungsi getData() setiap kali perlu mengambil data. Berdasarkan respons yang diberikan oleh getData(), Looker Studio akan merender dan memperbarui diagram di dasbor. getData()dapat dipanggil selama peristiwa berikut:

  • Pengguna menambahkan diagram ke dasbor
  • Pengguna mengedit diagram
  • Pengguna melihat dasbor
  • Pengguna mengedit filter atau kontrol data terkait
  • Looker Studio memerlukan sampel data

Anda tidak perlu menyalin kode apa pun dari halaman ini karena Anda akan menyalin

getData()

kode pada langkah berikutnya.

Memahami objek request

Looker Studio meneruskan objek request dengan setiap panggilan getData(). Tinjau struktur objek request di bawah. Hal ini akan membantu Anda menulis kode untuk fungsi getData().

Struktur objek request

{
  configParams: object,
  scriptParams: object,
  dateRange: {
    startDate: string,
    endDate: string
  },
  fields: [
    {
      name: Field.name
    }
  ]
}
  • Objek configParams akan berisi nilai konfigurasi untuk parameter yang ditentukan dalam getConfig() dan dikonfigurasi oleh pengguna.
  • Objek scriptParams akan berisi informasi yang relevan dengan eksekusi konektor. Anda tidak perlu menggunakannya untuk codelab ini.
  • dateRange akan berisi rentang tanggal yang diminta jika diminta dalam respons getConfig().
  • fields akan berisi daftar nama kolom yang datanya diminta.

Untuk konektor Anda, contoh request dari fungsi getData() mungkin terlihat seperti ini:

{
  configParams: {
    package: 'jquery'
  },
  dateRange: {
    startDate: '2017-07-16',
    endDate: '2017-07-18'
  },
  fields: [
    {
      name: 'day',
    },
    {
      name: 'downloads',
    }
  ]
}

Untuk panggilan getData() di request di atas, hanya dua kolom yang diminta meskipun skema konektor memiliki kolom tambahan. Halaman berikutnya akan berisi contoh respons untuk panggilan getData() ini dan struktur respons getData() umum.

10. Menentukan getData() : Bagian 2

Dalam respons getData(), Anda harus memberikan skema dan data untuk kolom yang diminta. Anda akan membagi kode menjadi tiga segmen:

  • Buat skema untuk kolom yang diminta.
  • Mengambil dan mengurai data dari API.
  • Transformasikan data yang diuraikan dan filter untuk kolom yang diminta.

Anda tidak perlu menyalin kode apa pun dari halaman ini karena Anda akan menyalin

getData()

kode di halaman berikutnya.

Ini adalah struktur getData() untuk konektor Anda.

function getData(request) {

  // TODO: Create schema for requested fields.
  
  // TODO: Fetch and parse data from API.
  
  // TODO: Transform parsed data and filter for requested fields.

  return {
    schema: <filtered schema>,
    rows: <transformed and filtered data>
  };
}

Membuat skema untuk kolom yang diminta

// Create schema for requested fields
  var requestedFieldIds = request.fields.map(function(field) {
    return field.name;
  });
  var requestedFields = getFields().forIds(requestedFieldIds);

Mengambil dan mengurai data dari API

URL npm API akan memiliki format berikut:

https://api.npmjs.org/downloads/point/{start_date}:{end_date}/{package}

Buat URL untuk API menggunakan request.dateRange.startDate, request.dateRange.endDate, dan request.configParams.package yang disediakan oleh Looker Studio. Kemudian, ambil data dari API menggunakan UrlFetchApp(Class Apps Script: referensi). Kemudian, urai respons yang diambil.

  // Fetch and parse data from API
  var url = [
    'https://api.npmjs.org/downloads/range/',
    request.dateRange.startDate,
    ':',
    request.dateRange.endDate,
    '/',
    request.configParams.package
  ];
  var response = UrlFetchApp.fetch(url.join(''));
  var parsedResponse = JSON.parse(response).downloads;

Mengubah data yang diuraikan dan memfilter kolom yang diminta

Respons dari npm API akan dalam format berikut:

{
  downloads: [
    {
    day: '2014-02-27',
    downloads: 1904088
    },
    ..
    {
    day: '2014-03-04',
    downloads: 7904294
    }
  ],
  start: '2014-02-25',
  end: '2014-03-04',
  package: 'somepackage'
}

Ubah respons dari npm API dan berikan respons getData() dalam format berikut. Jika format ini tidak jelas, lihat contoh respons di paragraf berikut.

{
  schema: [
    {
      object(Field)
    }
  ],
  rows: [
    {
      values: [string]
    }
  ]
}

Dalam respons, tampilkan skema hanya untuk kolom yang diminta menggunakan properti schema. Anda akan menampilkan data menggunakan properti rows sebagai daftar baris. Untuk setiap baris, urutan kolom di values harus cocok dengan urutan kolom di schema. Berdasarkan contoh request sebelumnya, berikut tampilan respons untuk getData():

{
  schema: requestedFields.build(),
  rows: [
    {
      values: [ 38949, '20170716']
    },
    {
      values: [ 165314, '20170717']
    },
    {
      values: [ 180124, '20170718']
    },
  ]
}

Anda telah membuat subset skema. Gunakan fungsi berikut untuk mengubah data yang diuraikan dan memfilternya untuk kolom yang diminta.

function responseToRows(requestedFields, response, packageName) {
  // Transform parsed data and filter for requested fields
  return response.map(function(dailyDownload) {
    var row = [];
    requestedFields.asArray().forEach(function (field) {
      switch (field.getId()) {
        case 'day':
          return row.push(dailyDownload.day.replace(/-/g, ''));
        case 'downloads':
          return row.push(dailyDownload.downloads);
        case 'packageName':
          return row.push(packageName);
        default:
          return row.push('');
      }
    });
    return { values: row };
  });
}

11. Menentukan getData() : Bagian 3

Kode getData() gabungan akan terlihat seperti di bawah. Tambahkan kode berikut ke file Code.gs Anda:

Code.gs

function responseToRows(requestedFields, response, packageName) {
  // Transform parsed data and filter for requested fields
  return response.map(function(dailyDownload) {
    var row = [];
    requestedFields.asArray().forEach(function (field) {
      switch (field.getId()) {
        case 'day':
          return row.push(dailyDownload.day.replace(/-/g, ''));
        case 'downloads':
          return row.push(dailyDownload.downloads);
        case 'packageName':
          return row.push(packageName);
        default:
          return row.push('');
      }
    });
    return { values: row };
  });
}

function getData(request) {
  var requestedFieldIds = request.fields.map(function(field) {
    return field.name;
  });
  var requestedFields = getFields().forIds(requestedFieldIds);

  // Fetch and parse data from API
  var url = [
    'https://api.npmjs.org/downloads/range/',
    request.dateRange.startDate,
    ':',
    request.dateRange.endDate,
    '/',
    request.configParams.package
  ];
  var response = UrlFetchApp.fetch(url.join(''));
  var parsedResponse = JSON.parse(response).downloads;
  var rows = responseToRows(requestedFields, parsedResponse, request.configParams.package);

  return {
    schema: requestedFields.build(),
    rows: rows
  };
}

Anda telah selesai menggunakan file Code.gs. Selanjutnya, perbarui manifes.

12. Memperbarui manifes

Di editor Apps Script, pilih Project Settings > Show "appsscript.json" manifest file in editor.

90a68a58bbbb63c4.png

Tindakan ini akan membuat file manifes appsscript.json baru.

1081c738d5d577a6.png

Ganti file appscript.json Anda dengan kode berikut:

appsscript.json

{
  "timeZone": "America/Los_Angeles",
  "exceptionLogging": "STACKDRIVER",
  "runtimeVersion": "V8",

  "dataStudio": {
    "name": "npm Downloads - From Codelab",
    "logoUrl": "https://raw.githubusercontent.com/npm/logos/master/npm%20logo/npm-logo-red.png",
    "company": "Codelab user",
    "companyUrl": "https://developers.google.com/looker-studio/",
    "addonUrl": "https://github.com/googledatastudio/example-connectors/tree/master/npm-downloads",
    "supportUrl": "https://github.com/googledatastudio/community-connectors/issues",
    "description": "Get npm package download counts.",
    "sources": ["npm"]
  }
}

Simpan project Apps Script.

5701ece1c89415c.png

Selamat! Anda telah membuat konektor komunitas pertama dan siap untuk diuji.

13. Menguji konektor di Looker Studio

Menggunakan deployment

Langkah 1: Di lingkungan pengembangan Apps Script, Klik Deploy > Test deployments untuk membuka dialog Test deployments.

3f57ea0feceb2596.png

Deployment default, Head Deployment, akan dicantumkan.

Langkah 2: Klik Salin untuk menyalin ID Deployment Head.

Langkah 3: Untuk memuat konektor di Looker Studio, ganti placeholder <HEAD_DEPLOYMENT_ID> di link berikut dengan ID Deployment Head konektor Anda, lalu buka link tersebut di browser:

https://lookerstudio.google.com/datasources/create?connectorId=<HEAD_DEPLOYMENT_ID>

Memberikan otorisasi pada konektor

Pengguna Looker Studio yang pertama kali menggunakan: Jika Anda belum pernah menggunakan Looker Studio, Anda akan diminta untuk mengizinkan Looker Studio dan menyetujui persyaratan dan ketentuan. Selesaikan proses otorisasi. Saat pertama kali menggunakan Looker Studio, Anda juga dapat melihat dialog untuk memperbarui preferensi pemasaran. Daftar ke Pengumuman produk jika Anda ingin mengetahui fitur, update, dan pengumuman produk terbaru melalui email.

Setelah dimuat, Anda akan melihat perintah untuk mengizinkan konektor.

d7e66726a1e64c05.png

Klik Beri otorisasi dan berikan otorisasi yang diperlukan ke konektor.

Mengonfigurasi konektor

Setelah otorisasi selesai, layar konfigurasi akan ditampilkan. Ketik "lighthouse" di area input teks, lalu klik Hubungkan di kanan atas.

ec7416d6dbeabc8f.png

Konfirmasi skema

Anda akan melihat layar kolom. Klik Buat Laporan di kanan atas.

4a9084bd51d2fbb8.png

Membuat dasbor

Anda akan berada di lingkungan dasbor Looker Studio. Klik Add to Report.

1ca21e327308237c.png

Di Looker Studio, setiap kali pengguna mengakses konektor dan menambahkan konfigurasi baru, sumber data baru akan dibuat di akun Looker Studio pengguna. Anda dapat menganggap sumber data sebagai instansiasi konektor berdasarkan konfigurasi tertentu. Berdasarkan konektor dan konfigurasi yang dipilih pengguna, sumber data akan menampilkan tabel data dengan kumpulan kolom tertentu. Pengguna dapat membuat beberapa sumber data dari konektor yang sama. Sumber data dapat digunakan dalam beberapa laporan, dan laporan yang sama dapat menggunakan beberapa sumber data.

Sekarang tambahkan Diagram Deret Waktu. Di menu, klik Sisipkan > Deret Waktu. Kemudian, tempatkan deret waktu di kanvas. Anda akan melihat diagram deret waktu jumlah download npm untuk paket yang dipilih. Tambahkan kontrol filter tanggal dan lihat dasbor seperti yang ditunjukkan di bawah.

4c076e07665f57aa.gif

Selesai! Anda baru saja membuat konektor komunitas pertama Anda. Tindakan ini akan membawa Anda ke akhir codelab ini. Sekarang, mari kita lihat langkah selanjutnya yang dapat Anda lakukan.

14. Langkah berikutnya

Meningkatkan kualitas konektor yang Anda buat

Lakukan peningkatan pada konektor yang baru saja Anda buat:

  • Di Looker Studio, jika Anda tidak memberikan nama paket di layar konfigurasi untuk konektor, Anda akan melihat pesan error saat menggambar diagram deret waktu. Coba tambahkan validasi input atau opsi default ke konfigurasi konektor Anda.
  • Coba tambahkan dukungan untuk membuat kueri beberapa nama paket secara bersamaan dalam konfigurasi konektor Anda. Petunjuk: npm package download counts API mendukung input beberapa nama paket yang dipisahkan dengan koma.
  • Anda dapat menemukan solusi untuk keduanya di kode konektor npm kami.

Lakukan lebih banyak hal dengan Konektor Komunitas

Referensi lainnya

Berikut adalah berbagai referensi yang dapat Anda akses untuk membantu Anda mempelajari lebih dalam materi yang dibahas dalam codelab ini.

Resource Type

Fitur Pengguna

Fitur Developer

Dokumentasi

Pusat Bantuan

Dokumentasi Developer

Berita & Info Terbaru

Daftar di Looker Studio > Setelan Pengguna

Mailing List Developer

Ajukan Pertanyaan

Forum Pengguna

Stack Overflow [looker-studio]

Video

DataVis DevTalk

Contoh

Repositori Open Source