Uncategorized – Visionmate – En fullservice digital partner

Amazon kommer till Sverige

Att Amazon kommer till Sverige har nog ingen missat, en jätte som slukad nästan hälften av all e-handel i USA och över en tredjedel i Tyskland. En del skeptiker säger att Amazon tagit andelar till stor del för att dom var tidiga utan riktiga utmanare. Andra skeptiker säger att svenska handlare skyddar sina varumärken och inte kommer vilja ingå i vad dom kallar en ”julgran” av produkter. Kanske blir Amazon inte så framgångsrika i Sverige direkt men på lång sikt blir Amazon definitivt en viktig marknadsplats vare sig bolagen vill eller inte. Men vad behövs då för att lyckas på Amazon? Det kommer behövas kunskap om hur Amazon fungerar och alla rätta tekniska lösningar. På Visionmate har vi noggrant studerat deras API:er och vi kommer börja leverera skarpa tekniska lösningar redan under 2020.

Vi alltid strävar alltid efter ett fullservice erbjudande, antingen i huset eller via partners vi litar på. För att ge våra kunder allra bästa möjligheten att lyckas på Amazon Sverige så har vi nu startat ett samarbete med AMZ. Medan vi står för tekniken kan AMZ ge experthjälp på säljbitarna så som Account Management, Product Ranking, Product Listing, Content Writing, PPC, SEO och Online Sales Analysis.

Wayne Smith från AMZ och vår VD Andreas berättar mer i klippet nedan.

 

 

Vi ses på Amazon! /

Daniel Karlsson, COO

Alla artiklar

Visma.net med Grails

E-handeln växer snabbt och många företag letar efter lösningar som kan automatisera alla processer så som komplexa finansiella transaktioner och avstå från repetitiv redovisning. Det finns en hel del tillgängliga verktyg på marknaden men bland de mer populära hittar vi i Visma.net Financials. Det är en ekonomilösning som har en flexibel konfiguration och fungerar väl för globala organisationer med krav på multinationell rapportering. I det här inlägget tänkte jag beröra dataflödet i det systemet.

Som backend använder jag den senaste versionen av Grails-framework (3.3.5) och för att göra HTTP-requests kommer jag att arbeta med modern Groovy DSL “HttpBuilder-NG”. Du kan hitta mer information om Grails-framework på https://grails.org/.

Först behöver vi inkludera rätt library i vårt projekt för att skapa HTTP- requests. Grails använder Gradle som default builder, så lägg bara till en rad för dependencies i filen build.gradle.

compile "io.github.http-builder-ng:http-builder-ng-core:1.0.3"

Jag vill också nämna credentials. I det här fallet krävs företags-ID och access token.

Sedan behöver vi en tjänst som samlar alla actions för att ta emot och skicka data. Denna tjänst bör vara multifunktionell eftersom vi vill återanvända det med olika API-förfrågningar. För att kontrollera alla tillgängliga API:er, besök https://integration.visma.net/API-index/#.

Det finns en del att välja på men idag kommer vi att arbeta med dessa två: inventering (inventory) och bilaga (attachment). I denna artikel strävar vi bara efter att få data, låt oss som exempel kolla hur man hämtar listan över produkter som ditt företag säljer.

def getFromVisma(String target, def params = [:]) {

   String requestUri = "https://integration.visma.net"

   String requestPath= "/API/controller/api/v1"

   def vismaHeaders = [

           'Accept': "application/json",

           'ipp-company-id': "your company id"

           'ipp-application-type': "Visma.net Financials",

           'Authorization': "your own token"

   ]

   def http = configure {

       request.uri = requestUri

       request.uri.path = requestPath + target

       request.uri.query = params

       request.headers = vismaHeaders

   }

   return http.get {

       response.exception { ex ->

           println ex

       }

   }

}

Controller action för anrop kan se ut såhär:

class AnyController {

    VismaConnectService vismaConnectService

    def getFromVisma() {

        String target = '/inventory'

        def vismaData = vismaConnectService.getFromVisma(target)

        vismaData.each { data ->

            if (data.status == "Active") {
                
                // do something //

            }

        }

    }

}

Vår service method innehåller två parametrar: den första är “target”. När du exempelvis requestar lagerobjekt, ska target vara “inventering”. Den andra är “map” som default är tom. Vanligtvis kan du i parametrarna ange alla de servicefält som Visma erbjuder som exempelvis “lastModifiedDateTime”. Eftersom vi i header angav “application/json” så är resultatet en lista med JSON-objekt och det ser ut så här:

{

        "inventoryId": 00001,

        "inventoryNumber": "00001",

        "status": "Active/Inactive",

        "type": "",

        "description": "",

        "defaultPrice": 0,

        "pendingCost": 0,

        "currentCost": 0,

        "lastCost": 0,

        "lastModifiedDateTime": "2018-01-01T00:00:00.00",

        "attachments": [

            {

                "name": "attachmentName",

                "id": "attachmentId",

                "revision": 1

            }

        ],

        "attributes": [ ],

}

Ovanstående är ett exempel och du kan ha fler eller färre fält. I det här exemplet markerade jag attachmentName och attachmentId. Vi behöver dessa parametrar för att få bifogade filer. Nu tittar vi på koden nedan:

def getAttachment(String attachmentName, String attachmentId) {

   String requestUri = "https://integration.visma.net"

   String requestPath= "/API/controller/api/v1/attachment"

   def vismaHeaders = [

           'ipp-company-id': "your company id",

           'ipp-application-type': "Visma.net Financials",

           'Authorization': "Bearer your own token"

   ]

   def http = configure {

       request.uri = requestUri

       request.uri.path = requestPath + attachmentId

       request.headers = vismaHeaders

   }

   return http.get {

       Download.toFile(delegate, new File("./attachments/${attachmentName}"))

       response.exception { ex ->

           println ex

       }

   }

}

Den sista koden är väldigt lik den första. Största skillnaden är dock att vi kommer att få en File och inte JSON. Men också istället för en File så kan vi få en TempFile eller en Stream. Det beror lite på ditt egna case och requirements.

Slutsats

Eftersom Visma erbjuder mycket större funktionalitet så är exemplen ovan bara toppen på ett isberget. Men du kan absolut använda ovanstående skeleton för att skapa komplexa lösningar för dina olika affärsuppgifter. Likväl, som visat ovan, kan Groovys kraft kombinerat med Grails flexibilitet med sina många plugins hjälpa dig att arbeta med data på ett mycket snabbt och effektivt sätt.

/ Olek, Javautvecklare på Visionmate

Alla artiklar

Funderat på att starta en webbshop? Här kommer 10 saker du bör tänka på

1. Försök att sticka ut. Det finns mycket webbshoppar där ute, så försök att hitta er unika nisch och målgrupp. Genom att fokusera på nischartiklar kan ni hamna högt upp i sökresultat på Google, få omnämningar i sociala medier och på så sätt få upp trafik till sidan. Det är också väldigt viktigt att shoppen har bra SEO-stöd. Att sätta upp en butik utan koppling till omvärlden är lite som att sätta upp en fysisk butik ute i vildmarken och hoppas på bra försäljning…

2. En bild säger mer än tusen ord. Välj ett tydligt bildspråk som ger rätt intryck av artiklarna. För att ge ett bra helhetsintryck är det också viktigt att bilderna har samma ljussättning och bildspråk. Är det kläder som ni ska sälja, då är det även en fördel att inkludera en film på en modell som bär upp plagget. Slutkunden kan enkelt se hur materialet rör sig, hur plagget sitter och du får ett snabbare beslut.

3. Lägg tid på kategorisering. Det är värt att lägga ner initial tankemöda på att hitta en bra kategorisering av dina artiklar och har du mycket artiklar kan du även koppla ihop databasen med en snabb sökmotor. På så sätt hittar din kunder snabbt de artiklar som de gillar. Ingen gillar att leta länge efter vad de söker.

4. Undvik standardmallar. Det finns gott om färdiga e-handelsmallar som ofta inte går att anpassa eller koppla ihop med lager, affär-och leveranssystem. När din webbshop tar fart, då kommer du inse att du saknar funktioner och måste börja om. Det är ofta 80/20 regeln som gäller. De viktigaste 20 % av funktionerna som du vill ha, kommer ta 80 % av tiden att sätta upp. Men för att kunna hantera stora volymer i din försäljning så är det viktigt att varje del i order- och leveranskedjan är automatiserad.

5. När du ska utanför Sverige. Se då till att webbshoppen har stöd för flera språk, valutor och fraktalternativ. Sverige är en förhållandevis liten marknad och säljer dina artiklar bra här, då finns det stora möjligheter att de även säljer bra i andra länder. Det finns också många tredjepartslager att anlita i de länder som du vill leverera till.

6. Undvik tung administration. Koppla ihop din webbshop med ett affärssystem. På så sätt behöver du inte göra dubbeljobb. Webbshoppens försäljning går direkt in i affärssystemet. Plocklistor, orderhantering och fakturering gör du i ditt affärssystem. Med system som exempelvis Visma.net kan du också synkronisera artiklar, priser och kunder från affärssystemet till din webbshop.

7. Även kunderna vill ha koll. Så se till att dina kunder kan se vad som händer med paketet från order till leverans. Med ett välfungerande TA-system integrerat mot webbshoppen kan du och kunden få en bra överblick av leveranserna. Det finns flera olika alternativ att välja på. Ett bra exempel är Unifaun som är väletablerade och täcker de flesta fraktbolagen.

8. Ingen vill vara först. Vi letar alltid efter bekräftelse på att våra val är rätt. Försök att så tidigt som möjligt få in rekommendationer från nöjda kunder. Har du kvalitativa och prisvärda artiklar, då kommer du att tjäna på det. Gör kampanjer och uppmuntra kunderna att skriva rekommendationer. Behandla alltid dina kunder väl. En dålig recension är inte bra så ge alltid kunderna det lilla extra. Du kan även välja att prissätta några utvalda artiklar för att hamna högt på jämförelsesidor. Alla kunder du fått in är din framtida kundgrupp som du sedan kan skicka erbjudanden till i framtiden.

9. Tänk på mobilen. Mer än 50 % av all försäljning sker från mobiler. Se till att din webbshop är optimerad för alla typer webbläsare, läsplattor och mobiler. Samt är certifierad för alla webbläsare.

10. Krångelfri betalning. Gör det enkelt att slutföra ett köp med en lättanvänd check out. Erbjud så många betallösningar som möjligt. Kunder tenderar att handla utifrån deras betalvanor. Till exempel Visa, Mastercard, American Express, PayPal, Klarna, Swish etc. Tänk på att betalningsvanor också skiljer sig mellan olika länder.

Andreas Bagge, VD
Visionmate AB

Alla artiklar

Push-notiser i Grails & React.js baserade webbprojekt

Det här inlägget riktar sig kanske mer mot dig som utvecklare än dig som beställare. Som oftast med system så krävs det en kommunikation mellan servern och klienten. Det är viktigt eftersom olika händelser kan uppstå på servern och en anmälan ska då skickas till klienten. Ett enkelt user cace är en chatt, där användarna kan utbyta meddelanden mellan varandra. Vi kan också använda annan teknik, till exempel Message Queue system. Beroende på behoven så kan man välja den man har mest användning för.

Beskrivning

I det här inlägget kommer vi att diskutera push-meddelanden med hjälp av WebSocket. Jag vill beskriva pedagogiken bakom arbetet, när det kommer till leverans och produktion så har vi självklart enklare rutiner på företaget för att bemöta varje beställning.

Mål

Att kunna genomföra push-notiser i webbapplikationer

Krav

Grails 3.* (Dependencies: Grails Spring WebSocket, Grails Spring Security Rest)

React.js (Dependencies: Sock.js client, Stomp.js)

 

1 – Server konfiguration

Vi måste konfigurera WebSocket message agent för att låta vårt system skicka och ta emot meddelanden via säkra kanaler.

Följande kod (configureClientInboundChannel metod) visar hur man autentiserar via WebSocket. Vi måste vara säkra på att endast en auktoriserad användare kan ansluta, prenumerera och ta emot meddelanden från vår server. Vårt system använder Spring Security Rest och följaktligen är användarna autentiserade via access token i varje förfrågan.

Vi konfigurerar också den endpoint där vår message agent lyssnar på subscription requests och meddelanden (registerStompEndpoints method)

import grails.plugin.springsecurity.rest.token.storage.TokenStorageService
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.messaging.simp.SimpMessageType
import org.springframework.messaging.simp.config.ChannelRegistration
import org.springframework.messaging.simp.stomp.StompHeaderAccessor
import org.springframework.messaging.support.ChannelInterceptorAdapter
import org.springframework.messaging.support.MessageBuilder
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker
import org.springframework.web.socket.config.annotation.StompEndpointRegistry
import org.springframework.messaging.Message
import org.springframework.messaging.MessageChannel
 

@Configuration
@EnableWebSocketMessageBroker
class DefaultWebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {

   public static final String AUTHORIZATION = 'Authorization'
   public static final String BEARER = 'Bearer'

   @Autowired
   TokenStorageService tokenStorageService
 

@Override
void registerStompEndpoints(StompEndpointRegistry stompEndpointRegistry) {
   stompEndpointRegistry.addEndpoint("/ws")
         .setAllowedOrigins("*")
         .withSockJS()
         .setSessionCookieNeeded(false)
}
 

void configureClientInboundChannel(ChannelRegistration registration) {
   registration.setInterceptors(new ChannelInterceptorAdapter() {
      Message<Object> preSend(Message<Object> message,  MessageChannel channel) {
         String token = null
         Boolean access = false
         StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message)
         List tokens = accessor.getNativeHeader(AUTHORIZATION)
         accessor.removeNativeHeader(AUTHORIZATION)

         if(tokens?.size > 0) {
            token = tokens[0]
         }

         if (token?.startsWith(BEARER)) {
            token = token.substring(BEARER.length()).trim()
         }

         switch (accessor.messageType){
            case SimpMessageType.CONNECT: access = (tokenStorageService.loadUserByToken(token))?true:false
               break
            case SimpMessageType.SUBSCRIBE: access = (tokenStorageService.loadUserByToken(token))?true:false
               break
         }
         accessor.setLeaveMutable(true)
         return (access)?MessageBuilder.createMessage(message.payload, accessor.messageHeaders):null
      }
   })
}

2 – Skicka meddelanden

Vi använder Grails Spring WebSocket vilket ger oss den funktionaliteten som behövs för att kunna använda konfigurationen.

I vårt exempel kommer meddelandena att ha en relaterad åtgärd, för att radera, uppdatera eller skapa meddelanden i klienten. Detta är också möjligt om vi skapar olika ämnen för det ändamålet, men i det här fallet är det inte relevant. BrokerService ansvarar för att skicka meddelanden till en destination med hjälp av brokerMesssagingService.

class BrokerService {

    def brokerMessagingTemplate
    /**
     * Send message to destination
     * @param destination
     * @param message
     */
    void send(String destination, Map message) {
        brokerMessagingTemplate.convertAndSend destination, (message as JSON) as String
    }
}

3 – Web client configuration

I webbklienten använder vi Sock.js och Stomp.js för att prenumerera på vår meddelandekanal. För att prenumerera på en kanal i connection request så skickar vi header configuration med användarens token som behörighet.

Vi ställer som en parameter en callback-funktion som kommer att utföras efter att ha mottagit ett meddelande från servern. Med unsubscribe method kan du ta bort en prenumeration, användaren kommer då inte längre att få push-meddelanden när det är gjort.

import {SERVER_URL} from "../config";
import headers from "../security/headers";
import SockJS from 'sockjs-client';
import Stomp from 'stompjs';

/**
 * WebSockets support
 */
const WebSocket = (endpoint) => {

    const socket = SockJS(`${SERVER_URL}/${endpoint}`);
    const client = Stomp.over(socket);

    function subscribe(destination, callback) {
        client.connect(headers(), function(frame) {
            client.subscribe(destination, function(message){
              callback(JSON.parse(message.body))
              }, headers());
           });
        return client;
    }

    function unsubscribe(client){
        for (const sub in client.subscriptions) {
            if (client.subscriptions.hasOwnProperty(sub)) {
                client.unsubscribe(sub);
            }
        }
    }

    return {
        subscribe,
        unsubscribe
    }

};

export default WebSocket;

4 – Prenumeration och mottagande av anmälningar

Vi lägger subscription call på componentDidMount med en klass baserad på React Component.

Vi måste också implementera funktionaliteten som ska utföras när vi får notiserna.

import React from 'react';
import WebSocket from "../../api/WebSocket";
import {ACTION_STATUS} from '../../constant';
import MessageList from "../../components/MessageList";
import { notifyAddedMessage, notifyDeletedMessage,  notifyUpdatedMessage} from "../../actions/actions";


/**
 * Real-time messages
 */
class MessagePanel extends React.Component {

    componentDidMount() {
        this.subscribe();

    }

    componentWillUnmount() {
        if(this.state.client !== undefined)
            WebSocket('ws').unsubscribe(this.state.client);
    }

    subscribe() {
        const callback = (message) => {
            switch (message.action){
                case ACTION_STATUS.DELETED:
                    this.props.notifyDeletedMessage(message.id);
                    break;
                case ACTION_STATUS.UPDATED:
                    this.props.notifyUpdatedMessage(message);
                    break;
                default:
                    this.props.notifyAddedMessage(message);
            }
        };
        callback.bind(this);

        let client;
        if (user instanceof Object) {
            client = WebSocket('ws').subscribe(‘/topic/messages’, callback);
            this.setState({client});
        }
    }

    render() {
        return (
            <MessageList/>
        )
    }
}

Slutsats

Push-meddelanden gör systemet attraktivt och mer användbart, det minskar också serverns latency och förbättrar användarupplevelsen. I många fall kan vi, på ett enkelt sätt och med ett bra resultat, lägga till det med hjälp av WebSockets .

Referens

https://docs.spring.io/spring/docs/5.0.0.BUILD-SNAPSHOT/spring-framework-reference/html/websocket.html

 

/Carlos Senior Developer på Visionmate

Alla artiklar

Introduktion till Kanban och hur det gynnar mindre IT-företag

Visionmate grundades för snart 2 år sedan. Vi har under hela tiden jobbat med att implementera nya verktyg och metoder för att leverera överlägsna tjänster men också för att upprätthålla integriteten inom teamet.

Målet är att alltid förbättras – annars försämras kvaliteten på grund av entropi.

Varför Kanban?

När vi började växa ordentligt under 2018 så behövde vi snabbt hitta lösningar för att hantera arbetsbelastningen på våra projekt. Efter en del research så blev de självklara kandidaterna Scrum och Kanban. Efter att ha undersökt båda metoderna så beslutades att Kanban var mer lämpligt för organisationens krav. Det främst beroende på att vi inte har fasta datum för release och att cyklerna då inte skulle vara lika effektiva. Det kontinuerliga Kanban-flödet var definitivt mer tilltalande.

De strategier som beskrivs här är baserade på “Kanban: Successful Evolutionary Change for Your Technology Business” av David James Anderson – med ändringar som passar vår organisation.

Vårt verktyg för att hålla reda på alla issues (ärenden) är JIRA, och vi använder samma system för våra Kanban-boards. JIRA är vidare länkad till GitHub, så vi enkelt kan spåra alla grenar för att se till att arbetsflödet är korrekt.

Det bör noteras att våra Kanban-implementeringar är ett konstant pågående arbete – vi försöker hela tiden identifiera eventuella problem och förbättra arbetsflödet.

Lägg också till att vi är spridda över 2 länder (lyckligtvis i samma tidszon), vilket nu underlättas av ett lättillgängligt molnbaserat system för alla anställda, det sänker kostnaderna snarare än att öka dem.

Fördelar med Kanban för Visionmate 

  • – Dramatiskt minskad e-post, Skype- och slackkommunikation
  • – Medarbetarna vet vad de ska göra och väljer enkelt sina uppgifter i ett visst projekt
  • – Ledningen har en bättre översikt över pågående projekt och vet vad deras team arbetar med
  • – Kanban-boards används för att visa upp aktuell projektstatus för kunderna
  • – Flaskhalsar identifieras snabbt och kan hanteras
  • – Antalet pågående problem har minskat, ledtiderna reduceras följaktligen också
  • – Eftersom alla problem är kopplade till GitHub-grenar är det mycket lättare att spåra (och återställa, om det behövs) införda ändringar

Genomförande

Vi har delat upp Kanban-board i 2 huvudsakliga sektioner – den första är intern (analys, utveckling och testning) och den andra är extern (UAT). Vi använder Kanban-board för att spåra ärenden som godkänts av kunden och releasar bara ut dem, medan resten får gå tillbaka till utveckling.

På skärmdumpen nedan kan du se kolumnerna som hör till vår Kanban-board.

Backlog

Det första steget är alltid att skapa ett nytt ärende i backlog. Detta kan göras av en utvecklare, projektledare eller en testare. Det är dock bara projektledaren som ska kunna flytta ärenden till input queue, där de plockas upp av en utvecklare för vidare arbete.

Input queue

Utvecklare väljer i allmänhet ärenden utifrån tidigare erfarenheter av det som behöver lösas (om de har arbetat med liknande frågor) eller vad de tycker kommer att gynna kunden mest. Andra kriterier är självklart hur intressant ärendena ser ut att vara :).

Under utveckling

När ett ärende har plockats flyttas det till kolumnen ‘In development’ där kommer det att stanna tills utvecklingen anses vara färdig. Sedan flyttas den till testkolumnen. Vi strävar efter att ha högst cirka 1,5 ärenden per utvecklare i den här kolumnen.

Analys

Om ett problem kräver extern information från projektledare/kund eller kräver ytterligare forskning eller till exempel skjutas på så flyttas den till “Analys” kolumnen. Där kommer den att stanna tills den är klar för utveckling igen. Att ha för många ärenden i kolumnen Analys är en signal till projektledaren att de borde ge mer detaljer till utvecklarna för att driva projektet framåt.

Test-kö

Ärenden från test-kön plockas upp av testteamet. Om utvecklarna är skyldiga att utföra testning är det viktigt att de inte väljer sina egna ärenden och testar – det är alltid bra att få en second opinion på arbetet.

Testning

Vissa ärenden kan kräva mer omfattande testning och kommer därför att ligga kvar i kolumnen ”Testing” tills det är klart.

Testad

Objekt i den testade kolumnen har genomfört intern utveckling och är redo att releasas för ett slutligt godkännande av användaren (UAT).

UAT

Står för User Acceptance Tests. Här mixar vi Kanban-board för utveckling och affärsändamål. Denna kolumn kan visas för våra kunder under projektmöten där vi diskuterar enskilda frågor och får feedback. Godkända ärenden flyttas till färdiga medan avvisade går tillbaka till backlog eller input queue.

Klart

Ärenden i denna kolumn är accepterade av kunden och redo att releasas. Förflyttningen mellan kolumner är begränsad – det är generellt sätt endast tillåtet att flytta ärenden framåt. Ärenden kan komma tillbaka till backlog men endast vid specifika lägen. Det är inte meningen att ärenden ska kunna flyttas fritt på boarden då det skulle skapa en del oreda i flödet.

Slutsats

Introduktionen av Kanban-boards i vårt arbete hade en dramatisk (utan att överdriva) effekt på projektstyrning och effektivitet. Även de mest grundläggande funktionerna hade positiva effekter på arbetsmoralen och bidrog till att förekomma ett kaos.

Om du väljer att implementera Kanban-metoden så är det viktigt att försöka anpassa det så långt det går till era organisatoriska krav.

Dessutom är benchmarking viktigt (särskilt i början). Granska genomförandet i några veckor för att säkerställa att det verkligen fungerar som ni tänkt.

Framtida arbete

Visst finns det några saker som vi planerar att introducera i vårt arbetsflöde inom en snar framtid för att ytterligare förbättra effektiviteten:

  • – expedite items (vi använder dem sällan)
  • – swim lanes
  • – WIP limits (nu har vi bara mjuka limits)

 

/ Radek, Technical Director på Visionmate

Alla artiklar

Tester i React med Jest och Enzyme

Programvarutestning är en väsentlig del av mjukvaruutveckling och säkerställer att koden är hållbar. Automatiska tester hjälper till att hitta nya buggar, förbättra kvaliteten och kompletterar också den manuella testningen.

I den här inlägget kommer vi att gå igenom några av de vanligaste sätten för att testa dina React-applikationer (med CRA ver, 2.1.3).

Då sätter vi igång så låt mig introducera dagens hjältar:

  1. Jest – ett JavaScript-testramverk framtaget av Facebook. Vi använder CRA där Jest installeras automatiskt. Jest’s jobb är att ta alla testfiler i vårt projekt, köra testerna och visa resultatet i terminalen. Jest kan användas med non-React applikationer.
  2. Enzyme – är ett testverktyg utvecklat av Airbnb. Enzyme använder många av de verktyg som tillhandahålls av React för att bygga sitt API. Enzyme fungerar också bara med React.

SET UP

  • React application with CRA
create-react-app testing
cd testing
npm start
  • Enzyme
npm install --save enzyme enzyme-adapter-react-16

Var uppmärksam på vilken versionen av React som du använder. Här använder vi 16, men Enzyme har adapters för alla versioner. Här hittar du fler detaljer: Enzyme installation guide.

För att slutföra konfigurationen måste vi skapa en ny fil som heter setupTests.js i src directory, på så sätt kan vi använda Enzyme i testfilerna. Observera att namnet är viktigt här.

setupTests.js

import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';

Enzyme.configure({adapter: new Adapter() });

Sådär, installationen är klar!

 

FUNKTIONER I ENZYME

Vi använder funktionerna nedan för att skapa instances av våra komponenter och returnera objekt som vi kan använda för att skriva tester.

  • Static – renders given component och skickar tillbaka ren HTML
  • Shallow – mest grundläggande versionen, renders only given component utan children. Perfekt för unit tests
  • Full DOM – renders the components with all children, här kan vi interagera med komponenterna

TEST STRUCTURE

Testerna kommer att placeras i en ny katalog __tests__

Ha för vana att organisera testerna på det här sättet. Jest kör alla filer som finns inuti.

För att göra det enkelt att identifiera vilket test som tillhör till vilken fil så beskriver vi dem på samma sätt som ”testad fil”: .test.js i slutet.

 

Våra test kommer att vara schemalagda:
it ('test description', function which contain logic of our test);

Funktionen i vårt test ska innehålla få förväntningar, vi kan beskriva det värde som vi förväntar oss att se.

Äntligen TEST!

Nedan är innehållet av två enkla components:

List.js

import React, { Component } from 'react';


class List extends Component {
    render() {
        return (
            <form>
              <h3>Add new element</h3>
              <textarea />
              <div>
                <button>Save</button>
              </div>
            </form>
        );
    }
}

export default List;

App.js

import React from 'react';
import List from './List'

export default()=>{
    return (
        <div><List /></div>
    );
};

Låt oss helt enkelt testa rendering List.js-komponenten. Vi kommer att använda shallow function eftersom vi inte behöver render children. Om komponenten fungerar korrekt kommer testet att fungera, annars misslyckas det.

List.test.js

import React from 'react';
import { shallow } from 'enzyme'; 
import List from '../List';

it('renders without crashing', () => {
  shallow(<List />);
});

Nu kollar vi om testet fungerade genom typing in terminal

npm test

Härligt! Vårt första test lyckades! För att avsluta tryck ctrl+c.

Nu ska vi kontrollera components instance. Vi vill kontrollera om List.js-komponenten finns i App.js.

App.test.js

import React from 'react';
import { shallow } from 'enzyme'; 
import App from '../App';
import List from '../List';

it('shows the list', () => {
  const wrapped = shallow(<App />);
  expect(wrapped.find(List).length).toEqual(1);
});

const wrapped betyder att objektet som vi kommer att ha från shallow (<App />) är en inbäddad version av App-komponenten.

Och båda testerna gick igenom!

På samma sätt som ovan kan vi kontrollera om din komponent innehåller button och input area:

List.test.js

import React from 'react';
import { mount } from 'enzyme'; 
import List from '../List';

it('contains button and text area', () => {
  const wrapped = mount(<List />);
  expect(wrapped.find('textarea').length).toEqual(1);
  expect(wrapped.find('button').length).toEqual(1);
});

Find är ett kraftfull testverktyg. Tack vare det kan vi upptäcka varje klass, HTML-element, attributsyntax, prop eller objektegenskapsväljare och kontrollera om det uppfyller våra förväntningar.

Nu är frågan, hur testar jag ett test?

Vi kan till exempel göra det genom att ändra ett förväntat värde till ett felaktigt. Låt oss låtsas att vi inte har någon button i List.js, vi sätter värde 0 istället för 1 i testfilen. Nu ska testet misslyckas

List.test.js

it('shows button', () => {
    const wrapped = shallow (<List />);
    expect(wrapped.find('button').length).toEqual(0);
});

Japp det misslyckades och Jest visar exakt vad som gick fel.

 

SLUTSATS

Jest och Enzyme är väl integrerade- och flexibla verktyg för att utföra mjukvarutester. Konfigurationen är lätt vilket såklart är en stor fördel. Jag grottade inte ner mig i specifika metoder utan tanken var att ge en god överblick av Jest’s och Enzyms effektivitet.

Origin: www.linkedin.com

Alla artiklar

Internationalisering och lokalisering i WordPress

När du utvecklar ett WordPress-tema från scratch så är det en bra ide att internationalisera temat och göra det tillgängligt översättning. Det kommer att låta människor från hela världen använda det på vilket språk de behöver.

Vad är internationalisering (i18n) och lokalisering (l10n)?

I18n är en process som gör ditt tema översättningsbart och det är utvecklarens jobb att få det klart. Den förkortas ofta som i18n eftersom det helt enkelt finns 18 bokstäver mellan i och n.

L10n hänvisar till processen som faktiskt översätter ditt tema till ett annat språk. Det här kan göras av temats användare. För att lokalisera temat måste det först internationaliseras. Lokalisering förkortas som l10n eftersom det finns 10 bokstäver mellan l och n.

Gettext libraries

WordPress använder Gettext libraries för att loada översättningarna. Den har tre typer av filer:

POT (Portable Object Template): Det första steget i översättningsprocessen. Den här filen är ansvarig för att generera strings som behöver översättas. Den innehåller dock inte några översättningar i sig självt.

PO (Portable Object): Dessa filer innehåller däremot översättningarna och ursprungliga strings.

MO (Machine Object): Dessa filer används av funktioner för att loada (ladda) översättningarna. De är viktigast men inte human-readable.

Förbereda temat

Att förbereda ditt tema för internationalisering är inte så komplicerat som det kan tyckas. WordPress tillhandahåller redan en hel uppsättning praktiska funktioner för det. Först och främst måste vi inkludera en funktion i functions.php som kommer att ladda översättningarna:

function my_theme_load_theme_textdomain() {
    load_theme_textdomain( 'my-theme', get_template_directory() . '/languages' );
}
add_action( ‘after_setup_theme','my_theme_load_theme_textdomain' );

Nästa steg är att redigera text strings. För att göra strings översättningsbara bör de inte vara hårdkodade. Wrappa de ursprungliga strängarna i ett av WordPress localization functions. De vanligaste funktionerna:

__()          /* returns the translation of its argument */

_e()          /* echos the string to the browser */

esc_html__()  /* escapes and returns the translation of its argument */

esc_html_e()  /* escapes and echos the translation of its argument */

Exempel:

<h1><?php __( 'Translatable text goes here', 'theme-domain' ); ?></h1>
<h3><?php esc_html__( 'Translatable text goes here', 'theme-domain' ); ?></h3>

Ibland behöver du skjuta ut variabler, datum eller plurals i dina strings. WordPress tillhandahåller lokaliseringsfunktioner för alla dessa fall. Kolla hela listan här.

När dina temafiler är klara så är nästa steg att generera POT-filen. Det finns ett par sätt att generera en POT-fil som beskrivs här. Jag brukar använda Gulp task runner när jag utvecklar WordPress-teman, så jag skapar även POT-fil med hjälp av Gulp (se gulp-wp-pot-paketet).

npm install --save-dev gulp-wp-pot

var gulp = require('gulp');
var wpPot = require('gulp-wp-pot');
 
gulp.task('default', function () {
    return gulp.src('src/*.php')
        .pipe(wpPot( {
            domain: 'domain',
            package: 'Example project'
        } ))
        .pipe(gulp.dest('file.pot'));
});

Pluginen letar efter strings som är insläppta i lokaliseringsfunktionerna och skapar POT-filer med det. POT-filen ska levereras tillsammans med temat så att användarna kan översätta filer med Poedit.

Poedit har öppen källkod och är det mest populära verktyget för att översätta WordPress-teman. Den tar en POT-fil och genererar PO och MO-filer på lämpligt språk.

Nu är ditt tema redo för översättning!

Internationalisering är något du som utvecklare alltid bör göra, det är idag snarare ett måste. Om du planerar att skicka in ditt tema till WordPress.org är det också ett krav.

/ Anastasia

Alla artiklar

Aspektorienterad programmering med Spring AOP över Grails framework

Förord

Aspektorienterad programmering (AOP) är ett nytt koncept och programmeringsparadigm. Det tillåter i princip möjligheten att lägga till funktionalitet till moduler och klasser utan att ändra i koden. Det möjliggörs genom att konfigurera Pointcut, som anger vilka bitar av koden som ska bli executed efter att ha skickat en viss händelse över en metod eller klass.

Mål

Vi strävar efter och antar att utvecklaren, med hjälp av denna artikel, kommer att kunna använda aspektorienterad programmering i Grails-projekt.

Requirements

Grails 3. *

Dependencies:

Spring Aspects 5. *

AspectJ Weaver 1. *

1- Aspektorienterad programmering approach

Oftast innebär våra mjukvarulösningar komplex logik och i detta fall är det särskilt viktigt att hålla en ren och förståelig kod. AOP som tillvägagångssätt hjälper till och möjliggör ett enkelt sätt för modularisering. I grund och botten så tillåter AOP att lägga till funktionalitet till moduler och klasser utan att ändra koden.

2- Bean setting med Spring

Det första steget är att konfigurera Spring AOP, i Spring resource file (conf / spring / resources.groovy). Vi behöver också ange vilken bean som ska fungera som aspect, i vårt fall kommer det att vara Trace bean. Den kan konfigureras på flera sätt, till exempel med annotation @Aspect in din Bean declaration.

I det här fallet måste vi injecta log dependencies som redan finns för Grails, så vi behöver inte skapa några instances själva.

// Aspect-Oriented

xmlns aop:"http://www.springframework.org/schema/aop"

trace(Trace){

    log = log

}

aop{

    config("proxy-target-class": true) {

        aspect(id: "traceService", ref: "trace")

    }

}

3- Implementing logging functionality

Några av de viktigaste begreppen vi behöver ha koll på är: Pointcut, Advice och JoinPoint. En Pointcut är huvudsakligen ett sätt att matcha en method call med en funktionalitet som vi lägger till ett nytt beteende, det behöver ange expression och signature. För att utföra det nya beteendet så behöver vi ange några advice och associera den med en Pointcut.

Advice är declared med annotations som exempelvis @Before och @After och anger när koden ska verkställas. Advice kommer att ta som parameter en JoinPoint, som innehåller information om det aktuella anropet, då menar jag info så som parametervärden, called metod osv.

Testprogrammet är relaterat till dokumenthanteringen. De viktigaste funktionerna är uppladdning, konvertering till HTML och att lagra dokument från olika format. Koden som vi visar ger prov på att vi för track actions över dokumenten.

Här hittar du Aspect bean kallad Trace:

@Aspect

class Trace {

    def log


    @Pointcut("execution(* my.company.myApp.document.DocumentService.add(..))")

    void createDocument(){}



    @Pointcut("execution(* my.company.myApp.document.DocumentService.update(..)) && args(document, user,..)")

    void updateDocument(Document document, User user){}



    @Pointcut("execution(* my.company.myApp.repository.RepositoryService.add*(..))")

    void storeDocument(){}



    @Before("createDocument()")

    void create(JoinPoint jp){

        Document document = jp.args[0] as Document

        User user = jp.args[1] as User

        log.info "The document: ${document?.name} has been created by user: ${user?.username}"

    }



    @Before("updateDocument(document, user)")

    void update(Document document, User user){

        log.info "The document: ${document?.name} has been modified by user: ${user?.username}"

    }

    

    @Before("storeDocument()")

    void storeDocument(JoinPoint jp){

        log.info "Called ${jp.signature.name} with ${jp.args} on ${jp.target}: Storing a new document"

    }
}

Jag har tagit upp tre Pointcut sentences, Jag kommer att förklara en del nedan.

Uppdatering av dokument:

@Pointcut("execution(* my.company.myApp.document.DocumentService.update(..)) && args(document, user,..)")
  
  void updateDocument(Document document, User user){}

I det här fallet anger vi vilken metod vi vill spåra. Vi kan ange arguments i Pointcut declaration, det funkar de flesta gånger.

Vår applikation innehåller method med texten:

void update(Document document, User user){

    Version version = new Version(state: State.UPDATED, user: user, date: new Date())

    document.addToVersions(version)

    document.save()   

}

Sedan efter declaring vår Pointcut så måste vi genomföra den metod som ska utföra required behavior. Vi vill lägga till nytt beteende exakt innan metoden exekveras och sedan spåra vilken användare som har uppdaterat dokumentet.

@Before("updateDocument(document, user)")

    void update(Document document, User user){
      
        log.info "The document: ${document?.name} has been modified by user: ${user?.username}"
    
    }

Lagring av dokument:

I det här nya fallet kommer vi att använda samma endpoint för att fånga ett annat method call:

@Pointcut("execution(* my.company.myApp.repository.RepositoryService.add*(..))")
    
    void storeDocument(){}

I vårt system har vi en Repository-klass som ansvarar för att arbeta med vårt files repository, i princip har vi två slags repository, HTML och Source. Vi vill spåra när ett dokument lagras i det.

def addHtmlDocument(String id, Format format, String fromPath){

    String repositoryPath = grailsApplication.config.dataStore.repository.html

    repository = new HtmlRepository(

            path: repositoryPath,

            converter: new DocumentFormat(format: Format.HTML),    

            builder: new AntBuilder())

    repository.addDocument(id, format, fromPath)

}


def addSourceDocument(String id, Format format, String fromPath){

    String repositoryPath = grailsApplication.config.dataStore.repository.source   

    repository = new SourceRepository(

            path: repositoryPath,

            builder: new AntBuilder())

    repository.addDocument(id, format, fromPath)

}

Behavior:

@Before("storeDocument()")

    void storeDocument(JoinPoint jp){

        log.info "Called ${jp.signature.name} with ${jp.args} on ${jp.target}: Storing a new document"

    }

Det nya handlingssättet kommer att skriva ut en logg med namnet på called method, arguments och instance description.

4- Slutsats

Aspektorienterad programmering (AOS) är ett nytt koncept, genom att använda det korrekt så ser vi till att vår kod hålls ren. Med AOS kan vi lägga till funktionalitet enklare utan att ändra befintliga klasser och metoder. Det är nödvändigt vara försiktig och hålla en ordentlig dokumentation för att undvika oönskat beteende i vår i vår applikation.

5- Referencer

https://docs.spring.io

Origin: www.linkedin.com

/ Carlos

Senior Web Application Developer på Visionmate

Alla artiklar

Form validation in VueJS

In this article I would like to focus your attention on a JavaScript framework called VueJS, in particular on form validation. I will not go into the details but overall VueJS promotes the idea of minimalism and therefore no validation tools are incorporated in its core. Accordingly, if validation is needed then we include a validation package, if not then the size of our application will be much smaller. At this point let’s finish the introduction and get down to business.

So let’s create a skeleton of the mini-application we will work with. For validation I will use the Vuelidate library. Detailed information about it can be found at: https://monterail.github.io/vuelidate/. Also, for visual decoration I will use Bootstrap 4 styles. A CDN link to bootstrap styles can be found here: https://www.bootstrapcdn.com/ and should be added to the index.html file.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>vue-project</title>
    <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
  </head>
  <body>
    <div id="app"></div>
    <script src="/dist/build.js"></script>
  </body>
</html>

Next we install the Vuelidate library with the following command:

npm install vuelidate –save

After the above is completed, we will see the following in the package.json file in the dependencies field:

"dependencies": {
    "vue": "^2.5.17",
    "vuelidate": "^0.7.3"
}

Now to complete the first section we only need to create a component and connect the library. Thus, main.js will look like this:

import Vue from 'vue'
import Vuelidate from 'vuelidate'
import App from './App.vue'

Vue.use(Vuelidate)

new Vue({
  el: '#app',
  render: h => h(App)
})

And the component will look like the below:

<template>
  <div class="container">
  </div>
</template>

<script>
export default {
  data () {
    return {
    }
  }
}
</script>

At this moment, we finish preparation and move on to more interesting stuff 🙂

Next stage is the preparation of the form to be validated. I suggest creating a form with 3 fields (Email, Password and Confirm password). Such forms are commonly used for registration. After updating the template will look like this:

<template>
  <div class="container">
    <div class="row justify-content-center">
      <div class="col-6">
        <form class="pt-3" @submit.prevent="onSubmit">
          <div class="form-group">
            <label for="email">Email</label>
            <input
              type="email"
              id="email"
              class="form-control"
              v-model="email"
            >
          </div>
          <div class="form-group">
            <label for="password">Password</label>
            <input
              type="password"
              id="password"
              class="form-control"
              v-model="password"
            >
          </div>
          <div class="form-group">
            <label for="confirm">Confirm password</label>
            <input
              type="password"
              id="confirm"
              class="form-control"
              v-model="confirmPassword"
            >
          </div>
          <button
            class="btn btn-success"
            type="submit"
          >Submit</button>
        </form>
      </div>
    </div>
  </div>
</template>

The new things here are @ submit.prevent = “onSubmit” and v-model = “password”. The first one is the event listener (here it is ‘submit’) and the second one is a so-called two-way binding. Next we need to process all this and right away we can add the necessary validators. I suggest initially using the pre-installed validators that can be found here: https://monterail.github.io/vuelidate/#sub-builtin-validators and later working on the custom ones. But first things first. In order to use the pre-installed validators they should be first imported like this:

import { required, email, minLength, sameAs } from 'vuelidate/lib/validators'

I imported only the validators I need now: the required field validation, validation of an email, validation of the minimum string length and validation of the string compatibility. This is also a kind of application optimization since nothing extra is imported. As we have already connected the Vuelidate library earlier, now we can use the “validations” object and validate all the existing fields.

<script>
import { required, email, minLength, sameAs } from 'vuelidate/lib/validators'

export default {
  data () {
    return {
      email: '',
      password: '',
      confirmPassword: ''
    }
  },
  methods: {
    onSubmit () {
      const data = {
        email: this.email,
        password: this.password
      }
      console.log(data)
    }
  },
  validations: {
    email: {
      required,
      email
    },
    password: {
      minLength: minLength(6)
    },
    confirmPassword: {
      sameAs: sameAs('password')
    }
  }
}
</script>

I will not focus now on what is happening in the “validations” object but rather explain how to process all this in the template and show it visually. I will take the email field as an example, still for the other fields the process will be the same. Since the bootstrap styles are available, I will use the “is-invalid” class for the form field and the “invalid-feedback” to display the message text. Next, I will add a wiretap for the “blur” event, which will signal that the validation should be done after the focus removal. After all changes are implemented, the email form group will look like this:

<div class="form-group">
  <label for="email">Email</label>
  <input
    type="email"
    id="email"
    class="form-control"
    :class="{'is-invalid': $v.email.$error}"
    @blur="$v.email.$touch()"
    v-model="email"
  >
  <div class="invalid-feedback" v-if="!$v.email.required">Email field is required</div>
  <div class="invalid-feedback" v-if="!$v.email.email">This field should be an email</div>  
</div>

The most interesting points here are the binded “is-invalid” class activated by any error in the e-mail field, the system variable “$v” which holds the instance to Vuelidate and the directive “v-if” which enables notification display only in case of a certain mistake. This part is now described so we can look at the last one.

Finally, I want to show you custom validation and an easy way to implement it. As an example we will validate the e-mail field for uniqueness. Generally, we would need to contact the server and react on its response. If the server returns information that such email is already in use, then this should be processed and the relevant information should be communicated to the user. However, since it is time consuming to raise the registration server, I will emulate the whole process. Below you can see what I did:

email: {
  required,
  email,
  uniqueEmail: function(newEmail) {
    if (newEmail === '') return true
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        const value = newEmail !== 'test@nowhere.com'
        resolve(value)
      }, 1000)
    })
  }
}

I emulated the server behavior with the help of “setTimeout” and if the entered email is the same as the predefined (in my case it is test@nowhere.com), then in 1 second the error will be displayed that this email is already in use. This is of course not the most elegant example, but it is enough to understand how the whole thing works.

In order to summarize everything, let’s first see how the code of the component looks like on the whole.

<template>
  <div class="container">
    <div class="row justify-content-center">
      <div class="col-6">
        <form class="pt-3" @submit.prevent="onSubmit">
          <div class="form-group">
            <label for="email">Email</label>
            <input
              type="email"
              id="email"
              class="form-control"
              :class="{'is-invalid': $v.email.$error}"
              @blur="$v.email.$touch()"
              v-model="email"
            >
            <div class="invalid-feedback" v-if="!$v.email.required">Email field is required</div>
            <div class="invalid-feedback" v-if="!$v.email.email">This field should be an email</div>
            <div class="invalid-feedback" v-if="!$v.email.uniqueEmail">This email is already exists</div>
          </div>
          <div class="form-group">
            <label for="password">Password</label>
            <input
              type="password"
              id="password"
              class="form-control"
              :class="{'is-invalid': $v.password.$error}"
              @blur="$v.password.$touch()"
              v-model="password"
            >
            <div class="invalid-feedback" v-if="!$v.password.minLength">
              Min length of password is {{ $v.password.$params.minLength.min }}. Now it is {{ password.length }}.
            </div>
          </div>
          <div class="form-group">
            <label for="confirm">Confirm password</label>
            <input
              type="password"
              id="confirm"
              class="form-control"
              :class="{'is-invalid': $v.confirmPassword.$error}"
              @blur="$v.confirmPassword.$touch()"
              v-model="confirmPassword"
            >
            <div class="invalid-feedback" v-if="!$v.confirmPassword.sameAs">
              Passwords should match
            </div>
          </div>

          <button
            class="btn btn-success"
            type="submit"
            :disabled="$v.$invalid"
          >Submit</button>
        </form>
      </div>
    </div>
  </div>
</template>

<script>
import { required, email, minLength, sameAs } from 'vuelidate/lib/validators'

export default {
  data () {
    return {
      email: '',
      password: '',
      confirmPassword: ''
    }
  },
  methods: {
    onSubmit () {
      const data = {
        email: this.email,
        password: this.password
      }
      console.log(data)
    }
  },
  validations: {
    email: {
      required,
      email,
      uniqueEmail: function(newEmail) {
        if (newEmail === '') return true
        return new Promise((resolve, reject) => {
          setTimeout(() => {
            const value = newEmail !== 'test@nowhere.com'
            resolve(value)
          }, 1000)
        })
      }
    },
    password: {
      minLength: minLength(6)
    },
    confirmPassword: {
      sameAs: sameAs('password')
    }
  }
}
</script>

And, of course, there is the screenshot below showing how it will be displayed for the user.

As you can see, bootstrap helped a lot in terms of design, and with small amendments this could even be used on a real project. The final conclusion is that forms validation in VueJS is mostly about knowing which library should be used in which case. Alternatively, the universal Vuelidate combine can be used. Vuelidate is for sure not the only library of this kind but it has many advantages: it’s well documented, easy to learn with built-in validators. Additionally it’s very flexible and optimized. Thank you for your attention, please write your thoughts on this subject in the comments and I hope this will be useful for you.

Origin: www.linkedin.com

Alla artiklar

Vi är nu en officiell integrationspartner till Visma

Visionmate är nu en officiell integrationspartner till Visma. Med våra senaste lösningar så kan vi hjälpa er med tillväxt på webben!

Att vara integrationspartner innebär att vi programmerar mot Visma.net på ett säkert och korrekt sätt. Som en del av Vismas certifierade integrationspartner-program erbjuder vi lösningar med färdiga kopplingar till Visma.net och kan integrera säkert och effektivt. För er betyder det att ni kan koppla på nya funktioner till ert Visma-system och på så sätt få en effektivare process.

Kontakta oss gärna för mer information eller se en del av våra lösningar här.

Alla artiklar