I forbindelse med nytt Public API, så har enkelte endepunkt en begrensning på mengde data som kan hentes ut pr. kall. Derfor er det innført paginering på disse endepunktene. For de som bruker Power BI for å hente ut data må man derfor endre disse uthentingene av data for å ivareta paginering. Det er ikke en stor operasjon. 

For å løse dette brukes Power Query M som er Microsoft sitt «kodespråk» som ikke krever noe kodekunnskap. Dette brukes mye i eksempelvis Power BI og Excel. 

 

I dette eksempelet skal vi hente ut data fra timelisten, altså registrerte timelinjer. Vi bruker APIet Timelinje og endepunktet GetTimelinje for dette. Dette er dokumentert på https://developer.tidsbanken.net/api-details#api=api-timelinje

I dag er det en begrensing på 1000 objekter pr. side. Dette er gjenstand for endring oppover uten varsel, men vi ikke ha noen praktisk betydning så lenge man holder seg til pagineringen det er lagt opp til og ikke gjør paginering manuelt. Ved håndtering av paginering manuelt vil endringen ikke ha noen konsekvenser annet enn at det gir mulighet for å hente ut flere objekter pr. kall. 

 

Dette er kun mulig å gjøre i Power BI Desktop for Windows. 

 

1. Koble til APIet 

Først må vi koble oss til APIet for å verifisere at vi har tilgang til dataene. Dette gjør vi på følgende måte.  

 

1.2. Åpne Power BI og klikk på Hent data og velg Web: 
 

1.2. I pop'up-vinduet velger vi Avansert og legger inn URLen til endepunktet og api-nøkkel, subscription key og x-api-version som headers. 

I eksempelt har vi valgt å legge $select og $top på egne linjer for å få best mulig oversikt. Vi har valgt her å hente ut Id, AnsattId, Dato og AntallTImer for å ha noe data å se på videre. Senere skal spørringen hente samtlige felter fra APIet. Vi har også lagt inn en topp 1000 i første omgang.


https://api.tidsbanken.net/timelinje/TimelinjeBehandlet? 
$select=Id, AnsattId, Dato, AntallTimer 
&$top=2000 
tb-key: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx 
Ocp-Apim-Subscription-Key: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx  
x-api-version: 3.0 
 

Ocp-Apim-Subscription-Key får du ved å opprette en bruker på developer.tidsbanken.net. Tb-key får du som administrator fra Tidsbanken support. Send epost til support@tidsbanken.no
 
Klikk OK. 
 
 
1.2.1. Hvis vi ikke har koblet oss til Tidsbanken sitt API tidligere vil du få opp et popup-vindu som spør om du vil koble til:

 
 Klikk på Koble til. 
 
 1.3. Vi skal nå motta en respons fra APIet med de 1000 første objektene/radene. 

 
Det er to egne kolonner med tittelen @odata.context og @odata.nextLink. I responsen fra APIet kommer alle de 1000 objektene inne i et array som heter value. Derfor står alle øvrige kolonnenavn med eksempelvis value.Id. @odata.context og @odata.nextLink er egne objekter i responsen og Power BI vil derfor liste ut dette på hvert objekt som vi ser i listen. 
 

 

2.Legge inn paginering 

I responsen fra APIet får vi en lenke (@odata.nextLink) til neste side med de neste 1000 objektene vi spør etter. Power BI kan spørre etter de neste 1000 slik at vi slipper å gjøre dette selv. 

 

2.1. Klikk på Avansert redigering i menyen: 



Her kommer det et popup-vindu for å endre på spørringen:

 
2.2. Her erstatter vi alt som står der og legger inn følgende kodesnutt: 
(Merk at tb-key og Ocp-Apim-Subscription-Key må endres til dine egne).

let 
BaseUrl = "https://api.tidsbanken.net/timelinje/TimelinjeBehandlet?$select=Id, AnsattId, Dato, AntallTimer&$top=200", 
getJson = (url) as record => 
let 
source = Json.Document(Web.Contents(url, [Headers=[#"tb-key"="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", #"x-api-version"="3.0", #"Ocp-Apim-Subscription-Key"="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"]])), 
data = source[value], 
next = Record.FieldOrDefault(source, "@odata.nextLink", null), 
output = [Data=data, Next=next] 
in 
output, 
generateData = (url) => 
let 
init = getJson(url), 
output = List.Generate(()=>init, each _ <> null, each if _[Next] <> null then getJson(_[Next]) else null, each _[Data]) 
in 
output, 
data = generateData(BaseUrl), 
table = Table.FromList(data, Splitter.SplitByNothing(), null, null, ExtraValues.Error), 
expandedTable = Table.ExpandRecordColumn(Table.ExpandListColumn(table, "Column1"), "Column1",  
{ 
"Id", "AnsattId", "Dato", "AntallTimer" 
},  
{ 
"Id", "AnsattId", "Dato", "AntallTimer" 
} 
) 
in 
expandedTable 

 

Dette vil da gjøre spørring for de neste 1000 objektene helt til det ikke finnes noen @odata.nextLink i responsen.  
 
Klikk Fullfør. 


Tabellen vil nå flylles med alle dataene fra API'et i kolonnene Id, AnsattId, Dato og AntallTimer.  

 

3. Filtrering med dynamisk dato 

Hvis du skal bruke denne spørringen til å hente de siste 30 dagene med data kan du  legge inn et filter i spørringen. I dette eksempelet vil jeg hente kun timelinjer som har dato innenfor de siste 30 dagene, altså de siste 30 dagers timelinjer. 
 

 

3.1. Klikk på Avansert redigering i menyen.  
Erstatt eksisterende kode og legg inn følgende kodesnutt: 
(Merk at tb-key og Ocp-Apim-Subscription-Key må endres til dine egne) 

let 
BaseUrl = "https://api.tidsbanken.net/timelinje/TimelinjeBehandlet?$select=Id, AnsattId, Dato, AntallTimer&$top=200&$filter=Dato gt " & Date.ToText(Date.AddDays(DateTime.Date(DateTime.LocalNow()), -30), "yyyy-MM-dd"), 
getJson = (url) as record => 
let 
source = Json.Document(Web.Contents(url, [Headers=[#"tb-key"="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", #"x-api-version"="3.0", #"Ocp-Apim-Subscription-Key"="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"]])), 
data = source[value], 
next = Record.FieldOrDefault(source, "@odata.nextLink", null), 
output = [Data=data, Next=next] 
in 
output, 
generateData = (url) => 
let 
init = getJson(url), 
output = List.Generate(()=>init, each _ <> null, each if _[Next] <> null then getJson(_[Next]) else null, each _[Data]) 
in 
output, 
data = generateData(BaseUrl), 
table = Table.FromList(data, Splitter.SplitByNothing(), null, null, ExtraValues.Error), 
expandedTable = Table.ExpandRecordColumn(Table.ExpandListColumn(table, "Column1"), "Column1",  
{ 
"Id", "AnsattId", "Dato", "AntallTimer" 
},  
{ 
"Id", "AnsattId", "Dato", "AntallTimer" 
} 
) 
in 
expandedTable

 


Klikk Fullfør 



Tabellen fylles nå med timelinjer som har dato siste 30 dager. Tilsvarende metode kan brukes på andre dato-/tidspunktfelt. Filteret kan også kombineres med andre elementer du ønsker å filtrere på som ArtId (lønnsart), AvdelingId eller ProsjektId for å nevne noen. Her er et eksempel på dynamisk datofilter kombinert med AvdelingId:   
 
BaseUrl = "https://api.tidsbanken.net/timelinje/TimelinjeBehandlet?$select=Id, AnsattId, Dato, AntallTimer, AvdelingId&$top=200&$filter=AvdelingId eq '100' and Dato gt " & Date.ToText(Date.AddDays(DateTime.Date(DateTime.LocalNow()), -30), "yyyy-MM-dd"), 

 
Husk å sjekke typen på verdien du skal filtrere på. I dette eksempelet bruker vi  (single quotes) rundt avdelingsnummeret da dette er en string/tekst. For ansattnummer brukes ikke  da dette er et tallfelt.
 


4. Hente ut flere felter 

 

For å kunne få ut flere felter er det bare å legge til i $select i BaseUrl og i expandedTable. Her er det vist med å legge til AvdelingId: 
 

BaseUrl = "https://api.tidsbanken.net/timelinje/TimelinjeBehandlet?$select=Id, AnsattId, Dato, AntallTimer, AvdelingId&$top=200&$filter=Dato gt " & Date.ToText(Date.AddDays(DateTime.Date(DateTime.LocalNow()), -30), "yyyy-MM-dd"), 
 

og  

expandedTable = Table.ExpandRecordColumn(Table.ExpandListColumn(table, "Column1"), "Column1",  
{ 
"Id", "AnsattId", "Dato", "AntallTimer", "AvdelingId" 
},  
{ 
"Id", "AnsattId", "Dato", "AntallTimer", "AvdelingId" 
} 
) 

 

5. Hente ut alle felter 

For å hente ut alle feltene som er tilgjengelig i APIet er det bare å legge til feltene i $select og expandedTable som vist i forrige punkt. Bruker du $select=* vil APIet omgjøre dette til å inneholde alle felt. 
I eksempelet kan du se hvordan: 
(Merk at tb-key og Ocp-Apim-Subscription-Key må endres til dine egne)
 

 

let 
    BaseUrl = "https://api.tidsbanken.net/timelinje/TimelinjeBehandlet?$select=Id, AnsattId, Dato, FraKlokken, TilKlokken, VirkeligFraKlokken, VirkeligTilKlokken, Pause1FraKlokken, Pause1TilKlokken, Pause2FraKlokken, Pause2TilKlokken, ArtId, AntallTimer, Kostpris, Salgspris, AvdelingId, ProsjektId, ProsjektLinjeId, AktivitetId, Notat, KontoId, ArbeidsTypeId, SumPauser, Faktureres, FakturaTimeAntall, FakturaNotat, Element1Id, Element2Id, ProduktId, AnleggId, Godkjent, PlanFraKlokken, PlanTilKlokken, ForventetArbeidstidForDag, Produktiv, OpprettetDato, OpprettetAvId, EndretDato, EndretAvId&$top=200&$filter=Dato gt " & Date.ToText(Date.AddDays(DateTime.Date(DateTime.LocalNow()), -30), "yyyy-MM-dd"), 
  
    getJson = (url) as record => 
        let 
            source = Json.Document(Web.Contents(url, [Headers=[#"tb-key"=" xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", #"x-api-version"="3.0", #"Ocp-Apim-Subscription-Key"=" xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"]])), 
            data = source[value], 
            next = Record.FieldOrDefault(source, "@odata.nextLink", null), 
            output = [Data=data, Next=next] 
        in 
            output, 
  
    generateData = (url) => 
        let 
            init = getJson(url), 
            output = List.Generate(()=>init, each _ <> null, each if _[Next] <> null then getJson(_[Next]) else null, each _[Data]) 
        in 
            output, 
  
    data = generateData(BaseUrl), 
    table = Table.FromList(data, Splitter.SplitByNothing(), null, null, ExtraValues.Error), 
    expandedTable = Table.ExpandRecordColumn(Table.ExpandListColumn(table, "Column1"), "Column1",  
        { 
            "Id", "AnsattId", "Dato", "FraKlokken", "TilKlokken", "VirkeligFraKlokken", "VirkeligTilKlokken",  
            "Pause1FraKlokken", "Pause1TilKlokken", "Pause2FraKlokken", "Pause2TilKlokken", "ArtId", "AntallTimer",  
            "Kostpris", "Salgspris", "AvdelingId", "ProsjektId", "ProsjektLinjeId", "AktivitetId", "Notat", "KontoId",  
            "ArbeidsTypeId", "SumPauser", "Faktureres", "FakturaTimeAntall", "FakturaNotat", "Element1Id", "Element2Id",  
            "ProduktId", "AnleggId", "Godkjent", "PlanFraKlokken", "PlanTilKlokken", "ForventetArbeidstidForDag",  
            "Produktiv", "OpprettetDato", "OpprettetAvId", "EndretDato", "EndretAvId" 
        },  
        { 
            "Id", "AnsattId", "Dato", "FraKlokken", "TilKlokken", "VirkeligFraKlokken", "VirkeligTilKlokken",  
            "Pause1FraKlokken", "Pause1TilKlokken", "Pause2FraKlokken", "Pause2TilKlokken", "ArtId", "AntallTimer",  
            "Kostpris", "Salgspris", "AvdelingId", "ProsjektId", "ProsjektLinjeId", "AktivitetId", "Notat", "KontoId",  
            "ArbeidsTypeId", "SumPauser", "Faktureres", "FakturaTimeAntall", "FakturaNotat", "Element1Id", "Element2Id",  
            "ProduktId", "AnleggId", "Godkjent", "PlanFraKlokken", "PlanTilKlokken", "ForventetArbeidstidForDag",  
            "Produktiv", "OpprettetDato", "OpprettetAvId", "EndretDato", "EndretAvId" 
        } 
    ) 
in 
    expandedTable 
 

 

Klikk Fullfør. 

 

 

Tabellen fylles nå med alle feltene som er spurt etter. 

 

6. Endre typene i responsen 

For å kunne bruke dataene i Power BI på en ryddig måte kan typene endres slik at tekstfelt er tekstfelt, nummer er nummer og datoer er datoer. Dette kan gjøres senere i datasettet i Power BI, men det kan også gjøres direkte i kode, som vist her. I dette eksempelet henter vi ut alle feltene fra APIet og gjør om typene for best mulig datakvalitet. Merk at dette kan ha påvirkning på hvordan Power BI behandler data fra det nye APIet hvis dette ikke er tatt hensyn til tidligere. 

(Merk at tb-key og Ocp-Apim-Subscription-Key må endres til dine egne) 

let 
    BaseUrl = "https://api.tidsbanken.net/timelinje/TimelinjeBehandlet?$select=*&$top=200&$filter=Dato gt " & Date.ToText(Date.AddDays(DateTime.Date(DateTime.LocalNow()), -30), "yyyy-MM-dd"), 
  
    getJson = (url) as record => 
        let 
            source = Json.Document(Web.Contents(url, [Headers=[#"tb-key"=" xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", #"x-api-version"="3.0", #"Ocp-Apim-Subscription-Key"=" xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"]])), 
            data = source[value], 
            next = Record.FieldOrDefault(source, "@odata.nextLink", null), 
            output = [Data=data, Next=next] 
        in 
            output, 
  
    generateData = (url) => 
        let 
            init = getJson(url), 
            output = List.Generate(()=>init, each _ <> null, each if _[Next] <> null then getJson(_[Next]) else null, each _[Data]) 
        in 
            output, 
  
    data = generateData(BaseUrl), 
    table = Table.FromList(data, Splitter.SplitByNothing(), null, null, ExtraValues.Error), 
    expandedTable = Table.ExpandRecordColumn(Table.ExpandListColumn(table, "Column1"), "Column1",  
        { 
            "Id", "AnsattId", "Dato", "FraKlokken", "TilKlokken", "VirkeligFraKlokken", "VirkeligTilKlokken",  
            "Pause1FraKlokken", "Pause1TilKlokken", "Pause2FraKlokken", "Pause2TilKlokken", "ArtId", "AntallTimer",  
            "Kostpris", "Salgspris", "AvdelingId", "ProsjektId", "ProsjektLinjeId", "AktivitetId", "Notat", "KontoId",  
            "ArbeidsTypeId", "SumPauser", "Faktureres", "FakturaTimeAntall", "FakturaNotat", "Element1Id", "Element2Id",  
            "ProduktId", "AnleggId", "Godkjent", "PlanFraKlokken", "PlanTilKlokken", "ForventetArbeidstidForDag",  
            "Produktiv", "OpprettetDato", "OpprettetAvId", "EndretDato", "EndretAvId" 
        },  
        { 
            "Id", "AnsattId", "Dato", "FraKlokken", "TilKlokken", "VirkeligFraKlokken", "VirkeligTilKlokken",  
            "Pause1FraKlokken", "Pause1TilKlokken", "Pause2FraKlokken", "Pause2TilKlokken", "ArtId", "AntallTimer",  
            "Kostpris", "Salgspris", "AvdelingId", "ProsjektId", "ProsjektLinjeId", "AktivitetId", "Notat", "KontoId",  
            "ArbeidsTypeId", "SumPauser", "Faktureres", "FakturaTimeAntall", "FakturaNotat", "Element1Id", "Element2Id",  
            "ProduktId", "AnleggId", "Godkjent", "PlanFraKlokken", "PlanTilKlokken", "ForventetArbeidstidForDag",  
            "Produktiv", "OpprettetDato", "OpprettetAvId", "EndretDato", "EndretAvId" 
        } 
    ), 
    transformedTable = Table.TransformColumnTypes(expandedTable,  
        { 
            {"Dato", type datetimezone}, {"FraKlokken", type datetimezone}, {"TilKlokken", type datetimezone},  
            {"VirkeligFraKlokken", type datetimezone}, {"VirkeligTilKlokken", type datetimezone},  
            {"Pause1FraKlokken", type datetimezone}, {"Pause1TilKlokken", type datetimezone},  
            {"Pause2FraKlokken", type datetimezone}, {"Pause2TilKlokken", type datetimezone},  
            {"PlanFraKlokken", type datetimezone}, {"PlanTilKlokken", type datetimezone},  
            {"OpprettetDato", type datetimezone}, {"EndretDato", type datetimezone},  
            {"AntallTimer", type number}, {"Kostpris", type number}, {"Salgspris", type number},  
            {"SumPauser", type number}, {"FakturaTimeAntall", type number}, {"ForventetArbeidstidForDag", type number},  
            {"Faktureres", type logical}, {"Godkjent", type logical}, {"Produktiv", type logical},  
            {"Id", Int64.Type}, {"AnsattId", Int64.Type}, {"OpprettetAvId", Int64.Type}, {"EndretAvId", Int64.Type} 
        } 
    ) 
in 
    transformedTable 


 Klikk Fullfør. 


 

Nå vil alle feltene settes til «riktige» typer for håndtering videre i Power BI. Dette ser vi på ikonet til venstre for kolonnetittelen. 


7. Antall i $top 

Når vi skal hente ut mye data er det viktig å legge inn en $top i baseURL som er tilstrekkelig slik at vi får med alle dataene vi trenger. Grunnen til at vi har påtvunget $top er for å redusere risiko for å hente ut ekstreme mengder data ved et uhell. 


Eksempel: 

En bedrift med 50 ansatte som bytter jobber 3 ganger om dagen vil ha 4.500 timelinjer på 30 dager.  50 ansatte x 3 timelinjer x 30 dager = 4.500. 

Men vi må huske på å ta høyde for tillegg som genereres, overtid og kanskje andre faktorer som påvirker antall timelinjer. I dette tilfellet kunne eksempelvis en $top=10000 blitt lagt inn. 

Det er også andre faktorer som påvirker antall timelinjer negativt også. Dette kan være eksempelvis timetype som forteller om det er normaltid, overtid eller konvertert overtid (tidskonto). Hvis du bare skal hente ut timelinjer med overtid legger korrekt timetype inn i $filter og $top kan kanskje settes lavere. 


Det viktigste er at det legges inn en $top, men om antallet er 1000 eller 50000 er ikke så viktig. 




Er du nysgjerrig på hvordan Tidsbanken kan hjelpe din bedrift?

Ta kontakt med oss i dag for å høre hvordan vi kan hjelpe deg

Hva er Tidsbanken?

Manuelt administrativt arbeid er tidkrevende og unødvendig. Vi hjelper kunder i alle bransjer med å unngå dette. Våre kunder er alt fra de største butikkjedene i landet med flere tusen ansatte, til små entreprenører med kun noen få. Felles for alle er at de ønsker å fange tiden! Vi gir kundene våre oversikt og kontroll på arbeidstiden til deres ansatte gjennom dynamiske vaktlister og automatisk genererte timelister, alt skreddersydd til hver enkelt kunde. Tidsbanken regner ut alt av overtid og tillegg automatisk, slik at du alltid kan være trygg på at du betaler riktig lønn, hver gang. Sammen skaper vi vinnere!

Les mer om Tidsbanken her

Kurs

Vi anbefaler ofte å heller få et grunnleggende eller viderkommende kurs enn hjelp til ett og ett spørsmål. Våre kunderådgivere vil da kunne hjelpe deg og se helheten i programmet på en ny måte og vi kan lære deg tips og triks som gjør din hverdag mer effektiv.

Se vår kurs katalog her

Send en e-post

Til tider kan det være stor pågang på telefonsupport, vi oppfordrer dere derfor til å sende deres supporthenvendelser pr e-post. Dette gjør at vår supportavdeling kan løses flere saker raskere. Kontakt oss på

support@tidsbanken.no

Ta en telefon

Når dere ringer til oss vil den som sitter på supportvakt forsøke å hjelpe dere umiddelbart. Hvis spørsmålet er komplisert eller krever endringer av beregningsregler i Tidsbanken, vil konsulenten opprette en sak som løses av en av våre kunderådgivere. Telefonnummer vårt er

55 27 37 00