1. Wprowadzenie
Celem tego laboratorium jest pokazanie, jak napisać funkcję w Cloud Functions, która będzie reagować na przesłanie pliku CSV do Cloud Storage, odczytywać jego zawartość i używać jej do aktualizowania arkusza Google za pomocą Sheets API.

Można to traktować jako automatyzację ręcznego kroku „Importuj jako CSV”. Dzięki temu będziesz mieć pewność, że możesz analizować dane (być może wygenerowane przez inny zespół) w arkuszu kalkulacyjnym od razu po ich udostępnieniu.
Implementacja wygląda tak :

2. Konfiguracja i wymagania
Samodzielne konfigurowanie środowiska
- Zaloguj się w konsoli Google Cloud i utwórz nowy projekt lub użyj istniejącego. (Jeśli nie masz jeszcze konta Gmail lub G Suite, musisz je utworzyć).
Zapamiętaj identyfikator projektu, czyli unikalną nazwę we wszystkich projektach Google Cloud (podana powyżej nazwa jest już zajęta i nie będzie działać w Twoim przypadku). W dalszej części tego laboratorium będzie on nazywany PROJECT_ID.
- Następnie musisz włączyć rozliczenia w konsoli Cloud, aby korzystać z zasobów Google Cloud.
Ukończenie tego laboratorium nie powinno wiązać się z dużymi kosztami, a nawet z żadnymi. Wykonaj instrukcje z sekcji „Czyszczenie”, w której znajdziesz informacje o tym, jak wyłączyć zasoby, aby uniknąć naliczenia opłat po zakończeniu tego samouczka. Nowi użytkownicy Google Cloud mogą skorzystać z programu bezpłatnego okresu próbnego, w którym mają do dyspozycji środki w wysokości 300 USD.
3. Tworzenie i konfigurowanie arkusza Google oraz włączanie interfejsu API
Najpierw utwórzmy nowy dokument Arkuszy (może on należeć do dowolnego użytkownika). Po utworzeniu zapamiętaj jego identyfikator. Będzie on używany jako zmienna środowiskowa w funkcji, którą napiszemy :

W konsoli GCP włącz interfejs Google Sheets API w nowo utworzonym projekcie. W tym celu otwórz sekcję „Interfejsy API i usługi”, a następnie „Biblioteka interfejsów API”:

W sekcji „Uprawnienia i administracja” otwórz „Konta usługi” i zanotuj adres e-mail domyślnego konta usługi App Engine. Powinien mieć format your-project-id@appspot.gserviceaccount.com. Możesz też utworzyć własne konto usługi przeznaczone do tego działania.

Na koniec przyznaj temu kontu usługi uprawnienia do edytowania arkusza kalkulacyjnego, klikając przycisk „Udostępnij”:

Po skonfigurowaniu możemy napisać funkcję Cloud Functions i skonfigurować ją tak, aby korzystała z tego konta usługi. Będzie mógł zapisywać dane w utworzonym przez nas arkuszu kalkulacyjnym.
4. Utworzenie zasobnika na dane
Utwórzmy zasobnik, w którym funkcja w chmurze będzie monitorować nowe pliki CSV.
W konsoli w menu po lewej stronie kliknij „Miejsce na dane”... :

... i utwórz nowy zasobnik o nazwie csv2sheet-POSTFIX (zastąp POSTFIX unikalną wartością), a wszystkie pozostałe ustawienia pozostaw domyślne :

5. Tworzenie funkcji w Cloud Functions
Możemy teraz utworzyć funkcję Cloud Function o nazwie csv2sheet, która jest wywoływana podczas przesyłania plików do określonego zasobnika Cloud Storage. Kod zostanie napisany w środowisku Node.js 8 z użyciem funkcji asynchronicznych w edytorze wbudowanym bezpośrednio w konsoli Cloud :

Pamiętaj, aby ustawić aktywator na „Cloud Storage” i dostosować nazwę zasobnika do nazwy utworzonej w poprzednim kroku.
Zaktualizuj też punkt wejścia dla funkcji, którą zamierzamy napisać, na csv2sheet :

Teraz zmień treść funkcji na :
- korzystać z interfejsów API Cloud Storage i Arkuszy,
- oznaczyć funkcję
csv2sheetjakoasync, - pobrać
fileNamez metadanych zdarzenia Cloud Storage i utworzyć nazwę nowego arkusza, który utworzymy :
const {google} = require("googleapis");
const {Storage} = require("@google-cloud/storage")
exports.csv2sheet = async (data, context) => {
var fileName = data.name;
// basic check that this is a *.csv file, etc...
if (!fileName.endsWith(".csv")) {
console.log("Not a .csv file, ignoring.");
return;
}
// define name of new sheet
const sheetName = fileName.slice(0, -4);
// TODO!
};
Użycie właściwości async jest tutaj wymagane, aby można było użyć właściwości await, o czym przekonamy się za chwilę.
Podczas tworzenia tej funkcji masz do dyspozycji kilka ważnych opcji (kliknij link „Więcej” u dołu ekranu) :
- W menu wybierz konto usługi omówione powyżej.
- Zdefiniuj zmienną środowiskową o nazwie
SPREADSHEET_ID, która powinna odpowiadać utworzonemu wcześniej dokumentowi arkusza :

Na koniec wykonaj ten krok konfiguracji. Oto zawartość pliku package.json z interfejsami Cloud Storage API i Google Sheets API jako 2 zależnościami, których będziemy używać (skorzystaj z edytora wbudowanego w konsoli na karcie PACKAGE.JSON) :
{
"name": "csv2sheet",
"version": "0.0.42",
"dependencies": {
"googleapis": "^51.0.0",
"@google-cloud/storage": "^5.0.1"
}
}
Po skonfigurowaniu wszystkiego zgodnie z opisem kliknij „Utwórz”. Po krótkiej chwili funkcja powinna zostać utworzona i wdrożona.
6. Konfigurowanie uwierzytelniania i interfejsu Sheets API
Zanim napiszemy w funkcji Cloud kolejne wiersze kodu za pomocą edytora wbudowanego, musimy zablokować tworzenie interfejsu Google Client API z odpowiednimi zakresami Storage i Sheet (pamiętaj, że jest to część funkcji async).
W edytorze funkcji w konsoli kliknij „EDIT” (EDYTUJ) i dodaj ten kod do treści funkcji csv2sheet :
// block on auth + getting the sheets API object
const auth = await google.auth.getClient({
scopes: [
"https://www.googleapis.com/auth/spreadsheets",
"https://www.googleapis.com/auth/devstorage.read_only"
]
});
Następnie możemy utworzyć klienta interfejsu Sheets API :
const sheetsAPI = google.sheets({version: 'v4', auth});
7. Tworzenie pustego arkusza za pomocą interfejsu Sheets API
Za pomocą klienta interfejsu API Arkuszy możemy utworzyć w dokumencie prosty nowy arkusz. Zanim jednak przejdziemy dalej, warto zapoznać się z kilkoma terminami:
- arkusz kalkulacyjny to rzeczywisty dokument, do którego odwołuje się jego identyfikator (omówiony powyżej i widoczny w adresie URL dokumentu);
- Arkusz to jedna z kart w dokumencie, do której można się odwoływać za pomocą nazwy (nazwy karty) lub identyfikatora wygenerowanego podczas tworzenia arkusza.
Mając to na uwadze, oto funkcja, która za pomocą klienta Sheets API tworzy pusty arkusz w pozycji 2 (zwykle po domyślnym arkuszu „Arkusz1”) z 26 kolumnami i 2000 wierszami, w którym pierwszy wiersz jest zamrożony (dodaj go do funkcji za pomocą edytora wbudowanego) :
function addEmptySheet(sheetsAPI, sheetName) {
return new Promise((resolve, reject) => {
const emptySheetParams = {
spreadsheetId: process.env.SPREADSHEET_ID,
resource: {
requests: [
{
addSheet: {
properties: {
title: sheetName,
index: 1,
gridProperties: {
rowCount: 2000,
columnCount: 26,
frozenRowCount: 1
}
}
}
}
]
}
};
sheetsAPI.spreadsheets.batchUpdate( emptySheetParams, function(err, response) {
if (err) {
reject("The Sheets API returned an error: " + err);
} else {
const sheetId = response.data.replies[0].addSheet.properties.sheetId;
console.log("Created empty sheet: " + sheetId);
resolve(sheetId);
}
}
);
});
}
Zwróć uwagę, że zamiast na stałe kodować odwołanie do arkusza kalkulacyjnego, korzystamy z utworzonej wcześniej zmiennej środowiskowej SPREADSHEET_ID.
Musimy zapamiętać sheetId, aby móc obsługiwać kolejne żądania dotyczące tego arkusza. Nazwa arkusza musi być też unikalna. Jeśli arkusz o nazwie sheetName już istnieje, utworzenie nowego arkusza się nie powiedzie.
Funkcja batchUpdate w interfejsie Sheets API to popularny sposób interakcji z dokumentami. Jest ona opisana tutaj.
8. Odczytywanie danych z pliku CSV w pamięci masowej
Mamy już miejsce na dane, więc rozwińmy funkcję w Cloud Functions w edytorze wbudowanym i użyjmy interfejsu Cloud Storage API, aby pobrać rzeczywiste dane z właśnie przesłanego pliku i zapisać je w ciągu znaków:
function readCSVContent(sheetsAPI, file, sheetName) {
return new Promise((resolve, reject) => {
const storage = new Storage();
let fileContents = new Buffer('');
storage.bucket(file.bucket).file(file.name).createReadStream()
.on('error', function(err) {
reject('The Storage API returned an error: ' + err);
})
.on('data', function(chunk) {
fileContents = Buffer.concat([fileContents, chunk]);
})
.on('end', function() {
let content = fileContents.toString('utf8');
console.log("CSV content read as string : " + content );
resolve(content);
});
});
}
9. Wypełnij nowo utworzony arkusz
Teraz musimy wypełnić utworzony arkusz, korzystając z tego samego interfejsu API klienta Arkuszy i zebranych właśnie danych. Przy okazji dodamy też do kolumn arkusza trochę stylów (zmienimy rozmiar czcionki w górnym wierszu i pogrubimy go) :
function populateAndStyle(sheetsAPI, theData, sheetId) {
return new Promise((resolve, reject) => {
// Using 'batchUpdate' allows for multiple 'requests' to be sent in a single batch.
// Populate the sheet referenced by its ID with the data received (a CSV string)
// Style: set first row font size to 11 and to Bold. Exercise left for the reader: resize columns
const dataAndStyle = {
spreadsheetId: process.env.SPREADSHEET_ID,
resource: {
requests: [
{
pasteData: {
coordinate: {
sheetId: sheetId,
rowIndex: 0,
columnIndex: 0
},
data: theData,
delimiter: ","
}
},
{
repeatCell: {
range: {
sheetId: sheetId,
startRowIndex: 0,
endRowIndex: 1
},
cell: {
userEnteredFormat: {
textFormat: {
fontSize: 11,
bold: true
}
}
},
fields: "userEnteredFormat(textFormat)"
}
}
]
}
};
sheetsAPI.spreadsheets.batchUpdate(dataAndStyle, function(err, response) {
if (err) {
reject("The Sheets API returned an error: " + err);
} else {
console.log(sheetId + " sheet populated with " + theData.length + " rows and column style set.");
resolve();
}
});
});
}
Ten kod należy dodać do funkcji Cloud, która jest już gotowa w 99%.
Zwróć uwagę, jak dane i styl są łączone jako wiele elementów requests w jedno wywołanie interfejsu API Arkuszy batchUpdate. Dzięki temu aktualizacja jest bardziej wydajna i atomowa.
Zwróć też uwagę, że definiujemy zakres edycji, który odpowiada rozmiarowi utworzonego arkusza. Oznacza to, że treści, które przekraczają 26 kolumn (wartość columnCount użyta podczas tworzenia arkusza), nie będą działać z tym konkretnym kodem.
Jeśli wszystko pójdzie dobrze, na tym etapie możesz:
- zapisz zaktualizowaną funkcję,
- upuścić plik CSV w zasobniku,
- zobaczysz odpowiednie dane w arkuszu kalkulacyjnym.
10. Łączenie wszystkiego w całość i testowanie automatyzacji
Wywołania funkcji, o których właśnie mówiliśmy, mogą być wykonywane jako kolejne wywołania blokujące w pierwotnej funkcji csv2sheet:
const sheetId = await addEmptySheet(sheetsAPI, sheetName);
const theData = await readCSVContent(sheetsAPI, data, sheetName);
await populateAndStyle(sheetsAPI, theData, sheetId);
Jeśli potrzebujesz pełnego kodu źródłowego funkcji, znajdziesz go tutaj (prawdopodobnie łatwiej będzie Ci pobrać go w jednym zestawie).
Gdy wszystko będzie gotowe, po prostu prześlij plik CSV do odpowiedniego zasobnika i obserwuj, jak arkusz kalkulacyjny jest aktualizowany o nowy arkusz z zawartością pliku. Jeśli nie masz pliku CSV, możesz pobrać przykładowy plik CSV.

Spróbuj przesłać do zasobnika kilka plików, aby zobaczyć, co się stanie.
11. To wszystko. Czas na demontaż infrastruktury
Żartuję, nie ma żadnej infrastruktury do likwidacji, wszystko zostało zrobione bezserwerowo.
Jeśli chcesz, możesz usunąć utworzoną funkcję Cloud Functions i utworzony przez siebie zasobnik, a nawet cały projekt.
12. Co dalej?
To koniec tego laboratorium, w którym pokazaliśmy, jak nasłuchiwać przesyłania do zasobnika Cloud Storage w funkcji Cloud Function, aby aktualizować arkusz Google za pomocą odpowiedniego interfejsu API.
Więcej propozycji :
- Zapoznaj się z przewodnikami Cloud Functions (zawierają niektóre sprawdzone metody).
- Przejdź jeden z samouczków Cloud Functions.
- Dowiedz się więcej o interfejsie Google Sheets API
Jeśli masz problemy z tym kursem, zgłoś je, korzystając z linku w lewym dolnym rogu.
Będziemy wdzięczni za Twoją opinię.