Category: TILT

TILT- React Material UI and Navigation–part 29

I wanted some good looking on the site – so I choose https://mui.com/

It does not have ready made navigation like in Angular – so I need to make on myself. Fortunately , it has the all the things that one need:

  1. Media Query: https://mui.com/material-ui/react-use-media-query/

  2. App Bar: https://mui.com/material-ui/react-app-bar/

The final code looks like that

const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down("md"));
const [userWantshowLeftMenu, showLeftMenu] = useState(false);
const showMenu= (event: React.MouseEvent<HTMLElement>) =>{
  showLeftMenu((prevState)=> !prevState )
}  

Created MenuLeft components to show menu left

Created PublicTilts component to show the main page

The GUI in React:

  <AppBar position="static">
          <Toolbar>          
            { isMobile && 
            <IconButton
              size="large"
              edge="start"
              color="inherit"
              aria-label="menu"
              sx={{ mr: 2 }}
              onClick={showMenu}
            >
              <MenuIcon />              
            </IconButton>
            }
            <Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
              TILT
            </Typography>
            <Button color="inherit">Login </Button>
          </Toolbar>
        </AppBar>
      </Box>
      {!isMobile || userWantshowLeftMenu ? (<span style={{float:"left"}}><MenuLeft /></span>):("")}
      
      <span style={{float:"left"}}><PublicTilts /></span>

Lessons Learned:

  1. It is better to write components and again components in React. If not , you will be overflow by the amount of GUI

  2. React does not know how to display a boolean . Use this

{userWantshowLeftMenu?”true”:”false”}

  1. It is somehow more difficult to make the React events in TypeScript rather than in JavaScript.

TILT- Angular2React–part 28

I was thinking to learn React – and transform the Angular site to React.

The steps that I took are the folllowing:

  1. Learn React from https://reactjs.org/tutorial/tutorial.html – and learning that React is a library, not a framework

  2. Learn useEffect, useState with Hooks – very easy

  3. Learn that React can have a TypeScript template – it will make harder to develop, but in the end it is safer.

Lesson Learned:

1.The typescript classes are the same – so can be translated from Angular to React

  1. The Observables are the same, with a difference: HttpClient does not exists. So we pass from HttpClient to Ajax

In Angular:

return ajax.get<string>(this.baseUrl+`PublicTILTs/CountTILTs/${id}/count`,options)

In React

this.http.get(this.baseUrl+`PublicTILTs/CountTILTs/${id}/count`,options)
  1. Environment.ts is modified to .env.local and .env.production

In Angular

export const environment = {
  production: true,
  url: 'https://tiltwebapp.azurewebsites.net/',  
};

As a usage ,

import { environment } from 'src/environments/environment';
//code
this.baseUrl=environment.url+'api/';

In React

PUBLIC_URL=/ReactTilt
REACT_APP_PRODUCTION=true
REACT_APP_URL=https://tiltwebapp.azurewebsites.net/

As a usage

this.baseUrl=process.env.REACT_APP_URL+'api/';
  1. UseEffect can give infinite loops

If you use set from hooks in useEffect, can give infinite loops. See very important below.

useEffect(()=>{
      var publicService= new PublicTiltsService();
      var x= publicService.getUrls().subscribe(data=>{
        data.forEach( it=>{
            publicService.nrTilts(it).subscribe(a=> 
            {
              console.log("obtaining ", a );            
              addTilts((prevState:publicTilt[])=>  [...prevState, a] );
            }
            );
          });
  
      });
      return ()=> x.unsubscribe();
    },[]);//very important!
  1. Do not forget about setting PUBLIC_URL .

  2. In development, if you investigate Developer Tools it is better refresh the page ( in angular it is auto refreshed)

TILT-REACT Router

Now to see how to handle FrontEnd routing ( From Backend it is simple – just transfer to index.html and React will handle it )

The long established in React world seems to be https://github.com/remix-run/react-router . There is a new kid , https://github.com/Paratron/hookrouter – that uses hooks. However, the documentation says:

This project is not and will not be written in typescript.

So back to the old react router.

To register you need:

  1. Some routes
<Routes>
<Route path="/" element={<PublicTilts />} />
<Route path="/public/:id" element={<OnePublicTiltComponent />} />
{/* <Route path="*" element={<NotFound />} /> */}
</Routes>
  1. And intercept
function OnePublicTiltComponent() : JSX.Element{

    let { id } = useParams();
//code
}

However, when I look to Angular and React, the code in React ( with Hooks) seems more clean and usable. Just the render and the functions.

TILT- Telemetry/Observability for FE and BE-part 27

Now it is the case to monitor the calls how they arrive from Web ( frontend ) and continue to the backend.

Fortunately, it is very simple in Application Insights

There are 2 relevant links for Angular

https://devblogs.microsoft.com/premier-developer/angular-how-to-add-application-insights-to-an-angular-spa/

https://learn.microsoft.com/en-us/azure/azure-monitor/app/javascript-angular-plugin

https://learn.microsoft.com/en-us/azure/azure-monitor/app/javascript?tabs=snippet#enable-distributed-tracing

The code is relative well written and appears ok. The fact that the AppInsights SDK is on the environment.ts is somewhat disturbing – but there is a way to circumvent it – see https://learn.microsoft.com/en-us/azure/azure-monitor/app/migrate-from-instrumentation-keys-to-connection-strings .

So now a large view of the calls looks like

And, if you want to see all calls

TILT-Count TILTS for each user-part 27

I just wanted to know for each user how many tilts have each user/ url . As a Business Requirement , it is not a big deal. Let’s see what it means for a programmer .

1. Add functions to the backend to calculate the count

2. Add to the frontend call to the function

3. Figure where in the frontend this information must be shown ( yes, Business Analysis is here )

4. Display and correct eventual errors.

Those are the files modified ( .cs for backend, other for backend)

src/backend/Net6/NetTilt/NetTilt.Logic/PublicTILTS.cs
src/backend/Net6/NetTilt/NetTilt/NetTilt.WebAPI/Controllers/PublicTILTsController.cs
src/frontend/AngTilt14/src/app/app.module.ts
src/frontend/AngTilt14/src/app/one-public-tilt/one-public-tilt.component.html
src/frontend/AngTilt14/src/app/one-public-tilt/one-public-tilt.component.spec.ts
src/frontend/AngTilt14/src/app/one-public-tilt/one-public-tilt.component.ts
src/frontend/AngTilt14/src/app/public-tilts/public-tilts.component.html
src/frontend/AngTilt14/src/app/public-tilts/public-tilts.component.spec.ts
src/frontend/AngTilt14/src/app/public-tilts/public-tilts.component.ts
src/frontend/AngTilt14/src/app/public-tilts/publicTilt.ts
src/frontend/AngTilt14/src/app/services/public-tilts.service.ts

( this list was obtained with git diff  cd75105 8e0ba8c –name-only )

And this is without counting the thinking of when this data should be obtained. You can see the end result at https://tiltwebapp.azurewebsites.net/AngTilt/tilt/public/ignatandrei

TILT-Typed Reactive Forms and arrays–part 26

I want now to pass to typed reactive forms ( with Angular 14 )- what is good is that are typed , i.e. I can see errors if some property is not correct.

The code previously was

profileForm = this.fb.group({
    url: [''],
    publicTILTS: this.fb.array([])
  });

Now the code looks like

profileForm =  new FormGroup({
    url: new FormControl(''),
    publicTILTS: new FormArray([new FormControl(new TILT())])
  });

And you may ask – what is the difference ? The difference is type inferring from generics. For example , the constructor for FormControl ( after several steps, since it is a interface)

new <T = any>(value: FormControlState<T> | T  //more code

So the above definition is , actually, this one:

profileForm =  new FormGroup({
    url: new FormControl<string>(''),
    publicTILTS: new FormArray<FormControl<TILT|null>>([new FormControl<TILT>(new TILT())])
  });

Now the other modifications are

  1. Get rid of formArray

  2. typed data in HTML :
    <div  *ngFor="let item of profileForm.value.publicTILTS; let i = index;let f=first"> 
  3. modify the string url It was hardcoded:
  tap(it => this.profileForm.controls['url'].setValue(it)),

Now it is typed patchValue that has a Partial – typed!

tap(it => this.profileForm.patchValue({url:it}))

Just technical, the relevant definitions are

patchValue(value: ɵFormGroupValue<TControl>, options?: {
        onlySelf?: boolean;
        emitEvent?: boolean;
    }): void;

export declare type ɵFormGroupValue<T extends {
    [K in keyof T]?: AbstractControl<any>;
}> = ɵTypedOrUntyped<T, Partial<{
    [K in keyof T]: ɵValue<T[K]>;
}>, {
    [key: string]: any;
}>;
  1. Modified the array – patchValue does not work with arrays, so setControl to the rescue

Now it is

//this do not work - do not modify the array length, but the contents
// this.profileForm.patchValue({publicTILTS: [...it]});
//setControl<K extends string & keyof TControl>
this.profileForm.setControl("publicTILTS",new FormArray(it.map(a=>new FormControl(a))));        

Again it is typed , even if it seems a string- I have put a comment above with the definition – see keyof ? It will raise an error at compile time!

For the end user, the site is THE SAME. However, the code now is typed – and , yes, this is a great deal for programmer!

TILT-Passing to IAsyncEnumerable instead of array–part 25

When tansmitting an array of data to an application , usually the application transfers all data – or in chunks – if it is a large numbers ( page 1, page 2 and so on). Both are having drawbacks

  • all data from start means that the time to see the data increases with the number of data
  • paging the data means to show the user the number of pages and an action from the user to go further to the next page.

What if I can use IASyncEnumerable to push data one by one to the GUI ?

So – those are the steps for the backend (.NET ) and frontend( Angular ) to pass the transfer from array to one by one ( IASyncEnumerable)

Backend

I have a function that loads the data from database and transforms into an array . Also caches the data

private async Task<TILT_Note_Table[]?> LatestTILTs(string urlPart, int numberTILTS){
    if (cache.TryGetValue<TILT_Note_Table[]>(urlPart, out var result))
    {
        return result;
    }
    //find id from urlPart - if not found , return null
    //caches the data and returns the array
}

Also a controller that sends the data

 public async Task<ActionResult<TILT_Note_Table[]?>> LatestTILTs(string urlPart, int numberTILTS, [FromServices] ISearchDataTILT_Note searchNotes)
{
    var data = await publicTILTS.LatestTILTs(urlPart,numberTILTS);
    if (data== null)
    {
        return new NotFoundObjectResult($"cannot find {urlPart}");
    }
    return data;
}

Also some tests that verifies that when I post a new tilt, the numbers is 1

Step 1: transform from array to IASyncEnumerable

transformation of the main function

 private async Task<IAsyncEnumerable<TILT_Note_Table>?> privateLatestTILTs(string urlPart, int numberTILTS)
        {
            if (cache.TryGetValue<TILT_Note_Table[]>(urlPart, out var result))
            {
                return result.ToAsyncEnumerable();// modification here
            }
    //find id from urlPart - if not found , return null
    //caches the data and returns the array.ToAsyncEnumerable();
        }

transformation of the controller- just add IAsyncEnumerable

 public async Task<ActionResult<IAsyncEnumerable<TILT_Note_Table[]>?>> LatestTILTs(string urlPart, int numberTILTS, [FromServices] ISearchDataTILT_Note searchNotes)
    {
        var data = await publicTILTS.LatestTILTs(urlPart,numberTILTS);
            return new NotFoundObjectResult($"cannot find {urlPart}");
        }
        return Ok(data);
    }

Also for the tests you can add .ToArrayAsync()

Step 2: Get rid of the Task

So now the obtaining of TILTS looks like this

private async IAsyncEnumerable<TILT_Note_Table> privateLatestTILTs(string urlPart, int numberTILTS)
{
    if (cache.TryGetValue<TILT_Note_Table[]>(urlPart, out var result))
    {
        //why I cant return result.ToAsyncEnumerable() ?
        await foreach (var item in result.ToAsyncEnumerable())
        {
            await Task.Delay(1000);
            yield return item;
        }
    }
    //same with retrieving data
    //when sending back data, we have to send one by one , as for the caching

The controller looks pretty much the same

[HttpGet("{urlPart}/{numberTILTS}")]
public ActionResult<IAsyncEnumerable<TILT_Note_Table>> LatestTILTs(string urlPart, int numberTILTS, [FromServices] ISearchDataTILT_Note searchNotes)
{
    var data =  publicTILTS.LatestTILTs(urlPart,numberTILTS);
    
    if (data== null)
    {
        return new NotFoundObjectResult($"cannot find {urlPart}");
    }

    return Ok(data);
}

And this is the modification for the backend . If you want to see in action , open your browser to https://tiltwebapp.azurewebsites.net/api/PublicTILTs/LatestTILTs/ignatandrei/100000 and see TILTS how they come one by one

Frontend

In Angular I have obtained the whole array at once .

 public getTilts(id:string, nr:number): Observable<TILT[]>{
    return this.http.get<TILT[]>(this.baseUrl+'PublicTILTs/LatestTILTs/'+id + '/'+nr)
    .pipe(
      tap(it=>console.log('received',it)),
      map(arr=>arr.map(it=>new TILT(it)))
    )
    ;
 }

Now we are obtaining one by one – how to let the page knows that it has an array instead of modifying also the page ?

( Yes, the display could be modified to accumulate – but I want minimal modifications to the page)

To obtain one by one we display the fetch

 //https://gist.github.com/markotny/d21ef4e1af3d6ea5332b948c9c9987e5
  //https://medium.com/@markotny97/streaming-iasyncenumerable-to-rxjs-front-end-8eb5323ca282
  public fromFetchStream<T>(input: RequestInfo, init?: RequestInit): Observable<T> {
    return new Observable<T>(observer => {
      const controller = new AbortController();
      fetch(input, { ...init, signal: controller.signal })
        .then(async response => {
          const reader = response.body?.getReader();
          if (!reader) {
            throw new Error('Failed to read response');
          }
          const decoder = new JsonStreamDecoder();
          while (true) {
            const { done, value } = await reader.read();
            if (done) break;
            if (!value) continue;
            decoder.decodeChunk<T>(value, item => observer.next(item));
          }
          observer.complete();
          reader.releaseLock();
        })
        .catch(err => observer.error(err));
      return () => controller.abort();
    });
  }

And for modifying the function to have an array, instead of just one, RXJS scan to the rescue

 public getTilts(id:string, nr:number): Observable<TILT[]>{
    return this.fromFetchStream<TILT>(this.baseUrl+'PublicTILTs/LatestTILTs/'+id + '/'+nr)
    .pipe(
      tap(it=>console.log('received',it)),
      map(it=>new TILT(it)),
      scan((acc,value)=>[...acc, value], [] as TILT[])
    );  
}

You can see the final result ( with 1 sec delay between tilts, to be visible ) here http://tiltwebapp.azurewebsites.net/AngTilt/tilt/public/ignatandrei

TILT–Details for programmers- part 24

I have organized the About in order to show more details. See https://tiltwebapp.azurewebsites.net/AngTilt/

Zero and the most important, the date when the CI was done

First , Licences – .NET Core and Angular . Usefull to know.

Second, Info about Versions – Repo and history – UI and JSON – mostly for making managers happy .

Third, Automation – Swagger and Blockly Automation – in order for others to try how to interact.

Fourth , Info about deployment – HealthCheck and info about deployment Environment – user, environment, error – for SRE .

And to have something new, this is the map of the API’s

obtained with the NetCoreUsefullEndpoints ( https://tiltwebapp.azurewebsites.net/api/usefull/graph/text ) – and digraph rendering https://dreampuf.github.io/GraphvizOnline/

Tools Used

Visual Studio

Visual Studio Code

https://github.com/ignatandrei/RSCG_AMS

https://github.com/ignatandrei/NetCoreUsefullEndpoints/

https://github.com/ignatandrei/blocklyAutomation/

https://github.com/domaindrivendev/Swashbuckle.AspNetCore

TILT- Docker with Ductus.FluentDocker–part 23

I have already tests with Sqlite – however, it will be better to try tests with a real SqlServer .

One of the way to have a sql server is to have docker – but how to start docker with sql server any time ?

One of answers is Ductus.FluentDocker – https://www.nuget.org/packages/Ductus.FluentDocker – and this is the code to start SqlServer:

public override void StartDatabase()
{
    //string guid = Guid.NewGuid().ToString("N");
    string uniqueId = Interlocked.Increment(ref uniq).ToString(); //Guid.NewGuid().ToString("N");
    container =
new Builder()
.UseContainer()
.WithName("sql" + uniqueId)
.UseImage("mcr.microsoft.com/mssql/server:2022-latest")
.ExposePort(1433, 1433)
.WithEnvironment("SA_PASSWORD=<YourStrong@Passw0rd>", "ACCEPT_EULA=Y")
.WaitForMessageInLog("Starting up database 'tempdb'.", TimeSpan.FromSeconds(30))
.Build()
.Start();
    ConstructServiceProvider();

}
static int uniq = 0;

I needed also a Base class for consolidating code between sql server and sqlite

  1. generating DI with for both with different context

  2. The Steps are the same = so base class

  3. The tests are the same= so base class

So this is the base class:

public  abstract partial class RealDBTests: FeatureFixture
{
    [SetUp]
    public void Start()
    {
        StartDatabase();
    }
    [TearDown]
    public void Stop()
    {
        StopDatabase();
    }

    public abstract void StartDatabase();

    public abstract void StopDatabase();

    public abstract IServiceCollection AddDB( IServiceCollection sc);


    public void ConstructServiceProvider()
    {
        serviceProvider = AddDB(new ServiceCollection())
//more DI
    }
}

Also , GitHub actions supports docker – so now TILT has a complete testing also in SqlServer.

Tools used

Docker

Visual Studio

Ductus.FluentDocker

TILT–Some improvements-part 23

I wanted to have a share on TILTs, instead of a clipboard copy. Discovered that browser navigator has a native share – https://developer.mozilla.org/en-US/docs/Web/API/Navigator/share .

Implementation very simple ( not so good, )

private share(str:string): boolean{
if ('share' in navigator) {
    navigator
    .share({
    title: 'TILT!',
    text: str,
    url: environment.url + 'AngTilt/tilt/public/'+this.profileForm.controls['url'].value
    })
    .then(() => {
    console.log('Callback after sharing');
    })
    .catch(console.error);
    return true;
} else {
    return false;
}
}
  1. Added .NET Tools to have data about the outdated and more
feat: dot net tools
e0fd82f
{
  "version": 1,
  "isRoot": true,
  "tools": {
    "dotnet-project-licenses": {
      "version": "2.4.0",
      "commands": [
        "dotnet-project-licenses"
      ]
    },
    "dotnetthx": {
      "version": "0.2.0",
      "commands": [
        "dotnet-thx"
      ]
    },
    "powershell": {
      "version": "7.2.6",
      "commands": [
        "pwsh"
      ]
    },
    "dotnet-depends": {
      "version": "0.6.1",
      "commands": [
        "dotnet-depends"
      ]
    },
    "dotnet-outdated-tool": {
      "version": "4.1.0",
      "commands": [
        "dotnet-outdated"
      ]
    },
    "run-script": {
      "version": "0.4.0",
      "commands": [
        "r"
      ]
    }
  }
}

Tools used

Visual Studio

Visual Studio Code

CLI

Andrei Ignat weekly software news(mostly .NET)

* indicates required

Please select all the ways you would like to hear from me:

You can unsubscribe at any time by clicking the link in the footer of our emails. For information about our privacy practices, please visit our website.

We use Mailchimp as our marketing platform. By clicking below to subscribe, you acknowledge that your information will be transferred to Mailchimp for processing. Learn more about Mailchimp's privacy practices here.