Saturday 26 February 2022

Post a Adaptive Card on Team members channel on their Birthday

1. Create a scheduled flow and set the recurrence to daily.

2.  Add 'List Group Members' action from Office 365 Groups connector and select your Team Id.

3. Loop all the Team members using apply to each.

  • To get the birth date information Add 'Get User Profile(v2)' action and select 'User Principal Name' in the User section.
  • Add condition and write expression to format the users birth date to retrieve only month and date.        
        formatDateTime(outputs('Get_user_profile_(V2)')?['body/birthday'],'MM-dd') is equal to                             formatDateTime(utcNow(),'MM-dd')
  • If the above condition is matches, add 'Get User Photo(v2) and select UPN.
  • Add a action ''Post a adaptive card in chat or channel' and select your teams channel details.
  • Paste the JSON into 'Adaptive card' section which was designed/copied from Designer | Adaptive Cards
{
    "type": "AdaptiveCard",
    "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
    "version": "1.3",
    "body": [
        {
            "type": "TextBlock",
            "text": "Happy Birthday !!!",
            "wrap": true,
            "size": "Large",
            "color": "Accent",
            "horizontalAlignment": "Center"
        },
        {
            "type": "ColumnSet",
            "columns": [
                {
                    "type": "Column",
                    "width": "stretch",
                    "items": [
                        {
                            "type": "Image",
                            "url": "data:image/png;base64,@{base64(outputs('Get_user_photo_(V2)')?['body'])}"
                        }
                    ]
                },
                {
                    "type": "Column",
                    "width": "stretch",
                    "items": [
                        {
                            "type": "TextBlock",
                            "text": "@{outputs('Get_user_profile_(V2)')?['body/displayName']}",
                            "wrap": true
                        }
                    ]
                }
            ]
        },
        {
            "type": "TextBlock",
            "text": "Have a nice day !",
            "wrap": true
        },
        {
            "type": "Image",
            "url": "https://media0.giphy.com/media/v1.Y2lkPTc5MGI3NjExcGswdGExaWl3NTYyMzNjZ2VkN2piaHI4emVmZjlzdTV5OXdoNDd2eCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/eLp6v4ca9zajMUnFDL/giphy.gif"
        }
    ]
}

Saturday 19 February 2022

Conditional formatting in Power Apps Gallery Control

Text Color:

I have label control that displays the Status of each issue, and the status is choice column in SharePoint list. Here I am trying to display different statuses in different colors.

Select Status label in the first row of the gallery, and at the top of the screen, select the Color property to type in the function box.

If the status is completed, show green, otherwise black.

If(
    ThisItem.Status.Value = "Completed",
    Color.Green,
    Color.Black
)

If you have multiple statuses wher you need to set up multiple colors: use Switch which is similar to IF.

In my scenario, if it’s completed show green, if In progress, purple, if Blocked, Red, otherwise if none of those are the case, the color will be black.

Switch(
    ThisItem.Status.Value,
    "Completed",
    Color.Green,
    "Blocked",
    Color.Red,
    "In Progress",
    Color.Purple,
    Color.Black
)


     When the record is selected:

If you want to fill the color for the selected record in gallery, please select the gallery control, and write the below function in TemplateFill property
If(
    ThisItem.IsSelected,
    ColorFade(
        RGBA(208,235,242,1
        ),
        -30%
    ),
    RGBA(0,0,0,0
    )
)



Bold Font:

If the task is assigned to the current user, the user’s name will be displayed in a bold font weight.

Click to select the label in the first row, that shows the “Assigned To” person’s name (It's a SharePoint People picker field). Select the FontWeight property.
If(
    ThisItem.'Assigned To'.Email = User().Email,
    FontWeight.Bold,
    FontWeight.Normal
)



Switch Icons:

If you would like to display a different icons for each of the 3 different priority levels. 

Select the first row of the gallery and Insert any icon from the Icons. There is a property called Icon on the icon, which tells you its name.

In my scenario, if the priority is High, show warning icon, if Medium, Information, if none of those are the case, Icon will be low/download.

Switch(
    ThisItem.Priority,
    "High",
    Icon.Warning,
    "Medium",
    Icon.Information,
    Icon.Download
)

Fill Color:

In Power App all the controls will have a Fill property, for the fill color. Here I want to show as a different color depending on the priority for a Icon. If it’s high priority, Red, if Medium, Orange, otherwise Aquamarine.

Switch(
    ThisItem.Priority,
    "High",
    Color.Red,
    "Medium",
    Color.Orange,
    Color.Aquamarine
)



Bow background Color:

If the status is equal to "In progress" show as Light yellow, otherwise white. To set the row’s background color, select the gallery, and go to the property called TemplateFill.

If(
    ThisItem.Status.Value = "In Progress",
    Color.LightYellow,
    Color.White
)



Disable a Icon or Button:

You can change the DisplayMode of any control, based on a condition. When you disable a button, by default it will show as grey, so that the button is still there, but clearly not clickable. In this example, I’ll use a Edit Icon, and I’ll disable it if the task has been completed. Select the Icon, go to the DisplayMode property:

If(
    ThisItem.Status.Value = "Completed",
    DisplayMode.Disabled,
    DisplayMode.Edit
)

Wednesday 9 February 2022

Generate dynamic poll using Microsoft Teams Adaptive Card

If you are looking to generate a dynamic poll in Microsoft Teams on a button click, we have default template available in Power Automat which you can see from the below screenshot.


You flow will look like below:

When you run the flow, you will be asked to enter the inputs:


And here is the poll which is posted in Teams channel:

Sunday 6 February 2022

Error: Type 'HTMLElement | null' is not assignable to type 'HTMLElement' ts(2322)

Add ! (exclamation mark) after a property/variable name. This feature is called “Non-null assertion operator“, basically it means that when you add the exclamation mark after a property/value, you are telling TypeScript that you are certain that value is not null or undefined.

Run With Elevated Privileges(App-Only) in SPFx webpart using Power Automate

  • SPFx Solution didn't support elevated permission using App-Only authentication, but with MS Power Automate, it's possible to use app-only elevated permission.
  • App-Only Token and elevated permission is got in MS Flow and in SPFX solution only call the flow using HttpClient object.
  • For example, in public facing sites Contact Us form, where a user will not have direct access to view or edit other items but he/she should be able to make an entry(create item) into the list. So even if the user don't have access to the list, you would like to collect information from them.Below is the Architecture diagram from Microsoft:

  1. Here in the diagram, user does not have access to the list but to input the data an interface has been provided which is build using SPFx webpart.
  2. User makes a POST request from SPFx webpart which triggers the MS Flow.
  3. Flow(HTTP) makes a POST request to ACS to retrieve the Access token.
  4. Once the ACS token received, SharePoint REST API POST call will perform using the token and that will create the item in SharePoint list. 

Make REST API Call from MS Flow:

Create and register an App in SPO with full control permissions

    1.       Login to SPO https://domainname.sharepoint.com/sites/LiakathDev/_layouts/15/appregnew.aspx  to generate Client ID and Secret ID.

    2.       Go to https://domainname.sharepoint.com/sites/LiakathDev/_layouts/15/appinv.aspx and register the app by entering the generated Client ID via lookup. Add the below XML to provide the full permissions to app and to elevate the permission at list level.

<AppPermissionRequests AllowAppOnlyPolicy="true">

<AppPermissionRequest Scope="http://sharepoint/content/sitecollection/web/list   " Right="FullControl" />

</AppPermissionRequests>

    3.      Select the list from the dropdown list where you need elevated permission and click on ‘Trust It’.

    4.      Verify the registered app and Note the Tenant ID from the below link:

https://domainname.sharepoint.com/sites/LiakathDev/_layouts/15/appprincipals.aspx

Create Power Automate Flow

    1.       Create flow from blank template and select the trigger as “When a Http request is received” and add the below JSON in the ‘Request Body’:

{
    "type": "object",
    "properties": {
        "title": {
            "type": "string"
        }
    }
}

    2.       Add action ‘HTTP’ post to fetch the access token from ACS using REST API call.

Body: replace Client ID, Secret ID and Tenant ID.

Grant_type=client_credentials&client_id=<<ClientID>>@<<TenantID>>&client_secret=<<ClientSecretID>>&resource=00000003-0000-0ff1-ce00-000000000000/<<TenantName>>.sharepoint.com@<<TenantID>>

    3.       Add action “Parse JSON”  and include the below JSON in to read the access token.

{

    "type""object",

    "properties": {

        "token_type": {

            "type""string"

        },

        "access_token": {

            "type""string"

        }

    }

}

         

     4.  Make Rest API call using Power Automate Flow

a.       Add action “HTTP” post to make a REST API call.

      

    5.   Add action "Response" to send a response back based on the out received from the previous step. Include Status code in the body parameter as displayed in the screenshot below.

    6.       Save the Flow and Note the URL of the HTTP Request: HTTP Post URL

https://prod-19.centralindia.logic.azure.com:443/workflows/02f9d3f348ec4f8ebee1f70be0b608f8/triggers/manual/paths/invoke?api-version=2016-06-01&sp=%2Ftriggers%2Fmanual%2Frun&sv=1.0&sig=XpvU6D81LOSEtAH3Nvl_qKeYfEWNnyvqU9v6JnsPzbs 

Call Power Automate Flow From SPFx webpart:

Create SPFx webpart

    1.       Create SPFx webpart and the template is: No framework.

    2.       Once the project is created open the VS Code using this command : Code .

    3.       Update the Webpart.ts file with the below code        

import { Version } from '@microsoft/sp-core-library';
import {
  IPropertyPaneConfiguration,
  PropertyPaneTextField
} from '@microsoft/sp-property-pane';
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
import { IReadonlyTheme } from '@microsoft/sp-component-base';
//import { escape } from '@microsoft/sp-lodash-subset';

import styles from './SpFxWebpartWebPart.module.scss';
import * as strings from 'SpFxWebpartWebPartStrings';
import { HttpClient, IHttpClientOptions, HttpClientResponse } from '@microsoft/sp-http';

export interface ISpFxWebpartWebPartProps {
  //description: string;
  flowURL: string;
  FlowURLLabel: string;
}
//import { SPComponentLoader } from '@microsoft/sp-loader';
//interface IResult {
  //status: string;
//}

export default class SpFxWebpartWebPart extends BaseClientSideWebPart<ISpFxWebpartWebPartProps> {

  //private _isDarkTheme: boolean = false;
  //private _environmentMessage: string = '';

  public render(): void {
    this.domElement.innerHTML = `
    <div class="${ styles.spFxWebpart }">
        <div class="${ styles.container }">
          <div class="${ styles.row }">
            <div class="${ styles.column }">
                 <span class="${styles.title} ms-Grid-col ms-u-sm12 block">
                   Item creation using SPFx & Flow
                </span>
                <div class="ms-Grid-col ms-u-sm12 block"><br/></div>
                <div class="ms-TextField ms-Grid-col ms-u-sm12 block">
                  <label class="ms-Label ms-Grid-col ms-u-sm4 block">Title</label>
                  <input id="spfxflowIm" class="ms-TextField-field ms-Grid-col ms-u-sm8 block" value="" type="text" placeholder="">
                </div>
                <div class="ms-TextField ms-Grid-col ms-u-sm12 block"><br/></div>
                <div class="ms-TextField ms-Grid-col ms-u-sm6 block"></div>
                <div class="ms-TextField ms-Grid-col ms-u-sm6 block">
                <button class="${styles.button} create-Button">
                <span class="${styles.label}">Create Item</span>
                </button></div>
                <div id="status"></div>
              </div>
            </div>
          </div>
        </div>
      </div>`;
      this.setButtonsEventHandlers();
  }

  private setButtonsEventHandlers(): void {
    const webPart: SpFxWebpartWebPart = this;
    this.domElement.querySelector('button.create-Button')!.addEventListener('click', () => { webPart.createItem(); });
  }

  private createItem(): void {
    let result: HTMLElement = document.getElementById("status")!;
    let responseText: string = "";
    //result.style.color = "white";
    result.innerText = "Updating item...";
    var itemTitle : string = (<HTMLInputElement>document.getElementById("spfxflowIm")).value;              
    const postURL = this.properties.flowURL;   
    const body: string = JSON.stringify({
      'title': itemTitle,
    });   
    const requestHeaders: Headers = new Headers();       
    requestHeaders.append('Content-type', 'application/json');
    requestHeaders.append('Cache-Control', 'no-cache');   
    const httpClientOptions: IHttpClientOptions = {
      body: body,
      headers: requestHeaders
    };     
    this.context.httpClient.post(
      postURL,
      HttpClient.configurations.v1,
      httpClientOptions).then((response: HttpClientResponse) => {
        response.json().then((responseJSON: JSON) => {
          responseText = JSON.stringify(responseJSON);            
        result.innerText = (responseText=="201")?"Item updated successfully":"Error received while updating item";
      });
    }).catch ((response: any) => {
      let errMsg: string = `Error = ${response.message}`;
      result.style.color = "red";
      console.log(errMsg);
      result.innerText = errMsg;
    });
}



  protected onInit(): Promise<void> {
    return this._getEnvironmentMessage().then(message => {
      //this._environmentMessage = message;
    });
  }


  private _getEnvironmentMessage(): Promise<string> {
    if (!!this.context.sdks.microsoftTeams) { // running in Teams, office.com or Outlook
      return this.context.sdks.microsoftTeams.teamsJs.app.getContext()
        .then(context => {
          let environmentMessage: string = '';
          switch (context.app.host.name) {
            case 'Office': // running in Office
              environmentMessage = this.context.isServedFromLocalhost ? strings.AppLocalEnvironmentOffice : strings.AppOfficeEnvironment;
              break;
            case 'Outlook': // running in Outlook
              environmentMessage = this.context.isServedFromLocalhost ? strings.AppLocalEnvironmentOutlook : strings.AppOutlookEnvironment;
              break;
            case 'Teams': // running in Teams
              environmentMessage = this.context.isServedFromLocalhost ? strings.AppLocalEnvironmentTeams : strings.AppTeamsTabEnvironment;
              break;
            default:
              throw new Error('Unknown host');
          }

          return environmentMessage;
        });
    }

    return Promise.resolve(this.context.isServedFromLocalhost ? strings.AppLocalEnvironmentSharePoint : strings.AppSharePointEnvironment);
  }

  protected onThemeChanged(currentTheme: IReadonlyTheme | undefined): void {
    if (!currentTheme) {
      return;
    }

    //this._isDarkTheme = !!currentTheme.isInverted;
    const {
      semanticColors
    } = currentTheme;

    if (semanticColors) {
      this.domElement.style.setProperty('--bodyText', semanticColors.bodyText || null);
      this.domElement.style.setProperty('--link', semanticColors.link || null);
      this.domElement.style.setProperty('--linkHovered', semanticColors.linkHovered || null);
    }

  }

  protected get dataVersion(): Version {
    return Version.parse('1.0');
  }

  protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
    return {
      pages: [
        {
          header: {
            description: strings.PropertyPaneDescription
          },
          groups: [
            {
              groupName: strings.BasicGroupName,
              groupFields: [
                PropertyPaneTextField('flowURL', {
                  label: strings.FlowURLLabel
                })
              ]
            }
          ]
        }
      ]
    };
  }
}

    4.       Update the en-us.js file with the below code which is located under ‘loc’ folder (solutionpath > src > webparts > spFxWebpart > loc).

define([], function() {

  return {

    "PropertyPaneDescription": "Description",

    "BasicGroupName": "Group Name",

    "DescriptionFieldLabel": "Description Field",

    "AppLocalEnvironmentSharePoint": "The app is running on your local environment as SharePoint web part",

    "AppLocalEnvironmentTeams": "The app is running on your local environment as Microsoft Teams app",

    "AppLocalEnvironmentOffice": "The app is running on your local environment in office.com",

    "AppLocalEnvironmentOutlook": "The app is running on your local environment in Outlook",

    "AppSharePointEnvironment": "The app is running on SharePoint page",

    "AppTeamsTabEnvironment": "The app is running in Microsoft Teams",

    "AppOfficeEnvironment": "The app is running in office.com",

    "AppOutlookEnvironment": "The app is running in Outlook",

    "PropertyPaneDescription": "SPFx FLow",

    "BasicGroupName": "Flow Properties",

    "FlowURLLabel": "Flow URL"

  }

});

    5.       Update the mystring.d.ts file with the below code which is located under ‘loc’ colder(solutionpath > src > webparts > spFxWebpart > loc)

declare interface ISpFxWebpartWebPartStrings {

  PropertyPaneDescription: string;

  BasicGroupName: string;

  DescriptionFieldLabel: string;

  AppLocalEnvironmentSharePoint: string;

  AppLocalEnvironmentTeams: string;

  AppLocalEnvironmentOffice: string;

  AppLocalEnvironmentOutlook: string;

  AppSharePointEnvironment: string;

  AppTeamsTabEnvironment: string;

  AppOfficeEnvironment: string;

  AppOutlookEnvironment: string;

  FlowURLLabel: string;

}

 

declare module 'SpFxWebpartWebPartStrings' {

  const strings: ISpFxWebpartWebPartStrings;

  export = strings;

}

    6.       Run ‘Gulp Serve’ to test the webpart in Workbench url.

7.       Add the webpart on workbench url, edit the webpart and provide URL of the Flow noted in the above step: HTTP Post URL.